/* * Copyright 2012 Benjamin Glatzel <benjamin.glatzel@me.com> * * 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 org.terasology.game; import static org.lwjgl.opengl.GL11.GL_CULL_FACE; import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST; import static org.lwjgl.opengl.GL11.GL_LEQUAL; import static org.lwjgl.opengl.GL11.GL_NORMALIZE; import static org.lwjgl.opengl.GL11.glDepthFunc; import static org.lwjgl.opengl.GL11.glEnable; import static org.lwjgl.opengl.GL11.glViewport; import java.io.File; import java.lang.reflect.Field; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import org.lwjgl.LWJGLException; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.GLContext; import org.spout.api.gamestate.GameState; import org.spout.engine.SpoutClient; import org.terasology.asset.AssetType; import org.terasology.asset.AssetUri; import org.terasology.asset.loaders.GLSLShaderLoader; import org.terasology.asset.loaders.MaterialLoader; import org.terasology.asset.loaders.ObjMeshLoader; import org.terasology.asset.loaders.OggSoundLoader; import org.terasology.asset.loaders.OggStreamingSoundLoader; import org.terasology.asset.loaders.PNGTextureLoader; import org.terasology.asset.sources.ClasspathSource; import org.terasology.logic.manager.AssetManager; import org.terasology.logic.manager.AudioManager; import org.terasology.logic.manager.Config; import org.terasology.logic.manager.FontManager; import org.terasology.logic.manager.PathManager; import org.terasology.logic.manager.ShaderManager; import org.terasology.logic.manager.VertexBufferObjectManager; import org.terasology.model.shapes.BlockShapeManager; import org.terasology.performanceMonitor.PerformanceMonitor; import org.terasology.teraspout.TeraSpout; import com.google.common.collect.Lists; /** * @author Immortius */ public class TerasologyEngine extends SpoutClient { private Deque<GameState> stateStack = new ArrayDeque<GameState>(); private boolean initialised; private boolean running; private boolean disposed; private List<StateChangeFunction> pendingStateChanges = Lists.newArrayList(); private GameState state; private final ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool(); private final TeraSpout teraspout; private Logger logger = super.getLogger(); public TerasologyEngine() { super(); this.teraspout = new TeraSpout(this); } /** * Gets the TeraSpout instance. * * @return */ public TeraSpout getTeraSpout() { return teraspout; } @Override public void initRenderer() { if (initialised) { return; } logger.log(Level.INFO, "Initializing TeraSpout..."); initDisplay(); initOpenGL(); initOpenAL(); initControls(); initManagers(); initialised = true; } public void run(GameState initialState) { if (!initialised) { throw new IllegalStateException("Game not initialized to run a GameState!"); } changeState(initialState); running = true; Thread.currentThread().setPriority(Thread.MAX_PRIORITY); CoreRegistry.put(TerasologyEngine.class, this); cleanup(); } @Override public void stop(String message) { running = false; disposed = true; initialised = false; AudioManager.getInstance().destroy(); Mouse.destroy(); Keyboard.destroy(); super.stop(message); } public boolean isRunning() { return running; } public boolean isDisposed() { return disposed; } public void changeState(GameState newState) { if (running) { pendingStateChanges.add(new ChangeState(newState)); } else { doPurgeStates(); doPushState(newState); } } public void pushState(GameState newState) { if (running) { pendingStateChanges.add(new PushState(newState)); } else { doPushState(newState); } } public void popState() { if (running) { pendingStateChanges.add(new PopState()); } else { doPopState(); } } public void submitTask(final String name, final Runnable task) { threadPool.execute(new Runnable() { @Override public void run() { Thread.currentThread().setPriority(Thread.MIN_PRIORITY); PerformanceMonitor.startThread(name); try { task.run(); } catch (RejectedExecutionException e) { logger.log(Level.SEVERE, "Thread submitted after shutdown requested: " + name); } finally { PerformanceMonitor.endThread(name); } } }); } public int getActiveTaskCount() { return threadPool.getActiveCount(); } private void addLibraryPath(File libPath) { try { String envPath = System.getProperty("java.library.path"); if (envPath == null || envPath.isEmpty()) { System.setProperty("java.library.path", libPath.getAbsolutePath()); } else { System.setProperty("java.library.path", envPath + File.pathSeparator + libPath.getAbsolutePath()); } final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths"); usrPathsField.setAccessible(true); List<String> paths = new ArrayList<String>(Arrays.asList((String[]) usrPathsField.get(null))); if (paths.contains(libPath)) { return; } paths.add(0, libPath.getAbsolutePath()); // Add to beginning, to override system libraries usrPathsField.set(null, paths.toArray(new String[paths.size()])); } catch (Exception e) { logger.log(Level.SEVERE, "Couldn't link static libraries. " + e.toString(), e); System.exit(1); } } private void initOpenAL() { // TODO: Put in registry AudioManager.getInstance().initialize(); } private void initDisplay() { try { if (Config.getInstance().isFullscreen()) { Display.setDisplayMode(Display.getDesktopDisplayMode()); Display.setFullscreen(true); } else { Display.setDisplayMode(Config.getInstance().getDisplayMode()); Display.setResizable(true); } Display.setTitle("Terasology" + " | " + "Pre Alpha"); Display.create(Config.getInstance().getPixelFormat()); } catch (LWJGLException e) { logger.log(Level.SEVERE, "Can not initialize graphics device.", e); System.exit(1); } } private void initOpenGL() { checkOpenGL(); resizeViewport(); initOpenGLParams(); } private void checkOpenGL() { boolean canRunGame = GLContext.getCapabilities().OpenGL20 & GLContext.getCapabilities().OpenGL11 & GLContext.getCapabilities().OpenGL12 & GLContext.getCapabilities().OpenGL14 & GLContext.getCapabilities().OpenGL15; if (!canRunGame) { logger.log(Level.SEVERE, "Your GPU driver is not supporting the mandatory versions of OpenGL. Considered updating your GPU drivers?"); System.exit(1); } } private void resizeViewport() { glViewport(0, 0, Display.getWidth(), Display.getHeight()); } public void initOpenGLParams() { glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glEnable(GL_NORMALIZE); glDepthFunc(GL_LEQUAL); } private void initControls() { try { Keyboard.create(); Keyboard.enableRepeatEvents(true); Mouse.create(); Mouse.setGrabbed(true); } catch (LWJGLException e) { logger.log(Level.SEVERE, "Could not initialize controls.", e); System.exit(1); } } private void initManagers() { AssetManager.getInstance().register(AssetType.MESH, "obj", new ObjMeshLoader()); AssetManager.getInstance().register(AssetType.MUSIC, "ogg", new OggStreamingSoundLoader()); AssetManager.getInstance().register(AssetType.SOUND, "ogg", new OggSoundLoader()); AssetManager.getInstance().register(AssetType.TEXTURE, "png", new PNGTextureLoader()); AssetManager.getInstance().register(AssetType.SHADER, "glsl", new GLSLShaderLoader()); AssetManager.getInstance().register(AssetType.MATERIAL, "mat", new MaterialLoader()); AssetManager.getInstance().addAssetSource(new ClasspathSource("engine", getClass().getProtectionDomain().getCodeSource(), "org/terasology/data")); // TODO: Shouldn't be setting up the block/block shape managers here (do on transition to StateSinglePlayer) BlockShapeManager.getInstance().reload(); for (AssetUri uri : AssetManager.list(AssetType.SHADER)) { AssetManager.load(uri); } for (AssetUri uri : AssetManager.list(AssetType.MATERIAL)) { AssetManager.load(uri); } // TODO: This has to occur after the BlockManager has been created, so that texture:engine:terrain exists. Fix this. ShaderManager.getInstance(); VertexBufferObjectManager.getInstance(); FontManager.getInstance(); } private void cleanup() { logger.log(Level.INFO, "Shutting down Terasology..."); Config.getInstance().saveConfig(new File(PathManager.getInstance().getWorldPath(), "last.cfg")); doPurgeStates(); terminateThreads(); } private void terminateThreads() { threadPool.shutdown(); try { threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (InterruptedException e) { logger.log(Level.SEVERE, e.toString(), e); } } @Override public void render(float dt) { // Only process rendering and updating once a second // TODO: Add debug config setting to run even if display inactive if (!Display.isActive()) { try { Thread.sleep(1000); } catch (InterruptedException e) { logger.log(Level.WARNING, e.toString(), e); } Display.processMessages(); return; } processStateChanges(); state = stateStack.peek(); if (state == null) { stop(); return; } PerformanceMonitor.startActivity("Render"); state.onRender(dt); Display.update(); Display.sync(60); PerformanceMonitor.endActivity(); PerformanceMonitor.startActivity("Audio"); AudioManager.getInstance().update(); PerformanceMonitor.endActivity(); PerformanceMonitor.rollCycle(); PerformanceMonitor.startActivity("Other"); if (Display.wasResized()) resizeViewport(); } private void processStateChanges() { for (StateChangeFunction func : pendingStateChanges) { func.enact(); } pendingStateChanges.clear(); } private void doPurgeStates() { while (!stateStack.isEmpty()) { doPopState(); } } private void doPopState() { GameState oldState = stateStack.pop(); oldState.unloadResources(); if (!stateStack.isEmpty()) { stateStack.peek().loadResources(); } } private void doPushState(GameState newState) { if (!stateStack.isEmpty()) { stateStack.peek().unloadResources(); } stateStack.push(newState); newState.initialize(); newState.loadResources(); } private interface StateChangeFunction { void enact(); } private class ChangeState implements StateChangeFunction { public GameState newState; public ChangeState(GameState newState) { this.newState = newState; } @Override public void enact() { doPurgeStates(); doPushState(newState); } } private class PushState implements StateChangeFunction { public GameState newState; public PushState(GameState newState) { this.newState = newState; } @Override public void enact() { doPushState(newState); } } private class PopState implements StateChangeFunction { public PopState() { } @Override public void enact() { doPopState(); } } }