/*******************************************************************************
* 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.jglfw;
import static com.badlogic.gdx.utils.SharedLibraryLoader.*;
import static com.badlogic.jglfw.Glfw.*;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Audio;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.LifecycleListener;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Clipboard;
import com.badlogic.gdx.utils.GdxNativesLoader;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.jglfw.GlfwCallbackAdapter;
import com.badlogic.jglfw.GlfwCallbacks;
import java.awt.EventQueue;
import java.util.HashMap;
import java.util.Map;
/** An OpenGL surface fullscreen or in a lightweight window using GLFW.
* @author mzechner
* @author Nathan Sweet */
public class JglfwApplication implements Application {
JglfwGraphics graphics;
JglfwFiles files;
JglfwInput input;
JglfwNet net;
final ApplicationListener listener;
private final Array<Runnable> runnables = new Array();
private final Array<Runnable> executedRunnables = new Array();
private final Array<LifecycleListener> lifecycleListeners = new Array();
private final Map<String, Preferences> preferences = new HashMap();
private final JglfwClipboard clipboard = new JglfwClipboard();
private final GlfwCallbacks callbacks = new GlfwCallbacks();
private int logLevel = LOG_INFO;
volatile boolean running = true;
boolean isPaused;
protected String preferencesdir;
private boolean forceExit, runOnEDT;
private int foregroundFPS, backgroundFPS, hiddenFPS;
public JglfwApplication (ApplicationListener listener) {
this(listener, listener.getClass().getSimpleName(), 640, 480);
}
public JglfwApplication (ApplicationListener listener, String title, int width, int height) {
this(listener, createConfig(title, width, height));
}
static private JglfwApplicationConfiguration createConfig (String title, int width, int height) {
JglfwApplicationConfiguration config = new JglfwApplicationConfiguration();
config.title = title;
config.width = width;
config.height = height;
return config;
}
public JglfwApplication (final ApplicationListener listener, final JglfwApplicationConfiguration config) {
if (listener == null) throw new IllegalArgumentException("listener cannot be null.");
if (config == null) throw new IllegalArgumentException("config cannot be null.");
this.listener = listener;
Runnable runnable = new Runnable() {
public void run () {
try {
initialize(config);
} catch (Throwable ex) {
exception(ex);
}
}
};
if (config.runOnEDT)
EventQueue.invokeLater(runnable);
else
new Thread(runnable, "MainLoop").start();
}
/** Called when an uncaught exception happens in the game loop. Default implementation prints the exception and calls
* System.exit(0). */
protected void exception (Throwable ex) {
ex.printStackTrace();
System.exit(0);
}
void initialize (JglfwApplicationConfiguration config) {
forceExit = config.forceExit;
runOnEDT = config.runOnEDT;
foregroundFPS = config.foregroundFPS;
backgroundFPS = config.backgroundFPS;
hiddenFPS = config.hiddenFPS;
preferencesdir = config.preferencesLocation;
final Thread glThread = Thread.currentThread();
GdxNativesLoader.load();
boolean inputCallbacksOnAppKitThread = isMac;
if (inputCallbacksOnAppKitThread) java.awt.Toolkit.getDefaultToolkit(); // Ensure AWT is initialized before GLFW.
if (!glfwInit()) throw new GdxRuntimeException("Unable to initialize GLFW.");
Gdx.app = this;
Gdx.graphics = graphics = new JglfwGraphics(config);
Gdx.files = files = new JglfwFiles();
Gdx.input = input = new JglfwInput(this, inputCallbacksOnAppKitThread);
Gdx.net = net = new JglfwNet();
callbacks.add(new GlfwCallbackAdapter() {
public void windowSize (long window, final int width, final int height) {
Runnable runnable = new Runnable() {
public void run () {
graphics.sizeChanged(width, height);
}
};
if (Thread.currentThread() != glThread)
postRunnable(runnable);
else
runnable.run();
}
public void windowPos (long window, final int x, final int y) {
Runnable runnable = new Runnable() {
public void run () {
graphics.positionChanged(x, y);
}
};
if (Thread.currentThread() != glThread)
postRunnable(runnable);
else
runnable.run();
}
public void windowRefresh (long window) {
if (Thread.currentThread() == glThread) render(System.nanoTime());
}
public void windowFocus (long window, boolean focused) {
graphics.foreground = focused;
}
public void windowIconify (long window, boolean iconified) {
graphics.minimized = iconified;
}
public boolean windowClose (long window) {
if (shouldExit()) exit();
return false;
}
public void error (int error, String description) {
throw new GdxRuntimeException("GLFW error " + error + ": " + description);
}
});
glfwSetCallback(callbacks);
start();
}
/** Starts the game loop after the application internals have been initialized. */
protected void start () {
listener.create();
listener.resize(graphics.getWidth(), graphics.getHeight());
if (runOnEDT) {
new Runnable() {
public void run () {
frame();
if (running)
EventQueue.invokeLater(this);
else
end();
}
}.run();
} else {
while (running)
frame();
end();
}
}
/** Handles posted runnables, input, and rendering for each frame. */
protected void frame () {
if (!running) return;
if (executeRunnables()) graphics.requestRendering();
if (!running) return;
input.update();
long frameStartTime = System.nanoTime();
int targetFPS = (graphics.isHidden() || graphics.isMinimized()) ? hiddenFPS : //
(graphics.isForeground() ? foregroundFPS : backgroundFPS);
if (targetFPS == -1) { // Rendering is paused.
if (!isPaused) listener.pause();
isPaused = true;
} else {
if (isPaused) listener.resume();
isPaused = false;
if (graphics.shouldRender()) render(frameStartTime);
}
if (targetFPS != 0) {
if (targetFPS == -1)
sleep(100);
else
Sync.sync(targetFPS);
}
}
public boolean executeRunnables () {
synchronized (runnables) {
executedRunnables.addAll(runnables);
runnables.clear();
}
if (executedRunnables.size == 0) return false;
for (int i = 0; i < executedRunnables.size; i++)
executedRunnables.get(i).run();
executedRunnables.clear();
return true;
}
void sleep (int millis) {
try {
if (millis > 0) Thread.sleep(millis);
} catch (InterruptedException ignored) {
}
}
void render (long time) {
graphics.frameStart(time);
listener.render();
glfwSwapBuffers(graphics.window);
}
/** Called when the game loop has exited. */
protected void end () {
synchronized (lifecycleListeners) {
for (LifecycleListener listener : lifecycleListeners) {
listener.pause();
listener.dispose();
}
}
listener.pause();
listener.dispose();
glfwTerminate();
if (forceExit) System.exit(-1);
}
public ApplicationListener getApplicationListener () {
return listener;
}
public JglfwGraphics getGraphics () {
return graphics;
}
public Audio getAudio () {
return null;
}
public JglfwInput getInput () {
return input;
}
public JglfwFiles getFiles () {
return files;
}
public JglfwNet getNet () {
return net;
}
public ApplicationType getType () {
return ApplicationType.Desktop;
}
public int getVersion () {
return 0;
}
public long getJavaHeap () {
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
}
public long getNativeHeap () {
return getJavaHeap();
}
public Preferences getPreferences (String name) {
if (preferences.containsKey(name))
return preferences.get(name);
else {
Preferences prefs = new JglfwPreferences(name, this.preferencesdir);
preferences.put(name, prefs);
return prefs;
}
}
public Clipboard getClipboard () {
return clipboard;
}
public void postRunnable (Runnable runnable) {
synchronized (runnables) {
runnables.add(runnable);
graphics.requestRendering();
}
}
public boolean isPaused () {
return isPaused;
}
public void setForegroundFPS (int foregroundFPS) {
this.foregroundFPS = foregroundFPS;
}
public void setBackgroundFPS (int backgroundFPS) {
this.backgroundFPS = backgroundFPS;
}
public void setHiddenFPS (int hiddenFPS) {
this.hiddenFPS = hiddenFPS;
}
protected boolean shouldExit () {
return true;
}
public void exit () {
running = false;
}
public void setLogLevel (int logLevel) {
this.logLevel = logLevel;
}
@Override
public int getLogLevel () {
return logLevel;
}
public void debug (String tag, String message) {
if (logLevel >= LOG_DEBUG) {
System.out.println(tag + ": " + message);
}
}
public void debug (String tag, String message, Throwable exception) {
if (logLevel >= LOG_DEBUG) {
System.out.println(tag + ": " + message);
exception.printStackTrace(System.out);
}
}
public void log (String tag, String message) {
if (logLevel >= LOG_INFO) {
System.out.println(tag + ": " + message);
}
}
public void log (String tag, String message, Throwable exception) {
if (logLevel >= LOG_INFO) {
System.out.println(tag + ": " + message);
exception.printStackTrace(System.out);
}
}
public void error (String tag, String message) {
if (logLevel >= LOG_ERROR) {
System.err.println(tag + ": " + message);
}
}
public void error (String tag, String message, Throwable exception) {
if (logLevel >= LOG_ERROR) {
System.err.println(tag + ": " + message);
exception.printStackTrace(System.err);
}
}
public void addLifecycleListener (LifecycleListener listener) {
synchronized (lifecycleListeners) {
lifecycleListeners.add(listener);
}
}
public void removeLifecycleListener (LifecycleListener listener) {
synchronized (lifecycleListeners) {
lifecycleListeners.removeValue(listener, true);
}
}
public GlfwCallbacks getCallbacks () {
return callbacks;
}
}