aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/app
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/app')
-rw-r--r--engine/src/core/com/jme3/app/AppTask.java167
-rw-r--r--engine/src/core/com/jme3/app/Application.java642
-rw-r--r--engine/src/core/com/jme3/app/DebugKeysAppState.java117
-rw-r--r--engine/src/core/com/jme3/app/FlyCamAppState.java94
-rw-r--r--engine/src/core/com/jme3/app/SimpleApplication.java277
-rw-r--r--engine/src/core/com/jme3/app/StatsAppState.java208
-rw-r--r--engine/src/core/com/jme3/app/StatsView.java132
-rw-r--r--engine/src/core/com/jme3/app/package.html80
-rw-r--r--engine/src/core/com/jme3/app/state/AbstractAppState.java89
-rw-r--r--engine/src/core/com/jme3/app/state/AppState.java119
-rw-r--r--engine/src/core/com/jme3/app/state/AppStateManager.java288
-rw-r--r--engine/src/core/com/jme3/app/state/package.html15
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>