Java tutorial
/* * Copyright 2014 MovingBlocks * * 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.engine; import com.google.common.base.Stopwatch; import com.google.common.collect.Queues; import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.asset.AssetFactory; import org.terasology.asset.AssetManager; import org.terasology.asset.AssetType; import org.terasology.asset.AssetUri; import org.terasology.config.Config; import org.terasology.engine.bootstrap.ApplyModulesUtil; import org.terasology.engine.modes.GameState; import org.terasology.engine.module.ModuleManager; import org.terasology.engine.paths.PathManager; import org.terasology.engine.subsystem.DisplayDevice; import org.terasology.engine.subsystem.EngineSubsystem; import org.terasology.engine.subsystem.RenderingSubsystemFactory; import org.terasology.entitySystem.prefab.Prefab; import org.terasology.entitySystem.prefab.PrefabData; import org.terasology.entitySystem.prefab.internal.PojoPrefab; import org.terasology.game.Game; import org.terasology.identity.CertificateGenerator; import org.terasology.identity.CertificatePair; import org.terasology.identity.PrivateIdentityCertificate; import org.terasology.identity.PublicIdentityCertificate; import org.terasology.input.InputSystem; import org.terasology.logic.behavior.asset.BehaviorTree; import org.terasology.logic.behavior.asset.BehaviorTreeData; import org.terasology.monitoring.PerformanceMonitor; import org.terasology.monitoring.ThreadActivity; import org.terasology.monitoring.ThreadMonitor; import org.terasology.monitoring.gui.AdvancedMonitor; import org.terasology.network.NetworkSystem; import org.terasology.network.internal.NetworkSystemImpl; import org.terasology.persistence.typeHandling.TypeSerializationLibrary; import org.terasology.physics.CollisionGroupManager; import org.terasology.reflection.copy.CopyStrategyLibrary; import org.terasology.reflection.reflect.ReflectFactory; import org.terasology.reflection.reflect.ReflectionReflectFactory; import org.terasology.registry.CoreRegistry; import org.terasology.rendering.nui.asset.UIData; import org.terasology.rendering.nui.asset.UIElement; import org.terasology.rendering.nui.skin.UISkin; import org.terasology.rendering.nui.skin.UISkinData; import org.terasology.utilities.concurrency.ShutdownTask; import org.terasology.utilities.concurrency.Task; import org.terasology.utilities.concurrency.TaskMaster; import org.terasology.version.TerasologyVersion; import org.terasology.world.block.shapes.BlockShape; import org.terasology.world.block.shapes.BlockShapeData; import org.terasology.world.block.shapes.BlockShapeImpl; import org.terasology.world.generator.internal.WorldGeneratorManager; import java.io.IOException; import java.nio.file.Files; import java.util.Collection; import java.util.Deque; import java.util.Iterator; import java.util.Set; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; /** * @author Immortius */ public class TerasologyEngine implements GameEngine { private static final Logger logger = LoggerFactory.getLogger(TerasologyEngine.class); private GameState currentState; private boolean initialised; private boolean running; private boolean disposed; private GameState pendingState; private Config config; private EngineTime time; private final TaskMaster<Task> commonThreadPool = TaskMaster.createFIFOTaskMaster("common", 16); private boolean hibernationAllowed; private boolean gameFocused = true; private Set<StateChangeSubscriber> stateChangeSubscribers = Sets.newLinkedHashSet(); private Deque<EngineSubsystem> subsystems; public TerasologyEngine(Collection<EngineSubsystem> subsystems) { this.subsystems = Queues.newArrayDeque(subsystems); } public Iterable<EngineSubsystem> getSubsystems() { return subsystems; } @Override public void init() { if (initialised) { return; } Stopwatch sw = Stopwatch.createStarted(); try { logger.info("Initializing Terasology..."); logger.info(TerasologyVersion.getInstance().toString()); logger.info("Home path: {}", PathManager.getInstance().getHomePath()); logger.info("Install path: {}", PathManager.getInstance().getInstallPath()); logger.info("Java: {} in {}", System.getProperty("java.version"), System.getProperty("java.home")); logger.info("Java VM: {}, version: {}", System.getProperty("java.vm.name"), System.getProperty("java.vm.version")); logger.info("OS: {}, arch: {}, version: {}", System.getProperty("os.name"), System.getProperty("os.arch"), System.getProperty("os.version")); logger.info("Max. Memory: {} MB", Runtime.getRuntime().maxMemory() / (1024 * 1024)); logger.info("Processors: {}", Runtime.getRuntime().availableProcessors()); initConfig(); for (EngineSubsystem subsystem : getSubsystems()) { subsystem.preInitialise(); } // Verify required systems are available time = (EngineTime) CoreRegistry.get(Time.class); if (time == null) { throw new IllegalStateException("Time not registered as a core system."); } initManagers(); for (EngineSubsystem subsystem : getSubsystems()) { subsystem.postInitialise(config); } // Verify required systems are available if (CoreRegistry.get(DisplayDevice.class) == null) { throw new IllegalStateException("DisplayDevice not registered as a core system."); } if (CoreRegistry.get(RenderingSubsystemFactory.class) == null) { throw new IllegalStateException("EngineSubsystemFactory not registered as a core system."); } if (CoreRegistry.get(InputSystem.class) == null) { throw new IllegalStateException("InputSystem not registered as a core system."); } initAssets(); if (config.getSystem().isMonitoringEnabled()) { new AdvancedMonitor().setVisible(true); } initialised = true; } catch (RuntimeException e) { logger.error("Failed to initialise Terasology", e); cleanup(); throw e; } double secs = 0.001 * sw.elapsed(TimeUnit.MILLISECONDS); logger.info("Initialization completed in {}sec.", String.format("%.2f", secs)); } private void initAssets() { AssetManager assetManager = CoreRegistry.get(AssetManager.class); assetManager.setAssetFactory(AssetType.PREFAB, new AssetFactory<PrefabData, Prefab>() { @Override public Prefab buildAsset(AssetUri uri, PrefabData data) { return new PojoPrefab(uri, data); } }); assetManager.setAssetFactory(AssetType.SHAPE, new AssetFactory<BlockShapeData, BlockShape>() { @Override public BlockShape buildAsset(AssetUri uri, BlockShapeData data) { return new BlockShapeImpl(uri, data); } }); assetManager.setAssetFactory(AssetType.UI_SKIN, new AssetFactory<UISkinData, UISkin>() { @Override public UISkin buildAsset(AssetUri uri, UISkinData data) { return new UISkin(uri, data); } }); assetManager.setAssetFactory(AssetType.BEHAVIOR, new AssetFactory<BehaviorTreeData, BehaviorTree>() { @Override public BehaviorTree buildAsset(AssetUri uri, BehaviorTreeData data) { return new BehaviorTree(uri, data); } }); assetManager.setAssetFactory(AssetType.UI_ELEMENT, new AssetFactory<UIData, UIElement>() { @Override public UIElement buildAsset(AssetUri uri, UIData data) { return new UIElement(uri, data); } }); } private void initConfig() { if (Files.isRegularFile(Config.getConfigFile())) { try { config = Config.load(Config.getConfigFile()); } catch (IOException e) { logger.error("Failed to load config", e); config = new Config(); } } else { config = new Config(); } if (!config.getDefaultModSelection().hasModule(TerasologyConstants.CORE_MODULE)) { config.getDefaultModSelection().addModule(TerasologyConstants.CORE_MODULE); } if (!validateServerIdentity()) { CertificateGenerator generator = new CertificateGenerator(); CertificatePair serverIdentity = generator.generateSelfSigned(); config.getSecurity().setServerCredentials(serverIdentity.getPublicCert(), serverIdentity.getPrivateCert()); config.save(); } logger.info("Video Settings: " + config.getRendering().toString()); CoreRegistry.putPermanently(Config.class, config); } private boolean validateServerIdentity() { PrivateIdentityCertificate privateCert = config.getSecurity().getServerPrivateCertificate(); PublicIdentityCertificate publicCert = config.getSecurity().getServerPublicCertificate(); if (privateCert == null || publicCert == null) { return false; } // Validate the signature if (!publicCert.verifySelfSigned()) { logger.error("Server signature is not self signed! Generating new server identity."); return false; } return true; } @Override public void run(GameState initialState) { try { CoreRegistry.putPermanently(GameEngine.class, this); if (!initialised) { init(); } changeState(initialState); running = true; Thread.currentThread().setPriority(Thread.MAX_PRIORITY); mainLoop(); cleanup(); } catch (RuntimeException e) { logger.error("Uncaught exception, attempting clean game shutdown", e); try { cleanup(); } catch (Throwable t) { logger.error("Clean game shutdown after an uncaught exception failed", t); logger.error("Rethrowing original exception"); } throw e; } } @Override public void shutdown() { running = false; } @Override public void dispose() { /* * The engine is shutdown even when running is true for so that terasology gets also properly disposed in * case of a crash: The mouse must be made visible again for the crash reporter and the main window needs to * be closed. */ disposed = true; initialised = false; Iterator<EngineSubsystem> iter = subsystems.descendingIterator(); while (iter.hasNext()) { EngineSubsystem subsystem = iter.next(); try { subsystem.dispose(); } catch (Throwable t) { logger.error("Unable to dispose subsystem {}", subsystem, t); } } } @Override public boolean isRunning() { return running; } @Override public boolean isDisposed() { return disposed; } @Override public GameState getState() { return currentState; } @Override public void changeState(GameState newState) { if (currentState != null) { pendingState = newState; } else { switchState(newState); } } @Override public void submitTask(final String name, final Runnable task) { try { commonThreadPool.put(new Task() { @Override public String getName() { return name; } @Override public void run() { Thread.currentThread().setPriority(Thread.MIN_PRIORITY); Thread.currentThread().setName("Engine-Task-Pool"); try (ThreadActivity ignored = ThreadMonitor .startThreadActivity(task.getClass().getSimpleName())) { task.run(); } catch (RejectedExecutionException e) { ThreadMonitor.addError(e); logger.error("Thread submitted after shutdown requested: {}", name); } catch (Throwable e) { ThreadMonitor.addError(e); } } @Override public boolean isTerminateSignal() { return false; } }); } catch (InterruptedException e) { logger.error("Failed to submit task {}, running on main thread", name, e); task.run(); } } private void initManagers() { GameThread.setGameThread(); ModuleManager moduleManager = CoreRegistry.putPermanently(ModuleManager.class, new ModuleManager()); ReflectFactory reflectFactory = CoreRegistry.putPermanently(ReflectFactory.class, new ReflectionReflectFactory()); CopyStrategyLibrary copyStrategyLibrary = CoreRegistry.putPermanently(CopyStrategyLibrary.class, new CopyStrategyLibrary(reflectFactory)); CoreRegistry.putPermanently(TypeSerializationLibrary.class, new TypeSerializationLibrary(reflectFactory, copyStrategyLibrary)); AssetManager assetManager = CoreRegistry.putPermanently(AssetManager.class, new AssetManager(moduleManager.getEnvironment())); assetManager.setEnvironment(moduleManager.getEnvironment()); CoreRegistry.putPermanently(CollisionGroupManager.class, new CollisionGroupManager()); CoreRegistry.putPermanently(WorldGeneratorManager.class, new WorldGeneratorManager()); CoreRegistry.putPermanently(ComponentSystemManager.class, new ComponentSystemManager()); CoreRegistry.putPermanently(NetworkSystem.class, new NetworkSystemImpl(time)); CoreRegistry.putPermanently(Game.class, new Game(this, time)); assetManager.setEnvironment(moduleManager.getEnvironment()); AssetType.registerAssetTypes(assetManager); ApplyModulesUtil.applyModules(); } private void cleanup() { logger.info("Shutting down Terasology..."); try { Iterator<EngineSubsystem> iter = subsystems.descendingIterator(); while (iter.hasNext()) { EngineSubsystem subsystem = iter.next(); subsystem.shutdown(config); } config.save(); if (currentState != null) { currentState.dispose(); currentState = null; } } finally { // Even if a graceful shutdown of the subsystems fails, // the thread pool has to be shut down stopThreads(); } } public void stopThreads() { commonThreadPool.shutdown(new ShutdownTask(), false); } public void restartThreads() { commonThreadPool.restart(); } private void mainLoop() { NetworkSystem networkSystem = CoreRegistry.get(NetworkSystem.class); DisplayDevice display = CoreRegistry.get(DisplayDevice.class); PerformanceMonitor.startActivity("Other"); // MAIN GAME LOOP while (running && !display.isCloseRequested()) { // Only process rendering and updating once a second if (!display.isActive() && isHibernationAllowed()) { time.setPaused(true); Iterator<Float> updateCycles = time.tick(); while (updateCycles.hasNext()) { updateCycles.next(); } try { Thread.sleep(100); } catch (InterruptedException e) { logger.warn("Display inactivity sleep interrupted", e); } display.processMessages(); time.setPaused(false); continue; } processStateChanges(); if (currentState == null) { shutdown(); break; } Iterator<Float> updateCycles = time.tick(); PerformanceMonitor.startActivity("Network Update"); networkSystem.update(); PerformanceMonitor.endActivity(); long totalDelta = 0; while (updateCycles.hasNext()) { float delta = updateCycles.next(); totalDelta += time.getDeltaInMs(); PerformanceMonitor.startActivity("Main Update"); currentState.update(delta); PerformanceMonitor.endActivity(); } float delta = totalDelta / 1000f; for (EngineSubsystem subsystem : getSubsystems()) { PerformanceMonitor.startActivity(subsystem.getClass().getSimpleName()); subsystem.preUpdate(currentState, delta); PerformanceMonitor.endActivity(); } GameThread.processWaitingProcesses(); for (EngineSubsystem subsystem : getSubsystems()) { PerformanceMonitor.startActivity(subsystem.getClass().getSimpleName()); subsystem.postUpdate(currentState, delta); PerformanceMonitor.endActivity(); } PerformanceMonitor.rollCycle(); PerformanceMonitor.startActivity("Other"); } PerformanceMonitor.endActivity(); running = false; } private void processStateChanges() { if (pendingState != null) { switchState(pendingState); pendingState = null; } } private void switchState(GameState newState) { if (currentState != null) { currentState.dispose(); } currentState = newState; newState.init(this); for (StateChangeSubscriber subscriber : stateChangeSubscribers) { subscriber.onStateChange(); } // drain input queues InputSystem inputSystem = CoreRegistry.get(InputSystem.class); inputSystem.getMouseDevice().getInputQueue(); inputSystem.getKeyboard().getInputQueue(); } public boolean isFullscreen() { return config.getRendering().isFullscreen(); } public void setFullscreen(boolean state) { if (config.getRendering().isFullscreen() != state) { config.getRendering().setFullscreen(state); DisplayDevice display = CoreRegistry.get(DisplayDevice.class); display.setFullscreen(state); } } @Override public boolean isHibernationAllowed() { return hibernationAllowed && currentState.isHibernationAllowed(); } @Override public void setHibernationAllowed(boolean allowed) { this.hibernationAllowed = allowed; } @Override public boolean hasFocus() { DisplayDevice display = CoreRegistry.get(DisplayDevice.class); return gameFocused && display.isActive(); } @Override public boolean hasMouseFocus() { return gameFocused; } @Override public void setFocus(boolean focused) { gameFocused = focused; } @Override public void subscribeToStateChange(StateChangeSubscriber subscriber) { stateChangeSubscribers.add(subscriber); } @Override public void unsubscribeToStateChange(StateChangeSubscriber subscriber) { stateChangeSubscribers.remove(subscriber); } }