Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.app;
     34 
     35 import com.jme3.app.state.AppStateManager;
     36 import com.jme3.asset.AssetManager;
     37 import com.jme3.audio.AudioContext;
     38 import com.jme3.audio.AudioRenderer;
     39 import com.jme3.audio.Listener;
     40 import com.jme3.input.*;
     41 import com.jme3.math.Vector3f;
     42 import com.jme3.renderer.Camera;
     43 import com.jme3.renderer.RenderManager;
     44 import com.jme3.renderer.Renderer;
     45 import com.jme3.renderer.ViewPort;
     46 import com.jme3.system.JmeContext.Type;
     47 import com.jme3.system.*;
     48 import java.net.MalformedURLException;
     49 import java.net.URL;
     50 import java.util.concurrent.Callable;
     51 import java.util.concurrent.ConcurrentLinkedQueue;
     52 import java.util.concurrent.Future;
     53 import java.util.logging.Level;
     54 import java.util.logging.Logger;
     55 
     56 /**
     57  * The <code>Application</code> class represents an instance of a
     58  * real-time 3D rendering jME application.
     59  *
     60  * An <code>Application</code> provides all the tools that are commonly used in jME3
     61  * applications.
     62  *
     63  * jME3 applications should extend this class and call start() to begin the
     64  * application.
     65  *
     66  */
     67 public class Application implements SystemListener {
     68 
     69     private static final Logger logger = Logger.getLogger(Application.class.getName());
     70 
     71     protected AssetManager assetManager;
     72 
     73     protected AudioRenderer audioRenderer;
     74     protected Renderer renderer;
     75     protected RenderManager renderManager;
     76     protected ViewPort viewPort;
     77     protected ViewPort guiViewPort;
     78 
     79     protected JmeContext context;
     80     protected AppSettings settings;
     81     protected Timer timer = new NanoTimer();
     82     protected Camera cam;
     83     protected Listener listener;
     84 
     85     protected boolean inputEnabled = true;
     86     protected boolean pauseOnFocus = true;
     87     protected float speed = 1f;
     88     protected boolean paused = false;
     89     protected MouseInput mouseInput;
     90     protected KeyInput keyInput;
     91     protected JoyInput joyInput;
     92     protected TouchInput touchInput;
     93     protected InputManager inputManager;
     94     protected AppStateManager stateManager;
     95 
     96     private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();
     97 
     98     /**
     99      * Create a new instance of <code>Application</code>.
    100      */
    101     public Application(){
    102         initStateManager();
    103     }
    104 
    105     /**
    106      * Returns true if pause on lost focus is enabled, false otherwise.
    107      *
    108      * @return true if pause on lost focus is enabled
    109      *
    110      * @see #setPauseOnLostFocus(boolean)
    111      */
    112     public boolean isPauseOnLostFocus() {
    113         return pauseOnFocus;
    114     }
    115 
    116     /**
    117      * Enable or disable pause on lost focus.
    118      * <p>
    119      * By default, pause on lost focus is enabled.
    120      * If enabled, the application will stop updating
    121      * when it loses focus or becomes inactive (e.g. alt-tab).
    122      * For online or real-time applications, this might not be preferable,
    123      * so this feature should be set to disabled. For other applications,
    124      * it is best to keep it on so that CPU usage is not used when
    125      * not necessary.
    126      *
    127      * @param pauseOnLostFocus True to enable pause on lost focus, false
    128      * otherwise.
    129      */
    130     public void setPauseOnLostFocus(boolean pauseOnLostFocus) {
    131         this.pauseOnFocus = pauseOnLostFocus;
    132     }
    133 
    134     @Deprecated
    135     public void setAssetManager(AssetManager assetManager){
    136         if (this.assetManager != null)
    137             throw new IllegalStateException("Can only set asset manager"
    138                                           + " before initialization.");
    139 
    140         this.assetManager = assetManager;
    141     }
    142 
    143     private void initAssetManager(){
    144         if (settings != null){
    145             String assetCfg = settings.getString("AssetConfigURL");
    146             if (assetCfg != null){
    147                 URL url = null;
    148                 try {
    149                     url = new URL(assetCfg);
    150                 } catch (MalformedURLException ex) {
    151                 }
    152                 if (url == null) {
    153                     url = Application.class.getClassLoader().getResource(assetCfg);
    154                     if (url == null) {
    155                         logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);
    156                         return;
    157                     }
    158                 }
    159                 assetManager = JmeSystem.newAssetManager(url);
    160             }
    161         }
    162         if (assetManager == null){
    163             assetManager = JmeSystem.newAssetManager(
    164                     Thread.currentThread().getContextClassLoader()
    165                     .getResource("com/jme3/asset/Desktop.cfg"));
    166         }
    167     }
    168 
    169     /**
    170      * Set the display settings to define the display created.
    171      * <p>
    172      * Examples of display parameters include display pixel width and height,
    173      * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency.
    174      * If this method is called while the application is already running, then
    175      * {@link #restart() } must be called to apply the settings to the display.
    176      *
    177      * @param settings The settings to set.
    178      */
    179     public void setSettings(AppSettings settings){
    180         this.settings = settings;
    181         if (context != null && settings.useInput() != inputEnabled){
    182             // may need to create or destroy input based
    183             // on settings change
    184             inputEnabled = !inputEnabled;
    185             if (inputEnabled){
    186                 initInput();
    187             }else{
    188                 destroyInput();
    189             }
    190         }else{
    191             inputEnabled = settings.useInput();
    192         }
    193     }
    194 
    195     /**
    196      * Sets the Timer implementation that will be used for calculating
    197      * frame times.  By default, Application will use the Timer as returned
    198      * by the current JmeContext implementation.
    199      */
    200     public void setTimer(Timer timer){
    201         this.timer = timer;
    202 
    203         if (timer != null) {
    204             timer.reset();
    205         }
    206 
    207         if (renderManager != null) {
    208             renderManager.setTimer(timer);
    209         }
    210     }
    211 
    212     public Timer getTimer(){
    213         return timer;
    214     }
    215 
    216     private void initDisplay(){
    217         // aquire important objects
    218         // from the context
    219         settings = context.getSettings();
    220 
    221         // Only reset the timer if a user has not already provided one
    222         if (timer == null) {
    223             timer = context.getTimer();
    224         }
    225 
    226         renderer = context.getRenderer();
    227     }
    228 
    229     private void initAudio(){
    230         if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){
    231             audioRenderer = JmeSystem.newAudioRenderer(settings);
    232             audioRenderer.initialize();
    233             AudioContext.setAudioRenderer(audioRenderer);
    234 
    235             listener = new Listener();
    236             audioRenderer.setListener(listener);
    237         }
    238     }
    239 
    240     /**
    241      * Creates the camera to use for rendering. Default values are perspective
    242      * projection with 45 field of view, with near and far values 1 and 1000
    243      * units respectively.
    244      */
    245     private void initCamera(){
    246         cam = new Camera(settings.getWidth(), settings.getHeight());
    247 
    248         cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);
    249         cam.setLocation(new Vector3f(0f, 0f, 10f));
    250         cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
    251 
    252         renderManager = new RenderManager(renderer);
    253         //Remy - 09/14/2010 setted the timer in the renderManager
    254         renderManager.setTimer(timer);
    255         viewPort = renderManager.createMainView("Default", cam);
    256         viewPort.setClearFlags(true, true, true);
    257 
    258         // Create a new cam for the gui
    259         Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
    260         guiViewPort = renderManager.createPostView("Gui Default", guiCam);
    261         guiViewPort.setClearFlags(false, false, false);
    262     }
    263 
    264     /**
    265      * Initializes mouse and keyboard input. Also
    266      * initializes joystick input if joysticks are enabled in the
    267      * AppSettings.
    268      */
    269     private void initInput(){
    270         mouseInput = context.getMouseInput();
    271         if (mouseInput != null)
    272             mouseInput.initialize();
    273 
    274         keyInput = context.getKeyInput();
    275         if (keyInput != null)
    276             keyInput.initialize();
    277 
    278         touchInput = context.getTouchInput();
    279         if (touchInput != null)
    280             touchInput.initialize();
    281 
    282         if (!settings.getBoolean("DisableJoysticks")){
    283             joyInput = context.getJoyInput();
    284             if (joyInput != null)
    285                 joyInput.initialize();
    286         }
    287 
    288         inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);
    289     }
    290 
    291     private void initStateManager(){
    292         stateManager = new AppStateManager(this);
    293 
    294         // Always register a ResetStatsState to make sure
    295         // that the stats are cleared every frame
    296         stateManager.attach(new ResetStatsState());
    297     }
    298 
    299     /**
    300      * @return The {@link AssetManager asset manager} for this application.
    301      */
    302     public AssetManager getAssetManager(){
    303         return assetManager;
    304     }
    305 
    306     /**
    307      * @return the {@link InputManager input manager}.
    308      */
    309     public InputManager getInputManager(){
    310         return inputManager;
    311     }
    312 
    313     /**
    314      * @return the {@link AppStateManager app state manager}
    315      */
    316     public AppStateManager getStateManager() {
    317         return stateManager;
    318     }
    319 
    320     /**
    321      * @return the {@link RenderManager render manager}
    322      */
    323     public RenderManager getRenderManager() {
    324         return renderManager;
    325     }
    326 
    327     /**
    328      * @return The {@link Renderer renderer} for the application
    329      */
    330     public Renderer getRenderer(){
    331         return renderer;
    332     }
    333 
    334     /**
    335      * @return The {@link AudioRenderer audio renderer} for the application
    336      */
    337     public AudioRenderer getAudioRenderer() {
    338         return audioRenderer;
    339     }
    340 
    341     /**
    342      * @return The {@link Listener listener} object for audio
    343      */
    344     public Listener getListener() {
    345         return listener;
    346     }
    347 
    348     /**
    349      * @return The {@link JmeContext display context} for the application
    350      */
    351     public JmeContext getContext(){
    352         return context;
    353     }
    354 
    355     /**
    356      * @return The {@link Camera camera} for the application
    357      */
    358     public Camera getCamera(){
    359         return cam;
    360     }
    361 
    362     /**
    363      * Starts the application in {@link Type#Display display} mode.
    364      *
    365      * @see #start(com.jme3.system.JmeContext.Type)
    366      */
    367     public void start(){
    368         start(JmeContext.Type.Display);
    369     }
    370 
    371     /**
    372      * Starts the application.
    373      * Creating a rendering context and executing
    374      * the main loop in a separate thread.
    375      */
    376     public void start(JmeContext.Type contextType){
    377         if (context != null && context.isCreated()){
    378             logger.warning("start() called when application already created!");
    379             return;
    380         }
    381 
    382         if (settings == null){
    383             settings = new AppSettings(true);
    384         }
    385 
    386         logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
    387         context = JmeSystem.newContext(settings, contextType);
    388         context.setSystemListener(this);
    389         context.create(false);
    390     }
    391 
    392     /**
    393      * Initializes the application's canvas for use.
    394      * <p>
    395      * After calling this method, cast the {@link #getContext() context} to
    396      * {@link JmeCanvasContext},
    397      * then acquire the canvas with {@link JmeCanvasContext#getCanvas() }
    398      * and attach it to an AWT/Swing Frame.
    399      * The rendering thread will start when the canvas becomes visible on
    400      * screen, however if you wish to start the context immediately you
    401      * may call {@link #startCanvas() } to force the rendering thread
    402      * to start.
    403      *
    404      * @see JmeCanvasContext
    405      * @see Type#Canvas
    406      */
    407     public void createCanvas(){
    408         if (context != null && context.isCreated()){
    409             logger.warning("createCanvas() called when application already created!");
    410             return;
    411         }
    412 
    413         if (settings == null){
    414             settings = new AppSettings(true);
    415         }
    416 
    417         logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
    418         context = JmeSystem.newContext(settings, JmeContext.Type.Canvas);
    419         context.setSystemListener(this);
    420     }
    421 
    422     /**
    423      * Starts the rendering thread after createCanvas() has been called.
    424      * <p>
    425      * Same as calling startCanvas(false)
    426      *
    427      * @see #startCanvas(boolean)
    428      */
    429     public void startCanvas(){
    430         startCanvas(false);
    431     }
    432 
    433     /**
    434      * Starts the rendering thread after createCanvas() has been called.
    435      * <p>
    436      * Calling this method is optional, the canvas will start automatically
    437      * when it becomes visible.
    438      *
    439      * @param waitFor If true, the current thread will block until the
    440      * rendering thread is running
    441      */
    442     public void startCanvas(boolean waitFor){
    443         context.create(waitFor);
    444     }
    445 
    446     /**
    447      * Internal use only.
    448      */
    449     public void reshape(int w, int h){
    450         renderManager.notifyReshape(w, h);
    451     }
    452 
    453     /**
    454      * Restarts the context, applying any changed settings.
    455      * <p>
    456      * Changes to the {@link AppSettings} of this Application are not
    457      * applied immediately; calling this method forces the context
    458      * to restart, applying the new settings.
    459      */
    460     public void restart(){
    461         context.setSettings(settings);
    462         context.restart();
    463     }
    464 
    465     /**
    466      * Requests the context to close, shutting down the main loop
    467      * and making necessary cleanup operations.
    468      *
    469      * Same as calling stop(false)
    470      *
    471      * @see #stop(boolean)
    472      */
    473     public void stop(){
    474         stop(false);
    475     }
    476 
    477     /**
    478      * Requests the context to close, shutting down the main loop
    479      * and making necessary cleanup operations.
    480      * After the application has stopped, it cannot be used anymore.
    481      */
    482     public void stop(boolean waitFor){
    483         logger.log(Level.FINE, "Closing application: {0}", getClass().getName());
    484         context.destroy(waitFor);
    485     }
    486 
    487     /**
    488      * Do not call manually.
    489      * Callback from ContextListener.
    490      * <p>
    491      * Initializes the <code>Application</code>, by creating a display and
    492      * default camera. If display settings are not specified, a default
    493      * 640x480 display is created. Default values are used for the camera;
    494      * perspective projection with 45 field of view, with near
    495      * and far values 1 and 1000 units respectively.
    496      */
    497     public void initialize(){
    498         if (assetManager == null){
    499             initAssetManager();
    500         }
    501 
    502         initDisplay();
    503         initCamera();
    504 
    505         if (inputEnabled){
    506             initInput();
    507         }
    508         initAudio();
    509 
    510         // update timer so that the next delta is not too large
    511 //        timer.update();
    512         timer.reset();
    513 
    514         // user code here..
    515     }
    516 
    517     /**
    518      * Internal use only.
    519      */
    520     public void handleError(String errMsg, Throwable t){
    521         logger.log(Level.SEVERE, errMsg, t);
    522         // user should add additional code to handle the error.
    523         stop(); // stop the application
    524     }
    525 
    526     /**
    527      * Internal use only.
    528      */
    529     public void gainFocus(){
    530         if (pauseOnFocus) {
    531             paused = false;
    532             context.setAutoFlushFrames(true);
    533             if (inputManager != null) {
    534                 inputManager.reset();
    535             }
    536         }
    537     }
    538 
    539     /**
    540      * Internal use only.
    541      */
    542     public void loseFocus(){
    543         if (pauseOnFocus){
    544             paused = true;
    545             context.setAutoFlushFrames(false);
    546         }
    547     }
    548 
    549     /**
    550      * Internal use only.
    551      */
    552     public void requestClose(boolean esc){
    553         context.destroy(false);
    554     }
    555 
    556     /**
    557      * Enqueues a task/callable object to execute in the jME3
    558      * rendering thread.
    559      * <p>
    560      * Callables are executed right at the beginning of the main loop.
    561      * They are executed even if the application is currently paused
    562      * or out of focus.
    563      */
    564     public <V> Future<V> enqueue(Callable<V> callable) {
    565         AppTask<V> task = new AppTask<V>(callable);
    566         taskQueue.add(task);
    567         return task;
    568     }
    569 
    570     /**
    571      * Do not call manually.
    572      * Callback from ContextListener.
    573      */
    574     public void update(){
    575         // Make sure the audio renderer is available to callables
    576         AudioContext.setAudioRenderer(audioRenderer);
    577 
    578         AppTask<?> task = taskQueue.poll();
    579         toploop: do {
    580             if (task == null) break;
    581             while (task.isCancelled()) {
    582                 task = taskQueue.poll();
    583                 if (task == null) break toploop;
    584             }
    585             task.invoke();
    586         } while (((task = taskQueue.poll()) != null));
    587 
    588         /* I think the above is really just doing this:
    589         AppTask<?> task;
    590         while( (task = taskQueue.poll()) != null ) {
    591             if (!task.isCancelled()) {
    592                 task.invoke();
    593             }
    594         }
    595         //...but it's hard to say for sure.  It's so twisted
    596         //up that I don't trust my eyes.  -pspeed
    597         */
    598 
    599         if (speed == 0 || paused)
    600             return;
    601 
    602         timer.update();
    603 
    604         if (inputEnabled){
    605             inputManager.update(timer.getTimePerFrame());
    606         }
    607 
    608         if (audioRenderer != null){
    609             audioRenderer.update(timer.getTimePerFrame());
    610         }
    611 
    612         // user code here..
    613     }
    614 
    615     protected void destroyInput(){
    616         if (mouseInput != null)
    617             mouseInput.destroy();
    618 
    619         if (keyInput != null)
    620             keyInput.destroy();
    621 
    622         if (joyInput != null)
    623             joyInput.destroy();
    624 
    625         if (touchInput != null)
    626             touchInput.destroy();
    627 
    628         inputManager = null;
    629     }
    630 
    631     /**
    632      * Do not call manually.
    633      * Callback from ContextListener.
    634      */
    635     public void destroy(){
    636         stateManager.cleanup();
    637 
    638         destroyInput();
    639         if (audioRenderer != null)
    640             audioRenderer.cleanup();
    641 
    642         timer.reset();
    643     }
    644 
    645     /**
    646      * @return The GUI viewport. Which is used for the on screen
    647      * statistics and FPS.
    648      */
    649     public ViewPort getGuiViewPort() {
    650         return guiViewPort;
    651     }
    652 
    653     public ViewPort getViewPort() {
    654         return viewPort;
    655     }
    656 
    657 }
    658