diff options
Diffstat (limited to 'engine/src/core/com/jme3/app')
-rw-r--r-- | engine/src/core/com/jme3/app/AppTask.java | 167 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/Application.java | 642 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/DebugKeysAppState.java | 117 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/FlyCamAppState.java | 94 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/SimpleApplication.java | 277 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/StatsAppState.java | 208 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/StatsView.java | 132 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/package.html | 80 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/state/AbstractAppState.java | 89 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/state/AppState.java | 119 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/state/AppStateManager.java | 288 | ||||
-rw-r--r-- | engine/src/core/com/jme3/app/state/package.html | 15 |
12 files changed, 2228 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/app/AppTask.java b/engine/src/core/com/jme3/app/AppTask.java new file mode 100644 index 0000000..1b1b68c --- /dev/null +++ b/engine/src/core/com/jme3/app/AppTask.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import java.util.concurrent.*; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * <code>AppTask</code> is used in <code>AppTaskQueue</code> to manage tasks that have + * yet to be accomplished. The AppTask system is used to execute tasks either + * in the OpenGL/Render thread, or outside of it. + * + * @author Matthew D. Hicks, lazloh + */ +public class AppTask<V> implements Future<V> { + private static final Logger logger = Logger.getLogger(AppTask.class + .getName()); + + private final Callable<V> callable; + + private V result; + private ExecutionException exception; + private boolean cancelled, finished; + private final ReentrantLock stateLock = new ReentrantLock(); + private final Condition finishedCondition = stateLock.newCondition(); + + /** + * Create an <code>AppTask</code> that will execute the given + * {@link Callable}. + * + * @param callable The callable to be executed + */ + public AppTask(Callable<V> callable) { + this.callable = callable; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + stateLock.lock(); + try { + if (result != null) { + return false; + } + cancelled = true; + + finishedCondition.signalAll(); + + return true; + } finally { + stateLock.unlock(); + } + } + + public V get() throws InterruptedException, ExecutionException { + stateLock.lock(); + try { + while (!isDone()) { + finishedCondition.await(); + } + if (exception != null) { + throw exception; + } + return result; + } finally { + stateLock.unlock(); + } + } + + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + stateLock.lock(); + try { + if (!isDone()) { + finishedCondition.await(timeout, unit); + } + if (exception != null) { + throw exception; + } + if (result == null) { + throw new TimeoutException("Object not returned in time allocated."); + } + return result; + } finally { + stateLock.unlock(); + } + } + + public boolean isCancelled() { + stateLock.lock(); + try { + return cancelled; + } finally { + stateLock.unlock(); + } + } + + public boolean isDone() { + stateLock.lock(); + try { + return finished || cancelled || (exception != null); + } finally { + stateLock.unlock(); + } + } + + public Callable<V> getCallable() { + return callable; + } + + public void invoke() { + try { + final V tmpResult = callable.call(); + + stateLock.lock(); + try { + result = tmpResult; + finished = true; + + finishedCondition.signalAll(); + } finally { + stateLock.unlock(); + } + } catch (Exception e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "invoke()", "Exception", e); + + stateLock.lock(); + try { + exception = new ExecutionException(e); + + finishedCondition.signalAll(); + } finally { + stateLock.unlock(); + } + } + } + +}
\ No newline at end of file diff --git a/engine/src/core/com/jme3/app/Application.java b/engine/src/core/com/jme3/app/Application.java new file mode 100644 index 0000000..517ec61 --- /dev/null +++ b/engine/src/core/com/jme3/app/Application.java @@ -0,0 +1,642 @@ +/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.app;
+
+import com.jme3.app.state.AppStateManager;
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioContext;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.audio.Listener;
+import com.jme3.input.*;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.system.JmeContext.Type;
+import com.jme3.system.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The <code>Application</code> class represents an instance of a
+ * real-time 3D rendering jME application.
+ *
+ * An <code>Application</code> provides all the tools that are commonly used in jME3
+ * applications.
+ *
+ * jME3 applications should extend this class and call start() to begin the
+ * application.
+ *
+ */
+public class Application implements SystemListener {
+
+ private static final Logger logger = Logger.getLogger(Application.class.getName());
+
+ protected AssetManager assetManager;
+
+ protected AudioRenderer audioRenderer;
+ protected Renderer renderer;
+ protected RenderManager renderManager;
+ protected ViewPort viewPort;
+ protected ViewPort guiViewPort;
+
+ protected JmeContext context;
+ protected AppSettings settings;
+ protected Timer timer = new NanoTimer();
+ protected Camera cam;
+ protected Listener listener;
+
+ protected boolean inputEnabled = true;
+ protected boolean pauseOnFocus = true;
+ 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;
+
+ private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();
+
+ /**
+ * Create a new instance of <code>Application</code>.
+ */
+ public Application(){
+ initStateManager();
+ }
+
+ /**
+ * Returns true if pause on lost focus is enabled, false otherwise.
+ *
+ * @return true if pause on lost focus is enabled
+ *
+ * @see #setPauseOnLostFocus(boolean)
+ */
+ public boolean isPauseOnLostFocus() {
+ return pauseOnFocus;
+ }
+
+ /**
+ * Enable or disable pause on lost focus.
+ * <p>
+ * By default, pause on lost focus is enabled.
+ * If enabled, the application will stop updating
+ * when it loses focus or becomes inactive (e.g. alt-tab).
+ * For online or real-time applications, this might not be preferable,
+ * so this feature should be set to disabled. For other applications,
+ * it is best to keep it on so that CPU usage is not used when
+ * not necessary.
+ *
+ * @param pauseOnLostFocus True to enable pause on lost focus, false
+ * otherwise.
+ */
+ public void setPauseOnLostFocus(boolean pauseOnLostFocus) {
+ this.pauseOnFocus = pauseOnLostFocus;
+ }
+
+ @Deprecated
+ public void setAssetManager(AssetManager assetManager){
+ if (this.assetManager != null)
+ throw new IllegalStateException("Can only set asset manager"
+ + " before initialization.");
+
+ this.assetManager = assetManager;
+ }
+
+ private void initAssetManager(){
+ if (settings != null){
+ String assetCfg = settings.getString("AssetConfigURL");
+ if (assetCfg != null){
+ URL url = null;
+ try {
+ url = new URL(assetCfg);
+ } catch (MalformedURLException ex) {
+ }
+ if (url == null) {
+ url = Application.class.getClassLoader().getResource(assetCfg);
+ if (url == null) {
+ logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);
+ return;
+ }
+ }
+ assetManager = JmeSystem.newAssetManager(url);
+ }
+ }
+ if (assetManager == null){
+ assetManager = JmeSystem.newAssetManager(
+ Thread.currentThread().getContextClassLoader()
+ .getResource("com/jme3/asset/Desktop.cfg"));
+ }
+ }
+
+ /**
+ * Set the display settings to define the display created.
+ * <p>
+ * Examples of display parameters include display pixel width and height,
+ * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency.
+ * If this method is called while the application is already running, then
+ * {@link #restart() } must be called to apply the settings to the display.
+ *
+ * @param settings The settings to set.
+ */
+ 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;
+ if (inputEnabled){
+ initInput();
+ }else{
+ destroyInput();
+ }
+ }else{
+ inputEnabled = settings.useInput();
+ }
+ }
+
+ /**
+ * Sets the Timer implementation that will be used for calculating
+ * frame times. By default, Application will use the Timer as returned
+ * by the current JmeContext implementation.
+ */
+ public void setTimer(Timer timer){
+ this.timer = timer;
+
+ if (timer != null) {
+ timer.reset();
+ }
+
+ if (renderManager != null) {
+ renderManager.setTimer(timer);
+ }
+ }
+
+ public Timer getTimer(){
+ return timer;
+ }
+
+ private void initDisplay(){
+ // aquire important objects
+ // from the context
+ settings = context.getSettings();
+
+ // Only reset the timer if a user has not already provided one
+ if (timer == null) {
+ timer = context.getTimer();
+ }
+
+ renderer = context.getRenderer();
+ }
+
+ private void initAudio(){
+ if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){
+ audioRenderer = JmeSystem.newAudioRenderer(settings);
+ audioRenderer.initialize();
+ AudioContext.setAudioRenderer(audioRenderer);
+
+ listener = new Listener();
+ audioRenderer.setListener(listener);
+ }
+ }
+
+ /**
+ * Creates the camera to use for rendering. Default values are perspective
+ * projection with 45° field of view, with near and far values 1 and 1000
+ * units respectively.
+ */
+ private void initCamera(){
+ cam = new Camera(settings.getWidth(), settings.getHeight());
+
+ cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);
+ cam.setLocation(new Vector3f(0f, 0f, 10f));
+ cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
+
+ renderManager = new RenderManager(renderer);
+ //Remy - 09/14/2010 setted the timer in the renderManager
+ renderManager.setTimer(timer);
+ viewPort = renderManager.createMainView("Default", cam);
+ viewPort.setClearFlags(true, true, true);
+
+ // Create a new cam for the gui
+ Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
+ guiViewPort = renderManager.createPostView("Gui Default", guiCam);
+ guiViewPort.setClearFlags(false, false, false);
+ }
+
+ /**
+ * Initializes mouse and keyboard input. Also
+ * initializes joystick input if joysticks are enabled in the
+ * AppSettings.
+ */
+ private void initInput(){
+ mouseInput = context.getMouseInput();
+ if (mouseInput != null)
+ mouseInput.initialize();
+
+ keyInput = context.getKeyInput();
+ if (keyInput != null)
+ keyInput.initialize();
+
+ touchInput = context.getTouchInput();
+ if (touchInput != null)
+ touchInput.initialize();
+
+ if (!settings.getBoolean("DisableJoysticks")){
+ joyInput = context.getJoyInput();
+ if (joyInput != null)
+ joyInput.initialize();
+ }
+
+ inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);
+ }
+
+ private void initStateManager(){
+ stateManager = new AppStateManager(this);
+ }
+
+ /**
+ * @return The {@link AssetManager asset manager} for this application.
+ */
+ public AssetManager getAssetManager(){
+ return assetManager;
+ }
+
+ /**
+ * @return the {@link InputManager input manager}.
+ */
+ public InputManager getInputManager(){
+ return inputManager;
+ }
+
+ /**
+ * @return the {@link AppStateManager app state manager}
+ */
+ public AppStateManager getStateManager() {
+ return stateManager;
+ }
+
+ /**
+ * @return the {@link RenderManager render manager}
+ */
+ public RenderManager getRenderManager() {
+ return renderManager;
+ }
+
+ /**
+ * @return The {@link Renderer renderer} for the application
+ */
+ public Renderer getRenderer(){
+ return renderer;
+ }
+
+ /**
+ * @return The {@link AudioRenderer audio renderer} for the application
+ */
+ public AudioRenderer getAudioRenderer() {
+ return audioRenderer;
+ }
+
+ /**
+ * @return The {@link Listener listener} object for audio
+ */
+ public Listener getListener() {
+ return listener;
+ }
+
+ /**
+ * @return The {@link JmeContext display context} for the application
+ */
+ public JmeContext getContext(){
+ return context;
+ }
+
+ /**
+ * @return The {@link Camera camera} for the application
+ */
+ public Camera getCamera(){
+ return cam;
+ }
+
+ /**
+ * Starts the application in {@link Type#Display display} mode.
+ *
+ * @see #start(com.jme3.system.JmeContext.Type)
+ */
+ public void start(){
+ start(JmeContext.Type.Display);
+ }
+
+ /**
+ * Starts the application.
+ * Creating a rendering context and executing
+ * the main loop in a separate thread.
+ */
+ public void start(JmeContext.Type contextType){
+ if (context != null && context.isCreated()){
+ logger.warning("start() called when application already created!");
+ return;
+ }
+
+ if (settings == null){
+ settings = new AppSettings(true);
+ }
+
+ logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
+ context = JmeSystem.newContext(settings, contextType);
+ context.setSystemListener(this);
+ context.create(false);
+ }
+
+ /**
+ * Initializes the application's canvas for use.
+ * <p>
+ * After calling this method, cast the {@link #getContext() context} to
+ * {@link JmeCanvasContext},
+ * then acquire the canvas with {@link JmeCanvasContext#getCanvas() }
+ * and attach it to an AWT/Swing Frame.
+ * The rendering thread will start when the canvas becomes visible on
+ * screen, however if you wish to start the context immediately you
+ * may call {@link #startCanvas() } to force the rendering thread
+ * to start.
+ *
+ * @see JmeCanvasContext
+ * @see Type#Canvas
+ */
+ public void createCanvas(){
+ if (context != null && context.isCreated()){
+ logger.warning("createCanvas() called when application already created!");
+ return;
+ }
+
+ if (settings == null){
+ settings = new AppSettings(true);
+ }
+
+ logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
+ context = JmeSystem.newContext(settings, JmeContext.Type.Canvas);
+ context.setSystemListener(this);
+ }
+
+ /**
+ * Starts the rendering thread after createCanvas() has been called.
+ * <p>
+ * Same as calling startCanvas(false)
+ *
+ * @see #startCanvas(boolean)
+ */
+ public void startCanvas(){
+ startCanvas(false);
+ }
+
+ /**
+ * Starts the rendering thread after createCanvas() has been called.
+ * <p>
+ * Calling this method is optional, the canvas will start automatically
+ * when it becomes visible.
+ *
+ * @param waitFor If true, the current thread will block until the
+ * rendering thread is running
+ */
+ public void startCanvas(boolean waitFor){
+ context.create(waitFor);
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void reshape(int w, int h){
+ renderManager.notifyReshape(w, h);
+ }
+
+ /**
+ * Restarts the context, applying any changed settings.
+ * <p>
+ * Changes to the {@link AppSettings} of this Application are not
+ * applied immediately; calling this method forces the context
+ * to restart, applying the new settings.
+ */
+ public void restart(){
+ context.setSettings(settings);
+ context.restart();
+ }
+
+ /**
+ * Requests the context to close, shutting down the main loop
+ * and making necessary cleanup operations.
+ *
+ * Same as calling stop(false)
+ *
+ * @see #stop(boolean)
+ */
+ public void stop(){
+ stop(false);
+ }
+
+ /**
+ * Requests the context to close, shutting down the main loop
+ * and making necessary cleanup operations.
+ * After the application has stopped, it cannot be used anymore.
+ */
+ public void stop(boolean waitFor){
+ logger.log(Level.FINE, "Closing application: {0}", getClass().getName());
+ context.destroy(waitFor);
+ }
+
+ /**
+ * Do not call manually.
+ * Callback from ContextListener.
+ * <p>
+ * Initializes the <code>Application</code>, by creating a display and
+ * default camera. If display settings are not specified, a default
+ * 640x480 display is created. Default values are used for the camera;
+ * perspective projection with 45° field of view, with near
+ * and far values 1 and 1000 units respectively.
+ */
+ public void initialize(){
+ if (assetManager == null){
+ initAssetManager();
+ }
+
+ initDisplay();
+ initCamera();
+
+ if (inputEnabled){
+ initInput();
+ }
+ initAudio();
+
+ // update timer so that the next delta is not too large
+// timer.update();
+ timer.reset();
+
+ // user code here..
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void handleError(String errMsg, Throwable t){
+ logger.log(Level.SEVERE, errMsg, t);
+ // user should add additional code to handle the error.
+ stop(); // stop the application
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void gainFocus(){
+ if (pauseOnFocus) {
+ paused = false;
+ context.setAutoFlushFrames(true);
+ if (inputManager != null) {
+ inputManager.reset();
+ }
+ }
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void loseFocus(){
+ if (pauseOnFocus){
+ paused = true;
+ context.setAutoFlushFrames(false);
+ }
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void requestClose(boolean esc){
+ context.destroy(false);
+ }
+
+ /**
+ * Enqueues a task/callable object to execute in the jME3
+ * rendering thread.
+ * <p>
+ * Callables are executed right at the beginning of the main loop.
+ * They are executed even if the application is currently paused
+ * or out of focus.
+ */
+ public <V> Future<V> enqueue(Callable<V> callable) {
+ AppTask<V> task = new AppTask<V>(callable);
+ taskQueue.add(task);
+ return task;
+ }
+
+ /**
+ * Do not call manually.
+ * Callback from ContextListener.
+ */
+ public void update(){
+ // Make sure the audio renderer is available to callables
+ AudioContext.setAudioRenderer(audioRenderer);
+
+ AppTask<?> task = taskQueue.poll();
+ toploop: do {
+ if (task == null) break;
+ while (task.isCancelled()) {
+ task = taskQueue.poll();
+ if (task == null) break toploop;
+ }
+ task.invoke();
+ } while (((task = taskQueue.poll()) != null));
+
+ if (speed == 0 || paused)
+ return;
+
+ timer.update();
+
+ if (inputEnabled){
+ inputManager.update(timer.getTimePerFrame());
+ }
+
+ if (audioRenderer != null){
+ audioRenderer.update(timer.getTimePerFrame());
+ }
+
+ // user code here..
+ }
+
+ protected void destroyInput(){
+ if (mouseInput != null)
+ mouseInput.destroy();
+
+ if (keyInput != null)
+ keyInput.destroy();
+
+ if (joyInput != null)
+ joyInput.destroy();
+
+ if (touchInput != null)
+ touchInput.destroy();
+
+ inputManager = null;
+ }
+
+ /**
+ * Do not call manually.
+ * Callback from ContextListener.
+ */
+ public void destroy(){
+ stateManager.cleanup();
+
+ destroyInput();
+ if (audioRenderer != null)
+ audioRenderer.cleanup();
+
+ timer.reset();
+ }
+
+ /**
+ * @return The GUI viewport. Which is used for the on screen
+ * statistics and FPS.
+ */
+ public ViewPort getGuiViewPort() {
+ return guiViewPort;
+ }
+
+ public ViewPort getViewPort() {
+ return viewPort;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/app/DebugKeysAppState.java b/engine/src/core/com/jme3/app/DebugKeysAppState.java new file mode 100644 index 0000000..2317c28 --- /dev/null +++ b/engine/src/core/com/jme3/app/DebugKeysAppState.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.input.InputManager; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.util.BufferUtils; + + +/** + * Registers a few keys that will dump debug information + * to the console. + * + * @author Paul Speed + */ +public class DebugKeysAppState extends AbstractAppState { + + public static final String INPUT_MAPPING_CAMERA_POS = "SIMPLEAPP_CameraPos"; + public static final String INPUT_MAPPING_MEMORY = "SIMPLEAPP_Memory"; + + private Application app; + private DebugKeyListener keyListener = new DebugKeyListener(); + private InputManager inputManager; + + public DebugKeysAppState() { + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + + this.app = app; + this.inputManager = app.getInputManager(); + + if (app.getInputManager() != null) { + + inputManager.addMapping(INPUT_MAPPING_CAMERA_POS, new KeyTrigger(KeyInput.KEY_C)); + inputManager.addMapping(INPUT_MAPPING_MEMORY, new KeyTrigger(KeyInput.KEY_M)); + + inputManager.addListener(keyListener, + INPUT_MAPPING_CAMERA_POS, + INPUT_MAPPING_MEMORY); + } + } + + @Override + public void cleanup() { + super.cleanup(); + + if (inputManager.hasMapping(INPUT_MAPPING_CAMERA_POS)) + inputManager.deleteMapping(INPUT_MAPPING_CAMERA_POS); + if (inputManager.hasMapping(INPUT_MAPPING_MEMORY)) + inputManager.deleteMapping(INPUT_MAPPING_MEMORY); + + inputManager.removeListener(keyListener); + } + + + private class DebugKeyListener implements ActionListener { + + public void onAction(String name, boolean value, float tpf) { + if (!value) { + return; + } + + if (name.equals(INPUT_MAPPING_CAMERA_POS)) { + Camera cam = app.getCamera(); + if (cam != null) { + Vector3f loc = cam.getLocation(); + Quaternion rot = cam.getRotation(); + System.out.println("Camera Position: (" + + loc.x + ", " + loc.y + ", " + loc.z + ")"); + System.out.println("Camera Rotation: " + rot); + System.out.println("Camera Direction: " + cam.getDirection()); + } + } else if (name.equals(INPUT_MAPPING_MEMORY)) { + BufferUtils.printCurrentDirectMemory(null); + } + } + } +} diff --git a/engine/src/core/com/jme3/app/FlyCamAppState.java b/engine/src/core/com/jme3/app/FlyCamAppState.java new file mode 100644 index 0000000..5a7b11e --- /dev/null +++ b/engine/src/core/com/jme3/app/FlyCamAppState.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.input.FlyByCamera; + + +/** + * Manages a FlyByCamera. + * + * @author Paul Speed + */ +public class FlyCamAppState extends AbstractAppState { + + private Application app; + private FlyByCamera flyCam; + + public FlyCamAppState() { + } + + /** + * This is called by SimpleApplication during initialize(). + */ + void setCamera( FlyByCamera cam ) { + this.flyCam = cam; + } + + public FlyByCamera getCamera() { + return flyCam; + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + + this.app = app; + + if (app.getInputManager() != null) { + + if (flyCam == null) { + flyCam = new FlyByCamera(app.getCamera()); + } + + flyCam.registerWithInput(app.getInputManager()); + } + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + + flyCam.setEnabled(enabled); + } + + @Override + public void cleanup() { + super.cleanup(); + + flyCam.unregisterInput(); + } + + +} diff --git a/engine/src/core/com/jme3/app/SimpleApplication.java b/engine/src/core/com/jme3/app/SimpleApplication.java new file mode 100644 index 0000000..c79ce83 --- /dev/null +++ b/engine/src/core/com/jme3/app/SimpleApplication.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.app.state.AppState; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.input.FlyByCamera; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext.Type; +import com.jme3.system.JmeSystem; +import com.jme3.util.BufferUtils; + +/** + * <code>SimpleApplication</code> extends the {@link com.jme3.app.Application} + * class to provide default functionality like a first-person camera, + * and an accessible root node that is updated and rendered regularly. + * Additionally, <code>SimpleApplication</code> will display a statistics view + * using the {@link com.jme3.app.StatsView} class. It will display + * the current frames-per-second value on-screen in addition to the statistics. + * Several keys have special functionality in <code>SimpleApplication</code>:<br/> + * + * <table> + * <tr><td>Esc</td><td>- Close the application</td></tr> + * <tr><td>C</td><td>- Display the camera position and rotation in the console.</td></tr> + * <tr><td>M</td><td>- Display memory usage in the console.</td></tr> + * </table> + */ +public abstract class SimpleApplication extends Application { + + public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit"; + public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS; + public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY; + public static final String INPUT_MAPPING_HIDE_STATS = "SIMPLEAPP_HideStats"; + + protected Node rootNode = new Node("Root Node"); + protected Node guiNode = new Node("Gui Node"); + protected BitmapText fpsText; + protected BitmapFont guiFont; + protected FlyByCamera flyCam; + protected boolean showSettings = true; + private AppActionListener actionListener = new AppActionListener(); + + private class AppActionListener implements ActionListener { + + public void onAction(String name, boolean value, float tpf) { + if (!value) { + return; + } + + if (name.equals(INPUT_MAPPING_EXIT)) { + stop(); + }else if (name.equals(INPUT_MAPPING_HIDE_STATS)){ + if (stateManager.getState(StatsAppState.class) != null) { + stateManager.getState(StatsAppState.class).toggleStats(); + } + } + } + } + + public SimpleApplication() { + this( new StatsAppState(), new FlyCamAppState(), new DebugKeysAppState() ); + } + + public SimpleApplication( AppState... initialStates ) { + super(); + + if (initialStates != null) { + for (AppState a : initialStates) { + if (a != null) { + stateManager.attach(a); + } + } + } + } + + @Override + public void start() { + // set some default settings in-case + // settings dialog is not shown + boolean loadSettings = false; + if (settings == null) { + setSettings(new AppSettings(true)); + loadSettings = true; + } + + // show settings dialog + if (showSettings) { + if (!JmeSystem.showSettingsDialog(settings, loadSettings)) { + return; + } + } + //re-setting settings they can have been merged from the registry. + setSettings(settings); + super.start(); + } + + /** + * Retrieves flyCam + * @return flyCam Camera object + * + */ + public FlyByCamera getFlyByCamera() { + return flyCam; + } + + /** + * Retrieves guiNode + * @return guiNode Node object + * + */ + public Node getGuiNode() { + return guiNode; + } + + /** + * Retrieves rootNode + * @return rootNode Node object + * + */ + public Node getRootNode() { + return rootNode; + } + + public boolean isShowSettings() { + return showSettings; + } + + /** + * Toggles settings window to display at start-up + * @param showSettings Sets true/false + * + */ + public void setShowSettings(boolean showSettings) { + this.showSettings = showSettings; + } + + @Override + public void initialize() { + super.initialize(); + + // Several things rely on having this + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + + guiNode.setQueueBucket(Bucket.Gui); + guiNode.setCullHint(CullHint.Never); + viewPort.attachScene(rootNode); + guiViewPort.attachScene(guiNode); + + if (inputManager != null) { + + // We have to special-case the FlyCamAppState because too + // many SimpleApplication subclasses expect it to exist in + // simpleInit(). But at least it only gets initialized if + // the app state is added. + if (stateManager.getState(FlyCamAppState.class) != null) { + flyCam = new FlyByCamera(cam); + flyCam.setMoveSpeed(1f); // odd to set this here but it did it before + stateManager.getState(FlyCamAppState.class).setCamera( flyCam ); + } + + if (context.getType() == Type.Display) { + inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE)); + } + + if (stateManager.getState(StatsAppState.class) != null) { + inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5)); + inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS); + } + + inputManager.addListener(actionListener, INPUT_MAPPING_EXIT); + } + + if (stateManager.getState(StatsAppState.class) != null) { + // Some of the tests rely on having access to fpsText + // for quick display. Maybe a different way would be better. + stateManager.getState(StatsAppState.class).setFont(guiFont); + fpsText = stateManager.getState(StatsAppState.class).getFpsText(); + } + + // call user code + simpleInitApp(); + } + + @Override + public void update() { + super.update(); // makes sure to execute AppTasks + if (speed == 0 || paused) { + return; + } + + float tpf = timer.getTimePerFrame() * speed; + + // update states + stateManager.update(tpf); + + // simple update and root node + simpleUpdate(tpf); + + rootNode.updateLogicalState(tpf); + guiNode.updateLogicalState(tpf); + + rootNode.updateGeometricState(); + guiNode.updateGeometricState(); + + // Moving this here to make sure it is always done. + // Now the sets are cleared every frame (guaranteed) + // and more than one viewer can access the data. This + // used to be cleared by StatsView but then only StatsView + // could get accurate counts. + renderer.getStatistics().clearFrame(); + + // render states + stateManager.render(renderManager); + renderManager.render(tpf, context.isRenderable()); + simpleRender(renderManager); + stateManager.postRender(); + } + + public void setDisplayFps(boolean show) { + if (stateManager.getState(StatsAppState.class) != null) { + stateManager.getState(StatsAppState.class).setDisplayFps(show); + } + } + + public void setDisplayStatView(boolean show) { + if (stateManager.getState(StatsAppState.class) != null) { + stateManager.getState(StatsAppState.class).setDisplayStatView(show); + } + } + + public abstract void simpleInitApp(); + + public void simpleUpdate(float tpf) { + } + + public void simpleRender(RenderManager rm) { + } +} diff --git a/engine/src/core/com/jme3/app/StatsAppState.java b/engine/src/core/com/jme3/app/StatsAppState.java new file mode 100644 index 0000000..d4c968b --- /dev/null +++ b/engine/src/core/com/jme3/app/StatsAppState.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial.CullHint; + + +/** + * Displays stats in SimpleApplication's GUI node or + * using the node and font parameters provided. + * + * @author Paul Speed + */ +public class StatsAppState extends AbstractAppState { + + private Application app; + protected StatsView statsView; + protected boolean showSettings = true; + private boolean showFps = true; + private boolean showStats = true; + + protected Node guiNode; + protected float secondCounter = 0.0f; + protected int frameCounter = 0; + protected BitmapText fpsText; + protected BitmapFont guiFont; + + public StatsAppState() { + } + + public StatsAppState( Node guiNode, BitmapFont guiFont ) { + this.guiNode = guiNode; + this.guiFont = guiFont; + } + + /** + * Called by SimpleApplication to provide an early font + * so that the fpsText can be created before init. This + * is because several applications expect to directly access + * fpsText... unfortunately. + */ + void setFont( BitmapFont guiFont ) { + this.guiFont = guiFont; + this.fpsText = new BitmapText(guiFont, false); + } + + public BitmapText getFpsText() { + return fpsText; + } + + public StatsView getStatsView() { + return statsView; + } + + public float getSecondCounter() { + return secondCounter; + } + + public void toggleStats() { + setDisplayFps( !showFps ); + setDisplayStatView( !showStats ); + } + + public void setDisplayFps(boolean show) { + showFps = show; + if (fpsText != null) { + fpsText.setCullHint(show ? CullHint.Never : CullHint.Always); + } + } + + public void setDisplayStatView(boolean show) { + showStats = show; + if (statsView != null ) { + statsView.setEnabled(show); + statsView.setCullHint(show ? CullHint.Never : CullHint.Always); + } + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + this.app = app; + + if (app instanceof SimpleApplication) { + SimpleApplication simpleApp = (SimpleApplication)app; + if (guiNode == null) + guiNode = simpleApp.guiNode; + if (guiFont == null ) + guiFont = simpleApp.guiFont; + } + + if (guiNode == null) { + throw new RuntimeException( "No guiNode specific and cannot be automatically determined." ); + } + + if (guiFont == null) { + guiFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt"); + } + + loadFpsText(); + loadStatsView(); + } + + /** + * Attaches FPS statistics to guiNode and displays it on the screen. + * + */ + public void loadFpsText() { + if (fpsText == null) { + fpsText = new BitmapText(guiFont, false); + } + + fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0); + fpsText.setText("Frames per second"); + fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always); + guiNode.attachChild(fpsText); + } + + /** + * Attaches Statistics View to guiNode and displays it on the screen + * above FPS statistics line. + * + */ + public void loadStatsView() { + statsView = new StatsView("Statistics View", + app.getAssetManager(), + app.getRenderer().getStatistics()); + // move it up so it appears above fps text + statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0); + statsView.setEnabled(showStats); + statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always); + guiNode.attachChild(statsView); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + + if (enabled) { + fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always); + statsView.setEnabled(showStats); + statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always); + } else { + fpsText.setCullHint(CullHint.Always); + statsView.setEnabled(false); + statsView.setCullHint(CullHint.Always); + } + } + + @Override + public void update(float tpf) { + if (showFps) { + secondCounter += app.getTimer().getTimePerFrame(); + frameCounter ++; + if (secondCounter >= 1.0f) { + int fps = (int) (frameCounter / secondCounter); + fpsText.setText("Frames per second: " + fps); + secondCounter = 0.0f; + frameCounter = 0; + } + } + } + + @Override + public void cleanup() { + super.cleanup(); + + guiNode.detachChild(statsView); + guiNode.detachChild(fpsText); + } + + +} diff --git a/engine/src/core/com/jme3/app/StatsView.java b/engine/src/core/com/jme3/app/StatsView.java new file mode 100644 index 0000000..49eeb13 --- /dev/null +++ b/engine/src/core/com/jme3/app/StatsView.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.app; + +import com.jme3.asset.AssetManager; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Statistics; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.Control; + +/** + * The <code>StatsView</code> provides a heads-up display (HUD) of various + * statistics of rendering. The data is retrieved every frame from a + * {@link com.jme3.renderer.Statistics} and then displayed on screen.<br/> + * <br/> + * Usage:<br/> + * To use the stats view, you need to retrieve the + * {@link com.jme3.renderer.Statistics} from the + * {@link com.jme3.renderer.Renderer} used by the application. Then, attach + * the <code>StatsView</code> to the scene graph.<br/> + * <code><br/> + * Statistics stats = renderer.getStatistics();<br/> + * StatsView statsView = new StatsView("MyStats", assetManager, stats);<br/> + * rootNode.attachChild(statsView);<br/> + * </code> + */ +public class StatsView extends Node implements Control { + + private BitmapText[] labels; + private Statistics statistics; + + private String[] statLabels; + private int[] statData; + + private boolean enabled = true; + + private final StringBuilder stringBuilder = new StringBuilder(); + + public StatsView(String name, AssetManager manager, Statistics stats){ + super(name); + + setQueueBucket(Bucket.Gui); + setCullHint(CullHint.Never); + + statistics = stats; + + statLabels = statistics.getLabels(); + statData = new int[statLabels.length]; + labels = new BitmapText[statLabels.length]; + + BitmapFont font = manager.loadFont("Interface/Fonts/Console.fnt"); + for (int i = 0; i < labels.length; i++){ + labels[i] = new BitmapText(font); + labels[i].setLocalTranslation(0, labels[i].getLineHeight() * (i+1), 0); + attachChild(labels[i]); + } + + addControl(this); + } + + public void update(float tpf) { + + if (!isEnabled()) + return; + + statistics.getData(statData); + for (int i = 0; i < labels.length; i++) { + stringBuilder.setLength(0); + stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]); + labels[i].setText(stringBuilder); + } + + // Moved to SimpleApplication to make sure it is + // done even if there is no StatsView or the StatsView + // is disable. + //statistics.clearFrame(); + } + + public Control cloneForSpatial(Spatial spatial) { + return (Control) spatial; + } + + public void setSpatial(Spatial spatial) { + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public void render(RenderManager rm, ViewPort vp) { + } + +} diff --git a/engine/src/core/com/jme3/app/package.html b/engine/src/core/com/jme3/app/package.html new file mode 100644 index 0000000..ec6bb9a --- /dev/null +++ b/engine/src/core/com/jme3/app/package.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> + +<head> +<title></title> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +</head> +<body> + +The <code>com.jme3.application</code> provides a toolset for jME3 applications +to interact with various components of the engine. Typically, the +{@link com.jme3.app.Application} class will be extended and the update() method +implemented to provide functionality for the main loop. <br> +<p> +An <code>Application</code> will typically provide the following services: +<ul> + <li>{@link com.jme3.asset.AssetManager} - A system for finding and loading + data assets included with the application, such as models and textures.</li> + <li>{@link com.jme3.renderer.RenderManager} - A high-level rendering + interface for 3D graphics, manages viewports and scenes assigned + to the viewports, as well as general high-level rendering.</li> + <li>{@link com.jme3.input.InputManager} - An interface for handling input + from devices such as keyboard, mouse, and gamepad/joystick.</li> + <li>{@link com.jme3.app.state.AppStateManager} - Manager for + {@link com.jme3.app.state.AppState}s, which are specific application + functionality to be executed inside the main loop.</li> + <li>{@link com.jme3.audio.AudioRenderer} - Allows playing sound effects and + music.</li> + <li>{@link com.jme3.system.Timer} - The timer keeps track of time and allows + computing the time since the last frame (TPF) that is neccessary + for framerate-independent updates and motion.</li> + <li>{@link com.jme3.system.AppSettings} - A database containing various + settings for the application. These settings may be set by the user + or the application itself.</li> +</ul> + + +<h3>Usage</h3> + +An example use of the Application class is as follows<br> +<br> + +<code> +public class ExampleUse extends Application {<br> +<br> + private Node rootNode = new Node("Root Node");<br> +<br> + public static void main(String[] args){<br> + ExampleUse app = new ExampleUse();<br> + app.start();<br> + }<br> +<br> + @Override<br> + public void initialize(){<br> + super.initialize();<br> +<br> + // attach root node to viewport<br> + viewPort.attachScene(rootNode);<br> + }<br> +<br> + @Override<br> + public void update(){<br> + super.update();<br> +<br> + float tpf = timer.getTimePerFrame();<br> +<br> + // update rootNode<br> + rootNode.updateLogicalState(tpf);<br> + rootNode.updateGeometricState();<br> +<br> + // render the viewports<br> + renderManager.render(tpf);<br> + }<br> +}<br> +<br> +</code> + +</body> +</html> + diff --git a/engine/src/core/com/jme3/app/state/AbstractAppState.java b/engine/src/core/com/jme3/app/state/AbstractAppState.java new file mode 100644 index 0000000..1ea1230 --- /dev/null +++ b/engine/src/core/com/jme3/app/state/AbstractAppState.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.app.state; + +import com.jme3.app.Application; +import com.jme3.renderer.RenderManager; + +/** + * <code>AbstractAppState</code> implements some common methods + * that make creation of AppStates easier. + * @author Kirill Vainer + */ +public class AbstractAppState implements AppState { + + /** + * <code>initialized</code> is set to true when the method + * {@link AbstractAppState#initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application) } + * is called. When {@link AbstractAppState#cleanup() } is called, <code>initialized</code> + * is set back to false. + */ + protected boolean initialized = false; + private boolean enabled = true; + + public void initialize(AppStateManager stateManager, Application app) { + initialized = true; + } + + public boolean isInitialized() { + return initialized; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public void stateAttached(AppStateManager stateManager) { + } + + public void stateDetached(AppStateManager stateManager) { + } + + public void update(float tpf) { + } + + public void render(RenderManager rm) { + } + + public void postRender(){ + } + + public void cleanup() { + initialized = false; + } + +} diff --git a/engine/src/core/com/jme3/app/state/AppState.java b/engine/src/core/com/jme3/app/state/AppState.java new file mode 100644 index 0000000..d94a35f --- /dev/null +++ b/engine/src/core/com/jme3/app/state/AppState.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.app.state; + +import com.jme3.app.Application; +import com.jme3.renderer.RenderManager; + +/** + * AppState represents a continously executing code inside the main loop. + * An <code>AppState</code> can track when it is attached to the + * {@link AppStateManager} or when it is detached. <br/><code>AppState</code>s + * are initialized in the render thread, upon a call to {@link AppState#initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application) } + * and are de-initialized upon a call to {@link AppState#cleanup()}. + * Implementations should return the correct value with a call to + * {@link AppState#isInitialized() } as specified above.<br/> + * + * + * @author Kirill Vainer + */ +public interface AppState { + + /** + * Called to initialize the AppState. + * + * @param stateManager The state manager + * @param app + */ + public void initialize(AppStateManager stateManager, Application app); + + /** + * @return True if <code>initialize()</code> was called on the state, + * false otherwise. + */ + public boolean isInitialized(); + + /** + * Enable or disable the functionality of the <code>AppState</code>. + * The effect of this call depends on implementation. An + * <code>AppState</code> starts as being enabled by default. + * + * @param active activate the AppState or not. + */ + public void setEnabled(boolean active); + + /** + * @return True if the <code>AppState</code> is enabled, false otherwise. + * + * @see AppState#setEnabled(boolean) + */ + public boolean isEnabled(); + /** + * Called when the state was attached. + * + * @param stateManager State manager to which the state was attached to. + */ + public void stateAttached(AppStateManager stateManager); + + /** + * Called when the state was detached. + * + * @param stateManager The state manager from which the state was detached from. + */ + public void stateDetached(AppStateManager stateManager); + + /** + * Called to update the state. + * + * @param tpf Time per frame. + */ + public void update(float tpf); + + /** + * Render the state. + * + * @param rm RenderManager + */ + public void render(RenderManager rm); + + /** + * Called after all rendering commands are flushed. + */ + public void postRender(); + + /** + * Cleanup the game state. + */ + public void cleanup(); + +} diff --git a/engine/src/core/com/jme3/app/state/AppStateManager.java b/engine/src/core/com/jme3/app/state/AppStateManager.java new file mode 100644 index 0000000..81228af --- /dev/null +++ b/engine/src/core/com/jme3/app/state/AppStateManager.java @@ -0,0 +1,288 @@ +/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.app.state;
+
+import com.jme3.app.Application;
+import com.jme3.renderer.RenderManager;
+import com.jme3.util.SafeArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The <code>AppStateManager</code> holds a list of {@link AppState}s which
+ * it will update and render.<br/>
+ * When an {@link AppState} is attached or detached, the
+ * {@link AppState#stateAttached(com.jme3.app.state.AppStateManager) } and
+ * {@link AppState#stateDetached(com.jme3.app.state.AppStateManager) } methods
+ * will be called respectively.
+ *
+ * <p>The lifecycle for an attached AppState is as follows:</p>
+ * <ul>
+ * <li>stateAttached() : called when the state is attached on the thread on which
+ * the state was attached.
+ * <li>initialize() : called ONCE on the render thread at the beginning of the next
+ * AppStateManager.update().
+ * <li>stateDetached() : called when the state is attached on the thread on which
+ * the state was detached. This is not necessarily on the
+ * render thread and it is not necessarily safe to modify
+ * the scene graph, etc..
+ * <li>cleanup() : called ONCE on the render thread at the beginning of the next update
+ * after the state has been detached or when the application is
+ * terminating.
+ * </ul>
+ *
+ * @author Kirill Vainer, Paul Speed
+ */
+public class AppStateManager {
+
+ /**
+ * List holding the attached app states that are pending
+ * initialization. Once initialized they will be added to
+ * the running app states.
+ */
+ private final SafeArrayList<AppState> initializing = new SafeArrayList<AppState>(AppState.class);
+
+ /**
+ * Holds the active states once they are initialized.
+ */
+ private final SafeArrayList<AppState> states = new SafeArrayList<AppState>(AppState.class);
+
+ /**
+ * List holding the detached app states that are pending
+ * cleanup.
+ */
+ private final SafeArrayList<AppState> terminating = new SafeArrayList<AppState>(AppState.class);
+
+ // All of the above lists need to be thread safe but access will be
+ // synchronized separately.... but always on the states list. This
+ // is to avoid deadlocking that may occur and the most common use case
+ // is that they are all modified from the same thread anyway.
+
+ private final Application app;
+ private AppState[] stateArray;
+
+ public AppStateManager(Application app){
+ this.app = app;
+ }
+
+ protected AppState[] getInitializing() {
+ synchronized (states){
+ return initializing.getArray();
+ }
+ }
+
+ protected AppState[] getTerminating() {
+ synchronized (states){
+ return terminating.getArray();
+ }
+ }
+
+ protected AppState[] getStates(){
+ synchronized (states){
+ return states.getArray();
+ }
+ }
+
+ /**
+ * Attach a state to the AppStateManager, the same state cannot be attached
+ * twice.
+ *
+ * @param state The state to attach
+ * @return True if the state was successfully attached, false if the state
+ * was already attached.
+ */
+ public boolean attach(AppState state){
+ synchronized (states){
+ if (!states.contains(state) && !initializing.contains(state)){
+ state.stateAttached(this);
+ initializing.add(state);
+ return true;
+ }else{
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Detaches the state from the AppStateManager.
+ *
+ * @param state The state to detach
+ * @return True if the state was detached successfully, false
+ * if the state was not attached in the first place.
+ */
+ public boolean detach(AppState state){
+ synchronized (states){
+ if (states.contains(state)){
+ state.stateDetached(this);
+ states.remove(state);
+ terminating.add(state);
+ return true;
+ } else if(initializing.contains(state)){
+ state.stateDetached(this);
+ initializing.remove(state);
+ return true;
+ }else{
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Check if a state is attached or not.
+ *
+ * @param state The state to check
+ * @return True if the state is currently attached to this AppStateManager.
+ *
+ * @see AppStateManager#attach(com.jme3.app.state.AppState)
+ */
+ public boolean hasState(AppState state){
+ synchronized (states){
+ return states.contains(state) || initializing.contains(state);
+ }
+ }
+
+ /**
+ * Returns the first state that is an instance of subclass of the specified class.
+ * @param <T>
+ * @param stateClass
+ * @return First attached state that is an instance of stateClass
+ */
+ public <T extends AppState> T getState(Class<T> stateClass){
+ synchronized (states){
+ AppState[] array = getStates();
+ for (AppState state : array) {
+ if (stateClass.isAssignableFrom(state.getClass())){
+ return (T) state;
+ }
+ }
+
+ // This may be more trouble than its worth but I think
+ // it's necessary for proper decoupling of states and provides
+ // similar behavior to before where a state could be looked
+ // up even if it wasn't initialized. -pspeed
+ array = getInitializing();
+ for (AppState state : array) {
+ if (stateClass.isAssignableFrom(state.getClass())){
+ return (T) state;
+ }
+ }
+ }
+ return null;
+ }
+
+ protected void initializePending(){
+ AppState[] array = getInitializing();
+ synchronized( states ) {
+ // Move the states that will be initialized
+ // into the active array. In all but one case the
+ // order doesn't matter but if we do this here then
+ // a state can detach itself in initialize(). If we
+ // did it after then it couldn't.
+ List<AppState> transfer = Arrays.asList(array);
+ states.addAll(transfer);
+ initializing.removeAll(transfer);
+ }
+ for (AppState state : array) {
+ state.initialize(this, app);
+ }
+ }
+
+ protected void terminatePending(){
+ AppState[] array = getTerminating();
+ for (AppState state : array) {
+ state.cleanup();
+ }
+ synchronized( states ) {
+ // Remove just the states that were terminated...
+ // which might now be a subset of the total terminating
+ // list.
+ terminating.removeAll(Arrays.asList(array));
+ }
+ }
+
+ /**
+ * Calls update for attached states, do not call directly.
+ * @param tpf Time per frame.
+ */
+ public void update(float tpf){
+
+ // Cleanup any states pending
+ terminatePending();
+
+ // Initialize any states pending
+ initializePending();
+
+ // Update enabled states
+ AppState[] array = getStates();
+ for (AppState state : array){
+ if (state.isEnabled()) {
+ state.update(tpf);
+ }
+ }
+ }
+
+ /**
+ * Calls render for all attached and initialized states, do not call directly.
+ * @param rm The RenderManager
+ */
+ public void render(RenderManager rm){
+ AppState[] array = getStates();
+ for (AppState state : array){
+ if (state.isEnabled()) {
+ state.render(rm);
+ }
+ }
+ }
+
+ /**
+ * Calls render for all attached and initialized states, do not call directly.
+ */
+ public void postRender(){
+ AppState[] array = getStates();
+ for (AppState state : array){
+ if (state.isEnabled()) {
+ state.postRender();
+ }
+ }
+ }
+
+ /**
+ * Calls cleanup on attached states, do not call directly.
+ */
+ public void cleanup(){
+ AppState[] array = getStates();
+ for (AppState state : array){
+ state.cleanup();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/app/state/package.html b/engine/src/core/com/jme3/app/state/package.html new file mode 100644 index 0000000..0e93b38 --- /dev/null +++ b/engine/src/core/com/jme3/app/state/package.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> + +<head> +<title></title> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +</head> +<body> + +The <code>com.jme3.app.state</code> package provides +{@link com.jme3.app.state.AppState app states}, +an abstract way of handling application logic. + +</body> +</html> |