From cd3aa325c5db5489ecb40c1f7832dd6271a071d8 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 22 Oct 2025 12:57:29 +0200 Subject: [PATCH] Refactor initialization to use background threads Moved several initialization tasks to background threads for improved startup performance and responsiveness, including tool, font, UI, and suggestion generator setup. Deferred some tool initialization until first use, added caching for SVG rendering, and simplified string loading in PApplet. Also made minor changes to library list and variable inspector initialization. # Conflicts: # app/src/processing/app/Base.java # app/src/processing/app/platform/DefaultPlatform.java --- app/src/processing/app/Base.java | 93 +++++++++---------- .../app/platform/DefaultPlatform.java | 31 +++++-- app/src/processing/app/ui/Editor.java | 2 +- app/src/processing/app/ui/Toolkit.java | 40 ++++++-- core/src/processing/core/PApplet.java | 23 +---- .../processing/mode/java/JavaTextArea.java | 4 +- .../processing/mode/java/PreprocService.java | 5 +- .../processing/mode/java/debug/Debugger.java | 6 +- 8 files changed, 114 insertions(+), 90 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 49a8625e51..c00e7786be 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -23,6 +23,17 @@ package processing.app; +import java.awt.*; +import java.awt.event.ActionListener; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.List; +import java.util.Map.Entry; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; + import com.formdev.flatlaf.FlatDarkLaf; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLightLaf; @@ -34,19 +45,6 @@ import processing.core.PApplet; import processing.data.StringList; -import javax.swing.*; -import javax.swing.tree.DefaultMutableTreeNode; -import java.awt.*; -import java.awt.event.ActionListener; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.util.*; -import java.util.List; -import java.util.Map.Entry; - /** * The base class for the main processing application. * Primary role of this class is for platform identification and @@ -231,7 +229,7 @@ static private void createAndShowGUI(String[] args) { Messages.log("Base() constructor succeeded"); // Prevent more than one copy of the PDE from running. - SingleInstance.startServer(base); + new Thread(() -> { SingleInstance.startServer(base); } ).start(); handleWelcomeScreen(base); handleCrustyDisplay(); @@ -850,7 +848,7 @@ public List getContribTools() { return contribTools; } - + private List toolsToInit = new ArrayList<>(); public void rebuildToolList() { // Only do these once because the list of internal tools will never change if (internalTools == null) { @@ -870,43 +868,12 @@ public void rebuildToolList() { // Only init() these the first time they're loaded if (coreTools == null) { coreTools = ToolContribution.loadAll(Base.getToolsFolder()); - for (Tool tool : coreTools) { - tool.init(this); - } + toolsToInit.addAll(coreTools); } // Reset the contributed tools and re-init() all of them. contribTools = ToolContribution.loadAll(Base.getSketchbookToolsFolder()); - for (Tool tool : contribTools) { - try { - tool.init(this); - - // With the exceptions, we can't call statusError because the window - // isn't completely set up yet. Also not gonna pop up a warning because - // people may still be running different versions of Processing. - - } catch (VerifyError | AbstractMethodError ve) { - System.err.println("\"" + tool.getMenuTitle() + "\" is not " + - "compatible with this version of Processing"); - Messages.err("Incompatible Tool found during tool.init()", ve); - - } catch (NoSuchMethodError nsme) { - System.err.println("\"" + tool.getMenuTitle() + "\" is not " + - "compatible with this version of Processing"); - System.err.println("The " + nsme.getMessage() + " method no longer exists."); - Messages.err("Incompatible Tool found during tool.init()", nsme); - - } catch (NoClassDefFoundError ncdfe) { - System.err.println("\"" + tool.getMenuTitle() + "\" is not " + - "compatible with this version of Processing"); - System.err.println("The " + ncdfe.getMessage() + " class is no longer available."); - Messages.err("Incompatible Tool found during tool.init()", ncdfe); - - } catch (Error | Exception e) { - System.err.println("An error occurred inside \"" + tool.getMenuTitle() + "\""); - e.printStackTrace(); - } - } + toolsToInit.addAll(contribTools); } @@ -915,7 +882,7 @@ protected void initInternalTool(Class toolClass) { final Tool tool = (Tool) toolClass.getDeclaredConstructor().newInstance(); - tool.init(this); + toolsToInit.add(tool); internalTools.add(tool); } catch (Exception e) { @@ -963,12 +930,40 @@ public void populateToolsMenu(JMenu toolsMenu) { toolsMenu.add(manageTools); } + void initTool(Tool tool) { + if(!toolsToInit.contains(tool)) {return;} + try { + tool.init(this); + toolsToInit.remove(tool); + } catch (VerifyError | AbstractMethodError ve) { + System.err.println("\"" + tool.getMenuTitle() + "\" is not " + + "compatible with this version of Processing"); + Messages.err("Incompatible Tool found during tool.init()", ve); + + } catch (NoSuchMethodError nsme) { + System.err.println("\"" + tool.getMenuTitle() + "\" is not " + + "compatible with this version of Processing"); + System.err.println("The " + nsme.getMessage() + " method no longer exists."); + Messages.err("Incompatible Tool found during tool.init()", nsme); + + } catch (NoClassDefFoundError ncdfe) { + System.err.println("\"" + tool.getMenuTitle() + "\" is not " + + "compatible with this version of Processing"); + System.err.println("The " + ncdfe.getMessage() + " class is no longer available."); + Messages.err("Incompatible Tool found during tool.init()", ncdfe); + + } catch (Error | Exception e) { + System.err.println("An error occurred inside \"" + tool.getMenuTitle() + "\""); + e.printStackTrace(); + } + } JMenuItem createToolItem(final Tool tool) { //, Map toolItems) { String title = tool.getMenuTitle(); final JMenuItem item = new JMenuItem(title); item.addActionListener(e -> { try { + initTool(tool); tool.run(); } catch (NoSuchMethodError | NoClassDefFoundError ne) { diff --git a/app/src/processing/app/platform/DefaultPlatform.java b/app/src/processing/app/platform/DefaultPlatform.java index 54f0ec2788..eed79bb0be 100644 --- a/app/src/processing/app/platform/DefaultPlatform.java +++ b/app/src/processing/app/platform/DefaultPlatform.java @@ -23,6 +23,12 @@ package processing.app.platform; +import java.awt.*; +import java.io.File; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; + import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLightLaf; import processing.app.Base; @@ -31,10 +37,6 @@ import processing.awt.ShimAWT; import processing.core.PApplet; -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.io.File; /** @@ -110,18 +112,29 @@ public void setLookAndFeel() throws Exception { // (i.e. Nimbus on Linux) with our custom components is badness. // dummy font call so that it's registered for FlatLaf - Font defaultFont = Toolkit.getSansFont(14, Font.PLAIN); - UIManager.put("defaultFont", defaultFont); + new Thread(() -> { + Font defaultFont = Toolkit.getSansFont(14, Font.PLAIN); + UIManager.put("defaultFont", defaultFont); + }).start(); + // pull in FlatLaf.properties from the processing.app.laf folder FlatLaf.registerCustomDefaultsSource("processing.app.laf"); - // start with Light, but updateTheme() will be called soon - UIManager.setLookAndFeel(new FlatLightLaf()); + new Thread(() -> { + // start with Light, but updateTheme() will be called soon + try { + UIManager.setLookAndFeel(new FlatLightLaf()); + } catch (UnsupportedLookAndFeelException e) { + throw new RuntimeException(e); + } + }).start(); // Does not fully remove the gray hairline (probably from a parent // Window object), but is an improvement from the heavier default. - UIManager.put("ToolTip.border", new EmptyBorder(0, 0, 0, 0)); + new Thread(() -> { + UIManager.put("ToolTip.border", new EmptyBorder(0, 0, 0, 0)); + }).start(); /* javax.swing.UIDefaults defaults = UIManager.getDefaults(); diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index d5d964b164..c74ba64807 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -193,7 +193,7 @@ public void windowDeactivated(WindowEvent e) { timer = new Timer(); - buildMenuBar(); + new Thread(this::buildMenuBar).start(); JPanel contentPain = new JPanel(); setContentPane(contentPain); diff --git a/app/src/processing/app/ui/Toolkit.java b/app/src/processing/app/ui/Toolkit.java index 8a5ae418bb..50eaf6a2c9 100644 --- a/app/src/processing/app/ui/Toolkit.java +++ b/app/src/processing/app/ui/Toolkit.java @@ -44,16 +44,19 @@ import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.image.ImageObserver; +import java.awt.image.RenderedImage; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.regex.Pattern; +import javax.imageio.ImageIO; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -68,14 +71,12 @@ import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.StyleSheet; -import processing.app.Language; -import processing.app.Messages; -import processing.app.Platform; -import processing.app.Preferences; -import processing.app.Util; +import processing.app.*; import processing.awt.PGraphicsJava2D; import processing.awt.PShapeJava2D; +import processing.awt.ShimAWT; import processing.core.PApplet; +import processing.core.PImage; import processing.core.PShape; import processing.data.StringDict; import processing.data.StringList; @@ -794,6 +795,7 @@ static private Image svgToImageMult(String xmlStr, int wide, int high) { */ + static public Image svgToImageMult(String xmlStr, int wide, int high, StringDict replacements) { /* for (StringDict.Entry entry : replacements.entries()) { @@ -823,8 +825,25 @@ static private Image svgToImage(String xmlStr, int wide, int high) { pg.setSize(wide, high); pg.smooth(); + + + + pg.beginDraw(); + var cacheKey = (xmlStr + "|" + wide + "x" + high).hashCode(); + var cachePath = Base.getSettingsFolder().toPath().resolve("svg_cache").resolve(String.valueOf(cacheKey) + ".png"); + if(!Base.DEBUG || true){ + if(Files.exists(cachePath)){ + byte[] bytes = PApplet.loadBytes(cachePath.toFile()); + if (bytes == null) { + return null; + } else { + return new ImageIcon(bytes).getImage(); + } + } + } + try { XML xml = XML.parse(xmlStr); PShape shape = new PShapeJava2D(xml); @@ -835,6 +854,12 @@ static private Image svgToImage(String xmlStr, int wide, int high) { } pg.endDraw(); + try { + Files.createDirectories(cachePath.getParent()); + pg.save(cachePath.toString()); + } catch (IOException e) { + e.printStackTrace(); + } return pg.image; } @@ -1361,8 +1386,9 @@ static private Font initFont(String filename, int size) throws IOException, Font Font font = Font.createFont(Font.TRUETYPE_FONT, input); input.close(); - // Register the font to be available for other function calls - GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font); + new Thread(() -> { + GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font); + }).start(); return font.deriveFont((float) size); } diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java index d9df211eb7..c4b6a5adbc 100644 --- a/core/src/processing/core/PApplet.java +++ b/core/src/processing/core/PApplet.java @@ -6822,28 +6822,9 @@ static public String[] loadStrings(InputStream input) { static public String[] loadStrings(BufferedReader reader) { try { - String[] lines = new String[100]; - int lineCount = 0; - String line; - while ((line = reader.readLine()) != null) { - if (lineCount == lines.length) { - String[] temp = new String[lineCount << 1]; - System.arraycopy(lines, 0, temp, 0, lineCount); - lines = temp; - } - lines[lineCount++] = line; - } + var lines = reader.lines().toArray(String[]::new); reader.close(); - - if (lineCount == lines.length) { - return lines; - } - - // resize array to appropriate amount for these lines - String[] output = new String[lineCount]; - System.arraycopy(lines, 0, output, 0, lineCount); - return output; - + return lines; } catch (IOException e) { e.printStackTrace(); //throw new RuntimeException("Error inside loadStrings()"); diff --git a/java/src/processing/mode/java/JavaTextArea.java b/java/src/processing/mode/java/JavaTextArea.java index ecab00eed1..628a47fdcc 100644 --- a/java/src/processing/mode/java/JavaTextArea.java +++ b/java/src/processing/mode/java/JavaTextArea.java @@ -52,7 +52,9 @@ public class JavaTextArea extends PdeTextArea { public JavaTextArea(TextAreaDefaults defaults, JavaEditor editor) { super(defaults, new JavaInputHandler(editor), editor); - suggestionGenerator = new CompletionGenerator((JavaMode) editor.getMode()); + new Thread(() -> { + suggestionGenerator = new CompletionGenerator((JavaMode) editor.getMode()); + }).start(); tweakMode = false; } diff --git a/java/src/processing/mode/java/PreprocService.java b/java/src/processing/mode/java/PreprocService.java index 410cff02f6..bf144fa542 100644 --- a/java/src/processing/mode/java/PreprocService.java +++ b/java/src/processing/mode/java/PreprocService.java @@ -80,7 +80,7 @@ public class PreprocService { protected final JavaMode javaMode; protected final Sketch sketch; - protected final ASTParser parser = ASTParser.newParser(AST.JLS11); + protected ASTParser parser; private final Thread preprocessingThread; private final BlockingQueue requestQueue = new ArrayBlockingQueue<>(1); @@ -116,6 +116,9 @@ public PreprocService(JavaMode javaMode, Sketch sketch) { * The "main loop" for the background thread that checks for code issues. */ private void mainLoop() { + if(parser == null) { + parser = ASTParser.newParser(AST.JLS11); + } running = true; PreprocSketch prevResult = null; CompletableFuture runningCallbacks = null; diff --git a/java/src/processing/mode/java/debug/Debugger.java b/java/src/processing/mode/java/debug/Debugger.java index e9d42895be..23cd7f1301 100644 --- a/java/src/processing/mode/java/debug/Debugger.java +++ b/java/src/processing/mode/java/debug/Debugger.java @@ -112,7 +112,7 @@ public class Debugger { public Debugger(JavaEditor editor) { this.editor = editor; - inspector = new VariableInspector(editor); +// inspector = new VariableInspector(editor); } @@ -207,6 +207,9 @@ public void toggleEnabled() { } else { debugItem.setText(Language.text("menu.debug.enable")); } + if(inspector == null) { + inspector = new VariableInspector(editor); + } inspector.setVisible(enabled); for (Component item : debugMenu.getMenuComponents()) { @@ -298,6 +301,7 @@ public void removeClassLoadListener(ClassLoadListener listener) { public void dispose() { + if(inspector == null) return; inspector.dispose(); }