com.n8lm.zener.app.AbstractAppContainer.java Source code

Java tutorial

Introduction

Here is the source code for com.n8lm.zener.app.AbstractAppContainer.java

Source

/*
 * This file is part of Zener.
 *
 * Zener is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * Zener is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with Zener.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

package com.n8lm.zener.app;

import com.n8lm.zener.audio.Music;
import com.n8lm.zener.audio.openal.SoundStore;
import com.n8lm.zener.general.ZenerException;
import com.n8lm.zener.graphics.GLRenderSystem;
import com.n8lm.zener.input.Input;
import org.lwjgl.LWJGLException;
import org.lwjgl.openal.AL;
import org.lwjgl.opengl.*;

import java.util.logging.Logger;

public abstract class AbstractAppContainer extends Container {

    private final static Logger LOGGER = Logger.getLogger(AbstractAppContainer.class.getName());

    /** The width of the display */
    protected int width;
    /** The height of the display */
    protected int height;
    /** The game being managed */
    protected BasicApp game;
    /** The shared drawable if any */
    protected static Drawable SHARED_DRAWABLE;

    /** The input system to pass to the game */
    protected Input input;
    /** The FPS we want to lock to */
    protected int targetFPS = -1;
    /** True if we should show the fps */
    private boolean showFPS = true;
    /** The minimum logic update interval */
    protected long minimumLogicInterval = 1;
    /** The stored delta */
    protected long storedDelta;
    /** The maximum logic update interval */
    protected long maximumLogicInterval = 0;
    /** True if we should clear the screen each frame */
    protected boolean clearEachFrame = true;

    /** True if the game is paused */
    protected boolean paused;
    /** True if we should force exit */
    protected boolean forceExit = true;
    /** True if vsync has been requested */
    protected boolean vsync;
    /** Smoothed deltas requested */
    protected boolean smoothDeltas;
    /** The number of samples we'll attempt through hardware */
    protected int samples;

    /** True if this context supports multisample */
    protected boolean supportsMultiSample;

    /** True if we should render when not focused */
    protected boolean alwaysRender;
    /** True if we require stencil bits */
    protected static boolean stencil;

    /** SoundManager to make sound with */
    // private SoundManager soundManager;

    /** Is this an application or applet */
    private static boolean isApplication;

    /**
     * Construct our game and set it running.
     *
     * 
     */
    public AbstractAppContainer(BasicApp game) {
        this.game = game;
        lastFrame = getTime();

        getBuildVersion();
        // Log.checkVerboseLogSetting();
    }

    public static void enableStencil() {
        stencil = true;
    }

    /**
     * Called to clean up the program's memory. Destroys AL and Display if
     * either is created.
     */
    public void destroy() {
        //InternalTextureLoader.get().clear();
        SoundStore.get().clear();
        if (Display.isCreated())
            Display.destroy();
        if (AL.isCreated())
            AL.destroy();
    }

    /**
     * Indicate whether we want to try to use fullscreen multisampling. This
     * will give antialiasing across the whole scene using a hardware feature.
     * 
     * @param samples
     *            The number of samples to attempt (2 is safe)
     */
    public void setMultiSample(int samples) {
        this.samples = samples;
    }

    /**
     * Check if this hardware can support multi-sampling
     * 
     * @return True if the hardware supports multi-sampling
     */
    public boolean supportsMultiSample() {
        return supportsMultiSample;
    }

    /**
     * The number of samples we're attempting to performing using hardware
     * multisampling
     * 
     * @return The number of samples requested
     */
    public int getSamples() {
        return samples;
    }

    /**
     * Indicate if we should force exitting the VM at the end of the game
     * (default = true)
     * 
     * @param forceExit
     *            True if we should force the VM exit
     */
    public void setForceExit(boolean forceExit) {
        this.forceExit = forceExit;
    }

    /**
     * Indicate if we want to smooth deltas. This feature will report a delta
     * based on the FPS not the time passed. This works well with vsync.
     * 
     * @param smoothDeltas
     *            True if we should report smooth deltas
     */
    public void setSmoothDeltas(boolean smoothDeltas) {
        this.smoothDeltas = smoothDeltas;
    }

    /**
     * Check if the display is in fullscreen mode
     * 
     * @return True if the display is in fullscreen mode
     */
    public boolean isFullscreen() {
        return false;
    }

    /**
     * Get the aspect ratio of the screen
     * 
     * @return The aspect ratio of the display
     */
    public float getAspectRatio() {
        return getWidth() / getHeight();
    }

    /**
     * Indicate whether we want to be in fullscreen mode. Note that the current
     * display mode must be valid as a fullscreen mode for this to work
     * 
     * @param fullscreen
     *            True if we want to be in fullscreen mode
     * @throws Exception
     */
    public void setFullscreen(boolean fullscreen) throws Exception {
    }

    /**
     * Enable shared OpenGL context. After calling this all containers created
     * will shared a single parent context
     * 
     * @throws ZenerException
     *             Indicates a failure to create the shared drawable
     */
    public static void enableSharedContext() throws ZenerException {
        try {
            SHARED_DRAWABLE = new Pbuffer(64, 64, new PixelFormat(8, 0, 0), null);
        } catch (LWJGLException e) {
            throw new ZenerException("Unable to create the pbuffer used for shard context, buffers not supported",
                    e);
        }
    }

    /**
     * Get the context shared by all containers
     * 
     * @return The context shared by all the containers or null if shared
     *         context isn't enabled
     */
    public static Drawable getSharedContext() {
        return SHARED_DRAWABLE;
    }

    /**
     * Indicate if we should clear the screen at the beginning of each frame. If
     * you're rendering to the whole screen each frame then setting this to
     * false can give some performance improvements
     * 
     * @param clear
     *            True if the the screen should be cleared each frame
     */
    public void setClearEachFrame(boolean clear) {
        this.clearEachFrame = clear;
    }

    /**
     * Renitialise the game and the context in which it's being rendered
     * 
     * @throws ZenerException
     *             Indicates a failure rerun initialisation routines
     */
    public void reinit() throws ZenerException {
    }

    /**
     * Pause the game - i.e. suspend updates
     */
    public void pause() {
        setPaused(true);
    }

    /**
     * Resumt the game - i.e. continue updates
     */
    public void resume() {
        setPaused(false);
    }

    /**
     * Check if the container is currently paused.
     * 
     * @return True if the container is paused
     */
    public boolean isPaused() {
        return paused;
    }

    /**
     * Indicates if the game should be paused, i.e. if updates should be
     * propogated through to the game.
     * 
     * @param paused
     *            True if the game should be paused
     */
    public void setPaused(boolean paused) {
        this.paused = paused;
    }

    /**
     * True if this container should render when it has focus
     * 
     * @return True if this container should render when it has focus
     */
    public boolean getAlwaysRender() {
        return alwaysRender;
    }

    /**
     * Indicate whether we want this container to render when it has focus
     * 
     * @param alwaysRender
     *            True if this container should render when it has focus
     */
    public void setAlwaysRender(boolean alwaysRender) {
        this.alwaysRender = alwaysRender;
    }

    /**
     * Check if sound effects are enabled
     * 
     * @return True if sound effects are enabled
     */
    public boolean isSoundOn() {
        return SoundStore.get().soundsOn();
    }

    /**
     * Check if music is enabled
     * 
     * @return True if music is enabled
     */
    public boolean isMusicOn() {
        return SoundStore.get().musicOn();
    }

    /**
     * Indicate whether music should be enabled
     * 
     * @param on
     *            True if music should be enabled
     */
    public void setMusicOn(boolean on) {
        SoundStore.get().setMusicOn(on);
    }

    /**
     * Indicate whether sound effects should be enabled
     * 
     * @param on
     *            True if sound effects should be enabled
     */
    public void setSoundOn(boolean on) {
        SoundStore.get().setSoundsOn(on);
    }

    /**
     * Retrieve the current default volume for music
     * 
     * @return the current default volume for music
     */
    public float getMusicVolume() {
        return SoundStore.get().getMusicVolume();
    }

    /**
     * Retrieve the current default volume for sound fx
     * 
     * @return the current default volume for sound fx
     */
    public float getSoundVolume() {
        return SoundStore.get().getSoundVolume();
    }

    /**
     * Set the default volume for sound fx
     * 
     * @param volume
     *            the new default value for sound fx volume
     */
    public void setSoundVolume(float volume) {
        SoundStore.get().setSoundVolume(volume);
    }

    /**
     * Set the default volume for music
     * 
     * @param volume
     *            the new default value for music volume
     */
    public void setMusicVolume(float volume) {
        SoundStore.get().setMusicVolume(volume);
    }

    /**
     * Get the width of the standard screen resolution
     * 
     * @return The screen width
     */
    public abstract int getScreenWidth();

    /**
     * Get the height of the standard screen resolution
     * 
     * @return The screen height
     */
    public abstract int getScreenHeight();

    /**
     * Get the width of the game canvas
     * 
     * @return The width of the game canvas
     */
    public int getWidth() {
        return width;
    }

    /**
     * Get the height of the game canvas
     * 
     * @return The height of the game canvas
     */
    public int getHeight() {
        return height;
    }

    /**
     * Set the icon to be displayed if possible in this type of container
     * 
     * @param ref
     *            The reference to the icon to be displayed
     * @throws ZenerException
     *             Indicates a failure to load the icon
     */
    public abstract void setIcon(String ref) throws ZenerException;

    /**
     * Set the icons to be used for this application. Note that the size of the
     * icon defines how it will be used. Important ones to note
     * 
     * Windows window icon must be 16x16 Windows alt-tab icon must be 24x24 or
     * 32x32 depending on Windows version (XP=32)
     * 
     * @param refs
     *            The reference to the icon to be displayed
     * @throws ZenerException
     *             Indicates a failure to load the icon
     */
    public abstract void setIcons(String[] refs) throws ZenerException;

    /**
     * Set the mouse cursor to be displayed - this is a hardware cursor and
     * hence shouldn't have any impact on FPS.
     * 
     * @param ref
     *            The location of the image to be loaded for the cursor
     * @param hotSpotX
     *            The x coordinate of the hotspot within the cursor image
     * @param hotSpotY
     *            The y coordinate of the hotspot within the cursor image
     * @throws ZenerException
     *             Indicates a failure to load the cursor image or create the
     *             hardware cursor
     */
    /*
    public abstract void setMouseCursor(String ref, int hotSpotX, int hotSpotY)
     throws ZenerException;
    */
    /**
     * Set the mouse cursor to be displayed - this is a hardware cursor and
     * hence shouldn't have any impact on FPS.
     * 
     * @param data
     *            The image data from which the cursor can be construted
     * @param hotSpotX
     *            The x coordinate of the hotspot within the cursor image
     * @param hotSpotY
     *            The y coordinate of the hotspot within the cursor image
     * @throws ZenerException
     *             Indicates a failure to load the cursor image or create the
     *             hardware cursor
     */
    /*public abstract void setMouseCursor(ImageData data, int hotSpotX,
     int hotSpotY) throws ZenerException;*/

    /**
     * Set the mouse cursor based on the contents of the image. Note that this
     * will not take account of render state type changes to images (rotation
     * and such). If these effects are required it is recommended that an
     * offscreen buffer be used to produce an appropriate image. An offscreen
     * buffer will always be used to produce the new cursor and as such this
     * operation an be very expensive
     * 
     * @param image
     *            The image to use as the cursor
     * @param hotSpotX
     *            The x coordinate of the hotspot within the cursor image
     * @param hotSpotY
     *            The y coordinate of the hotspot within the cursor image
     * @throws ZenerException
     *             Indicates a failure to load the cursor image or create the
     *             hardware cursor
     */
    // public abstract void setMouseCursor(Image image, int hotSpotX, int
    // hotSpotY) throws ZenerException;

    /**
     * Set the mouse cursor to be displayed - this is a hardware cursor and
     * hence shouldn't have any impact on FPS.
     * 
     * @param cursor
     *            The cursor to use
     * @param hotSpotX
     *            The x coordinate of the hotspot within the cursor image
     * @param hotSpotY
     *            The y coordinate of the hotspot within the cursor image
     * @throws ZenerException
     *             Indicates a failure to load the cursor image or create the
     *             hardware cursor
     */
    /*
    public abstract void setMouseCursor(Cursor cursor, int hotSpotX,
     int hotSpotY) throws ZenerException;
     */
    /*
    * Get a cursor based on a image reference on the classpath. The image is
    * assumed to be a set/strip of cursor animation frames running from top to
    * bottom.
    * 
    * @param ref
    *            The reference to the image to be loaded
    * @param x
    *            The x-coordinate of the cursor hotspot (left -> right)
    * @param y
    *            The y-coordinate of the cursor hotspot (bottom -> top)
    * @param width
    *            The x width of the cursor
    * @param height
    *            The y height of the cursor
    * @param cursorDelays
    *            image delays between changing frames in animation
    * 
    * @throws ZenerException
    *             Indicates a failure to load the image or a failure to create
    *             the hardware cursor
        
    public void setAnimatedMouseCursor(String ref, int x, int y, int width,
     int height, int[] cursorDelays) throws ZenerException {
      try {
     Cursor cursor;
     cursor = CursorLoader.get().getAnimatedCursor(ref, x, y, width,
           height, cursorDelays);
     setMouseCursor(cursor, x, y);
      } catch (IOException e) {
     throw new ZenerException("Failed to set mouse cursor", e);
      } catch (LWJGLException e) {
     throw new ZenerException("Failed to set mouse cursor", e);
      }
    }
    */

    /**
     * Set the default mouse cursor - i.e. the original cursor before any native
     * cursor was set
     */
    public abstract void setDefaultMouseCursor();

    /**
     * Get the input system
     * 
     * @return The input system available to this game container
     */
    public Input getInput() {
        return input;
    }

    /**
     * Indicate whether mouse cursor should be grabbed or not
     * 
     * @param grabbed
     *            True if mouse cursor should be grabbed
     */
    public abstract void setMouseGrabbed(boolean grabbed);

    /**
     * Check if the mouse cursor is current grabbed. This will cause it not to
     * be seen.
     * 
     * @return True if the mouse is currently grabbed
     */
    public abstract boolean isMouseGrabbed();

    /**
     * Set the minimum amount of time in milliseonds that has to pass before
     * update() is called on the container game. This gives a way to limit logic
     * updates compared to renders.
     *
     * @param interval
     *            The minimum interval between logic updates
     */
    public void setMinimumLogicUpdateInterval(int interval) {
        minimumLogicInterval = interval;
    }

    /**
     * Set the maximum amount of time in milliseconds that can passed into the
     * update method. Useful for collision detection without sweeping.
     *
     * @param interval
     *            The maximum interval between logic updates
     */
    public void setMaximumLogicUpdateInterval(int interval) {
        maximumLogicInterval = interval;
    }

    /**
    * Update and render the game
    * 
    * @param delta
    *            The change in time since last update and render
    * @throws ZenerException
    *             Indicates an internal fault to the game.
    */
    protected void updateAndRender(int delta) throws ZenerException {
        if (smoothDeltas) {
            if (getFPS() != 0) {
                delta = 1000 / getFPS();
            }
        }

        input.poll(width, height);

        Music.poll(delta);

        if (clearEachFrame) {
            GLRenderSystem.clear();
        }

        if (!paused) {
            storedDelta += delta;

            if (storedDelta >= minimumLogicInterval) {
                try {
                    if (maximumLogicInterval != 0) {
                        long cycles = storedDelta / maximumLogicInterval;
                        for (int i = 0; i < cycles; i++) {
                            game.update(this, (int) maximumLogicInterval);
                        }

                        int remainder = (int) (storedDelta % maximumLogicInterval);
                        if (remainder > minimumLogicInterval) {
                            game.update(this, (int) (remainder % maximumLogicInterval));
                            storedDelta = 0;
                        } else {
                            storedDelta = remainder;
                        }
                    } else {
                        game.update(this, (int) storedDelta);
                        storedDelta = 0;
                    }

                } catch (Throwable e) {
                    LOGGER.severe(e.toString());
                    e.printStackTrace();
                    throw new ZenerException("Game.update() failure - check the game code.");
                }
            }
        } else if (hasFocus() || getAlwaysRender()) {
            game.update(this, 0);
        }

        if (targetFPS != -1) {
            Display.sync(targetFPS);
        }
    }

    /**
     * Indicate if the display should update only when the game is visible (the
     * default is true)
     * 
     * @param updateOnlyWhenVisible
     *            True if we should updated only when the display is visible
     */
    public void setUpdateOnlyWhenVisible(boolean updateOnlyWhenVisible) {
    }

    /**
     * Check if this game is only updating when visible to the user (default =
     * true)
     * 
     * @return True if the game is only updated when the display is visible
     */
    public boolean isUpdatingOnlyWhenVisible() {
        return true;
    }

    /**
     * Called when the GL context is resized. This does not resize the GL view;
     * instead, it simply re-enters orthographic projection.
     */
    protected void onResize() {
        if (getInput() != null) {
            getInput().init(getHeight());
        }

        GL11.glViewport(0, 0, width, height);
    }

    /**
     * Initialize the GL context
     */
    protected void initGL() {
        LOGGER.info("Starting display " + width + " x " + height);
        initDisplay(width, height);

        if (input == null) {
            input = new Input(height, game.getInputManager());
        }
        input.init(height);
        // no need to remove listeners?
        // input.removeAllListeners();
    }

    /**
     * Initializes default states (texturing, shade model, disable depth test,
     * etc).
     *
     */
    public void initDisplay(int width, int height) {
        this.width = width;
        this.height = height;

        // glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    }

    /**
     * Initialize the system components, OpenGL and OpenAL.
     *
     */
    protected void initSystem() {
        initGL();
        //setMusicVolume(1.0f);
        //setSoundVolume(1.0f);

        lastFPS = getTime();
    }

    /**
     * Indicate whether the container should show the FPS
     * 
     * @param show
     *            True if the container should show the FPS
     */
    public void setShowFPS(boolean show) {
        showFPS = show;
    }

    /**
     * Check if the FPS is currently showing
     * 
     * @return True if the FPS is showing
     */
    public boolean isShowingFPS() {
        return showFPS;
    }

    /**
     * Set the target fps we're hoping to get
     * 
     * @param fps
     *            The target fps we're hoping to get
     */
    public void setTargetFrameRate(int fps) {
        targetFPS = fps;
    }

    /**
     * Indicate whether the display should be synced to the vertical refresh
     * (stops tearing)
     * 
     * @param vsync
     *            True if we want to sync to vertical refresh
     */
    public void setVSync(boolean vsync) {
        this.vsync = vsync;
        Display.setVSyncEnabled(vsync);
    }

    /**
     * True if vsync is requested
     * 
     * @return True if vsync is requested
     */
    public boolean isVSyncRequested() {
        return vsync;
    }

    /**
     * Inidcate we want verbose logging
     * 
     * @param verbose
     *            True if we want verbose logging (INFO and DEBUG)
     */
    /*
     * public void setVerbose(boolean verbose) { Log.setVerbose(verbose); }
     */
    /**
     * Check if the game currently has focus
     * 
     * @return True if the game currently has focus
     */
    public abstract boolean hasFocus();

}