Optimize FileSection.parse()/parseMap().

This was showing up in profiles with simulation AI. The change
makes constants for the patterns used, so they don't have to be
"compiled" each time and also introduces a cache for these.

With this change, a GameCopier operation is sped up by about 30%
from my local measurement (I tried with a modern deck I have).
This commit is contained in:
Alexei Svitkine
2019-12-20 11:22:06 -05:00
parent 0e1c82a31f
commit dd4df9baaa
15 changed files with 69 additions and 53 deletions

View File

@@ -58,7 +58,11 @@ public class GameCopier {
public Game makeCopy() { public Game makeCopy() {
return makeCopy(null); return makeCopy(null);
} }
static int copies;
static double totalTime;
public Game makeCopy(PhaseType advanceToPhase) { public Game makeCopy(PhaseType advanceToPhase) {
long t = System.currentTimeMillis();
List<RegisteredPlayer> origPlayers = origGame.getMatch().getPlayers(); List<RegisteredPlayer> origPlayers = origGame.getMatch().getPlayers();
List<RegisteredPlayer> newPlayers = new ArrayList<>(); List<RegisteredPlayer> newPlayers = new ArrayList<>();
for (RegisteredPlayer p : origPlayers) { for (RegisteredPlayer p : origPlayers) {
@@ -146,7 +150,14 @@ public class GameCopier {
if (advanceToPhase != null) { if (advanceToPhase != null) {
newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase); newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase);
} }
totalTime += (System.currentTimeMillis()-t) * 1000;
if ((copies++ % 100) == 0) {
System.out.println("Time per copy: " + totalTime/copies);
if (copies >= 10000) {
System.exit(-1);
}
}
return newGame; return newGame;
} }

View File

@@ -303,7 +303,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
tokenNormalized tokenNormalized
); );
FileSection section = FileSection.parse(contents.get("metadata"), "="); FileSection section = FileSection.parse(contents.get("metadata"), FileSection.EQUALS_KV_SEPARATOR);
res.name = section.get("name"); res.name = section.get("name");
res.date = parseDate(section.get("date")); res.date = parseDate(section.get("date"));
res.code = section.get("code"); res.code = section.get("code");

View File

@@ -27,7 +27,7 @@ public class DeckSerializer {
} }
final List<String> metadata = map.get("metadata"); final List<String> metadata = map.get("metadata");
if (metadata != null) { if (metadata != null) {
return new DeckFileHeader(FileSection.parse(metadata, "=")); return new DeckFileHeader(FileSection.parse(metadata, FileSection.EQUALS_KV_SEPARATOR));
} }
final List<String> general = map.get("general"); final List<String> general = map.get("general");
if (general != null) { if (general != null) {

View File

@@ -108,7 +108,7 @@ public class PreconDeck implements InventoryItemFromSet {
// To be able to read "shops" section in overloads // To be able to read "shops" section in overloads
protected PreconDeck getPreconDeckFromSections(final Map<String, List<String>> sections) { protected PreconDeck getPreconDeckFromSections(final Map<String, List<String>> sections) {
FileSection kv = FileSection.parse(sections.get("metadata"), "="); FileSection kv = FileSection.parse(sections.get("metadata"), FileSection.EQUALS_KV_SEPARATOR);
String imageFilename = kv.get("Image"); String imageFilename = kv.get("Image");
String description = kv.get("Description"); String description = kv.get("Description");
String deckEdition = kv.get("set"); String deckEdition = kv.get("set");

View File

@@ -17,9 +17,12 @@
*/ */
package forge.util; package forge.util;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@@ -56,34 +59,40 @@ public class FileSection {
protected FileSection(Map<String, String> lines0) { protected FileSection(Map<String, String> lines0) {
lines = lines0; lines = lines0;
} }
/** public static final Pattern DOLLAR_SIGN_KV_SEPARATOR = Pattern.compile(Pattern.quote("$"));
* Parses the. public static final Pattern ARROW_KV_SEPARATOR = Pattern.compile(Pattern.quote("->"));
* public static final Pattern EQUALS_KV_SEPARATOR = Pattern.compile(Pattern.quote("="));
* @param line the line public static final Pattern COLON_KV_SEPARATOR = Pattern.compile(Pattern.quote(":"));
* @param kvSeparator the kv separator
* @param pairSeparator the pair separator private static final String BAR_PAIR_SPLITTER = Pattern.quote("|");
* @return the file section
*/ private static Table<String, Pattern, Map<String, String>> parseToMapCache = HashBasedTable.create();
public static FileSection parse(final String line, final String kvSeparator, final String pairSeparator) {
Map<String, String> map = parseToMap(line, kvSeparator, pairSeparator); public static Map<String, String> parseToMap(final String line, final Pattern kvSeparator) {
return new FileSection(map); Map<String, String> result = parseToMapCache.get(line, kvSeparator);
} if (result != null) {
return result;
public static Map<String, String> parseToMap(final String line, final String kvSeparator, final String pairSeparator) {
Map<String, String> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
if (!StringUtils.isEmpty(line)) {
final String[] pairs = line.split(Pattern.quote(pairSeparator));
final Pattern splitter = Pattern.compile(Pattern.quote(kvSeparator));
for (final String dd : pairs) {
final String[] v = splitter.split(dd, 2);
result.put(v[0].trim(), v.length > 1 ? v[1].trim() : "");
}
} }
result = parseToMapImpl(line, kvSeparator);
parseToMapCache.put(line, kvSeparator, result);
return result; return result;
} }
private static Map<String, String> parseToMapImpl(final String line, final Pattern kvSeparator) {
if (StringUtils.isEmpty(line)) {
return Collections.emptyMap();
}
final Map<String, String> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
final String[] pairs = line.split(BAR_PAIR_SPLITTER);
for (final String dd : pairs) {
final String[] v = kvSeparator.split(dd, 2);
result.put(v[0].trim(), v.length > 1 ? v[1].trim() : "");
}
return Collections.unmodifiableMap(result);
}
/** /**
* Parses the. * Parses the.
* *
@@ -91,11 +100,10 @@ public class FileSection {
* @param kvSeparator the kv separator * @param kvSeparator the kv separator
* @return the file section * @return the file section
*/ */
public static FileSection parse(final Iterable<String> lines, final String kvSeparator) { public static FileSection parse(final Iterable<String> lines, final Pattern kvSeparator) {
final FileSection result = new FileSection(); final FileSection result = new FileSection();
final Pattern splitter = Pattern.compile(Pattern.quote(kvSeparator));
for (final String dd : lines) { for (final String dd : lines) {
final String[] v = splitter.split(dd, 2); final String[] v = kvSeparator.split(dd, 2);
result.lines.put(v[0].trim(), v.length > 1 ? v[1].trim() : ""); result.lines.put(v[0].trim(), v.length > 1 ? v[1].trim() : "");
} }

View File

@@ -321,7 +321,7 @@ public class GameFormat implements Comparable<GameFormat> {
if (formatStrings == null){ if (formatStrings == null){
return null; return null;
} }
FileSection section = FileSection.parse(formatStrings, ":"); FileSection section = FileSection.parse(formatStrings, FileSection.COLON_KV_SEPARATOR);
String title = section.get("name"); String title = section.get("name");
FormatType formatType; FormatType formatType;
try { try {

View File

@@ -449,7 +449,7 @@ public final class AbilityFactory {
} }
public static final Map<String, String> getMapParams(final String abString) { public static final Map<String, String> getMapParams(final String abString) {
return FileSection.parseToMap(abString, "$", "|"); return FileSection.parseToMap(abString, FileSection.DOLLAR_SIGN_KV_SEPARATOR);
} }
public static final void adjustChangeZoneTarget(final Map<String, String> params, final SpellAbility sa) { public static final void adjustChangeZoneTarget(final Map<String, String> params, final SpellAbility sa) {

View File

@@ -1893,11 +1893,11 @@ public class Card extends GameEntity implements Comparable<Card> {
sb.append("\r\n"); sb.append("\r\n");
} }
while (sb.toString().endsWith("\r\n")) { String result = sb.toString();
sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); while (result.endsWith("\r\n")) {
result = sb.substring(0, sb.length() - 2);
} }
return TextUtil.fastReplace(result, "CARDNAME", state.getName());
return TextUtil.fastReplace(sb.toString(), "CARDNAME", state.getName());
} }
if (monstrous) { if (monstrous) {
@@ -2071,14 +2071,11 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
// replace triple line feeds with double line feeds // replace triple line feeds with double line feeds
int start;
final String s = "\r\n\r\n\r\n"; final String s = "\r\n\r\n\r\n";
while (sb.toString().contains(s)) { int start = sb.lastIndexOf(s);
start = sb.lastIndexOf(s); while (start != -1) {
if ((start < 0) || (start >= sb.length())) {
break;
}
sb.replace(start, start + 4, "\r\n"); sb.replace(start, start + 4, "\r\n");
start = sb.lastIndexOf(s);
} }
String desc = TextUtil.fastReplace(sb.toString(), "CARDNAME", state.getName()); String desc = TextUtil.fastReplace(sb.toString(), "CARDNAME", state.getName());
@@ -2503,7 +2500,7 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
} }
public final FCollectionView<SpellAbility> getIntrinsicSpellAbilities() { public final Iterable<SpellAbility> getIntrinsicSpellAbilities() {
return currentState.getIntrinsicSpellAbilities(); return currentState.getIntrinsicSpellAbilities();
} }

View File

@@ -273,8 +273,8 @@ public class CardState extends GameObject {
return newCol; return newCol;
} }
public final FCollectionView<SpellAbility> getIntrinsicSpellAbilities() { public final Iterable<SpellAbility> getIntrinsicSpellAbilities() {
return new FCollection<>(Iterables.filter(getSpellAbilities(), SpellAbilityPredicates.isIntrinsic())); return Iterables.filter(getSpellAbilities(), SpellAbilityPredicates.isIntrinsic());
} }
public final boolean hasSpellAbility(final SpellAbility sa) { public final boolean hasSpellAbility(final SpellAbility sa) {

View File

@@ -389,7 +389,7 @@ public class ReplacementHandler {
} }
public static Map<String, String> parseParams(final String repParse) { public static Map<String, String> parseParams(final String repParse) {
return FileSection.parseToMap(repParse, "$", "|"); return FileSection.parseToMap(repParse, FileSection.DOLLAR_SIGN_KV_SEPARATOR);
} }
/** /**

View File

@@ -96,7 +96,7 @@ public class CustomLimited extends DeckBase {
* @return the custom limited * @return the custom limited
*/ */
public static CustomLimited parse(final List<String> dfData, final IStorage<Deck> cubes) { public static CustomLimited parse(final List<String> dfData, final IStorage<Deck> cubes) {
final FileSection data = FileSection.parse(dfData, ":"); final FileSection data = FileSection.parse(dfData, FileSection.COLON_KV_SEPARATOR);
List<Pair<String, Integer>> slots = new ArrayList<>(); List<Pair<String, Integer>> slots = new ArrayList<>();
String boosterData = data.get("Booster"); String boosterData = data.get("Booster");

View File

@@ -133,7 +133,7 @@ public class ForgeProfileProperties {
private static Map<String, String> getMap(final Properties props, final String propertyKey) { private static Map<String, String> getMap(final Properties props, final String propertyKey) {
final String strMap = props.getProperty(propertyKey, "").trim(); final String strMap = props.getProperty(propertyKey, "").trim();
return FileSection.parseToMap(strMap, "->", "|"); return FileSection.parseToMap(strMap, FileSection.ARROW_KV_SEPARATOR);
} }
private static int getInt(final Properties props, final String propertyKey, final int defaultValue) { private static int getInt(final Properties props, final String propertyKey, final int defaultValue) {

View File

@@ -43,7 +43,7 @@ public class SellRules {
return; return;
} }
FileSection section = FileSection.parse(questShop, "="); FileSection section = FileSection.parse(questShop, FileSection.EQUALS_KV_SEPARATOR);
minWins = section.getInt("WinsToUnlock"); minWins = section.getInt("WinsToUnlock");
cost = section.getInt("Credits", 250); cost = section.getInt("Credits", 250);
maxDifficulty = section.getInt("MaxDifficulty", 5); maxDifficulty = section.getInt("MaxDifficulty", 5);

View File

@@ -28,7 +28,7 @@ public class QuestChallengeReader extends StorageReaderFolder<QuestEventChalleng
final QuestEventChallenge qc = new QuestEventChallenge(); final QuestEventChallenge qc = new QuestEventChallenge();
// Unique properties // Unique properties
FileSection sectionQuest = FileSection.parse(contents.get("quest"), "="); FileSection sectionQuest = FileSection.parse(contents.get("quest"), FileSection.EQUALS_KV_SEPARATOR);
qc.setId(sectionQuest.get("ID", "-1")); qc.setId(sectionQuest.get("ID", "-1"));
qc.setOpponentName(sectionQuest.get("OpponentName")); qc.setOpponentName(sectionQuest.get("OpponentName"));
qc.setRepeatable(sectionQuest.getBoolean("Repeat", false)); qc.setRepeatable(sectionQuest.getBoolean("Repeat", false));
@@ -60,7 +60,7 @@ public class QuestChallengeReader extends StorageReaderFolder<QuestEventChalleng
} }
// Common properties // Common properties
FileSection sectionMeta = FileSection.parse(contents.get("metadata"), "="); FileSection sectionMeta = FileSection.parse(contents.get("metadata"), FileSection.EQUALS_KV_SEPARATOR);
qc.setTitle(sectionMeta.get("Title")); qc.setTitle(sectionMeta.get("Title"));
qc.setName(qc.getTitle()); // Challenges have unique titles qc.setName(qc.getTitle()); // Challenges have unique titles
qc.setDifficulty(QuestEventDifficulty.fromString(sectionMeta.get("Difficulty"))); qc.setDifficulty(QuestEventDifficulty.fromString(sectionMeta.get("Difficulty")));

View File

@@ -27,7 +27,7 @@ public class QuestDuelReader extends StorageReaderFolder<QuestEventDuel> {
final QuestEventDuel qc = new QuestEventDuel(); final QuestEventDuel qc = new QuestEventDuel();
// Common properties // Common properties
FileSection sectionMeta = FileSection.parse(contents.get("metadata"), "="); FileSection sectionMeta = FileSection.parse(contents.get("metadata"), FileSection.EQUALS_KV_SEPARATOR);
qc.setTitle(sectionMeta.get("Title")); qc.setTitle(sectionMeta.get("Title"));
qc.setName(sectionMeta.get("Name")); // Challenges have unique titles qc.setName(sectionMeta.get("Name")); // Challenges have unique titles
qc.setDifficulty(QuestEventDifficulty.fromString(sectionMeta.get("Difficulty"))); qc.setDifficulty(QuestEventDifficulty.fromString(sectionMeta.get("Difficulty")));