From 0418460b5ff12af5bc0b4078ec43210489d6ae99 Mon Sep 17 00:00:00 2001 From: CraftyOldMiner <85420839+CraftyOldMiner@users.noreply.github.com> Date: Sat, 30 Apr 2022 09:17:14 -0500 Subject: Crash & perf fixes (#121) - Fix crash in profile viewer when name not found - Parse numbers using exception-free methods in hot code paths - Update price graph to handle items transitioning from the bz to ah --- .../notenoughupdates/auction/APIManager.java | 2 +- .../listener/ItemTooltipListener.java | 74 +-------- .../notenoughupdates/miscgui/GuiPriceGraph.java | 175 ++++++++++++--------- .../profileviewer/GuiProfileViewer.java | 2 +- .../moulberry/notenoughupdates/util/Utils.java | 35 +++++ 5 files changed, 144 insertions(+), 144 deletions(-) (limited to 'src/main') diff --git a/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java b/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java index f46c8334..cb2f06d6 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java @@ -249,7 +249,7 @@ public class APIManager { if (lowestBins != null && lowestBins.has(internalname)) { JsonElement e = lowestBins.get(internalname); if (e.isJsonPrimitive() && e.getAsJsonPrimitive().isNumber()) { - return e.getAsInt(); + return e.getAsBigDecimal().intValue(); } } return -1; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/listener/ItemTooltipListener.java b/src/main/java/io/github/moulberry/notenoughupdates/listener/ItemTooltipListener.java index 501ed8a3..a00e7e8a 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/listener/ItemTooltipListener.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/listener/ItemTooltipListener.java @@ -40,6 +40,7 @@ import java.util.List; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; public class ItemTooltipListener { private static final String petToolTipRegex = @@ -389,9 +390,9 @@ public class ItemTooltipListener { try { pattern = Pattern.compile("(\\u00A79|\\u00A7(9|l)\\u00A7d\\u00A7l)(?" + enchantName + ") " + "(?[0-9]+|(I|II|III|IV|V|VI|VII|VIII|IX|X|XI|XII|XIII|XIV|XV|XVI|XVII|XVIII|XIX|XX))((\\u00A79)?,|( \\u00A78(?:,?[0-9]+)*)?$)"); - } catch (Exception e) { + } catch (PatternSyntaxException e) { continue; - } //malformed regex + } Matcher matcher = pattern.matcher(line); int matchCount = 0; while (matcher.find() && matchCount < 5) { @@ -400,73 +401,8 @@ public class ItemTooltipListener { matchCount++; int level = -1; String levelStr = matcher.group("level"); - if (levelStr == null) continue; - try { - level = Integer.parseInt(levelStr); - } catch (Exception e) { - switch (levelStr) { - case "I": - level = 1; - break; - case "II": - level = 2; - break; - case "III": - level = 3; - break; - case "IV": - level = 4; - break; - case "V": - level = 5; - break; - case "VI": - level = 6; - break; - case "VII": - level = 7; - break; - case "VIII": - level = 8; - break; - case "IX": - level = 9; - break; - case "X": - level = 10; - break; - case "XI": - level = 11; - break; - case "XII": - level = 12; - break; - case "XIII": - level = 13; - break; - case "XIV": - level = 14; - break; - case "XV": - level = 15; - break; - case "XVI": - level = 16; - break; - case "XVII": - level = 17; - break; - case "XVIII": - level = 18; - break; - case "XIX": - level = 19; - break; - case "XX": - level = 20; - break; - } - } + if (levelStr == null || levelStr.isEmpty()) continue; + level = Utils.parseIntOrRomanNumeral(levelStr); boolean matches = false; if (level > 0) { switch (comparator) { diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiPriceGraph.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiPriceGraph.java index 66014b1f..e2381503 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiPriceGraph.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiPriceGraph.java @@ -36,7 +36,7 @@ public class GuiPriceGraph extends GuiScreen { private final ResourceLocation TEXTURE; private static final int X_SIZE = 364; private static final int Y_SIZE = 215; - private Data dataPoints; + private ItemData itemData; private float highestValue; private long firstTime; private long lastTime; @@ -117,7 +117,8 @@ public class GuiPriceGraph extends GuiScreen { Utils.drawStringCentered("Loading...", Minecraft.getMinecraft().fontRendererObj, guiLeft + 166, guiTop + 116, false, 0xffffff00 ); - else if (dataPoints == null || dataPoints.get() == null || dataPoints.get().size() <= 1 || lowestValue == null) + else if ( + itemData == null || itemData.get() == null || itemData.get().size() <= 1 || lowestValue == null) Utils.drawStringCentered("No data found.", Minecraft.getMinecraft().fontRendererObj, guiLeft + 166, guiTop + 116, false, 0xffff0000 ); @@ -127,16 +128,16 @@ public class GuiPriceGraph extends GuiScreen { Integer lowestDist = null; Long lowestDistTime = null; HashMap secondLineData = new HashMap<>(); - for (int i = (dataPoints.isBz() ? 1 : 0); i >= 0; i--) { + for (int i = (itemData.isBz() ? 1 : 0); i >= 0; i--) { Utils.drawGradientRect(0, guiLeft + 17, guiTop + 35, guiLeft + 315, guiTop + 198, changeAlpha(i == 0 ? graphColor : graphColor2, 120), changeAlpha(i == 0 ? graphColor : graphColor2, 10) ); Integer prevX = null; Integer prevY = null; - for (Long time : dataPoints.get().keySet()) { - float price = dataPoints.isBz() - ? i == 0 ? dataPoints.bz.get(time).b : dataPoints.bz.get(time).s - : dataPoints.ah.get(time); + for (Long time : itemData.get().keySet()) { + float price = itemData.isBz() + ? i == 0 ? itemData.bz.get(time).b : itemData.bz.get(time).s + : itemData.ah.get(time); int xPos = (int) map(time, firstTime, lastTime, guiLeft + 17, guiLeft + 315); int yPos = (int) map(price, highestValue + 10d, lowestValue - 10d, guiTop + 35, guiTop + 198); if (prevX != null) { @@ -159,7 +160,7 @@ public class GuiPriceGraph extends GuiScreen { ); if (i == 0) { Utils.drawLine(prevX, prevY + 0.5f, xPos, yPos + 0.5f, 2, graphColor); - if (dataPoints.isBz()) + if (itemData.isBz()) Utils.drawLine( prevX, secondLineData.get(prevX) + 0.5f, @@ -218,8 +219,8 @@ public class GuiPriceGraph extends GuiScreen { Utils.drawDottedLine(customStart, guiTop + 197, customEnd, guiTop + 197, 2, 10, 0xFFc6c6c6); } if (lowestDist != null && !customSelecting) { - float price = dataPoints.isBz() ? dataPoints.bz.get(lowestDistTime).b : dataPoints.ah.get(lowestDistTime); - Float price2 = dataPoints.isBz() ? dataPoints.bz.get(lowestDistTime).s : null; + float price = itemData.isBz() ? itemData.bz.get(lowestDistTime).b : itemData.ah.get(lowestDistTime); + Float price2 = itemData.isBz() ? itemData.bz.get(lowestDistTime).s : null; int xPos = (int) map(lowestDistTime, firstTime, lastTime, guiLeft + 17, guiLeft + 315); int yPos = (int) map(price, highestValue + 10d, lowestValue - 10d, guiTop + 35, guiTop + 198); int yPos2 = price2 != null @@ -243,7 +244,7 @@ public class GuiPriceGraph extends GuiScreen { NumberFormat nf = NumberFormat.getInstance(); ArrayList text = new ArrayList<>(); text.add(displayFormat.format(date)); - if (dataPoints.isBz()) { + if (itemData.isBz()) { text.add(EnumChatFormatting.YELLOW + "" + EnumChatFormatting.BOLD + "Bazaar Insta-Buy: " + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + nf.format(price)); text.add(EnumChatFormatting.YELLOW + "" + EnumChatFormatting.BOLD + "Bazaar Insta-Sell: " + @@ -320,7 +321,7 @@ public class GuiPriceGraph extends GuiScreen { } private void loadData() { - dataPoints = null; + itemData = null; loaded = false; new Thread(() -> { File dir = new File("config/notenoughupdates/prices"); @@ -329,46 +330,46 @@ public class GuiPriceGraph extends GuiScreen { return; } File[] files = dir.listFiles(); - Data data = new Data(); + ItemData itemData = new ItemData(); if (files == null) return; for (File file : files) { if (!file.getName().endsWith(".gz")) continue; - HashMap data2 = load(file); + HashMap data2 = load(file); if (data2 == null || !data2.containsKey(itemId)) continue; if (data2.get(itemId).isBz()) { - if (data.bz == null) data.bz = data2.get(itemId).bz; - else data.bz.putAll(data2.get(itemId).bz); - } else if (data.ah == null) data.ah = data2.get(itemId).ah; - else data.ah.putAll(data2.get(itemId).ah); + if (itemData.bz == null) itemData.bz = data2.get(itemId).bz; + else itemData.bz.putAll(data2.get(itemId).bz); + } else if (itemData.ah == null) itemData.ah = data2.get(itemId).ah; + else itemData.ah.putAll(data2.get(itemId).ah); } - if (data.get() != null && !data.get().isEmpty()) { + if (itemData.get() != null && !itemData.get().isEmpty()) { if (mode < 3) - data = new Data( - new TreeMap<>(data.get().entrySet().stream() - .filter(e -> e.getKey() > System.currentTimeMillis() / 1000 - + itemData = new ItemData( + new TreeMap<>(itemData.get().entrySet().stream() + .filter(e -> e.getKey() > System.currentTimeMillis() / 1000 - (mode == 0 ? 3600 : mode == 1 ? 86400 : 604800)) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))), - data.isBz() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))), + itemData.isBz() ); else if (mode == 4) - data = new Data( - new TreeMap<>(data.get().entrySet().stream() - .filter(e -> e.getKey() >= customStart && e.getKey() <= customEnd) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))), - data.isBz() + itemData = new ItemData( + new TreeMap<>(itemData.get().entrySet().stream() + .filter(e -> e.getKey() >= customStart && e.getKey() <= customEnd) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))), + itemData.isBz() ); - if (data.get() == null || data.get().isEmpty()) { + if (itemData.get() == null || itemData.get().isEmpty()) { loaded = true; return; } - dataPoints = trimData(data); - firstTime = dataPoints.get().firstKey(); - lastTime = dataPoints.get().lastKey(); + this.itemData = trimData(itemData); + firstTime = this.itemData.get().firstKey(); + lastTime = this.itemData.get().lastKey(); highestValue = 0; lowestValue = null; - for (long key : dataPoints.get().keySet()) { - float value1 = dataPoints.isBz() ? dataPoints.bz.get(key).b : dataPoints.ah.get(key); - Float value2 = dataPoints.isBz() ? dataPoints.bz.get(key).s : null; + for (long key : this.itemData.get().keySet()) { + float value1 = this.itemData.isBz() ? this.itemData.bz.get(key).b : this.itemData.ah.get(key); + Float value2 = this.itemData.isBz() ? this.itemData.bz.get(key).s : null; if (value1 > highestValue) { highestValue = value1; } @@ -404,33 +405,13 @@ public class GuiPriceGraph extends GuiScreen { Date date = new Date(); Long epochSecond = date.toInstant().getEpochSecond(); File file = new File(dir, "prices_" + format.format(date) + ".gz"); - HashMap prices = new HashMap<>(); + HashMap prices = new HashMap<>(); if (file.exists()) { - HashMap tempPrices = load(file); + HashMap tempPrices = load(file); if (tempPrices != null) prices = tempPrices; } for (Map.Entry item : items.entrySet()) { - if (prices.containsKey(item.getKey())) { - if (bazaar && item.getValue().getAsJsonObject().has("curr_buy") && item.getValue().getAsJsonObject().has( - "curr_sell")) - prices.get(item.getKey()).bz.put(epochSecond, new BzData( - item.getValue().getAsJsonObject().get("curr_buy").getAsFloat(), - item.getValue().getAsJsonObject().get("curr_sell").getAsFloat() - )); - else if (!bazaar) - prices.get(item.getKey()).ah.put(epochSecond, item.getValue().getAsBigDecimal().intValue()); - } else { - TreeMap mapData = new TreeMap<>(); - if (bazaar && item.getValue().getAsJsonObject().has("curr_buy") && item.getValue().getAsJsonObject().has( - "curr_sell")) - mapData.put(epochSecond, new BzData( - item.getValue().getAsJsonObject().get("curr_buy").getAsFloat(), - item.getValue().getAsJsonObject().get("curr_sell").getAsFloat() - )); - else if (!bazaar) - mapData.put(epochSecond, item.getValue().getAsLong()); - prices.put(item.getKey(), new Data(mapData, bazaar)); - } + addOrUpdateItemPriceInfo(item, prices, epochSecond, bazaar); } //noinspection ResultOfMethodCallIgnored file.createNewFile(); @@ -447,16 +428,64 @@ public class GuiPriceGraph extends GuiScreen { } } - private Data trimData(Data data) { - long first = data.get().firstKey(); - long last = data.get().lastKey(); - Data trimmed = new Data(); - if (data.isBz()) + private static void addOrUpdateItemPriceInfo(Map.Entry item, HashMap prices, long timestamp, boolean bazaar) { + String itemName = item.getKey(); + ItemData existingItemData = null; + if (prices.containsKey(itemName)) { + existingItemData = prices.get(itemName); + } + + // Handle transitions from ah to bz (the other direction typically doesn't happen) + if (existingItemData != null) { + if (existingItemData.isBz() && !bazaar) { + return; + } + + if (!existingItemData.isBz() && bazaar) { + prices.remove(itemName); + existingItemData = null; + } + } + + if (bazaar) { + if (!item.getValue().getAsJsonObject().has("curr_buy") || + !item.getValue().getAsJsonObject().has("curr_sell") + ) { + return; + } + + BzData bzData = new BzData( + item.getValue().getAsJsonObject().get("curr_buy").getAsFloat(), + item.getValue().getAsJsonObject().get("curr_sell").getAsFloat()); + + if (existingItemData != null) { + existingItemData.bz.put(timestamp, bzData); + } else { + TreeMap mapData = new TreeMap<>(); + mapData.put(timestamp, bzData); + prices.put(item.getKey(), new ItemData(mapData, true)); + } + } else { + if (existingItemData != null) { + prices.get(item.getKey()).ah.put(timestamp, item.getValue().getAsBigDecimal().intValue()); + } else { + TreeMap mapData = new TreeMap<>(); + mapData.put(timestamp, item.getValue().getAsLong()); + prices.put(item.getKey(), new ItemData(mapData, false)); + } + } + } + + private ItemData trimData(ItemData itemData) { + long first = itemData.get().firstKey(); + long last = itemData.get().lastKey(); + ItemData trimmed = new ItemData(); + if (itemData.isBz()) trimmed.bz = new TreeMap<>(); else trimmed.ah = new TreeMap<>(); int zones = NotEnoughUpdates.INSTANCE.config.ahGraph.graphZones; - Long[] dataArray = !data.isBz() ? data.ah.keySet().toArray(new Long[0]) : data.bz.keySet().toArray(new Long[0]); + Long[] dataArray = !itemData.isBz() ? itemData.ah.keySet().toArray(new Long[0]) : itemData.bz.keySet().toArray(new Long[0]); int prev = 0; for (int i = 0; i < zones; i++) { long lowest = (long) map(i, 0, zones, first, last); @@ -467,14 +496,14 @@ public class GuiPriceGraph extends GuiScreen { for (int l = prev; l < dataArray.length; l++) { if (dataArray[l] >= lowest && dataArray[l] <= highest) { amount++; - sumBuy += data.isBz() ? data.bz.get(dataArray[l]).b : data.ah.get(dataArray[l]); - if (data.isBz()) sumSell += data.bz.get(dataArray[l]).s; + sumBuy += itemData.isBz() ? itemData.bz.get(dataArray[l]).b : itemData.ah.get(dataArray[l]); + if (itemData.isBz()) sumSell += itemData.bz.get(dataArray[l]).s; prev = l + 1; } else if (dataArray[l] > highest) break; } if (amount > 0) { - if (data.isBz()) + if (itemData.isBz()) trimmed.bz.put(lowest, new BzData((float) (sumBuy / amount), (float) (sumSell / amount))); else trimmed.ah.put(lowest, (int) (sumBuy / amount)); @@ -483,8 +512,8 @@ public class GuiPriceGraph extends GuiScreen { return trimmed; } - private static HashMap load(File file) { - Type type = new TypeToken>() { + private static HashMap load(File file) { + Type type = new TypeToken>() { }.getType(); if (file.exists()) { try ( @@ -525,14 +554,14 @@ public class GuiPriceGraph extends GuiScreen { } } -class Data { +class ItemData { public TreeMap ah = null; public TreeMap bz = null; - public Data() { + public ItemData() { } - public Data(TreeMap map, boolean bz) { + public ItemData(TreeMap map, boolean bz) { if (bz) this.bz = (TreeMap) map; else diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java index 597b6b4d..06ba32ad 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java @@ -478,7 +478,7 @@ public class GuiProfileViewer extends GuiScreen { this.guiTop = (this.height - this.sizeY) / 2; boolean bingo = false; - JsonObject currProfileInfo = profile.getProfileInformation(profileId); + JsonObject currProfileInfo = profile != null ? profile.getProfileInformation(profileId) : null; if (NotEnoughUpdates.INSTANCE.config.profileViewer.alwaysShowBingoTab) { showBingoPage = true; } else { diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java b/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java index 5fac9208..7b714cd0 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java @@ -653,6 +653,41 @@ public class Utils { return (float) Math.round(value * scale) / scale; } + // Parses Roman numerals, allowing for single character irregular subtractive notation (e.g. IL is 49, IIL is invalid) + public static int parseRomanNumeral(String input) { + int prevVal = 0; + int total = 0; + for (int i = input.length()-1; i >= 0; i--) { + int val; + char ch = input.charAt(i); + switch (ch) { + case 'I' : val = 1; break; + case 'V' : val = 5; break; + case 'X' : val = 10; break; + case 'L' : val = 50; break; + case 'C' : val = 100; break; + case 'D' : val = 500; break; + case 'M' : val = 1000; break; + default: throw new IllegalArgumentException("Invalid Roman Numeral Character: " + ch); + } + if (val < prevVal) val = -val; + total += val; + prevVal = val; + } + + return total; + } + + public static int parseIntOrRomanNumeral(String input) { + // 0 through 9, '-', and '+' come before 'A' in ANSI, UTF8, and UTF16 character sets + // + if (input.charAt(0) < 'A') { + return Integer.parseInt(input); + } + + return parseRomanNumeral(input); + } + public static void playPressSound() { playSound(new ResourceLocation("gui.button.press"), true); } -- cgit