Skip to content

Commit 24d3649

Browse files
authored
Merge pull request #2490 from capdevon/capdevon-SimpleApplication
Refactor: SimpleApplication optimization + javadoc
2 parents dc61e08 + bd1032d commit 24d3649

File tree

1 file changed

+147
-51
lines changed

1 file changed

+147
-51
lines changed

jme3-core/src/main/java/com/jme3/app/SimpleApplication.java

Lines changed: 147 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009-2022 jMonkeyEngine
2+
* Copyright (c) 2009-2025 jMonkeyEngine
33
* All rights reserved.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -50,22 +50,42 @@
5050
import com.jme3.system.JmeContext.Type;
5151
import com.jme3.system.JmeSystem;
5252

53+
import java.util.logging.Level;
54+
import java.util.logging.Logger;
55+
5356
/**
54-
* <code>SimpleApplication</code> is the base class for all jME3 Applications.
55-
* <code>SimpleApplication</code> will display a statistics view
56-
* using the {@link com.jme3.app.StatsAppState} AppState. It will display
57-
* the current frames-per-second value on-screen in addition to the statistics.
58-
* Several keys have special functionality in <code>SimpleApplication</code>:<br>
57+
* `SimpleApplication` is the foundational base class for all jMonkeyEngine 3 (jME3) applications.
58+
* It provides a streamlined setup for common game development tasks, including scene management,
59+
* camera controls, and performance monitoring.
60+
*
61+
* <p>By default, `SimpleApplication` attaches several essential {@link com.jme3.app.state.AppState} instances:
62+
* <ul>
63+
* <li>{@link com.jme3.app.StatsAppState}: Displays real-time frames-per-second (FPS) and
64+
* detailed performance statistics on-screen.</li>
65+
* <li>{@link com.jme3.app.FlyCamAppState}: Provides a convenient first-person fly-by camera
66+
* controller, allowing easy navigation within the scene.</li>
67+
* <li>{@link com.jme3.audio.AudioListenerState}: Manages the audio listener, essential for 3D sound.</li>
68+
* <li>{@link com.jme3.app.DebugKeysAppState}: Enables debug functionalities like displaying
69+
* camera position and memory usage in the console.</li>
70+
* <li>{@link com.jme3.app.state.ConstantVerifierState}: A utility state for verifying constant
71+
* values, primarily for internal engine debugging.</li>
72+
* </ul>
5973
*
60-
* Esc - Close the application.<br>
61-
* C - Display the camera position and rotation in the console.<br>
62-
* M - Display memory usage in the console.<br>
74+
* <p><b>Default Key Bindings:</b></p>
75+
* <ul>
76+
* <li><b>Esc:</b> Closes and exits the application.</li>
77+
* <li><b>F5:</b> Toggles the visibility of the statistics view (FPS and debug stats).</li>
78+
* <li><b>C:</b> Prints the current camera position and rotation to the console.</li>
79+
* <li><b>M:</b> Prints memory usage statistics to the console.</li>
80+
* </ul>
6381
*
64-
* A {@link com.jme3.app.FlyCamAppState} is by default attached as well and can
65-
* be removed by calling <code>stateManager.detach(stateManager.getState(FlyCamAppState.class));</code>
82+
* <p>Applications extending `SimpleApplication` should implement the
83+
* {@link #simpleInitApp()} method to set up their initial scene and game logic.
6684
*/
6785
public abstract class SimpleApplication extends LegacyApplication {
6886

87+
protected static final Logger logger = Logger.getLogger(SimpleApplication.class.getName());
88+
6989
public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit";
7090
public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS;
7191
public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY;
@@ -82,26 +102,43 @@ public abstract class SimpleApplication extends LegacyApplication {
82102
private class AppActionListener implements ActionListener {
83103

84104
@Override
85-
public void onAction(String name, boolean value, float tpf) {
86-
if (!value) {
105+
public void onAction(String name, boolean isPressed, float tpf) {
106+
if (!isPressed) {
87107
return;
88108
}
89109

90110
if (name.equals(INPUT_MAPPING_EXIT)) {
91111
stop();
92112
} else if (name.equals(INPUT_MAPPING_HIDE_STATS)) {
93-
if (stateManager.getState(StatsAppState.class) != null) {
94-
stateManager.getState(StatsAppState.class).toggleStats();
113+
StatsAppState statsState = stateManager.getState(StatsAppState.class);
114+
if (statsState != null) {
115+
statsState.toggleStats();
95116
}
96117
}
97118
}
98119
}
99120

121+
/**
122+
* Constructs a `SimpleApplication` with a predefined set of default
123+
* {@link com.jme3.app.state.AppState} instances.
124+
* These states provide common functionalities like statistics display,
125+
* fly camera control, audio listener, debug keys, and constant verification.
126+
*/
100127
public SimpleApplication() {
101-
this(new StatsAppState(), new FlyCamAppState(), new AudioListenerState(), new DebugKeysAppState(),
128+
this(new StatsAppState(),
129+
new FlyCamAppState(),
130+
new AudioListenerState(),
131+
new DebugKeysAppState(),
102132
new ConstantVerifierState());
103133
}
104134

135+
/**
136+
* Constructs a `SimpleApplication` with a custom array of initial
137+
* {@link com.jme3.app.state.AppState} instances.
138+
*
139+
* @param initialStates An array of `AppState` instances to be attached
140+
* to the `stateManager` upon initialization.
141+
*/
105142
public SimpleApplication(AppState... initialStates) {
106143
super(initialStates);
107144
}
@@ -112,6 +149,7 @@ public void start() {
112149
// settings dialog is not shown
113150
boolean loadSettings = false;
114151
if (settings == null) {
152+
logger.log(Level.INFO, "AppSettings not set, creating default settings.");
115153
setSettings(new AppSettings(true));
116154
loadSettings = true;
117155
}
@@ -128,57 +166,73 @@ public void start() {
128166
}
129167

130168
/**
131-
* Returns the application's speed.
169+
* Returns the current speed multiplier of the application.
170+
* This value affects how quickly the game world updates relative to real time.
171+
* A value of 1.0f means normal speed, 0.5f means half speed, 2.0f means double speed.
132172
*
133-
* @return The speed of the application.
173+
* @return The current speed of the application.
134174
*/
135175
public float getSpeed() {
136176
return speed;
137177
}
138178

139179
/**
140-
* Changes the application's speed. 0.0f prevents the application from updating.
141-
* @param speed The speed to set.
180+
* Changes the application's speed multiplier.
181+
* A `speed` of 0.0f effectively pauses the application's update cycle.
182+
*
183+
* @param speed The desired speed multiplier. A value of 1.0f is normal speed.
184+
* Must be non-negative.
142185
*/
143186
public void setSpeed(float speed) {
144187
this.speed = speed;
145188
}
146189

147190
/**
148-
* Retrieves flyCam
149-
* @return flyCam Camera object
191+
* Retrieves the `FlyByCamera` instance associated with this application.
192+
* This camera allows free-form navigation within the 3D scene.
150193
*
194+
* @return The `FlyByCamera` object, or `null` if `FlyCamAppState` is not attached
195+
* or has not yet initialized the camera.
151196
*/
152197
public FlyByCamera getFlyByCamera() {
153198
return flyCam;
154199
}
155200

156201
/**
157-
* Retrieves guiNode
158-
* @return guiNode Node object
202+
* Retrieves the `Node` dedicated to 2D graphical user interface (GUI) elements.
203+
* Objects attached to this node are rendered on top of the 3D scene,
204+
* typically without perspective effects, suitable for HUDs and UI.
159205
*
206+
* @return The `Node` object representing the GUI root.
160207
*/
161208
public Node getGuiNode() {
162209
return guiNode;
163210
}
164211

165212
/**
166-
* Retrieves rootNode
167-
* @return rootNode Node object
213+
* Retrieves the root `Node` of the 3D scene graph.
214+
* All main 3D spatial objects and models should be attached to this node
215+
* to be part of the rendered scene.
168216
*
217+
* @return The `Node` object representing the 3D scene root.
169218
*/
170219
public Node getRootNode() {
171220
return rootNode;
172221
}
173222

223+
/**
224+
* Checks whether the settings dialog is configured to be shown at application startup.
225+
*
226+
* @return `true` if the settings dialog will be displayed, `false` otherwise.
227+
*/
174228
public boolean isShowSettings() {
175229
return showSettings;
176230
}
177231

178232
/**
179-
* Toggles settings window to display at start-up
180-
* @param showSettings Sets true/false
233+
* Sets whether the jME3 settings dialog should be displayed before the application starts.
181234
*
235+
* @param showSettings `true` to show the settings dialog, `false` to suppress it.
182236
*/
183237
public void setShowSettings(boolean showSettings) {
184238
this.showSettings = showSettings;
@@ -208,41 +262,48 @@ public void initialize() {
208262

209263
guiNode.setQueueBucket(Bucket.Gui);
210264
guiNode.setCullHint(CullHint.Never);
265+
211266
viewPort.attachScene(rootNode);
212267
guiViewPort.attachScene(guiNode);
213268

214269
if (inputManager != null) {
215-
216-
// We have to special-case the FlyCamAppState because too
217-
// many SimpleApplication subclasses expect it to exist in
218-
// simpleInit(). But at least it only gets initialized if
219-
// the app state is added.
220-
if (stateManager.getState(FlyCamAppState.class) != null) {
270+
// Special handling for FlyCamAppState:
271+
// Although FlyCamAppState manages the FlyByCamera, SimpleApplication
272+
// historically initializes and configures a default FlyByCamera instance
273+
// and sets its initial speed. This allows subclasses to directly access
274+
// 'flyCam' early in simpleInitApp().
275+
276+
FlyCamAppState flyCamState = stateManager.getState(FlyCamAppState.class);
277+
if (flyCamState != null) {
221278
flyCam = new FlyByCamera(cam);
222-
flyCam.setMoveSpeed(1f); // odd to set this here but it did it before
223-
stateManager.getState(FlyCamAppState.class).setCamera(flyCam);
279+
flyCam.setMoveSpeed(1f); // Set a default movement speed for the camera
280+
flyCamState.setCamera(flyCam); // Link the FlyCamAppState to this camera instance
224281
}
225282

283+
// Register the "Exit" input mapping for the Escape key, but only for Display contexts.
226284
if (context.getType() == Type.Display) {
227285
inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE));
228286
}
229287

230-
if (stateManager.getState(StatsAppState.class) != null) {
288+
// Register the "Hide Stats" input mapping for the F5 key, if StatsAppState is active.
289+
StatsAppState statsState = stateManager.getState(StatsAppState.class);
290+
if (statsState != null) {
231291
inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5));
232292
inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);
233293
}
234294

295+
// Attach the action listener to the "Exit" mapping.
235296
inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);
236297
}
237298

238-
if (stateManager.getState(StatsAppState.class) != null) {
239-
// Some tests rely on having access to fpsText
240-
// for quick display. Maybe a different way would be better.
241-
stateManager.getState(StatsAppState.class).setFont(guiFont);
242-
fpsText = stateManager.getState(StatsAppState.class).getFpsText();
299+
// Configure the StatsAppState if it exists.
300+
StatsAppState statsState = stateManager.getState(StatsAppState.class);
301+
if (statsState != null) {
302+
statsState.setFont(guiFont);
303+
fpsText = statsState.getFpsText();
243304
}
244305

245-
// call user code
306+
// Call the user's application initialization code.
246307
simpleInitApp();
247308
}
248309

@@ -259,22 +320,26 @@ public void update() {
259320
prof.appStep(AppStep.BeginFrame);
260321
}
261322

262-
super.update(); // makes sure to execute AppTasks
323+
// Executes AppTasks from the main thread
324+
super.update();
325+
326+
// Skip updates if paused or speed is zero
263327
if (speed == 0 || paused) {
264328
return;
265329
}
266330

267331
float tpf = timer.getTimePerFrame() * speed;
268332

269-
// update states
333+
// Update AppStates
270334
if (prof != null) {
271335
prof.appStep(AppStep.StateManagerUpdate);
272336
}
273337
stateManager.update(tpf);
274338

275-
// simple update and root node
339+
// Call user's per-frame update method
276340
simpleUpdate(tpf);
277341

342+
// Update scene graph nodes (logical and geometric states)
278343
if (prof != null) {
279344
prof.appStep(AppStep.SpatialUpdate);
280345
}
@@ -284,7 +349,7 @@ public void update() {
284349
rootNode.updateGeometricState();
285350
guiNode.updateGeometricState();
286351

287-
// render states
352+
// Render AppStates and the scene
288353
if (prof != null) {
289354
prof.appStep(AppStep.StateManagerRender);
290355
}
@@ -294,6 +359,7 @@ public void update() {
294359
prof.appStep(AppStep.RenderFrame);
295360
}
296361
renderManager.render(tpf, context.isRenderable());
362+
// Call user's custom render method
297363
simpleRender(renderManager);
298364
stateManager.postRender();
299365

@@ -302,23 +368,53 @@ public void update() {
302368
}
303369
}
304370

371+
/**
372+
* Controls the visibility of the frames-per-second (FPS) display on the screen.
373+
*
374+
* @param show `true` to display the FPS, `false` to hide it.
375+
*/
305376
public void setDisplayFps(boolean show) {
306-
if (stateManager.getState(StatsAppState.class) != null) {
307-
stateManager.getState(StatsAppState.class).setDisplayFps(show);
377+
StatsAppState statsState = stateManager.getState(StatsAppState.class);
378+
if (statsState != null) {
379+
statsState.setDisplayFps(show);
308380
}
309381
}
310382

383+
/**
384+
* Controls the visibility of the comprehensive statistics view on the screen.
385+
* This view typically includes details about memory, triangles, and other performance metrics.
386+
*
387+
* @param show `true` to display the statistics view, `false` to hide it.
388+
*/
311389
public void setDisplayStatView(boolean show) {
312-
if (stateManager.getState(StatsAppState.class) != null) {
313-
stateManager.getState(StatsAppState.class).setDisplayStatView(show);
390+
StatsAppState statsState = stateManager.getState(StatsAppState.class);
391+
if (statsState != null) {
392+
statsState.setDisplayStatView(show);
314393
}
315394
}
316395

317396
public abstract void simpleInitApp();
318397

398+
/**
399+
* An optional method that can be overridden by subclasses for per-frame update logic.
400+
* This method is called during the application's update loop, after AppStates are updated
401+
* and before the scene graph's logical state is updated.
402+
*
403+
* @param tpf The time per frame (in seconds), adjusted by the application's speed.
404+
*/
319405
public void simpleUpdate(float tpf) {
406+
// Default empty implementation; subclasses can override
320407
}
321408

409+
/**
410+
* An optional method that can be overridden by subclasses for custom rendering logic.
411+
* This method is called during the application's render loop, after the main scene
412+
* has been rendered and before post-rendering for states.
413+
* Useful for drawing overlays or specific rendering tasks outside the main scene graph.
414+
*
415+
* @param rm The `RenderManager` instance, which provides access to rendering functionalities.
416+
*/
322417
public void simpleRender(RenderManager rm) {
418+
// Default empty implementation; subclasses can override
323419
}
324420
}

0 commit comments

Comments
 (0)