diff --git a/src/main/java/net/rptools/lib/CodeTimer.java b/src/main/java/net/rptools/lib/CodeTimer.java index bbcb28bd07..00cfe35cd7 100644 --- a/src/main/java/net/rptools/lib/CodeTimer.java +++ b/src/main/java/net/rptools/lib/CodeTimer.java @@ -14,30 +14,59 @@ */ package net.rptools.lib; -import java.text.DecimalFormat; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import net.rptools.maptool.client.AppState; +import net.rptools.maptool.client.MapTool; public class CodeTimer { - private final Map timeMap = new HashMap(); - private final Map orderMap = new HashMap(); - private final String name; - private final long created = System.currentTimeMillis(); - private boolean enabled; - private int threshold = 1; + private static final ThreadLocal ROOT_TIMER = + ThreadLocal.withInitial(() -> new CodeTimer("")); + private static final ThreadLocal> timerStack = + ThreadLocal.withInitial(ArrayList::new); + + @FunctionalInterface + public interface TimedSection { + void call(CodeTimer timer) throws Ex; + } - private final DecimalFormat df = new DecimalFormat(); + public static void using(String name, TimedSection callback) + throws Ex { + var stack = timerStack.get(); - public CodeTimer() { - this(""); + var timer = new CodeTimer(name); + timer.setEnabled(AppState.isCollectProfilingData()); + + stack.addLast(timer); + try { + callback.call(timer); + } finally { + final var lastTimer = stack.removeLast(); + assert lastTimer == timer : "Timer stack is corrupted"; + + if (timer.isEnabled()) { + String results = timer.toString(); + MapTool.getProfilingNoteFrame().addText(results); + } + timer.clear(); + } } + public static CodeTimer get() { + final var stack = timerStack.get(); + return stack.isEmpty() ? ROOT_TIMER.get() : stack.getLast(); + } + + private final Map timeMap = new LinkedHashMap<>(); + private final String name; + private boolean enabled; + private int threshold = 1; + public CodeTimer(String n) { name = n; enabled = true; - df.setMinimumIntegerDigits(5); } public boolean isEnabled() { @@ -52,55 +81,34 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } - public void start(String id) { + public void start(String id, Object... parameters) { if (!enabled) { return; } - int count = orderMap.size(); - orderMap.putIfAbsent(id, count); - Timer timer = timeMap.get(id); - if (timer == null) { - timer = new Timer(); - timeMap.put(id, timer); + if (parameters.length > 0) { + id = String.format(id, parameters); } + + Timer timer = timeMap.computeIfAbsent(id, key -> new Timer()); timer.start(); } - public void stop(String id) { + public void stop(String id, Object... parameters) { if (!enabled) { return; } - if (!orderMap.containsKey(id)) { - throw new IllegalArgumentException("Could not find orderMap id: " + id); - } - if (!timeMap.containsKey(id)) { - throw new IllegalArgumentException("Could not find timer id: " + id); + if (parameters.length > 0) { + id = String.format(id, parameters); } - timeMap.get(id).stop(); - } - public long getElapsed(String id) { - if (!enabled) { - return 0; - } - if (!orderMap.containsKey(id)) { - throw new IllegalArgumentException("Could not find orderMap id: " + id); - } - if (!timeMap.containsKey(id)) { + Timer timer = timeMap.get(id); + if (timer == null) { throw new IllegalArgumentException("Could not find timer id: " + id); } - return timeMap.get(id).getElapsed(); - } - - public void reset(String id) { - if (!orderMap.containsKey(id)) { - throw new IllegalArgumentException("Could not find orderMap id: " + id); - } - timeMap.remove(id); + timer.stop(); } public void clear() { - orderMap.clear(); timeMap.clear(); } @@ -112,19 +120,19 @@ public String toString() { .append("Timer ") .append(name) .append(" (") - .append(orderMap.size()) + .append(timeMap.size()) .append(" elements)\n"); - List idSet = new ArrayList(timeMap.keySet()); - idSet.sort((arg0, arg1) -> orderMap.get(arg0) - orderMap.get(arg1)); - for (String id : idSet) { - long elapsed = timeMap.get(id).getElapsed(); + var i = -1; + for (var entry : timeMap.entrySet()) { + ++i; + + var id = entry.getKey(); + long elapsed = entry.getValue().getElapsed(); if (elapsed < threshold) { continue; } - builder.append(String.format(" %3d. %6d ms %s\n", orderMap.get(id), elapsed, id)); - // builder.append("\t").append(orderMap.get(id)).append(". ").append(id).append(": - // ").append(timer.getElapsed()).append(" ms\n"); + builder.append(String.format(" %3d. %6d ms %s\n", i, elapsed, id)); } return builder.toString(); } diff --git a/src/main/java/net/rptools/lib/io/PackedFile.java b/src/main/java/net/rptools/lib/io/PackedFile.java index c622ad07dc..8d33a4c80d 100644 --- a/src/main/java/net/rptools/lib/io/PackedFile.java +++ b/src/main/java/net/rptools/lib/io/PackedFile.java @@ -52,8 +52,6 @@ import net.rptools.lib.CodeTimer; import net.rptools.lib.FileUtil; import net.rptools.lib.ModelVersionManager; -import net.rptools.maptool.client.AppState; -import net.rptools.maptool.client.MapTool; import net.rptools.maptool.model.Asset; import net.rptools.maptool.model.AssetManager; import net.rptools.maptool.model.GUID; @@ -286,128 +284,118 @@ public boolean isDirty() { } public void save() throws IOException { - CodeTimer saveTimer; - if (!dirty) { return; } - saveTimer = new CodeTimer("PackedFile.save"); - saveTimer.setEnabled(AppState.isCollectProfilingData()); - - // Create the new file - File newFile = new File(tmpDir, new GUID() + ".pak"); - ZipOutputStream zout = - new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(newFile))); - zout.setLevel(Deflater.BEST_COMPRESSION); // fast compression - try { - saveTimer.start(CONTENT_FILE); - if (hasFile(CONTENT_FILE)) { - saveEntry(zout, CONTENT_FILE); - } - saveTimer.stop(CONTENT_FILE); - saveTimer.start(PROPERTY_FILE); - if (getPropertyMap().isEmpty()) { - removeFile(PROPERTY_FILE); - } else { - zout.putNextEntry(new ZipEntry(PROPERTY_FILE)); - xstream.toXML(getPropertyMap(), zout); - zout.closeEntry(); - } - saveTimer.stop(PROPERTY_FILE); - - // Now put each file - saveTimer.start("addFiles"); - addedFileSet.remove(CONTENT_FILE); - for (String path : addedFileSet) { - saveEntry(zout, path); - } - saveTimer.stop("addFiles"); - - // Copy the rest of the zip entries over - saveTimer.start("copyFiles"); - if (file.exists()) { - Enumeration entries = zFile.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - if (!entry.isDirectory() - && !addedFileSet.contains(entry.getName()) - && !removedFileSet.contains(entry.getName()) - && !CONTENT_FILE.equals(entry.getName()) - && !PROPERTY_FILE.equals(entry.getName())) { - // if (entry.getName().endsWith(".png") || - // entry.getName().endsWith(".gif") || - // entry.getName().endsWith(".jpeg")) - // zout.setLevel(Deflater.NO_COMPRESSION); // none needed for images as they are already - // compressed - // else - // zout.setLevel(Deflater.BEST_COMPRESSION); // fast compression - zout.putNextEntry(entry); - try (InputStream is = getFileAsInputStream(entry.getName())) { - // When copying, always use an InputStream - IOUtils.copy(is, zout); + CodeTimer.using( + "PackedFile.save", + saveTimer -> { + // Create the new file + File newFile = new File(tmpDir, new GUID() + ".pak"); + ZipOutputStream zout = + new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(newFile))); + zout.setLevel(Deflater.BEST_COMPRESSION); // fast compression + + try { + saveTimer.start(CONTENT_FILE); + if (hasFile(CONTENT_FILE)) { + saveEntry(zout, CONTENT_FILE); } - zout.closeEntry(); - } else if (entry.isDirectory()) { - zout.putNextEntry(entry); - zout.closeEntry(); - } - } - } - try { - if (zFile != null) zFile.close(); - } catch (IOException e) { - // ignore close exception - } - zFile = null; - saveTimer.stop("copyFiles"); - - saveTimer.start("close"); - IOUtils.closeQuietly(zout); - zout = null; - saveTimer.stop("close"); - - // Backup the original - saveTimer.start("backup"); - File backupFile = new File(tmpDir, new GUID() + ".mv"); - if (file.exists()) { - backupFile.delete(); // Always delete the old backup file first; renameTo() is very - // platform-dependent - if (!file.renameTo(backupFile)) { - saveTimer.start("backup file"); - FileUtil.copyFile(file, backupFile); - file.delete(); - saveTimer.stop("backup file"); - } - } - saveTimer.stop("backup"); - - saveTimer.start("finalize"); - // Finalize - if (!newFile.renameTo(file)) { - saveTimer.start("backup newFile"); - FileUtil.copyFile(newFile, file); - saveTimer.stop("backup newFile"); - } - if (backupFile.exists()) backupFile.delete(); - saveTimer.stop("finalize"); + saveTimer.stop(CONTENT_FILE); - dirty = false; - } finally { - saveTimer.start("cleanup"); - try { - if (zFile != null) zFile.close(); - } catch (IOException e) { - // ignore close exception - } - if (newFile.exists()) newFile.delete(); - IOUtils.closeQuietly(zout); - saveTimer.stop("cleanup"); + saveTimer.start(PROPERTY_FILE); + if (getPropertyMap().isEmpty()) { + removeFile(PROPERTY_FILE); + } else { + zout.putNextEntry(new ZipEntry(PROPERTY_FILE)); + xstream.toXML(getPropertyMap(), zout); + zout.closeEntry(); + } + saveTimer.stop(PROPERTY_FILE); - if (saveTimer.isEnabled()) { - MapTool.getProfilingNoteFrame().addText(saveTimer.toString()); - } - } + // Now put each file + saveTimer.start("addFiles"); + addedFileSet.remove(CONTENT_FILE); + for (String path : addedFileSet) { + saveEntry(zout, path); + } + saveTimer.stop("addFiles"); + + // Copy the rest of the zip entries over + saveTimer.start("copyFiles"); + if (file.exists()) { + Enumeration entries = zFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (!entry.isDirectory() + && !addedFileSet.contains(entry.getName()) + && !removedFileSet.contains(entry.getName()) + && !CONTENT_FILE.equals(entry.getName()) + && !PROPERTY_FILE.equals(entry.getName())) { + zout.putNextEntry(entry); + try (InputStream is = getFileAsInputStream(entry.getName())) { + // When copying, always use an InputStream + IOUtils.copy(is, zout); + } + zout.closeEntry(); + } else if (entry.isDirectory()) { + zout.putNextEntry(entry); + zout.closeEntry(); + } + } + } + try { + if (zFile != null) zFile.close(); + } catch (IOException e) { + // ignore close exception + } + zFile = null; + saveTimer.stop("copyFiles"); + + saveTimer.start("close"); + IOUtils.closeQuietly(zout); + zout = null; + saveTimer.stop("close"); + + // Backup the original + saveTimer.start("backup"); + File backupFile = new File(tmpDir, new GUID() + ".mv"); + if (file.exists()) { + backupFile.delete(); // Always delete the old backup file first; renameTo() is very + // platform-dependent + if (!file.renameTo(backupFile)) { + saveTimer.start("backup file"); + FileUtil.copyFile(file, backupFile); + file.delete(); + saveTimer.stop("backup file"); + } + } + saveTimer.stop("backup"); + + saveTimer.start("finalize"); + // Finalize + if (!newFile.renameTo(file)) { + saveTimer.start("backup newFile"); + FileUtil.copyFile(newFile, file); + saveTimer.stop("backup newFile"); + } + if (backupFile.exists()) backupFile.delete(); + saveTimer.stop("finalize"); + + dirty = false; + } finally { + saveTimer.start("cleanup"); + try { + if (zFile != null) zFile.close(); + } catch (IOException e) { + // ignore close exception + } + if (newFile.exists()) newFile.delete(); + IOUtils.closeQuietly(zout); + saveTimer.stop("cleanup"); + } + }); } private void saveEntry(ZipOutputStream zout, String path) throws IOException { diff --git a/src/main/java/net/rptools/maptool/client/swing/ImagePanel.java b/src/main/java/net/rptools/maptool/client/swing/ImagePanel.java index b745603727..bb7b191c38 100644 --- a/src/main/java/net/rptools/maptool/client/swing/ImagePanel.java +++ b/src/main/java/net/rptools/maptool/client/swing/ImagePanel.java @@ -205,133 +205,132 @@ public boolean isOpaque() { @Override protected void paintComponent(Graphics gfx) { - var g = (Graphics2D) gfx; - CodeTimer timer = new CodeTimer("ImagePanel.paintComponent"); - timer.setEnabled(false); // Change this to turn on perf data to System.out + CodeTimer.using( + "ImagePanel.paintComponent", + timer -> { + timer.setEnabled(false); // Change this to turn on perf data - Rectangle clipBounds = g.getClipBounds(); - Dimension size = getSize(); - var savedFont = g.getFont(); - g.setFont(UIManager.getFont("Label.font")); - FontMetrics fm = g.getFontMetrics(); - fontHeight = fm.getHeight(); + var g = (Graphics2D) gfx; - g.setColor(getBackground()); - g.fillRect(0, 0, size.width, size.height); + Rectangle clipBounds = g.getClipBounds(); + Dimension size = getSize(); + var savedFont = g.getFont(); + g.setFont(UIManager.getFont("Label.font")); + FontMetrics fm = g.getFontMetrics(); + fontHeight = fm.getHeight(); - if (model == null) { - return; - } - imageBoundsMap.clear(); - - int itemsPerRow = calculateItemsPerRow(); - int itemWidth = getItemWidth(); - int itemHeight = getItemHeight(); - - int numToProcess = model.getImageCount(); - String timerField = null; - if (timer.isEnabled()) { - timerField = "time to process " + numToProcess + " images"; - timer.start(timerField); - } - for (int i = 0; i < numToProcess; i++) { - int row = i / itemsPerRow; - int column = i % itemsPerRow; - - int x = gridPadding.width + column * itemWidth; - int y = gridPadding.height + row * itemHeight; - - Image image; - - Rectangle bounds = new Rectangle(x, y, gridSize, gridSize); - imageBoundsMap.put( - new Rectangle(x, y, itemWidth - gridPadding.width, itemHeight - gridPadding.height), i); + g.setColor(getBackground()); + g.fillRect(0, 0, size.width, size.height); - // Background - Paint paint = model.getBackground(i); - if (paint != null) { - g.setPaint(paint); - g.fillRect(x - 2, y - 2, gridSize + 4, gridSize + 4); // bleed out a little - } - if (bounds.intersects(clipBounds)) { - image = model.getImage(i); - if (image != null) { - Dimension dim = constrainSize(image, gridSize); - var savedRenderingHints = g.getRenderingHints(); - if (dim.width < image.getWidth(null) || dim.height < image.getHeight(null)) { - AppPreferences.getRenderQuality().setShrinkRenderingHints(g); - } else if (dim.width > image.getWidth(null) || dim.height > image.getHeight(null)) { - AppPreferences.getRenderQuality().setRenderingHints(g); + if (model == null) { + return; } - g.drawImage( - image, - x + (gridSize - dim.width) / 2, - y + (gridSize - dim.height) / 2, - dim.width, - dim.height, - this); - - // Image border - g.setRenderingHints(savedRenderingHints); - if (showImageBorder) { - g.setColor(Color.black); - g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height); + imageBoundsMap.clear(); + + int itemsPerRow = calculateItemsPerRow(); + int itemWidth = getItemWidth(); + int itemHeight = getItemHeight(); + + int numToProcess = model.getImageCount(); + String timerField = null; + timer.start("time to process %d images", numToProcess); + for (int i = 0; i < numToProcess; i++) { + int row = i / itemsPerRow; + int column = i % itemsPerRow; + + int x = gridPadding.width + column * itemWidth; + int y = gridPadding.height + row * itemHeight; + + Image image; + + Rectangle bounds = new Rectangle(x, y, gridSize, gridSize); + imageBoundsMap.put( + new Rectangle(x, y, itemWidth - gridPadding.width, itemHeight - gridPadding.height), + i); + + // Background + Paint paint = model.getBackground(i); + if (paint != null) { + g.setPaint(paint); + g.fillRect(x - 2, y - 2, gridSize + 4, gridSize + 4); // bleed out a little + } + if (bounds.intersects(clipBounds)) { + image = model.getImage(i); + if (image != null) { + Dimension dim = constrainSize(image, gridSize); + var savedRenderingHints = g.getRenderingHints(); + if (dim.width < image.getWidth(null) || dim.height < image.getHeight(null)) { + AppPreferences.getRenderQuality().setShrinkRenderingHints(g); + } else if (dim.width > image.getWidth(null) || dim.height > image.getHeight(null)) { + AppPreferences.getRenderQuality().setRenderingHints(g); + } + g.drawImage( + image, + x + (gridSize - dim.width) / 2, + y + (gridSize - dim.height) / 2, + dim.width, + dim.height, + this); + + // Image border + g.setRenderingHints(savedRenderingHints); + if (showImageBorder) { + g.setColor(Color.black); + g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height); + } + } + } + // Selected + if (selectedIDList.contains(model.getID(i))) { + // TODO: Let the user pick the border + RessourceManager.getBorder(Borders.RED) + .paintAround(g, bounds.x, bounds.y, bounds.width, bounds.height); + } + // Decorations + Image[] decorations = model.getDecorations(i); + if (decorations != null) { + int offx = x; + int offy = y + gridSize; + int rowHeight = 0; + for (Image decoration : decorations) { + g.drawImage(decoration, offx, offy - decoration.getHeight(null), this); + + rowHeight = Math.max(rowHeight, decoration.getHeight(null)); + offx += decoration.getWidth(null); + if (offx > gridSize) { + offx = x; + offy -= rowHeight + 2; + rowHeight = 0; + } + } + } + // Caption + if (showCaptions) { + String caption = model.getCaption(i); + if (caption != null) { + boolean nameTooLong = false; + int strWidth = fm.stringWidth(caption); + if (strWidth > bounds.width) { + var avgCharWidth = (double) strWidth / caption.length(); + var fittableChars = (int) (bounds.width / avgCharWidth); + caption = String.format("%s...", caption.substring(0, fittableChars - 2)); + strWidth = fm.stringWidth(caption); + } + int cx = x + (gridSize - strWidth) / 2; + int cy = y + gridSize + fm.getHeight(); + + g.setColor(getForeground()); + var savedRenderingHints = g.getRenderingHints(); + g.setRenderingHint( + RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g.drawString(caption, cx, cy); + g.setRenderingHints(savedRenderingHints); + } + } } - } - } - // Selected - if (selectedIDList.contains(model.getID(i))) { - // TODO: Let the user pick the border - RessourceManager.getBorder(Borders.RED) - .paintAround(g, bounds.x, bounds.y, bounds.width, bounds.height); - } - // Decorations - Image[] decorations = model.getDecorations(i); - if (decorations != null) { - int offx = x; - int offy = y + gridSize; - int rowHeight = 0; - for (Image decoration : decorations) { - g.drawImage(decoration, offx, offy - decoration.getHeight(null), this); - - rowHeight = Math.max(rowHeight, decoration.getHeight(null)); - offx += decoration.getWidth(null); - if (offx > gridSize) { - offx = x; - offy -= rowHeight + 2; - rowHeight = 0; - } - } - } - // Caption - if (showCaptions) { - String caption = model.getCaption(i); - if (caption != null) { - boolean nameTooLong = false; - int strWidth = fm.stringWidth(caption); - if (strWidth > bounds.width) { - var avgCharWidth = (double) strWidth / caption.length(); - var fittableChars = (int) (bounds.width / avgCharWidth); - caption = String.format("%s...", caption.substring(0, fittableChars - 2)); - strWidth = fm.stringWidth(caption); - } - int cx = x + (gridSize - strWidth) / 2; - int cy = y + gridSize + fm.getHeight(); - - g.setColor(getForeground()); - var savedRenderingHints = g.getRenderingHints(); - g.setRenderingHint( - RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g.drawString(caption, cx, cy); - g.setRenderingHints(savedRenderingHints); - } - } - } - g.setFont(savedFont); - if (timer.isEnabled()) { - timer.stop(timerField); - System.out.println(timer); - } + g.setFont(savedFont); + timer.stop("time to process %d images", numToProcess); + }); } /** diff --git a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java index ac0e2f47f3..091741f7ec 100644 --- a/src/main/java/net/rptools/maptool/client/tool/PointerTool.java +++ b/src/main/java/net/rptools/maptool/client/tool/PointerTool.java @@ -1756,39 +1756,38 @@ && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet()) LinkedList lineLayouts = new LinkedList(); if (AppPreferences.getShowStatSheet() && new StatSheetManager().isLegacyStatSheet(tokenUnderMouse.getStatSheet())) { - CodeTimer timer = new CodeTimer("statSheet"); - timer.setEnabled(AppState.isCollectProfilingData()); - timer.setThreshold(5); - timer.start("allProps"); - for (TokenProperty property : - MapTool.getCampaign().getTokenPropertyList(tokenUnderMouse.getPropertyType())) { - if (property.isShowOnStatSheet()) { - if (property.isGMOnly() && !MapTool.getPlayer().isGM()) { - continue; - } - if (property.isOwnerOnly() && !AppUtil.playerOwns(tokenUnderMouse)) { - continue; - } - timer.start(property.getName()); - MapToolVariableResolver resolver = new MapToolVariableResolver(tokenUnderMouse); - resolver.initialize(); - resolver.setAutoPrompt(false); - Object propertyValue = - tokenUnderMouse.getEvaluatedProperty(resolver, property.getName()); - resolver.flush(); - if (propertyValue != null && propertyValue.toString().length() > 0) { - String propName = property.getShortName(); - if (StringUtils.isEmpty(propName)) propName = property.getName(); - propertyMap.put(propName, propertyValue.toString()); - } - timer.stop(property.getName()); - } - } - timer.stop("allProps"); - if (timer.isEnabled()) { - String results = timer.toString(); - MapTool.getProfilingNoteFrame().addText(results); - } + CodeTimer.using( + "statSheet", + timer -> { + timer.setThreshold(5); + + timer.start("allProps"); + for (TokenProperty property : + MapTool.getCampaign().getTokenPropertyList(tokenUnderMouse.getPropertyType())) { + if (property.isShowOnStatSheet()) { + if (property.isGMOnly() && !MapTool.getPlayer().isGM()) { + continue; + } + if (property.isOwnerOnly() && !AppUtil.playerOwns(tokenUnderMouse)) { + continue; + } + timer.start(property.getName()); + MapToolVariableResolver resolver = new MapToolVariableResolver(tokenUnderMouse); + resolver.initialize(); + resolver.setAutoPrompt(false); + Object propertyValue = + tokenUnderMouse.getEvaluatedProperty(resolver, property.getName()); + resolver.flush(); + if (propertyValue != null && propertyValue.toString().length() > 0) { + String propName = property.getShortName(); + if (StringUtils.isEmpty(propName)) propName = property.getName(); + propertyMap.put(propName, propertyValue.toString()); + } + timer.stop(property.getName()); + } + } + timer.stop("allProps"); + }); } if (tokenUnderMouse.getPortraitImage() != null || !propertyMap.isEmpty()) { Font font = AppStyle.labelFont; diff --git a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/panels/SelectionPanel.java b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/panels/SelectionPanel.java index 853e858278..00f4d26245 100644 --- a/src/main/java/net/rptools/maptool/client/ui/macrobuttons/panels/SelectionPanel.java +++ b/src/main/java/net/rptools/maptool/client/ui/macrobuttons/panels/SelectionPanel.java @@ -19,7 +19,6 @@ import java.util.*; import javax.swing.SwingUtilities; import net.rptools.lib.CodeTimer; -import net.rptools.maptool.client.AppState; import net.rptools.maptool.client.AppUtil; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.ui.MapToolFrame; @@ -43,7 +42,6 @@ public class SelectionPanel extends AbstractMacroPanel { private static final Logger log = LogManager.getLogger(SelectionPanel.class); private List commonMacros = new ArrayList(); - private CodeTimer timer; public SelectionPanel() { // TODO: refactoring reminder @@ -68,49 +66,48 @@ public void init() { } public void init(List selectedTokenList) { - boolean panelVisible = true; - - if (MapTool.getFrame() != null) { - DockableFrame selectionPanel = MapTool.getFrame().getDockingManager().getFrame("SELECTION"); - if (selectionPanel != null) - panelVisible = - (selectionPanel.isVisible() && !selectionPanel.isAutohide()) - || selectionPanel.isAutohideShowing(); - } // Set up a code timer to get some performance data - timer = new CodeTimer("selectionpanel"); - timer.setEnabled(AppState.isCollectProfilingData()); - timer.setThreshold(10); - - timer.start("painting"); - - // paint panel only when it's visible or active - if (panelVisible) { - - // draw common group only when there is more than one token selected - if (selectedTokenList.size() > 1) { - populateCommonButtons(selectedTokenList); - addArea(commonMacros, I18N.getText("component.areaGroup.macro.commonMacros")); - // add(new ButtonGroup(selectedTokenList, commonMacros, this)); - } - for (Token token : selectedTokenList) { - if (!AppUtil.playerOwns(token)) { - continue; - } - addArea(token.getId()); - } - if (selectedTokenList.size() == 1 && AppUtil.playerOwns(selectedTokenList.get(0))) { - // if only one token selected, show its image as tab icon - MapTool.getFrame() - .getFrame(MTFrame.SELECTION) - .setFrameIcon(selectedTokenList.get(0).getIcon(16, 16)); - } - } - timer.stop("painting"); + CodeTimer.using( + "selectionpanel", + timer -> { + timer.setThreshold(10); + + boolean panelVisible = true; + if (MapTool.getFrame() != null) { + DockableFrame selectionPanel = + MapTool.getFrame().getDockingManager().getFrame("SELECTION"); + if (selectionPanel != null) + panelVisible = + (selectionPanel.isVisible() && !selectionPanel.isAutohide()) + || selectionPanel.isAutohideShowing(); + } - if (timer.isEnabled()) { - MapTool.getProfilingNoteFrame().addText(timer.toString()); - } + timer.start("painting"); + + // paint panel only when it's visible or active + if (panelVisible) { + + // draw common group only when there is more than one token selected + if (selectedTokenList.size() > 1) { + populateCommonButtons(selectedTokenList); + addArea(commonMacros, I18N.getText("component.areaGroup.macro.commonMacros")); + // add(new ButtonGroup(selectedTokenList, commonMacros, this)); + } + for (Token token : selectedTokenList) { + if (!AppUtil.playerOwns(token)) { + continue; + } + addArea(token.getId()); + } + if (selectedTokenList.size() == 1 && AppUtil.playerOwns(selectedTokenList.get(0))) { + // if only one token selected, show its image as tab icon + MapTool.getFrame() + .getFrame(MTFrame.SELECTION) + .setFrameIcon(selectedTokenList.get(0).getIcon(16, 16)); + } + } + timer.stop("painting"); + }); } @Subscribe diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/FogUtil.java b/src/main/java/net/rptools/maptool/client/ui/zone/FogUtil.java index 214163066a..4f33fc5574 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/FogUtil.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/FogUtil.java @@ -509,82 +509,82 @@ public static void restoreFoW(final ZoneRenderer renderer) { } public static void exposeLastPath(final ZoneRenderer renderer, final Set tokenSet) { - CodeTimer timer = new CodeTimer("exposeLastPath"); - - final Zone zone = renderer.getZone(); - final Grid grid = zone.getGrid(); - GridCapabilities caps = grid.getCapabilities(); - - if (!caps.isPathingSupported() || !caps.isSnapToGridSupported()) { - return; - } - - final Set filteredToks = new HashSet(2); - - for (final GUID tokenGUID : tokenSet) { - final Token token = zone.getToken(tokenGUID); - timer.start("exposeLastPath-" + token.getName()); - - Path lastPath = token.getLastPath(); - - if (lastPath == null) return; - - Map fullMeta = zone.getExposedAreaMetaData(); - GUID exposedGUID = token.getExposedAreaGUID(); - final ExposedAreaMetaData meta = - fullMeta.computeIfAbsent(exposedGUID, guid -> new ExposedAreaMetaData()); - - final Token tokenClone = new Token(token); - final ZoneView zoneView = renderer.getZoneView(); - Area visionArea = new Area(); - - // Lee: get path according to zone's way point exposure toggle... - List processPath = - zone.getWaypointExposureToggle() ? lastPath.getWayPointList() : lastPath.getCellPath(); - - int stepCount = processPath.size(); - log.debug("Path size = " + stepCount); - - Consumer revealAt = - zp -> { - tokenClone.setX(zp.x); - tokenClone.setY(zp.y); + CodeTimer.using( + "exposeLastPath", + timer -> { + final Zone zone = renderer.getZone(); + final Grid grid = zone.getGrid(); + GridCapabilities caps = grid.getCapabilities(); + + if (!caps.isPathingSupported() || !caps.isSnapToGridSupported()) { + return; + } - Area currVisionArea = zoneView.getVisibleArea(tokenClone, renderer.getPlayerView()); - if (currVisionArea != null) { - visionArea.add(currVisionArea); - meta.addToExposedAreaHistory(currVisionArea); + final Set filteredToks = new HashSet(2); + + for (final GUID tokenGUID : tokenSet) { + final Token token = zone.getToken(tokenGUID); + timer.start("exposeLastPath-" + token.getName()); + + Path lastPath = token.getLastPath(); + + if (lastPath == null) return; + + Map fullMeta = zone.getExposedAreaMetaData(); + GUID exposedGUID = token.getExposedAreaGUID(); + final ExposedAreaMetaData meta = + fullMeta.computeIfAbsent(exposedGUID, guid -> new ExposedAreaMetaData()); + + final Token tokenClone = new Token(token); + final ZoneView zoneView = renderer.getZoneView(); + Area visionArea = new Area(); + + // Lee: get path according to zone's way point exposure toggle... + List processPath = + zone.getWaypointExposureToggle() + ? lastPath.getWayPointList() + : lastPath.getCellPath(); + + int stepCount = processPath.size(); + log.debug("Path size = " + stepCount); + + Consumer revealAt = + zp -> { + tokenClone.setX(zp.x); + tokenClone.setY(zp.y); + + Area currVisionArea = + zoneView.getVisibleArea(tokenClone, renderer.getPlayerView()); + if (currVisionArea != null) { + visionArea.add(currVisionArea); + meta.addToExposedAreaHistory(currVisionArea); + } + + zoneView.flush(tokenClone); + }; + if (token.isSnapToGrid()) { + // For each cell point along the path, reveal FoW. + for (final AbstractPoint cell : processPath) { + assert cell instanceof CellPoint; + revealAt.accept(grid.convert((CellPoint) cell)); + } + } else { + // Only reveal the final position. + final AbstractPoint finalCell = processPath.get(processPath.size() - 1); + assert finalCell instanceof ZonePoint; + revealAt.accept((ZonePoint) finalCell); } - zoneView.flush(tokenClone); - }; - if (token.isSnapToGrid()) { - // For each cell point along the path, reveal FoW. - for (final AbstractPoint cell : processPath) { - assert cell instanceof CellPoint; - revealAt.accept(grid.convert((CellPoint) cell)); - } - } else { - // Only reveal the final position. - final AbstractPoint finalCell = processPath.get(processPath.size() - 1); - assert finalCell instanceof ZonePoint; - revealAt.accept((ZonePoint) finalCell); - } - - timer.stop("exposeLastPath-" + token.getName()); - renderer.flush(tokenClone); + timer.stop("exposeLastPath-" + token.getName()); + renderer.flush(tokenClone); - filteredToks.clear(); - filteredToks.add(token.getId()); - zone.putToken(token); - MapTool.serverCommand().exposeFoW(zone.getId(), visionArea, filteredToks); - MapTool.serverCommand().updateExposedAreaMeta(zone.getId(), exposedGUID, meta); - } - - String results = timer.toString(); - MapTool.getProfilingNoteFrame().addText(results); - // System.out.println(results); - timer.clear(); + filteredToks.clear(); + filteredToks.add(token.getId()); + zone.putToken(token); + MapTool.serverCommand().exposeFoW(zone.getId(), visionArea, filteredToks); + MapTool.serverCommand().updateExposedAreaMeta(zone.getId(), exposedGUID, meta); + } + }); } /** diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/PartitionedDrawableRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/PartitionedDrawableRenderer.java index 248d9fddba..72b33859b3 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/PartitionedDrawableRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/PartitionedDrawableRenderer.java @@ -56,8 +56,6 @@ public class PartitionedDrawableRenderer implements DrawableRenderer { private boolean dirty = false; - private CodeTimer timer; - public void flush() { int unusedSize = unusedChunkList.size(); for (Tuple tuple : chunkList) { @@ -78,115 +76,113 @@ public void setDirty() { public void renderDrawables( Graphics g, List drawableList, Rectangle viewport, double scale) { - timer = new CodeTimer("Renderer"); - timer.setThreshold(10); - timer.setEnabled(false); - - // NOTHING TO DO - if (drawableList == null || drawableList.isEmpty()) { - if (dirty) flush(); - return; - } - // View changed ? - if (dirty || lastScale != scale) { - flush(); - } - if (lastViewport == null - || viewport.width != lastViewport.width - || viewport.height != lastViewport.height) { - horizontalChunkCount = (int) Math.ceil(viewport.width / (double) CHUNK_SIZE) + 1; - verticalChunkCount = (int) Math.ceil(viewport.height / (double) CHUNK_SIZE) + 1; - - maxChunks = (horizontalChunkCount * verticalChunkCount * 2); - } - // Compute grid - int gridx = (int) Math.floor(-viewport.x / (double) CHUNK_SIZE); - int gridy = (int) Math.floor(-viewport.y / (double) CHUNK_SIZE); - - // OK, weirdest hack ever. Basically, when the viewport.x is exactly divisible by the chunk - // size, the gridx decrements - // too early, creating a visual jump in the drawables. I don't know the exact cause, but this - // seems to account for it - // note that it only happens in the negative space. Weird. - gridx += (viewport.x > CHUNK_SIZE && (viewport.x % CHUNK_SIZE == 0) ? -1 : 0); - gridy += (viewport.y > CHUNK_SIZE && (viewport.y % CHUNK_SIZE == 0) ? -1 : 0); - - for (int row = 0; row < verticalChunkCount; row++) { - for (int col = 0; col < horizontalChunkCount; col++) { - int cellX = gridx + col; - int cellY = gridy + row; - - String key = getKey(cellX, cellY); - if (noImageSet.contains(key)) { - continue; - } - Tuple chunk = findChunk(chunkList, key); - if (chunk == null) { - chunk = new Tuple(key, createChunk(drawableList, cellX, cellY, scale)); - - if (chunk.image == null) { - noImageSet.add(key); - continue; + CodeTimer.using( + "Renderer", + timer -> { + timer.setThreshold(10); + timer.setEnabled(false); + + // NOTHING TO DO + if (drawableList == null || drawableList.isEmpty()) { + if (dirty) flush(); + return; } - } - // Most recently used is at the front - chunkList.add(0, chunk); - - // Trim to the right size - if (chunkList.size() > maxChunks) { - int chunkSize = chunkList.size(); - // chunkList.subList(maxChunks, chunkSize).clear(); - while (chunkSize > maxChunks) { - chunkList.remove(--chunkSize); + // View changed ? + if (dirty || lastScale != scale) { + flush(); } - } - int x = - col * CHUNK_SIZE - - ((CHUNK_SIZE - viewport.x)) % CHUNK_SIZE - - (gridx < -1 ? CHUNK_SIZE : 0); - int y = - row * CHUNK_SIZE - - ((CHUNK_SIZE - viewport.y)) % CHUNK_SIZE - - (gridy < -1 ? CHUNK_SIZE : 0); - - timer.start("render:DrawImage"); - g.drawImage(chunk.image, x, y, null); - timer.stop("render:DrawImage"); - - // DEBUG: Show partition boundaries - if (DeveloperOptions.Toggle.ShowPartitionDrawableBoundaries.isEnabled()) { - if (!messageLogged) { - messageLogged = true; - log.debug( - "DEBUG logging of " - + this.getClass().getSimpleName() - + " causes colored rectangles and message strings."); + if (lastViewport == null + || viewport.width != lastViewport.width + || viewport.height != lastViewport.height) { + horizontalChunkCount = (int) Math.ceil(viewport.width / (double) CHUNK_SIZE) + 1; + verticalChunkCount = (int) Math.ceil(viewport.height / (double) CHUNK_SIZE) + 1; + + maxChunks = (horizontalChunkCount * verticalChunkCount * 2); } - if (col % 2 == 0) { - if (row % 2 == 0) { - g.setColor(Color.white); - } else { - g.setColor(Color.green); - } - } else { - if (row % 2 == 0) { - g.setColor(Color.green); - } else { - g.setColor(Color.white); + // Compute grid + int gridx = (int) Math.floor(-viewport.x / (double) CHUNK_SIZE); + int gridy = (int) Math.floor(-viewport.y / (double) CHUNK_SIZE); + + // OK, weirdest hack ever. Basically, when the viewport.x is exactly divisible by the + // chunk size, the gridx decrements too early, creating a visual jump in the drawables. I + // don't know the exact cause, but this seems to account for it + // note that it only happens in the negative space. Weird. + gridx += (viewport.x > CHUNK_SIZE && (viewport.x % CHUNK_SIZE == 0) ? -1 : 0); + gridy += (viewport.y > CHUNK_SIZE && (viewport.y % CHUNK_SIZE == 0) ? -1 : 0); + + for (int row = 0; row < verticalChunkCount; row++) { + for (int col = 0; col < horizontalChunkCount; col++) { + int cellX = gridx + col; + int cellY = gridy + row; + + String key = getKey(cellX, cellY); + if (noImageSet.contains(key)) { + continue; + } + Tuple chunk = findChunk(chunkList, key); + if (chunk == null) { + chunk = new Tuple(key, createChunk(drawableList, cellX, cellY, scale)); + + if (chunk.image == null) { + noImageSet.add(key); + continue; + } + } + // Most recently used is at the front + chunkList.add(0, chunk); + + // Trim to the right size + if (chunkList.size() > maxChunks) { + int chunkSize = chunkList.size(); + // chunkList.subList(maxChunks, chunkSize).clear(); + while (chunkSize > maxChunks) { + chunkList.remove(--chunkSize); + } + } + int x = + col * CHUNK_SIZE + - ((CHUNK_SIZE - viewport.x)) % CHUNK_SIZE + - (gridx < -1 ? CHUNK_SIZE : 0); + int y = + row * CHUNK_SIZE + - ((CHUNK_SIZE - viewport.y)) % CHUNK_SIZE + - (gridy < -1 ? CHUNK_SIZE : 0); + + timer.start("render:DrawImage"); + g.drawImage(chunk.image, x, y, null); + timer.stop("render:DrawImage"); + + // DEBUG: Show partition boundaries + if (DeveloperOptions.Toggle.ShowPartitionDrawableBoundaries.isEnabled()) { + if (!messageLogged) { + messageLogged = true; + log.debug( + "DEBUG logging of " + + this.getClass().getSimpleName() + + " causes colored rectangles and message strings."); + } + if (col % 2 == 0) { + if (row % 2 == 0) { + g.setColor(Color.white); + } else { + g.setColor(Color.green); + } + } else { + if (row % 2 == 0) { + g.setColor(Color.green); + } else { + g.setColor(Color.white); + } + } + g.drawRect(x, y, CHUNK_SIZE - 1, CHUNK_SIZE - 1); + g.drawString(key, x + CHUNK_SIZE / 2, y + CHUNK_SIZE / 2); + } } } - g.drawRect(x, y, CHUNK_SIZE - 1, CHUNK_SIZE - 1); - g.drawString(key, x + CHUNK_SIZE / 2, y + CHUNK_SIZE / 2); - } - } - } - // REMEMBER - lastViewport = viewport; - lastScale = scale; - - if (timer.isEnabled()) { - // System.out.println(timer); - } + // REMEMBER + lastViewport = viewport; + lastScale = scale; + }); } /** @@ -210,6 +206,8 @@ private Tuple findChunk(List list, String key) { private BufferedImage createChunk( List drawableList, int gridx, int gridy, double scale) { + final var timer = CodeTimer.get(); + int x = gridx * CHUNK_SIZE; int y = gridy * CHUNK_SIZE; diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/TokenLocation.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/TokenLocation.java index 7a1a33dfe2..1f95f76940 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/TokenLocation.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/TokenLocation.java @@ -17,6 +17,7 @@ import java.awt.*; import java.awt.geom.Area; import java.awt.geom.Rectangle2D; +import net.rptools.lib.CodeTimer; import net.rptools.maptool.model.Token; class TokenLocation { @@ -81,12 +82,12 @@ public boolean maybeOnscreen(Rectangle viewport) { offsetX = renderer.getViewOffsetX(); offsetY = renderer.getViewOffsetY(); - renderer.timer.start("maybeOnsceen"); - if (!boundsCache.intersects(viewport)) { - renderer.timer.stop("maybeOnsceen"); - return false; + final var timer = CodeTimer.get(); + timer.start("maybeOnsceen"); + try { + return boundsCache.intersects(viewport); + } finally { + timer.stop("maybeOnsceen"); } - renderer.timer.stop("maybeOnsceen"); - return true; } } diff --git a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java index f2ab8d1baa..71cba559db 100644 --- a/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java +++ b/src/main/java/net/rptools/maptool/client/ui/zone/renderer/ZoneRenderer.java @@ -144,7 +144,6 @@ public class ZoneRenderer extends JComponent private final List itemRenderList = new LinkedList(); private PlayerView lastView; private Set visibleTokenSet = new HashSet<>(); - CodeTimer timer; private boolean autoResizeStamp = false; @@ -385,7 +384,6 @@ public void commitMoveSelectionSet(GUID keyTokenId) { removeMoveSelectionSet(keyTokenId); MapTool.serverCommand().stopTokenMove(getZone().getId(), keyTokenId); Token keyToken = zone.getToken(keyTokenId); - boolean topologyTokenMoved = false; // If any token has topology we need to reset FoW /* * Lee: if the lead token is snapped-to-grid and has not moved, every follower should return to where they were. Flag set at PointerTool and StampTool's stopTokenDrag() Handling the rest here. @@ -403,148 +401,146 @@ public void commitMoveSelectionSet(GUID keyTokenId) { // Lee: check only matters for snap-to-grid if (stg) { - CodeTimer moveTimer = new CodeTimer("ZoneRenderer.commitMoveSelectionSet"); - moveTimer.setEnabled(AppState.isCollectProfilingData()); - moveTimer.setThreshold(1); + CodeTimer.using( + "ZoneRenderer.commitMoveSelectionSet", + moveTimer -> { + moveTimer.setThreshold(1); - moveTimer.start("setup"); + moveTimer.start("setup"); - // Lee: the 1st of evils. changing it to handle proper computation - // for a key token's snapped state - AbstractPoint originPoint, tokenCell; - if (keyToken.isSnapToGrid()) { - originPoint = zone.getGrid().convert(new ZonePoint(keyToken.getX(), keyToken.getY())); - } else { - originPoint = new ZonePoint(keyToken.getX(), keyToken.getY()); - } + boolean topologyTokenMoved = false; // If any token has topology we need to reset FoW - Path path = - set.getWalker() != null ? set.getWalker().getPath() : set.gridlessPath; - // Jamz: add final path render here? + // Lee: the 1st of evils. changing it to handle proper computation + // for a key token's snapped state + AbstractPoint originPoint, tokenCell; + if (keyToken.isSnapToGrid()) { + originPoint = zone.getGrid().convert(new ZonePoint(keyToken.getX(), keyToken.getY())); + } else { + originPoint = new ZonePoint(keyToken.getX(), keyToken.getY()); + } - List filteredTokens = new ArrayList(); - moveTimer.stop("setup"); + Path path = + set.getWalker() != null ? set.getWalker().getPath() : set.gridlessPath; + // Jamz: add final path render here? - int offsetX, offsetY; + List filteredTokens = new ArrayList(); + moveTimer.stop("setup"); - moveTimer.start("eachtoken"); - for (GUID tokenGUID : selectionSet) { - Token token = zone.getToken(tokenGUID); - // If the token has been deleted, the GUID will still be in the - // set but getToken() will return null. - if (token == null) { - continue; - } + int offsetX, offsetY; - // Lee: get offsets based on key token's snapped state - if (token.isSnapToGrid()) { - tokenCell = zone.getGrid().convert(new ZonePoint(token.getX(), token.getY())); - } else { - tokenCell = new ZonePoint(token.getX(), token.getY()); - } + moveTimer.start("eachtoken"); + for (GUID tokenGUID : selectionSet) { + Token token = zone.getToken(tokenGUID); + // If the token has been deleted, the GUID will still be in the + // set but getToken() will return null. + if (token == null) { + continue; + } - int cellOffX, cellOffY; - if (token.isSnapToGrid() == keyToken.isSnapToGrid()) { - cellOffX = originPoint.x - tokenCell.x; - cellOffY = originPoint.y - tokenCell.y; - } else { - cellOffX = cellOffY = 0; // not used unless both are of same SnapToGrid - } + // Lee: get offsets based on key token's snapped state + if (token.isSnapToGrid()) { + tokenCell = zone.getGrid().convert(new ZonePoint(token.getX(), token.getY())); + } else { + tokenCell = new ZonePoint(token.getX(), token.getY()); + } - if (token.isSnapToGrid() - && (!AppPreferences.getTokensSnapWhileDragging() || !keyToken.isSnapToGrid())) { - // convert to Cellpoint and back to ensure token ends up at correct X and Y - CellPoint cellEnd = - zone.getGrid() - .convert( - new ZonePoint( - token.getX() + set.getOffsetX(), token.getY() + set.getOffsetY())); - ZonePoint pointEnd = cellEnd.convertToZonePoint(zone.getGrid()); - offsetX = pointEnd.x - token.getX(); - offsetY = pointEnd.y - token.getY(); - } else { - offsetX = set.getOffsetX(); - offsetY = set.getOffsetY(); - } + int cellOffX, cellOffY; + if (token.isSnapToGrid() == keyToken.isSnapToGrid()) { + cellOffX = originPoint.x - tokenCell.x; + cellOffY = originPoint.y - tokenCell.y; + } else { + cellOffX = cellOffY = 0; // not used unless both are of same SnapToGrid + } - /* - * Lee: the problem now is to keep the precise coordinate computations for unsnapped tokens following a snapped key token. The derived path in the following section contains rounded - * down values because the integer cell values were passed. If these were double in nature, the precision would be kept, but that would be too difficult to change at this stage... - */ + if (token.isSnapToGrid() + && (!AppPreferences.getTokensSnapWhileDragging() || !keyToken.isSnapToGrid())) { + // convert to Cellpoint and back to ensure token ends up at correct X and Y + CellPoint cellEnd = + zone.getGrid() + .convert( + new ZonePoint( + token.getX() + set.getOffsetX(), token.getY() + set.getOffsetY())); + ZonePoint pointEnd = cellEnd.convertToZonePoint(zone.getGrid()); + offsetX = pointEnd.x - token.getX(); + offsetY = pointEnd.y - token.getY(); + } else { + offsetX = set.getOffsetX(); + offsetY = set.getOffsetY(); + } - token.applyMove(set, path, offsetX, offsetY, keyToken, cellOffX, cellOffY); + /* + * Lee: the problem now is to keep the precise coordinate computations for unsnapped tokens following a snapped key token. The derived path in the following section contains rounded + * down values because the integer cell values were passed. If these were double in nature, the precision would be kept, but that would be too difficult to change at this stage... + */ - // Lee: setting originPoint to landing point - token.setOriginPoint(new ZonePoint(token.getX(), token.getY())); + token.applyMove(set, path, offsetX, offsetY, keyToken, cellOffX, cellOffY); - flush(token); - MapTool.serverCommand().putToken(zone.getId(), token); + // Lee: setting originPoint to landing point + token.setOriginPoint(new ZonePoint(token.getX(), token.getY())); - // No longer need this version - // Lee: redundant flush() already did this above - // replacementImageMap.remove(token); + flush(token); + MapTool.serverCommand().putToken(zone.getId(), token); - // Only add certain tokens to the list to process in the move - // Macro function(s). - if (token.getLayer().supportsWalker() && token.isVisible()) { - filteredTokens.add(tokenGUID); - } + // No longer need this version + // Lee: redundant flush() already did this above + // replacementImageMap.remove(token); - if (token.hasAnyTopology()) { - topologyTokenMoved = true; - } + // Only add certain tokens to the list to process in the move + // Macro function(s). + if (token.getLayer().supportsWalker() && token.isVisible()) { + filteredTokens.add(tokenGUID); + } - // renderPath((Graphics2D) this.getGraphics(), path, token.getFootprint(zone.getGrid())); - } - moveTimer.stop("eachtoken"); - - moveTimer.start("onTokenMove"); - if (!filteredTokens.isEmpty()) { - // give onTokenMove a chance to reject each token's movement. - // pass in all the tokens at once so it doesn't have to re-scan for handlers for each token - List tokensToCheck = - filteredTokens.stream().map(zone::getToken).collect(Collectors.toList()); - List tokensDenied = - TokenMoveFunctions.callForIndividualTokenMoveVetoes(path, tokensToCheck); - for (Token token : tokensDenied) { - denyMovement(token); - } - } - moveTimer.stop("onTokenMove"); - - moveTimer.start("onMultipleTokensMove"); - // Multiple tokens, the list of tokens and call - // onMultipleTokensMove() macro function. - if (filteredTokens.size() > 1) { - // now determine if the macro returned false and if so - // revert each token's move to the last path. - boolean moveDenied = TokenMoveFunctions.callForMultiTokenMoveVeto(filteredTokens); - if (moveDenied) { - for (GUID tokenGUID : filteredTokens) { - Token token = zone.getToken(tokenGUID); - denyMovement(token); - } - } - } - moveTimer.stop("onMultipleTokensMove"); + if (token.hasAnyTopology()) { + topologyTokenMoved = true; + } + } + moveTimer.stop("eachtoken"); + + moveTimer.start("onTokenMove"); + if (!filteredTokens.isEmpty()) { + // give onTokenMove a chance to reject each token's movement. + // pass in all the tokens at once so it doesn't have to re-scan for handlers for each + // token + List tokensToCheck = + filteredTokens.stream().map(zone::getToken).collect(Collectors.toList()); + List tokensDenied = + TokenMoveFunctions.callForIndividualTokenMoveVetoes(path, tokensToCheck); + for (Token token : tokensDenied) { + denyMovement(token); + } + } + moveTimer.stop("onTokenMove"); + + moveTimer.start("onMultipleTokensMove"); + // Multiple tokens, the list of tokens and call + // onMultipleTokensMove() macro function. + if (filteredTokens.size() > 1) { + // now determine if the macro returned false and if so + // revert each token's move to the last path. + boolean moveDenied = TokenMoveFunctions.callForMultiTokenMoveVeto(filteredTokens); + if (moveDenied) { + for (GUID tokenGUID : filteredTokens) { + Token token = zone.getToken(tokenGUID); + denyMovement(token); + } + } + } + moveTimer.stop("onMultipleTokensMove"); - moveTimer.start("updateTokenTree"); - MapTool.getFrame().updateTokenTree(); - moveTimer.stop("updateTokenTree"); + moveTimer.start("updateTokenTree"); + MapTool.getFrame().updateTokenTree(); + moveTimer.stop("updateTokenTree"); - if (moveTimer.isEnabled()) { - MapTool.getProfilingNoteFrame().addText(moveTimer.toString()); - moveTimer.clear(); - } + if (topologyTokenMoved) { + zone.tokenTopologyChanged(); + } + }); } else { for (GUID tokenGUID : selectionSet) { denyMovement(zone.getToken(tokenGUID)); } } - - if (topologyTokenMoved) { - zone.tokenTopologyChanged(); - } } /** @@ -784,57 +780,53 @@ public BufferedImage getMiniImage(int size) { @Override public void paintComponent(Graphics g) { - if (timer == null) { - timer = new CodeTimer("ZoneRenderer.renderZone"); - } - timer.setEnabled(AppState.isCollectProfilingData()); - timer.clear(); - timer.setThreshold(10); - timer.start("paintComponent"); - - Graphics2D g2d = (Graphics2D) g; - - timer.start("paintComponent:allocateBuffer"); - tempBufferPool.setWidth(getSize().width); - tempBufferPool.setHeight(getSize().height); - tempBufferPool.setConfiguration(g2d.getDeviceConfiguration()); - timer.stop("paintComponent:allocateBuffer"); - - try (final var bufferHandle = tempBufferPool.acquire()) { - final var buffer = bufferHandle.get(); - final var bufferG2d = buffer.createGraphics(); - // Keep the clip so we don't render more than we have to. - bufferG2d.setClip(g2d.getClip()); - - timer.start("paintComponent:createView"); - PlayerView pl = getPlayerView(); - timer.stop("paintComponent:createView"); - - renderZone(bufferG2d, pl); - int noteVPos = 20; - if (MapTool.getFrame().areFullScreenToolsShown()) noteVPos += 40; - - if (!AppPreferences.getMapVisibilityWarning() && (!zone.isVisible() && pl.isGMView())) { - GraphicsUtil.drawBoxedString( - bufferG2d, I18N.getText("zone.map_not_visible"), getSize().width / 2, noteVPos); - noteVPos += 20; - } - if (AppState.isShowAsPlayer()) { - GraphicsUtil.drawBoxedString( - bufferG2d, I18N.getText("zone.player_view"), getSize().width / 2, noteVPos); - } + CodeTimer.using( + "ZoneRenderer.renderZone", + timer -> { + timer.setThreshold(10); + + timer.start("paintComponent"); + + Graphics2D g2d = (Graphics2D) g; + + timer.start("paintComponent:allocateBuffer"); + tempBufferPool.setWidth(getSize().width); + tempBufferPool.setHeight(getSize().height); + tempBufferPool.setConfiguration(g2d.getDeviceConfiguration()); + timer.stop("paintComponent:allocateBuffer"); + + try (final var bufferHandle = tempBufferPool.acquire()) { + final var buffer = bufferHandle.get(); + final var bufferG2d = buffer.createGraphics(); + // Keep the clip so we don't render more than we have to. + bufferG2d.setClip(g2d.getClip()); + + timer.start("paintComponent:createView"); + PlayerView pl = getPlayerView(); + timer.stop("paintComponent:createView"); + + renderZone(bufferG2d, pl); + int noteVPos = 20; + if (MapTool.getFrame().areFullScreenToolsShown()) noteVPos += 40; + + if (!AppPreferences.getMapVisibilityWarning() && (!zone.isVisible() && pl.isGMView())) { + GraphicsUtil.drawBoxedString( + bufferG2d, I18N.getText("zone.map_not_visible"), getSize().width / 2, noteVPos); + noteVPos += 20; + } + if (AppState.isShowAsPlayer()) { + GraphicsUtil.drawBoxedString( + bufferG2d, I18N.getText("zone.player_view"), getSize().width / 2, noteVPos); + } - timer.start("paintComponent:renderBuffer"); - bufferG2d.dispose(); - g2d.drawImage(buffer, null, 0, 0); - timer.stop("paintComponent:renderBuffer"); - } + timer.start("paintComponent:renderBuffer"); + bufferG2d.dispose(); + g2d.drawImage(buffer, null, 0, 0); + timer.stop("paintComponent:renderBuffer"); + } - timer.stop("paintComponent"); - if (timer.isEnabled()) { - MapTool.getProfilingNoteFrame().addText(timer.toString()); - timer.clear(); - } + timer.stop("paintComponent"); + }); } public PlayerView getPlayerView() { @@ -913,6 +905,8 @@ private boolean shouldRenderLayer(Layer layer, PlayerView view) { * @param view PlayerView object that describes whether the view is a Player or GM view */ public void renderZone(Graphics2D g2d, PlayerView view) { + final var timer = CodeTimer.get(); + timer.start("setup"); // store previous rendering settings RenderingHints oldRenderingHints = g2d.getRenderingHints(); @@ -1211,15 +1205,9 @@ public void renderZone(Graphics2D g2d, PlayerView view) { } timer.start("overlays"); for (ZoneOverlay overlay : overlayList) { - String msg = null; - if (timer.isEnabled()) { - msg = "overlays:" + overlay.getClass().getSimpleName(); - timer.start(msg); - } + timer.start("overlays: %s", overlay.getClass().getSimpleName()); overlay.paintOverlay(this, g2d); - if (timer.isEnabled()) { - timer.stop(msg); - } + timer.stop("overlays: %s", overlay.getClass().getSimpleName()); } timer.stop("overlays"); @@ -1277,6 +1265,8 @@ private void renderRenderables(Graphics2D g) { * @param view the player view */ private void renderLights(Graphics2D g, PlayerView view) { + final var timer = CodeTimer.get(); + // Collect and organize lights timer.start("renderLights:getLights"); final var drawableLights = zoneView.getDrawableLights(view); @@ -1326,6 +1316,8 @@ private void renderLights(Graphics2D g, PlayerView view) { * @param view the player view. */ private void renderAuras(Graphics2D g, PlayerView view) { + final var timer = CodeTimer.get(); + // Setup timer.start("renderAuras:getAuras"); final var drawableAuras = zoneView.getDrawableAuras(); @@ -1347,6 +1339,8 @@ private void renderLumensOverlay( PlayerView view, @Nullable ZoneRendererConstants.LightOverlayClipStyle clipStyle, float overlayOpacity) { + final var timer = CodeTimer.get(); + g = (Graphics2D) g.create(); final var disjointLumensLevels = zoneView.getDisjointObscuredLumensLevels(view); @@ -1459,6 +1453,8 @@ private void renderLightOverlay( @Nullable ZoneRendererConstants.LightOverlayClipStyle clipStyle, Collection lights, Paint backgroundFill) { + final var timer = CodeTimer.get(); + if (lights.isEmpty()) { // No point spending resources accomplishing nothing. return; @@ -1525,6 +1521,8 @@ private void renderLightOverlay( * @param view The player view. */ private void renderPlayerDarkness(Graphics2D g, PlayerView view) { + final var timer = CodeTimer.get(); + if (view.isGMView()) { // GMs see the darkness rendered as lights, not as blackness. return; @@ -1653,6 +1651,8 @@ private void renderHaloArea(Graphics2D g, Area visible) { } private void renderLabels(Graphics2D g, PlayerView view) { + final var timer = CodeTimer.get(); + timer.start("labels-1"); labelLocationList.clear(); for (Label label : zone.getLabels()) { @@ -1692,6 +1692,8 @@ private void renderLabels(Graphics2D g, PlayerView view) { } private void renderFog(Graphics2D g, PlayerView view) { + final var timer = CodeTimer.get(); + Dimension size = getSize(); Area fogClip = new Area(new Rectangle(0, 0, size.width, size.height)); @@ -1736,13 +1738,9 @@ private void renderFog(Graphics2D g, PlayerView view) { Area visibleArea = zoneView.getVisibleArea(view); timer.stop("renderFog-visibleArea"); - String msg = null; - if (timer.isEnabled()) { - msg = "renderFog-combined(" + (view.isUsingTokenView() ? view.getTokens().size() : 0) + ")"; - } - timer.start(msg); + timer.start("renderFog-combined(%d)", view.isUsingTokenView() ? view.getTokens().size() : 0); Area combined = zoneView.getExposedArea(view); - timer.stop(msg); + timer.stop("renderFog-combined(%d)", view.isUsingTokenView() ? view.getTokens().size() : 0); timer.start("renderFogArea"); buffG.fill(combined); @@ -2241,6 +2239,8 @@ protected void showBlockedMoves(Graphics2D g, PlayerView view, Set @SuppressWarnings("unchecked") public void renderPath( Graphics2D g, Path path, TokenFootprint footprint) { + final var timer = CodeTimer.get(); + if (path == null) { return; } @@ -2738,6 +2738,8 @@ protected void renderTokens(Graphics2D g, List tokenList, PlayerView view protected void renderTokens( Graphics2D g, List tokenList, PlayerView view, boolean figuresOnly) { + final var timer = CodeTimer.get(); + Graphics2D clippedG = g; boolean isGMView = view.isGMView(); // speed things up diff --git a/src/main/java/net/rptools/maptool/util/PersistenceUtil.java b/src/main/java/net/rptools/maptool/util/PersistenceUtil.java index cdf0a539fe..2731c4ca43 100644 --- a/src/main/java/net/rptools/maptool/util/PersistenceUtil.java +++ b/src/main/java/net/rptools/maptool/util/PersistenceUtil.java @@ -44,7 +44,6 @@ import net.rptools.lib.io.PackedFile; import net.rptools.maptool.client.AppConstants; import net.rptools.maptool.client.AppPreferences; -import net.rptools.maptool.client.AppState; import net.rptools.maptool.client.AppUtil; import net.rptools.maptool.client.MapTool; import net.rptools.maptool.client.swing.SwingUtil; @@ -272,126 +271,119 @@ private static String fixupZoneName(String n) { } public static void saveCampaign(Campaign campaign, File campaignFile) throws IOException { - CodeTimer saveTimer; // FJE Previously this was 'private static' -- why? - saveTimer = new CodeTimer("CampaignSave"); - saveTimer.setThreshold(5); - // Don't bother keeping track if it won't be displayed... - saveTimer.setEnabled(AppState.isCollectProfilingData()); - - // Strategy: save the file to a tmp location so that if there's a failure the original file - // won't be touched. Then once we're finished, replace the old with the new. - File tmpDir = AppUtil.getTmpDir(); - File tmpFile = new File(tmpDir.getAbsolutePath(), campaignFile.getName()); - if (tmpFile.exists()) tmpFile.delete(); - - PackedFile pakFile = null; - try { - pakFile = new PackedFile(tmpFile); - // Configure the meta file (this is for legacy support) - PersistedCampaign persistedCampaign = new PersistedCampaign(); - - persistedCampaign.campaign = campaign; - - // Keep track of the current view - ZoneRenderer currentZoneRenderer = MapTool.getFrame().getCurrentZoneRenderer(); - if (currentZoneRenderer != null) { - persistedCampaign.currentZoneId = currentZoneRenderer.getZone().getId(); - persistedCampaign.currentView = currentZoneRenderer.getZoneScale(); - } - // Save all assets in active use (consolidate duplicates between maps) - saveTimer.start("Collect all assets"); - Set allAssetIds = campaign.getAllAssetIds(); - for (MD5Key key : allAssetIds) { - // Put in a placeholder; all we really care about is the MD5Key for now... - persistedCampaign.assetMap.put(key, null); - } - saveTimer.stop("Collect all assets"); - - // And store the asset elsewhere - saveTimer.start("Save assets"); - saveAssets(allAssetIds, pakFile); - saveTimer.stop("Save assets"); - - // Store the Drop In Libraries. - saveTimer.start("Save Drop In Libraries"); - saveAddOnLibraries(pakFile); - saveTimer.stop("Save Drop In Libraries"); - - // Store the Game Data - saveTimer.start("Save Game Data"); - saveGameData(pakFile); - saveTimer.stop("Save Game Data"); - - try { - saveTimer.start("Set content"); - - pakFile.setContent(persistedCampaign); - pakFile.setProperty(PROP_CAMPAIGN_VERSION, CAMPAIGN_VERSION); - pakFile.setProperty(PROP_VERSION, MapTool.getVersion()); - - saveTimer.stop("Set content"); - saveTimer.start("Save"); - pakFile.save(); - saveTimer.stop("Save"); - } catch (OutOfMemoryError oom) { - /* - * This error is normally because the heap space has been exceeded while trying to save the campaign. Since MapTool caches the images used by the current Zone, and since the - * VersionManager must keep the XML for objects in memory in order to apply transforms to them, the memory usage can spike very high during the save() operation. A common solution is - * to switch to an empty map and perform the save from there; this causes MapTool to unload any images that it may have had cached and this can frequently free up enough memory for the - * save() to work. We'll tell the user all this right here and then fail the save and they can try again. - */ - saveTimer.start("OOM Close"); - pakFile.close(); // Have to close the tmpFile first on some OSes - pakFile = null; - tmpFile.delete(); // Delete the temporary file - saveTimer.stop("OOM Close"); - if (saveTimer.isEnabled()) { - MapTool.getProfilingNoteFrame().addText(saveTimer.toString()); - } - MapTool.showError("msg.error.failedSaveCampaignOOM"); - return; - } - } finally { - saveTimer.start("Close"); - try { - if (pakFile != null) pakFile.close(); - } catch (Exception e) { - } - saveTimer.stop("Close"); - pakFile = null; - } - - /* - * Copy to the new location. Not the fastest solution in the world if renameTo() fails, but worth the safety net it provides. Jamz: So, renameTo() is causing more issues than it is worth. It - * has a tendency to lock a file under Google Drive/Drop box causing the save to fail. Removed the for final save location... - */ - saveTimer.start("Backup"); - File bakFile = new File(tmpDir.getAbsolutePath(), campaignFile.getName() + ".bak"); - - bakFile.delete(); // Delete the last backup file... + CodeTimer.using( + "CampaignSave", + saveTimer -> { + saveTimer.setThreshold(5); + + // Strategy: save the file to a tmp location so that if there's a failure the original + // file won't be touched. Then once we're finished, replace the old with the new. + File tmpDir = AppUtil.getTmpDir(); + File tmpFile = new File(tmpDir.getAbsolutePath(), campaignFile.getName()); + if (tmpFile.exists()) tmpFile.delete(); + + PackedFile pakFile = null; + try { + pakFile = new PackedFile(tmpFile); + // Configure the meta file (this is for legacy support) + PersistedCampaign persistedCampaign = new PersistedCampaign(); + + persistedCampaign.campaign = campaign; + + // Keep track of the current view + ZoneRenderer currentZoneRenderer = MapTool.getFrame().getCurrentZoneRenderer(); + if (currentZoneRenderer != null) { + persistedCampaign.currentZoneId = currentZoneRenderer.getZone().getId(); + persistedCampaign.currentView = currentZoneRenderer.getZoneScale(); + } + // Save all assets in active use (consolidate duplicates between maps) + saveTimer.start("Collect all assets"); + Set allAssetIds = campaign.getAllAssetIds(); + for (MD5Key key : allAssetIds) { + // Put in a placeholder; all we really care about is the MD5Key for now... + persistedCampaign.assetMap.put(key, null); + } + saveTimer.stop("Collect all assets"); + + // And store the asset elsewhere + saveTimer.start("Save assets"); + saveAssets(allAssetIds, pakFile); + saveTimer.stop("Save assets"); + + // Store the Drop In Libraries. + saveTimer.start("Save Drop In Libraries"); + saveAddOnLibraries(pakFile); + saveTimer.stop("Save Drop In Libraries"); + + // Store the Game Data + saveTimer.start("Save Game Data"); + saveGameData(pakFile); + saveTimer.stop("Save Game Data"); + + try { + saveTimer.start("Set content"); + + pakFile.setContent(persistedCampaign); + pakFile.setProperty(PROP_CAMPAIGN_VERSION, CAMPAIGN_VERSION); + pakFile.setProperty(PROP_VERSION, MapTool.getVersion()); + + saveTimer.stop("Set content"); + saveTimer.start("Save"); + pakFile.save(); + saveTimer.stop("Save"); + } catch (OutOfMemoryError oom) { + /* + * This error is normally because the heap space has been exceeded while trying to save the campaign. Since MapTool caches the images used by the current Zone, and since the + * VersionManager must keep the XML for objects in memory in order to apply transforms to them, the memory usage can spike very high during the save() operation. A common solution is + * to switch to an empty map and perform the save from there; this causes MapTool to unload any images that it may have had cached and this can frequently free up enough memory for the + * save() to work. We'll tell the user all this right here and then fail the save and they can try again. + */ + saveTimer.start("OOM Close"); + pakFile.close(); // Have to close the tmpFile first on some OSes + pakFile = null; + tmpFile.delete(); // Delete the temporary file + saveTimer.stop("OOM Close"); + MapTool.showError("msg.error.failedSaveCampaignOOM"); + return; + } + } finally { + saveTimer.start("Close"); + try { + if (pakFile != null) pakFile.close(); + } catch (Exception e) { + } + saveTimer.stop("Close"); + pakFile = null; + } - if (campaignFile.exists()) { - saveTimer.start("Backup campaignFile"); - FileUtil.copyFile(campaignFile, bakFile); - // campaignFile.delete(); - saveTimer.stop("Backup campaignFile"); - } + /* + * Copy to the new location. Not the fastest solution in the world if renameTo() fails, but worth the safety net it provides. Jamz: So, renameTo() is causing more issues than it is worth. It + * has a tendency to lock a file under Google Drive/Drop box causing the save to fail. Removed the for final save location... + */ + saveTimer.start("Backup"); + File bakFile = new File(tmpDir.getAbsolutePath(), campaignFile.getName() + ".bak"); - saveTimer.start("Backup tmpFile"); - FileUtil.copyFile(tmpFile, campaignFile); - tmpFile.delete(); - saveTimer.stop("Backup tmpFile"); - if (bakFile.exists()) bakFile.delete(); - saveTimer.stop("Backup"); + bakFile.delete(); // Delete the last backup file... - // Save the campaign thumbnail - saveTimer.start("Thumbnail"); - saveCampaignThumbnail(campaignFile.getName()); - saveTimer.stop("Thumbnail"); + if (campaignFile.exists()) { + saveTimer.start("Backup campaignFile"); + FileUtil.copyFile(campaignFile, bakFile); + // campaignFile.delete(); + saveTimer.stop("Backup campaignFile"); + } - if (saveTimer.isEnabled()) { - MapTool.getProfilingNoteFrame().addText(saveTimer.toString()); - } + saveTimer.start("Backup tmpFile"); + FileUtil.copyFile(tmpFile, campaignFile); + tmpFile.delete(); + saveTimer.stop("Backup tmpFile"); + if (bakFile.exists()) bakFile.delete(); + saveTimer.stop("Backup"); + + // Save the campaign thumbnail + saveTimer.start("Thumbnail"); + saveCampaignThumbnail(campaignFile.getName()); + saveTimer.stop("Thumbnail"); + }); } /*