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