1/*******************************************************************************
2 * Copyright 2011 See AUTHORS file.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 ******************************************************************************/
16
17package com.badlogic.gdx.backends.lwjgl;
18
19import java.awt.Canvas;
20import java.io.File;
21
22import org.lwjgl.LWJGLException;
23import org.lwjgl.opengl.Display;
24
25import com.badlogic.gdx.Application;
26import com.badlogic.gdx.ApplicationListener;
27import com.badlogic.gdx.Audio;
28import com.badlogic.gdx.Files;
29import com.badlogic.gdx.Gdx;
30import com.badlogic.gdx.Input;
31import com.badlogic.gdx.LifecycleListener;
32import com.badlogic.gdx.Net;
33import com.badlogic.gdx.Preferences;
34import com.badlogic.gdx.backends.lwjgl.audio.OpenALAudio;
35import com.badlogic.gdx.utils.Array;
36import com.badlogic.gdx.utils.Clipboard;
37import com.badlogic.gdx.utils.GdxRuntimeException;
38import com.badlogic.gdx.utils.ObjectMap;
39import com.badlogic.gdx.utils.SnapshotArray;
40
41/** An OpenGL surface fullscreen or in a lightweight window. */
42public class LwjglApplication implements Application {
43	protected final LwjglGraphics graphics;
44	protected OpenALAudio audio;
45	protected final LwjglFiles files;
46	protected final LwjglInput input;
47	protected final LwjglNet net;
48	protected final ApplicationListener listener;
49	protected Thread mainLoopThread;
50	protected boolean running = true;
51	protected final Array<Runnable> runnables = new Array<Runnable>();
52	protected final Array<Runnable> executedRunnables = new Array<Runnable>();
53	protected final SnapshotArray<LifecycleListener> lifecycleListeners = new SnapshotArray<LifecycleListener>(LifecycleListener.class);
54	protected int logLevel = LOG_INFO;
55	protected String preferencesdir;
56	protected Files.FileType preferencesFileType;
57
58	public LwjglApplication (ApplicationListener listener, String title, int width, int height) {
59		this(listener, createConfig(title, width, height));
60	}
61
62	public LwjglApplication (ApplicationListener listener) {
63		this(listener, null, 640, 480);
64	}
65
66	public LwjglApplication (ApplicationListener listener, LwjglApplicationConfiguration config) {
67		this(listener, config, new LwjglGraphics(config));
68	}
69
70	public LwjglApplication (ApplicationListener listener, Canvas canvas) {
71		this(listener, new LwjglApplicationConfiguration(), new LwjglGraphics(canvas));
72	}
73
74	public LwjglApplication (ApplicationListener listener, LwjglApplicationConfiguration config, Canvas canvas) {
75		this(listener, config, new LwjglGraphics(canvas, config));
76	}
77
78	public LwjglApplication (ApplicationListener listener, LwjglApplicationConfiguration config, LwjglGraphics graphics) {
79		LwjglNativesLoader.load();
80
81		if (config.title == null) config.title = listener.getClass().getSimpleName();
82
83		this.graphics = graphics;
84		if (!LwjglApplicationConfiguration.disableAudio) {
85			try {
86				audio = new OpenALAudio(config.audioDeviceSimultaneousSources, config.audioDeviceBufferCount,
87					config.audioDeviceBufferSize);
88			} catch (Throwable t) {
89				log("LwjglApplication", "Couldn't initialize audio, disabling audio", t);
90				LwjglApplicationConfiguration.disableAudio = true;
91			}
92		}
93		files = new LwjglFiles();
94		input = new LwjglInput();
95		net = new LwjglNet();
96		this.listener = listener;
97		this.preferencesdir = config.preferencesDirectory;
98		this.preferencesFileType = config.preferencesFileType;
99
100		Gdx.app = this;
101		Gdx.graphics = graphics;
102		Gdx.audio = audio;
103		Gdx.files = files;
104		Gdx.input = input;
105		Gdx.net = net;
106		initialize();
107	}
108
109	private static LwjglApplicationConfiguration createConfig (String title, int width, int height) {
110		LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
111		config.title = title;
112		config.width = width;
113		config.height = height;
114		config.vSyncEnabled = true;
115		return config;
116	}
117
118	private void initialize () {
119		mainLoopThread = new Thread("LWJGL Application") {
120			@Override
121			public void run () {
122				graphics.setVSync(graphics.config.vSyncEnabled);
123				try {
124					LwjglApplication.this.mainLoop();
125				} catch (Throwable t) {
126					if (audio != null) audio.dispose();
127					Gdx.input.setCursorCatched(false);
128					if (t instanceof RuntimeException)
129						throw (RuntimeException)t;
130					else
131						throw new GdxRuntimeException(t);
132				}
133			}
134		};
135		mainLoopThread.start();
136	}
137
138	void mainLoop () {
139		SnapshotArray<LifecycleListener> lifecycleListeners = this.lifecycleListeners;
140
141		try {
142			graphics.setupDisplay();
143		} catch (LWJGLException e) {
144			throw new GdxRuntimeException(e);
145		}
146
147		listener.create();
148		graphics.resize = true;
149
150		int lastWidth = graphics.getWidth();
151		int lastHeight = graphics.getHeight();
152
153		graphics.lastTime = System.nanoTime();
154		boolean wasActive = true;
155		while (running) {
156			Display.processMessages();
157			if (Display.isCloseRequested()) exit();
158
159			boolean isActive = Display.isActive();
160			if (wasActive && !isActive) { // if it's just recently minimized from active state
161				wasActive = false;
162				synchronized (lifecycleListeners) {
163					LifecycleListener[] listeners = lifecycleListeners.begin();
164					for (int i = 0, n = lifecycleListeners.size; i < n; ++i)
165						 listeners[i].pause();
166					lifecycleListeners.end();
167				}
168				listener.pause();
169			}
170			if (!wasActive && isActive) { // if it's just recently focused from minimized state
171				wasActive = true;
172				synchronized (lifecycleListeners) {
173					LifecycleListener[] listeners = lifecycleListeners.begin();
174					for (int i = 0, n = lifecycleListeners.size; i < n; ++i)
175						listeners[i].resume();
176					lifecycleListeners.end();
177				}
178				listener.resume();
179			}
180
181			boolean shouldRender = false;
182
183			if (graphics.canvas != null) {
184				int width = graphics.canvas.getWidth();
185				int height = graphics.canvas.getHeight();
186				if (lastWidth != width || lastHeight != height) {
187					lastWidth = width;
188					lastHeight = height;
189					Gdx.gl.glViewport(0, 0, lastWidth, lastHeight);
190					listener.resize(lastWidth, lastHeight);
191					shouldRender = true;
192				}
193			} else {
194				graphics.config.x = Display.getX();
195				graphics.config.y = Display.getY();
196				if (graphics.resize || Display.wasResized()
197					|| (int)(Display.getWidth() * Display.getPixelScaleFactor()) != graphics.config.width
198					|| (int)(Display.getHeight() * Display.getPixelScaleFactor()) != graphics.config.height) {
199					graphics.resize = false;
200					graphics.config.width = (int)(Display.getWidth() * Display.getPixelScaleFactor());
201					graphics.config.height = (int)(Display.getHeight() * Display.getPixelScaleFactor());
202					Gdx.gl.glViewport(0, 0, graphics.config.width, graphics.config.height);
203					if (listener != null) listener.resize(graphics.config.width, graphics.config.height);
204					graphics.requestRendering();
205				}
206			}
207
208			if (executeRunnables()) shouldRender = true;
209
210			// If one of the runnables set running to false, for example after an exit().
211			if (!running) break;
212
213			input.update();
214			shouldRender |= graphics.shouldRender();
215			input.processEvents();
216			if (audio != null) audio.update();
217
218			if (!isActive && graphics.config.backgroundFPS == -1) shouldRender = false;
219			int frameRate = isActive ? graphics.config.foregroundFPS : graphics.config.backgroundFPS;
220			if (shouldRender) {
221				graphics.updateTime();
222				graphics.frameId++;
223				listener.render();
224				Display.update(false);
225			} else {
226				// Sleeps to avoid wasting CPU in an empty loop.
227				if (frameRate == -1) frameRate = 10;
228				if (frameRate == 0) frameRate = graphics.config.backgroundFPS;
229				if (frameRate == 0) frameRate = 30;
230			}
231			if (frameRate > 0) Display.sync(frameRate);
232		}
233
234		synchronized (lifecycleListeners) {
235			LifecycleListener[] listeners = lifecycleListeners.begin();
236			for (int i = 0, n = lifecycleListeners.size; i < n; ++i) {
237				listeners[i].pause();
238				listeners[i].dispose();
239			}
240			lifecycleListeners.end();
241		}
242		listener.pause();
243		listener.dispose();
244		Display.destroy();
245		if (audio != null) audio.dispose();
246		if (graphics.config.forceExit) System.exit(-1);
247	}
248
249	public boolean executeRunnables () {
250		synchronized (runnables) {
251			for (int i = runnables.size - 1; i >= 0; i--)
252				executedRunnables.add(runnables.get(i));
253			runnables.clear();
254		}
255		if (executedRunnables.size == 0) return false;
256		do
257			executedRunnables.pop().run();
258		while (executedRunnables.size > 0);
259		return true;
260	}
261
262	@Override
263	public ApplicationListener getApplicationListener () {
264		return listener;
265	}
266
267	@Override
268	public Audio getAudio () {
269		return audio;
270	}
271
272	@Override
273	public Files getFiles () {
274		return files;
275	}
276
277	@Override
278	public LwjglGraphics getGraphics () {
279		return graphics;
280	}
281
282	@Override
283	public Input getInput () {
284		return input;
285	}
286
287	@Override
288	public Net getNet () {
289		return net;
290	}
291
292	@Override
293	public ApplicationType getType () {
294		return ApplicationType.Desktop;
295	}
296
297	@Override
298	public int getVersion () {
299		return 0;
300	}
301
302	public void stop () {
303		running = false;
304		try {
305			mainLoopThread.join();
306		} catch (Exception ex) {
307		}
308	}
309
310	@Override
311	public long getJavaHeap () {
312		return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
313	}
314
315	@Override
316	public long getNativeHeap () {
317		return getJavaHeap();
318	}
319
320	ObjectMap<String, Preferences> preferences = new ObjectMap<String, Preferences>();
321
322	@Override
323	public Preferences getPreferences (String name) {
324		if (preferences.containsKey(name)) {
325			return preferences.get(name);
326		} else {
327			Preferences prefs = new LwjglPreferences(new LwjglFileHandle(new File(preferencesdir, name), preferencesFileType));
328			preferences.put(name, prefs);
329			return prefs;
330		}
331	}
332
333	@Override
334	public Clipboard getClipboard () {
335		return new LwjglClipboard();
336	}
337
338	@Override
339	public void postRunnable (Runnable runnable) {
340		synchronized (runnables) {
341			runnables.add(runnable);
342			Gdx.graphics.requestRendering();
343		}
344	}
345
346	@Override
347	public void debug (String tag, String message) {
348		if (logLevel >= LOG_DEBUG) {
349			System.out.println(tag + ": " + message);
350		}
351	}
352
353	@Override
354	public void debug (String tag, String message, Throwable exception) {
355		if (logLevel >= LOG_DEBUG) {
356			System.out.println(tag + ": " + message);
357			exception.printStackTrace(System.out);
358		}
359	}
360
361	@Override
362	public void log (String tag, String message) {
363		if (logLevel >= LOG_INFO) {
364			System.out.println(tag + ": " + message);
365		}
366	}
367
368	@Override
369	public void log (String tag, String message, Throwable exception) {
370		if (logLevel >= LOG_INFO) {
371			System.out.println(tag + ": " + message);
372			exception.printStackTrace(System.out);
373		}
374	}
375
376	@Override
377	public void error (String tag, String message) {
378		if (logLevel >= LOG_ERROR) {
379			System.err.println(tag + ": " + message);
380		}
381	}
382
383	@Override
384	public void error (String tag, String message, Throwable exception) {
385		if (logLevel >= LOG_ERROR) {
386			System.err.println(tag + ": " + message);
387			exception.printStackTrace(System.err);
388		}
389	}
390
391	@Override
392	public void setLogLevel (int logLevel) {
393		this.logLevel = logLevel;
394	}
395
396	@Override
397	public int getLogLevel () {
398		return logLevel;
399	}
400
401	@Override
402	public void exit () {
403		postRunnable(new Runnable() {
404			@Override
405			public void run () {
406				running = false;
407			}
408		});
409	}
410
411	@Override
412	public void addLifecycleListener (LifecycleListener listener) {
413		synchronized (lifecycleListeners) {
414			lifecycleListeners.add(listener);
415		}
416	}
417
418	@Override
419	public void removeLifecycleListener (LifecycleListener listener) {
420		synchronized (lifecycleListeners) {
421			lifecycleListeners.removeValue(listener, true);
422		}
423	}
424}
425