Versatile Java game engine with pluggable backends (this was used in Rogue, I think)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

387 lines
7.9 KiB

package mightypork.gamecore.core;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import mightypork.gamecore.audio.AudioModule;
import mightypork.gamecore.core.config.Config;
import mightypork.gamecore.core.events.MainLoopRequest;
import mightypork.gamecore.core.events.ShutdownRequest;
import mightypork.gamecore.core.init.InitSequence;
import mightypork.gamecore.core.init.InitTask;
import mightypork.gamecore.core.init.InitTaskBackend;
import mightypork.gamecore.core.init.InitTaskCrashHandler;
import mightypork.gamecore.core.init.InitTaskIonizables;
import mightypork.gamecore.core.init.InitTaskLog;
import mightypork.gamecore.core.init.InitTaskLogHeader;
import mightypork.gamecore.core.init.InitTaskMainLoop;
import mightypork.gamecore.core.init.InitTaskResourceLoaderAsync;
import mightypork.gamecore.core.init.InitTaskWorkdir;
import mightypork.gamecore.core.plugins.AppPlugin;
import mightypork.gamecore.graphics.GraphicsModule;
import mightypork.gamecore.graphics.Renderable;
import mightypork.gamecore.input.InputModule;
import mightypork.utils.Str;
import mightypork.utils.annotations.Stub;
import mightypork.utils.eventbus.EventBus;
import mightypork.utils.eventbus.clients.BusNode;
import mightypork.utils.eventbus.clients.DelegatingList;
import mightypork.utils.eventbus.events.DestroyEvent;
import mightypork.utils.logging.Log;
/**
* Game base class & static subsystem access
*
* @author MightyPork
*/
public class App extends BusNode {
private static App instance;
private final AppBackend backend;
private final EventBus eventBus = new EventBus();
private boolean started = false;
private boolean inited = false;
/** List of installed App plugins */
protected final DelegatingList plugins = new DelegatingList();
/** List of initializers */
protected final InitSequence initTasks = new InitSequence();
/** The used main loop instance */
protected MainLoop mainLoop;
private Renderable mainRenderable;
/**
* Create an app with given backend.
*
* @param backend the backend to use
*/
public App(AppBackend backend)
{
if (App.instance != null) {
throw new IllegalStateException("App already initialized");
}
// store current instance in static field
App.instance = this;
// join the bus
this.eventBus.subscribe(this);
// create plugin registry attached to bus
addChildClient(this.plugins);
// initialize and use backend
this.backend = backend;
this.backend.bind(this);
addChildClient(backend);
addDefaultInitTasks();
this.backend.addInitTasks();
}
private void addDefaultInitTasks()
{
addInitTask(new InitTaskCrashHandler());
addInitTask(new InitTaskWorkdir(new File("."), true));
addInitTask(new InitTaskLog());
addInitTask(new InitTaskBackend());
addInitTask(new InitTaskIonizables());
addInitTask(new InitTaskMainLoop());
addInitTask(new InitTaskResourceLoaderAsync());
addInitTask(new InitTaskLogHeader());
}
/**
* Add a plugin to the app. Plugins can eg. listen to bus events and react
* to them.
*
* @param plugin the added plugin.
*/
public void addPlugin(AppPlugin plugin)
{
// attach to event bus
plugins.add(plugin);
plugin.bind(this);
plugin.initialize();
}
/**
* Add an initializer to the app.
*
* @param initializer the added init task
*/
public void addInitTask(InitTask initializer)
{
if (started) {
throw new IllegalStateException("App already started, cannot add initializers.");
}
initTasks.addTask(initializer);
}
/**
* Set the main loop implementation
*
* @param loop main loop impl
*/
public void setMainLoop(MainLoop loop)
{
this.mainLoop = loop;
addChildClient(loop); // ?
}
/**
* Set the main renderable
*
* @param renderable the main renderable
*/
public void setMainRenderable(Renderable renderable)
{
this.mainRenderable = renderable;
}
/**
* Get current backend
*
* @return the backend
*/
public AppBackend getBackend()
{
return backend;
}
/**
* Initialize the App and start operating.<br>
* This method should be called after adding all required initializers and
* plugins.
*/
public final void start()
{
if (started) {
throw new IllegalStateException("Already started.");
}
started = true;
Log.i("Starting init...");
init();
if (mainLoop == null) {
throw new IllegalStateException("The app has no main loop assigned.");
}
Log.i("Starting main loop...");
mainLoop.setRootRenderable(mainRenderable);
mainLoop.start();
}
private void init()
{
if (inited) {
throw new IllegalStateException("Already inited.");
}
inited = true;
// pre-init hook, just in case anyone wanted to have one.
Log.f2("Calling pre-init hook...");
preInit();
Log.f2("Running init tasks...");
// sort initializers based on dependencies
final List<InitTask> orderedInitializers = initTasks.getSequence();
// detailed logging
Log.f3("=== Task overview ===");
for (final InitTask t : orderedInitializers) {
Log.f3("Task " + Str.pad(t.getName(), 20) + " class = " + Str.pad(Str.val(t), 30) + " prio = " + Str.pad("" + t.getPriority(), 12) + " deps = "
+ Arrays.toString(t.getDependencies()));
}
for (final InitTask initTask : orderedInitializers) {
Log.f1("Running init task \"" + initTask.getName() + "\"...");
initTask.bind(this);
// set the task options
initTask.init();
initTask.before();
// main task action
initTask.run();
// after hook for extra actions immeditaely after the task completes
initTask.after();
}
// user can now start the main loop etc.
Log.f2("Calling post-init hook...");
postInit();
}
/**
* Hook called before the initialization sequence starts.
*/
@Stub
protected void preInit()
{
}
/**
* Hook called after the initialization sequence is finished.
*/
@Stub
protected void postInit()
{
}
/**
* Shut down the running instance.<br>
* Deinitialize backend modules and terminate the JVM.
*/
public static void requestShutdown()
{
if (instance == null) {
Log.w("App is not running.");
System.exit(0);
}
Log.i("Sending a shutdown request...");
bus().send(new ShutdownRequest());
}
/**
* Shut down the running instance.<br>
* Deinitialize backend modules and terminate the JVM.
*/
public static void shutdown()
{
if (instance == null) {
Log.w("App is not running.");
System.exit(0);
}
// It's safer to shutdown in rendering context
// (LWJGL backend has problems otherwise)
App.bus().send(new MainLoopRequest(new Runnable() {
@Override
public void run()
{
try {
final EventBus bus = bus();
if (bus != null) {
bus.send(new DestroyEvent());
}
} catch (final Throwable e) {
Log.e(e);
}
}
}, true));
Log.i("Shutdown completed.");
System.exit(0);
}
/**
* Get the currently running App instance.
*
* @return app instance
*/
public static App instance()
{
return instance;
}
/**
* Get graphics module from the running app's backend
*
* @return graphics module
*/
public static GraphicsModule gfx()
{
return instance.backend.getGraphics();
}
/**
* Get audio module from the running app's backend
*
* @return audio module
*/
public static AudioModule sound()
{
return instance.backend.getAudio();
}
/**
* Get input module from the running app's backend
*
* @return input module
*/
public static InputModule input()
{
return instance.backend.getInput();
}
/**
* Get event bus instance.
*
* @return event bus
*/
public static EventBus bus()
{
return instance.eventBus;
}
/**
* Get the main config, if initialized.
*
* @return main config
* @throws IllegalArgumentException if there is no such config.
*/
public static Config cfg()
{
return cfg("main");
}
/**
* Get a config by alias.
*
* @param alias config alias
* @return the config
* @throws IllegalArgumentException if there is no such config.
*/
public static Config cfg(String alias)
{
return Config.forAlias(alias);
}
}