/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.backends.lwjgl; import java.awt.Canvas; import java.io.File; import com.badlogic.gdx.ApplicationLogger; import org.lwjgl.LWJGLException; import org.lwjgl.opengl.Display; import com.badlogic.gdx.Application; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Audio; import com.badlogic.gdx.Files; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.LifecycleListener; import com.badlogic.gdx.Net; import com.badlogic.gdx.Preferences; import com.badlogic.gdx.backends.lwjgl.audio.OpenALAudio; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Clipboard; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.SnapshotArray; /** An OpenGL surface fullscreen or in a lightweight window. */ public class LwjglApplication implements Application { protected final LwjglGraphics graphics; protected OpenALAudio audio; protected final LwjglFiles files; protected final LwjglInput input; protected final LwjglNet net; protected final ApplicationListener listener; protected Thread mainLoopThread; protected boolean running = true; protected final Array<Runnable> runnables = new Array<Runnable>(); protected final Array<Runnable> executedRunnables = new Array<Runnable>(); protected final SnapshotArray<LifecycleListener> lifecycleListeners = new SnapshotArray<LifecycleListener>(LifecycleListener.class); protected int logLevel = LOG_INFO; protected ApplicationLogger applicationLogger; protected String preferencesdir; protected Files.FileType preferencesFileType; public LwjglApplication (ApplicationListener listener, String title, int width, int height) { this(listener, createConfig(title, width, height)); } public LwjglApplication (ApplicationListener listener) { this(listener, null, 640, 480); } public LwjglApplication (ApplicationListener listener, LwjglApplicationConfiguration config) { this(listener, config, new LwjglGraphics(config)); } public LwjglApplication (ApplicationListener listener, Canvas canvas) { this(listener, new LwjglApplicationConfiguration(), new LwjglGraphics(canvas)); } public LwjglApplication (ApplicationListener listener, LwjglApplicationConfiguration config, Canvas canvas) { this(listener, config, new LwjglGraphics(canvas, config)); } public LwjglApplication (ApplicationListener listener, LwjglApplicationConfiguration config, LwjglGraphics graphics) { LwjglNativesLoader.load(); setApplicationLogger(new LwjglApplicationLogger()); if (config.title == null) config.title = listener.getClass().getSimpleName(); this.graphics = graphics; if (!LwjglApplicationConfiguration.disableAudio) { try { audio = new OpenALAudio(config.audioDeviceSimultaneousSources, config.audioDeviceBufferCount, config.audioDeviceBufferSize); } catch (Throwable t) { log("LwjglApplication", "Couldn't initialize audio, disabling audio", t); LwjglApplicationConfiguration.disableAudio = true; } } files = new LwjglFiles(); input = new LwjglInput(); net = new LwjglNet(); this.listener = listener; this.preferencesdir = config.preferencesDirectory; this.preferencesFileType = config.preferencesFileType; Gdx.app = this; Gdx.graphics = graphics; Gdx.audio = audio; Gdx.files = files; Gdx.input = input; Gdx.net = net; initialize(); } private static LwjglApplicationConfiguration createConfig (String title, int width, int height) { LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.title = title; config.width = width; config.height = height; config.vSyncEnabled = true; return config; } private void initialize () { mainLoopThread = new Thread("LWJGL Application") { @Override public void run () { graphics.setVSync(graphics.config.vSyncEnabled); try { LwjglApplication.this.mainLoop(); } catch (Throwable t) { if (audio != null) audio.dispose(); Gdx.input.setCursorCatched(false); if (t instanceof RuntimeException) throw (RuntimeException)t; else throw new GdxRuntimeException(t); } } }; mainLoopThread.start(); } void mainLoop () { SnapshotArray<LifecycleListener> lifecycleListeners = this.lifecycleListeners; try { graphics.setupDisplay(); } catch (LWJGLException e) { throw new GdxRuntimeException(e); } listener.create(); graphics.resize = true; int lastWidth = graphics.getWidth(); int lastHeight = graphics.getHeight(); graphics.lastTime = System.nanoTime(); boolean wasActive = true; while (running) { Display.processMessages(); if (Display.isCloseRequested()) exit(); boolean isActive = Display.isActive(); if (wasActive && !isActive) { // if it's just recently minimized from active state wasActive = false; synchronized (lifecycleListeners) { LifecycleListener[] listeners = lifecycleListeners.begin(); for (int i = 0, n = lifecycleListeners.size; i < n; ++i) listeners[i].pause(); lifecycleListeners.end(); } listener.pause(); } if (!wasActive && isActive) { // if it's just recently focused from minimized state wasActive = true; synchronized (lifecycleListeners) { LifecycleListener[] listeners = lifecycleListeners.begin(); for (int i = 0, n = lifecycleListeners.size; i < n; ++i) listeners[i].resume(); lifecycleListeners.end(); } listener.resume(); } boolean shouldRender = false; if (graphics.canvas != null) { int width = graphics.canvas.getWidth(); int height = graphics.canvas.getHeight(); if (lastWidth != width || lastHeight != height) { lastWidth = width; lastHeight = height; Gdx.gl.glViewport(0, 0, lastWidth, lastHeight); listener.resize(lastWidth, lastHeight); shouldRender = true; } } else { graphics.config.x = Display.getX(); graphics.config.y = Display.getY(); if (graphics.resize || Display.wasResized() || (int)(Display.getWidth() * Display.getPixelScaleFactor()) != graphics.config.width || (int)(Display.getHeight() * Display.getPixelScaleFactor()) != graphics.config.height) { graphics.resize = false; graphics.config.width = (int)(Display.getWidth() * Display.getPixelScaleFactor()); graphics.config.height = (int)(Display.getHeight() * Display.getPixelScaleFactor()); Gdx.gl.glViewport(0, 0, graphics.config.width, graphics.config.height); if (listener != null) listener.resize(graphics.config.width, graphics.config.height); graphics.requestRendering(); } } if (executeRunnables()) shouldRender = true; // If one of the runnables set running to false, for example after an exit(). if (!running) break; input.update(); shouldRender |= graphics.shouldRender(); input.processEvents(); if (audio != null) audio.update(); if (!isActive && graphics.config.backgroundFPS == -1) shouldRender = false; int frameRate = isActive ? graphics.config.foregroundFPS : graphics.config.backgroundFPS; if (shouldRender) { graphics.updateTime(); graphics.frameId++; listener.render(); Display.update(false); } else { // Sleeps to avoid wasting CPU in an empty loop. if (frameRate == -1) frameRate = 10; if (frameRate == 0) frameRate = graphics.config.backgroundFPS; if (frameRate == 0) frameRate = 30; } if (frameRate > 0) Display.sync(frameRate); } synchronized (lifecycleListeners) { LifecycleListener[] listeners = lifecycleListeners.begin(); for (int i = 0, n = lifecycleListeners.size; i < n; ++i) { listeners[i].pause(); listeners[i].dispose(); } lifecycleListeners.end(); } listener.pause(); listener.dispose(); Display.destroy(); if (audio != null) audio.dispose(); if (graphics.config.forceExit) System.exit(-1); } public boolean executeRunnables () { synchronized (runnables) { for (int i = runnables.size - 1; i >= 0; i--) executedRunnables.add(runnables.get(i)); runnables.clear(); } if (executedRunnables.size == 0) return false; do executedRunnables.pop().run(); while (executedRunnables.size > 0); return true; } @Override public ApplicationListener getApplicationListener () { return listener; } @Override public Audio getAudio () { return audio; } @Override public Files getFiles () { return files; } @Override public LwjglGraphics getGraphics () { return graphics; } @Override public Input getInput () { return input; } @Override public Net getNet () { return net; } @Override public ApplicationType getType () { return ApplicationType.Desktop; } @Override public int getVersion () { return 0; } public void stop () { running = false; try { mainLoopThread.join(); } catch (Exception ex) { } } @Override public long getJavaHeap () { return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } @Override public long getNativeHeap () { return getJavaHeap(); } ObjectMap<String, Preferences> preferences = new ObjectMap<String, Preferences>(); @Override public Preferences getPreferences (String name) { if (preferences.containsKey(name)) { return preferences.get(name); } else { Preferences prefs = new LwjglPreferences(new LwjglFileHandle(new File(preferencesdir, name), preferencesFileType)); preferences.put(name, prefs); return prefs; } } @Override public Clipboard getClipboard () { return new LwjglClipboard(); } @Override public void postRunnable (Runnable runnable) { synchronized (runnables) { runnables.add(runnable); Gdx.graphics.requestRendering(); } } @Override public void debug (String tag, String message) { if (logLevel >= LOG_DEBUG) getApplicationLogger().debug(tag, message); } @Override public void debug (String tag, String message, Throwable exception) { if (logLevel >= LOG_DEBUG) getApplicationLogger().debug(tag, message, exception); } @Override public void log (String tag, String message) { if (logLevel >= LOG_INFO) getApplicationLogger().log(tag, message); } @Override public void log (String tag, String message, Throwable exception) { if (logLevel >= LOG_INFO) getApplicationLogger().log(tag, message, exception); } @Override public void error (String tag, String message) { if (logLevel >= LOG_ERROR) getApplicationLogger().error(tag, message); } @Override public void error (String tag, String message, Throwable exception) { if (logLevel >= LOG_ERROR) getApplicationLogger().error(tag, message, exception); } @Override public void setLogLevel (int logLevel) { this.logLevel = logLevel; } @Override public int getLogLevel () { return logLevel; } @Override public void setApplicationLogger (ApplicationLogger applicationLogger) { this.applicationLogger = applicationLogger; } @Override public ApplicationLogger getApplicationLogger () { return applicationLogger; } @Override public void exit () { postRunnable(new Runnable() { @Override public void run () { running = false; } }); } @Override public void addLifecycleListener (LifecycleListener listener) { synchronized (lifecycleListeners) { lifecycleListeners.add(listener); } } @Override public void removeLifecycleListener (LifecycleListener listener) { synchronized (lifecycleListeners) { lifecycleListeners.removeValue(listener, true); } } }