diff --git a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java index 1e0a75daf7..8ce4f81627 100644 --- a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java +++ b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,6 +37,7 @@ import com.jme3.audio.AudioContext; import com.jme3.audio.AudioRenderer; import com.jme3.audio.Listener; +import com.jme3.input.Input; import com.jme3.input.InputManager; import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; @@ -58,6 +59,7 @@ import com.jme3.system.SystemListener; import com.jme3.system.Timer; import com.jme3.util.res.Resources; + import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.Callable; @@ -70,7 +72,7 @@ * The LegacyApplication class represents an instance of a * real-time 3D rendering jME application. * - * An LegacyApplication provides all the tools that are commonly used in jME3 + * A LegacyApplication provides all the tools that are commonly used in jME3 * applications. * * jME3 applications *SHOULD NOT EXTEND* this class but extend {@link com.jme3.app.SimpleApplication} instead. @@ -80,40 +82,46 @@ public class LegacyApplication implements Application, SystemListener { private static final Logger logger = Logger.getLogger(LegacyApplication.class.getName()); + // Core jME3 components protected AssetManager assetManager; - protected AudioRenderer audioRenderer; protected Renderer renderer; protected RenderManager renderManager; + + // Viewports protected ViewPort viewPort; protected ViewPort guiViewPort; + // System components protected JmeContext context; protected AppSettings settings; - protected Timer timer = new NanoTimer(); + protected Timer timer = new NanoTimer(); // Default timer protected Camera cam; protected Listener listener; + // Input management protected boolean inputEnabled = true; protected LostFocusBehavior lostFocusBehavior = LostFocusBehavior.ThrottleOnLostFocus; - protected float speed = 1f; - protected boolean paused = false; protected MouseInput mouseInput; protected KeyInput keyInput; protected JoyInput joyInput; protected TouchInput touchInput; protected InputManager inputManager; - protected AppStateManager stateManager; + // Application state and performance + protected float speed = 1f; + protected boolean paused = false; + protected AppStateManager stateManager; protected AppProfiler prof; + // Task queue for main thread operations private final ConcurrentLinkedQueue> taskQueue = new ConcurrentLinkedQueue<>(); /** * Create a new instance of LegacyApplication. */ public LegacyApplication() { - this((AppState[]) null); + initStateManager(); } /** @@ -123,8 +131,7 @@ public LegacyApplication() { * @param initialStates app states to pre-attach, or null for none */ public LegacyApplication(AppState... initialStates) { - initStateManager(); - + this(); if (initialStates != null) { for (AppState a : initialStates) { if (a != null) { @@ -200,13 +207,20 @@ public void setPauseOnLostFocus(boolean pauseOnLostFocus) { @Deprecated public void setAssetManager(AssetManager assetManager) { if (this.assetManager != null) { - throw new IllegalStateException("Can only set asset manager" + " before initialization."); + throw new IllegalStateException("Can only set asset manager before initialization."); } this.assetManager = assetManager; } + /** + * Initializes the asset manager based on settings or platform defaults. + */ private void initAssetManager() { + if (assetManager != null) { + return; // Already initialized or set externally + } + URL assetCfgUrl = null; if (settings != null) { @@ -220,11 +234,7 @@ private void initAssetManager() { if (assetCfgUrl == null) { assetCfgUrl = Resources.getResource(assetCfg); if (assetCfgUrl == null) { - logger.log( - Level.SEVERE, - "Unable to access AssetConfigURL in asset config:{0}", - assetCfg - ); + logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config: {0}", assetCfg); return; } } @@ -233,9 +243,7 @@ private void initAssetManager() { if (assetCfgUrl == null) { assetCfgUrl = JmeSystem.getPlatformAssetConfigURL(); } - if (assetManager == null) { - assetManager = JmeSystem.newAssetManager(assetCfgUrl); - } + assetManager = JmeSystem.newAssetManager(assetCfgUrl); } /** @@ -251,17 +259,16 @@ private void initAssetManager() { @Override public void setSettings(AppSettings settings) { this.settings = settings; - if (context != null && settings.useInput() != inputEnabled) { - // may need to create or destroy input based - // on settings change - inputEnabled = !inputEnabled; + boolean newUseInput = settings.useInput(); + if (context != null && newUseInput != inputEnabled) { + inputEnabled = newUseInput; if (inputEnabled) { initInput(); } else { destroyInput(); } } else { - inputEnabled = settings.useInput(); + inputEnabled = newUseInput; } } @@ -288,6 +295,9 @@ public Timer getTimer() { return timer; } + /** + * Initializes display-related components from the context. + */ private void initDisplay() { // acquire important objects // from the context @@ -301,6 +311,9 @@ private void initDisplay() { renderer = context.getRenderer(); } + /** + * Initializes audio renderer and listener if audio is enabled in settings. + */ private void initAudio() { if (settings.getAudioRenderer() != null && context.getType() != Type.Headless) { audioRenderer = JmeSystem.newAudioRenderer(settings); @@ -325,7 +338,6 @@ private void initCamera() { cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y); renderManager = new RenderManager(renderer); - //Remy - 09/14/2010 set the timer in the renderManager renderManager.setTimer(timer); if (prof != null) { @@ -342,36 +354,37 @@ private void initCamera() { } /** - * Initializes mouse and keyboard input. Also - * initializes joystick input if joysticks are enabled in the - * AppSettings. + * Initializes mouse, keyboard, and optionally joystick/touch input based on context and settings. */ private void initInput() { + // Retrieve and initialize all available input devices mouseInput = context.getMouseInput(); - if (mouseInput != null) { - mouseInput.initialize(); - } + initializeInputDevice(mouseInput); keyInput = context.getKeyInput(); - if (keyInput != null) { - keyInput.initialize(); - } + initializeInputDevice(keyInput); touchInput = context.getTouchInput(); - if (touchInput != null) { - touchInput.initialize(); - } + initializeInputDevice(touchInput); if (settings.useJoysticks()) { joyInput = context.getJoyInput(); - if (joyInput != null) { - joyInput.initialize(); - } + initializeInputDevice(joyInput); } + // Create the InputManager with the initialized devices inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput); } + private void initializeInputDevice(Input input) { + if (input != null) { + input.initialize(); + } + } + + /** + * Initializes the application state manager. + */ private void initStateManager() { stateManager = new AppStateManager(this); @@ -453,9 +466,11 @@ public Camera getCamera() { } /** - * Starts the application in {@link Type#Display display} mode. + * Starts the application in {@link JmeContext.Type#Display display} mode. + * This method creates a new rendering context and starts the main application + * loop in a separate thread without waiting for initialization to complete. * - * @see #start(com.jme3.system.JmeContext.Type) + * @see #start(com.jme3.system.JmeContext.Type, boolean) */ @Override public void start() { @@ -463,11 +478,15 @@ public void start() { } /** - * Starts the application in {@link Type#Display display} mode. + * Starts the application in {@link JmeContext.Type#Display display} mode. + * This method creates a new rendering context and starts the main application + * loop in a separate thread. * - * @param waitFor true→wait for the context to be initialized, - * false→don't wait - * @see #start(com.jme3.system.JmeContext.Type) + * @param waitFor If true, the current thread will block until the + * display context is fully initialized and ready. + * If false, the method returns immediately and initialization + * continues in the background. + * @see #start(com.jme3.system.JmeContext.Type, boolean) */ @Override public void start(boolean waitFor) { @@ -475,24 +494,29 @@ public void start(boolean waitFor) { } /** - * Starts the application. - * Creating a rendering context and executing - * the main loop in a separate thread. + * Starts the application with the specified {@link JmeContext.Type}. + * This method creates a rendering context and executes the main loop + * in a separate thread without waiting for initialization to complete. * - * @param contextType the type of context to create + * @param contextType The type of {@link JmeContext}. + * @see #start(com.jme3.system.JmeContext.Type, boolean) */ public void start(JmeContext.Type contextType) { start(contextType, false); } /** - * Starts the application. - * Creating a rendering context and executing - * the main loop in a separate thread. + * Starts the application with the specified {@link JmeContext.Type}. + * This method creates a rendering context and executes the main loop + * in a separate thread. * - * @param contextType the type of context to create - * @param waitFor true→wait for the context to be initialized, - * false→don't wait + * @param contextType The type of {@link JmeContext} to create (e.g., {@link JmeContext.Type#Display}, + * {@link JmeContext.Type#Headless}, {@link JmeContext.Type#Canvas}). + * @param waitFor If true, the current thread will block until the + * context is fully initialized and ready. + * If false, the method returns immediately and initialization + * continues in the background. + * @throws IllegalStateException if the context is already created and running. */ public void start(JmeContext.Type contextType, boolean waitFor) { if (context != null && context.isCreated()) { @@ -501,10 +525,13 @@ public void start(JmeContext.Type contextType, boolean waitFor) { } if (settings == null) { + logger.log(Level.INFO, "AppSettings not set, creating default settings."); settings = new AppSettings(true); } - logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); + logger.log(Level.FINE, "Starting application: {0} with context type {1}", + new Object[]{getClass().getName(), contextType}); + context = JmeSystem.newContext(settings, contextType); context.setSystemListener(this); context.create(waitFor); @@ -554,12 +581,11 @@ public void createCanvas() { } if (settings == null) { + logger.log(Level.INFO, "AppSettings not set, creating default settings."); settings = new AppSettings(true); } - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); - } + logger.log(Level.FINE, "Starting application as canvas: {0}", getClass().getName()); context = JmeSystem.newContext(settings, JmeContext.Type.Canvas); context.setSystemListener(this); } @@ -657,10 +683,7 @@ public void stop(boolean waitFor) { */ @Override public void initialize() { - if (assetManager == null) { - initAssetManager(); - } - + initAssetManager(); initDisplay(); initCamera(); @@ -669,10 +692,7 @@ public void initialize() { } initAudio(); - // update timer so that the next delta is not too large - // timer.update(); timer.reset(); - // user code here } /** @@ -766,7 +786,6 @@ public Future enqueue(Callable callable) { * @param runnable The runnable to run in the main jME3 thread */ @Override - @SuppressWarnings("unchecked") public void enqueue(Runnable runnable) { enqueue(new RunnableWrapper(runnable)); } @@ -797,10 +816,12 @@ public void update() { } runQueuedTasks(); + // Skip further updates if paused or speed is zero if (speed == 0 || paused) { return; } + // Update the application's timer timer.update(); if (inputEnabled) { @@ -819,24 +840,21 @@ public void update() { // user code here } + /** + * Destroys all initialized input devices. + */ protected void destroyInput() { - if (mouseInput != null) { - mouseInput.destroy(); - } - - if (keyInput != null) { - keyInput.destroy(); - } - - if (joyInput != null) { - joyInput.destroy(); - } + destroyInputDevice(mouseInput); + destroyInputDevice(keyInput); + destroyInputDevice(joyInput); + destroyInputDevice(touchInput); + inputManager = null; + } - if (touchInput != null) { - touchInput.destroy(); + private void destroyInputDevice(Input input) { + if (input != null) { + input.destroy(); } - - inputManager = null; } /** @@ -853,6 +871,7 @@ public void destroy() { } timer.reset(); + logger.log(Level.FINE, "Application destroyed: {0}", getClass().getName()); } /** @@ -864,12 +883,20 @@ public ViewPort getGuiViewPort() { return guiViewPort; } + /** + * @return The main scene viewport. + */ @Override public ViewPort getViewPort() { return viewPort; } - private class RunnableWrapper implements Callable { + /** + * A private wrapper class that adapts a {@link Runnable} to a {@link Callable} + * so it can be enqueued in the {@link LegacyApplication}'s task queue. + * The {@code call()} method simply executes the wrapped {@link Runnable} and returns null. + */ + private static class RunnableWrapper implements Callable { private final Runnable runnable; @@ -905,4 +932,5 @@ public Displays getDisplays() { public int getPrimaryDisplay() { return context.getPrimaryDisplay(); } + }