From 3d39804e391ab3b8b8f90d01f9aba5f4ba5fbe7a 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. --- app/src/processing/app/Base.java | 83 ++++++++++--------- app/src/processing/app/Processing.kt | 2 +- .../app/platform/DefaultPlatform.java | 23 +++-- 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 +- 9 files changed, 109 insertions(+), 79 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 2551a54d64..fb6f1cd820 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -302,7 +302,7 @@ static private void createAndShowGUI(String[] args) { // t6 = System.currentTimeMillis(); // Prevent more than one copy of the PDE from running. - SingleInstance.startServer(base); + new Thread(() -> { SingleInstance.startServer(base); } ).start(); handleWelcomeScreen(base); handleCrustyDisplay(); @@ -485,12 +485,18 @@ static public boolean isCommandLine() { public Base(String[] args) throws Exception { long t1 = System.currentTimeMillis(); - ContributionManager.init(this); + new Thread(() -> { + try { + ContributionManager.init(this); + } catch (Exception e) { + throw new RuntimeException(e); + } + }).start(); long t2 = System.currentTimeMillis(); buildCoreModes(); long t2b = System.currentTimeMillis(); - rebuildContribModes(); + new Thread(this::rebuildContribModes).start(); long t2c = System.currentTimeMillis(); rebuildContribExamples(); @@ -498,7 +504,7 @@ public Base(String[] args) throws Exception { // Needs to happen after the sketchbook folder has been located. // Also relies on the modes to be loaded, so it knows what can be // marked as an example. - Recent.init(this); + new Thread(() -> { Recent.init(this); }).start(); long t4 = System.currentTimeMillis(); String lastModeIdentifier = Preferences.get("mode.last"); //$NON-NLS-1$ @@ -523,7 +529,7 @@ public Base(String[] args) throws Exception { long t5 = System.currentTimeMillis(); // Make sure ThinkDifferent has library examples too - nextMode.rebuildLibraryList(); +// nextMode.rebuildLibraryList(); // Put this after loading the examples, so that building the default file // menu works on Mac OS X (since it needs examplesFolder to be set). @@ -863,7 +869,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) { @@ -883,43 +889,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); } @@ -928,7 +903,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) { @@ -976,12 +951,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/Processing.kt b/app/src/processing/app/Processing.kt index 6bc6b64a7e..e1b07737a5 100644 --- a/app/src/processing/app/Processing.kt +++ b/app/src/processing/app/Processing.kt @@ -56,7 +56,7 @@ class Processing: SuspendingCliktCommand("processing"){ val subcommand = currentContext.invokedSubcommand if (subcommand == null) { - Start.main(sketches.toTypedArray()) + Base.main(sketches.toTypedArray()) } } } diff --git a/app/src/processing/app/platform/DefaultPlatform.java b/app/src/processing/app/platform/DefaultPlatform.java index 18997755b7..10dc90e2f2 100644 --- a/app/src/processing/app/platform/DefaultPlatform.java +++ b/app/src/processing/app/platform/DefaultPlatform.java @@ -27,7 +27,7 @@ import java.awt.Font; import java.io.File; -import javax.swing.UIManager; +import javax.swing.*; import javax.swing.border.EmptyBorder; import com.formdev.flatlaf.FlatLaf; @@ -115,18 +115,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 df2440d391..c1250777a1 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -189,7 +189,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 4fccd1a535..f3dc64af5f 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 0136793200..03c431ab8b 100644 --- a/java/src/processing/mode/java/debug/Debugger.java +++ b/java/src/processing/mode/java/debug/Debugger.java @@ -111,7 +111,7 @@ public class Debugger { public Debugger(JavaEditor editor) { this.editor = editor; - inspector = new VariableInspector(editor); +// inspector = new VariableInspector(editor); } @@ -206,6 +206,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()) { @@ -297,6 +300,7 @@ public void removeClassLoadListener(ClassLoadListener listener) { public void dispose() { + if(inspector == null) return; inspector.dispose(); }