diff --git a/src/mightypork/gamecore/core/App.java b/src/mightypork/gamecore/core/App.java index 111e799..9e0c1bf 100644 --- a/src/mightypork/gamecore/core/App.java +++ b/src/mightypork/gamecore/core/App.java @@ -10,6 +10,7 @@ import mightypork.gamecore.core.events.ShutdownEvent; import mightypork.gamecore.core.init.InitTask; import mightypork.gamecore.core.plugins.AppPlugin; import mightypork.gamecore.graphics.GraphicsModule; +import mightypork.gamecore.graphics.Renderable; import mightypork.gamecore.input.InputModule; import mightypork.utils.annotations.Stub; import mightypork.utils.eventbus.EventBus; @@ -25,23 +26,25 @@ import mightypork.utils.logging.Log; * @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 List initializers = new ArrayList<>(); - + /** The used main loop instance */ protected MainLoop mainLoop; - - + + private Renderable mainRenderable; + + /** * Create an app with given backend. * @@ -52,24 +55,24 @@ public class App extends BusNode { 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 this.eventBus.subscribe(this.plugins); - + // initialize and use backend this.backend = backend; this.backend.bind(this); this.eventBus.subscribe(backend); this.backend.initialize(); } - - + + /** * Add a plugin to the app. Plugins can eg. listen to bus events and react * to them. @@ -78,17 +81,13 @@ public class App extends BusNode { */ public void addPlugin(AppPlugin plugin) { - if (started) { - throw new IllegalStateException("App already started, cannot add plugins."); - } - // attach to event bus plugins.add(plugin); plugin.bind(this); plugin.initialize(); } - - + + /** * Add an initializer to the app. * @@ -99,11 +98,11 @@ public class App extends BusNode { if (started) { throw new IllegalStateException("App already started, cannot add initializers."); } - + initializers.add(initializer); } - - + + /** * Set the main loop implementation * @@ -112,9 +111,21 @@ public class App extends BusNode { public void setMainLoop(MainLoop loop) { this.mainLoop = loop; + bus().subscribe(loop); // ? } - - + + + /** + * Set the main renderable + * + * @param renderable the main renderable + */ + public void setMainRenderable(Renderable renderable) + { + this.mainRenderable = renderable; + } + + /** * Get current backend * @@ -124,8 +135,8 @@ public class App extends BusNode { { return backend; } - - + + /** * Initialize the App and start operating.
* This method should be called after adding all required initializers and @@ -137,59 +148,60 @@ public class App extends BusNode { 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 by order. final List orderedInitializers = InitTask.inOrder(initializers); - + 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. */ @@ -197,8 +209,8 @@ public class App extends BusNode { protected void preInit() { } - - + + /** * Hook called after the initialization sequence is finished. */ @@ -206,8 +218,8 @@ public class App extends BusNode { protected void postInit() { } - - + + /** * Shut down the running instance.
* Deinitialize backend modules and terminate the JVM. @@ -216,9 +228,9 @@ public class App extends BusNode { { if (instance != null) { Log.i("Dispatching Shutdown event..."); - + bus().send(new ShutdownEvent(new Runnable() { - + @Override public void run() { @@ -231,19 +243,19 @@ public class App extends BusNode { } catch (final Throwable e) { Log.e(e); } - + Log.i("Shutdown completed."); System.exit(0); } })); - + } else { Log.w("App is not running."); System.exit(0); } } - - + + /** * Get the currently running App instance. * @@ -253,8 +265,8 @@ public class App extends BusNode { { return instance; } - - + + /** * Get graphics module from the running app's backend * @@ -264,8 +276,8 @@ public class App extends BusNode { { return instance.backend.getGraphics(); } - - + + /** * Get audio module from the running app's backend * @@ -275,8 +287,8 @@ public class App extends BusNode { { return instance.backend.getAudio(); } - - + + /** * Get input module from the running app's backend * @@ -286,8 +298,8 @@ public class App extends BusNode { { return instance.backend.getInput(); } - - + + /** * Get event bus instance. * @@ -297,8 +309,8 @@ public class App extends BusNode { { return instance.eventBus; } - - + + /** * Get the main config, if initialized. * @@ -309,8 +321,8 @@ public class App extends BusNode { { return cfg("main"); } - - + + /** * Get a config by alias. * diff --git a/src/mightypork/gamecore/core/events/ShutdownEvent.java b/src/mightypork/gamecore/core/events/ShutdownEvent.java index 132a029..46da99c 100644 --- a/src/mightypork/gamecore/core/events/ShutdownEvent.java +++ b/src/mightypork/gamecore/core/events/ShutdownEvent.java @@ -1,6 +1,7 @@ package mightypork.gamecore.core.events; +import mightypork.gamecore.core.App; import mightypork.utils.eventbus.BusEvent; import mightypork.utils.eventbus.EventBus; import mightypork.utils.logging.Log; @@ -42,7 +43,9 @@ public class ShutdownEvent extends BusEvent { { if (!isConsumed()) { Log.i("Shutting down..."); - shutdownTask.run(); + + App.bus().send(new MainLoopRequest(shutdownTask, true)); + } else { Log.i("Shutdown aborted."); } diff --git a/src/mightypork/gamecore/core/init/InitTask.java b/src/mightypork/gamecore/core/init/InitTask.java index 7eb5eed..142feed 100644 --- a/src/mightypork/gamecore/core/init/InitTask.java +++ b/src/mightypork/gamecore/core/init/InitTask.java @@ -21,11 +21,11 @@ import mightypork.utils.logging.Log; * @author Ondřej Hruška (MightyPork) */ public abstract class InitTask { - + /** App instance assigned using bind() */ protected App app; - - + + /** * Assign the initialized app instance to an "app" field. * @@ -35,8 +35,8 @@ public abstract class InitTask { { this.app = app; } - - + + /** * An init method that is called before the run() method.
* This method should be left unimplemented in the task, and can be used to @@ -47,8 +47,8 @@ public abstract class InitTask { { // } - - + + /** * Hook for extra action before the main task action.
* Can be overridden during app configuration to "bake-in" extra actions. @@ -58,14 +58,14 @@ public abstract class InitTask { { // } - - + + /** * Run the initializer on app. */ public abstract void run(); - - + + /** * Hook executed after the "run()" method.
* Can be overridden during app configuration to "bake-in" extra actions. @@ -75,8 +75,8 @@ public abstract class InitTask { { // } - - + + /** * Get name of this initializer (for dependency resolver).
* The name should be short, snake_case and precise. @@ -84,8 +84,8 @@ public abstract class InitTask { * @return name */ public abstract String getName(); - - + + /** * Get what other initializers must be already loaded before this can load.
* Depending on itself or creating a circular dependency will cause error.
@@ -99,8 +99,8 @@ public abstract class InitTask { { return new String[] {}; } - - + + /** * Order init tasks so that all dependencies are loaded before thye are * needed by the tasks. @@ -111,25 +111,26 @@ public abstract class InitTask { public static List inOrder(List tasks) { final List remaining = new ArrayList<>(tasks); - + final List ordered = new ArrayList<>(); final Set loaded = new HashSet<>(); - + // resolve task order int addedThisIteration = 0; do { + addedThisIteration = 0; for (final Iterator i = remaining.iterator(); i.hasNext();) { final InitTask task = i.next(); - + String[] deps = task.getDependencies(); if (deps == null) deps = new String[] {}; - + int unmetDepsCount = deps.length; - + for (final String d : deps) { if (loaded.contains(d)) unmetDepsCount--; } - + if (unmetDepsCount == 0) { ordered.add(task); loaded.add(task.getName()); @@ -138,38 +139,38 @@ public abstract class InitTask { } } } while (addedThisIteration > 0); - + // check if any tasks are left out if (remaining.size() > 0) { - + // build error message for each bad task int badInitializers = 0; for (final InitTask task : remaining) { if (Reflect.hasAnnotation(task.getClass(), OptionalInitTask.class)) { continue; } - + badInitializers++; - + String notSatisfied = ""; - + for (final String d : task.getDependencies()) { if (!loaded.contains(d)) { - + if (!notSatisfied.isEmpty()) { notSatisfied += ", "; } - + notSatisfied += d; } } - + Log.w("InitTask \"" + task.getName() + "\" - missing dependencies: " + notSatisfied); } - + if (badInitializers > 0) throw new RuntimeException("Some InitTask dependencies could not be satisfied."); } - + return ordered; } } diff --git a/src/mightypork/gamecore/core/init/InitTaskDisplay.java b/src/mightypork/gamecore/core/init/InitTaskDisplay.java index dfbc09c..230860b 100644 --- a/src/mightypork/gamecore/core/init/InitTaskDisplay.java +++ b/src/mightypork/gamecore/core/init/InitTaskDisplay.java @@ -10,12 +10,12 @@ import mightypork.gamecore.graphics.GraphicsModule; * @author Ondřej Hruška (MightyPork) */ public class InitTaskDisplay extends InitTask { - + private int width = 800, height = 600, fps = 60; private boolean resizable, fullscreen; private String title = "Game"; - - + + /** * Set initial window size * @@ -27,8 +27,8 @@ public class InitTaskDisplay extends InitTask { this.width = width; this.height = height; } - - + + /** * Set whether the window should be resizable * @@ -38,8 +38,8 @@ public class InitTaskDisplay extends InitTask { { this.resizable = resizable; } - - + + /** * Set window title * @@ -49,8 +49,8 @@ public class InitTaskDisplay extends InitTask { { this.title = title; } - - + + /** * Set desired framerate. * @@ -60,8 +60,8 @@ public class InitTaskDisplay extends InitTask { { this.fps = fps; } - - + + /** * Set whether the window should start in fullscreen * @@ -71,26 +71,28 @@ public class InitTaskDisplay extends InitTask { { this.fullscreen = fullscreen; } - - + + @Override public void run() { final GraphicsModule gfx = app.getBackend().getGraphics(); - + gfx.setSize(width, height); gfx.setResizable(resizable); gfx.setTitle(title); gfx.setTargetFps(fps); - + if (fullscreen) gfx.setFullscreen(true); + + gfx.createDisplay(); } - - + + @Override public String getName() { return "display"; } - + } diff --git a/src/mightypork/gamecore/core/init/InitTaskMainLoop.java b/src/mightypork/gamecore/core/init/InitTaskMainLoop.java index 8b53820..4a4b376 100644 --- a/src/mightypork/gamecore/core/init/InitTaskMainLoop.java +++ b/src/mightypork/gamecore/core/init/InitTaskMainLoop.java @@ -11,30 +11,30 @@ import mightypork.gamecore.core.MainLoop; * @author Ondřej Hruška (MightyPork) */ public abstract class InitTaskMainLoop extends InitTask { - + /** The loader. */ protected MainLoop loop; - - + + @Override public void run() { loop = getLoopImpl(); app.setMainLoop(loop); } - - + + /** * Create a loader impl * * @return loader */ protected abstract MainLoop getLoopImpl(); - - + + @Override public String getName() { - return "resource_loader"; + return "main_loop"; } } diff --git a/src/mightypork/gamecore/core/init/InitTaskResources.java b/src/mightypork/gamecore/core/init/InitTaskResources.java index abc6071..4602a44 100644 --- a/src/mightypork/gamecore/core/init/InitTaskResources.java +++ b/src/mightypork/gamecore/core/init/InitTaskResources.java @@ -11,24 +11,24 @@ import mightypork.gamecore.resources.ResourceInitializer; * @author Ondřej Hruška (MightyPork) */ public abstract class InitTaskResources extends InitTask implements ResourceInitializer { - + @Override public void run() { Res.load(this); } - - + + @Override public String getName() { return "resources"; } - - + + @Override public String[] getDependencies() { - return new String[] { "resource_loader" }; + return new String[] { "resource_loader", "main_loop" }; } } diff --git a/src/mightypork/gamecore/core/plugins/screenshot/InitTaskPluginScreenshot.java b/src/mightypork/gamecore/core/plugins/screenshot/InitTaskPluginScreenshot.java index 5f41b84..8b3247f 100644 --- a/src/mightypork/gamecore/core/plugins/screenshot/InitTaskPluginScreenshot.java +++ b/src/mightypork/gamecore/core/plugins/screenshot/InitTaskPluginScreenshot.java @@ -2,6 +2,7 @@ package mightypork.gamecore.core.plugins.screenshot; import mightypork.gamecore.core.init.InitTask; +import mightypork.gamecore.core.plugins.AppPlugin; import mightypork.utils.files.WorkDir; @@ -11,10 +12,10 @@ import mightypork.utils.files.WorkDir; * @author Ondřej Hruška (MightyPork) */ public class InitTaskPluginScreenshot extends InitTask { - + private String screenshotDir; - - + + /** * Initialize to use the "screenshots" directory */ @@ -22,8 +23,8 @@ public class InitTaskPluginScreenshot extends InitTask { { this("screenshots"); } - - + + /** * Initialize to use the given directory for saving. * @@ -33,8 +34,8 @@ public class InitTaskPluginScreenshot extends InitTask { { this.screenshotDir = dir; } - - + + /** * Set screenshot directory * @@ -44,13 +45,19 @@ public class InitTaskPluginScreenshot extends InitTask { { this.screenshotDir = dir; } - - + + @Override public void run() { WorkDir.addPath("_screenshot_dir", screenshotDir); - app.addPlugin(new ScreenshotPlugin()); + app.addPlugin(getPluginImpl()); + } + + + protected AppPlugin getPluginImpl() + { + return new ScreenshotPlugin(); } @@ -59,12 +66,12 @@ public class InitTaskPluginScreenshot extends InitTask { { return "plugin_screenshot"; } - - + + @Override public String[] getDependencies() { return new String[] { "workdir" }; } - + } diff --git a/src/mightypork/gamecore/core/plugins/screenshot/ScreenshotPlugin.java b/src/mightypork/gamecore/core/plugins/screenshot/ScreenshotPlugin.java index ed73064..38060a0 100644 --- a/src/mightypork/gamecore/core/plugins/screenshot/ScreenshotPlugin.java +++ b/src/mightypork/gamecore/core/plugins/screenshot/ScreenshotPlugin.java @@ -14,15 +14,16 @@ import mightypork.utils.Support; * * @author Ondřej Hruška (MightyPork) */ -public class ScreenshotPlugin extends AppPlugin { - +public class ScreenshotPlugin extends AppPlugin implements ScreenshotRequestListener { + /** * Take screenshot. Called by the trigger event. */ - void takeScreenshot() + @Override + public void onScreenshotRequest() { App.bus().send(new MainLoopRequest(new Runnable() { - + @Override public void run() { diff --git a/src/mightypork/gamecore/core/plugins/screenshot/ScreenshotRequest.java b/src/mightypork/gamecore/core/plugins/screenshot/ScreenshotRequest.java index 6b68966..c4f08f7 100644 --- a/src/mightypork/gamecore/core/plugins/screenshot/ScreenshotRequest.java +++ b/src/mightypork/gamecore/core/plugins/screenshot/ScreenshotRequest.java @@ -11,12 +11,12 @@ import mightypork.utils.eventbus.events.flags.SingleReceiverEvent; * @author MightyPork */ @SingleReceiverEvent -public class ScreenshotRequest extends BusEvent { - +public class ScreenshotRequest extends BusEvent { + @Override - protected void handleBy(ScreenshotPlugin handler) + protected void handleBy(ScreenshotRequestListener handler) { - handler.takeScreenshot(); + handler.onScreenshotRequest(); } - + } diff --git a/src/mightypork/gamecore/graphics/GraphicsModule.java b/src/mightypork/gamecore/graphics/GraphicsModule.java index a380561..895901d 100644 --- a/src/mightypork/gamecore/graphics/GraphicsModule.java +++ b/src/mightypork/gamecore/graphics/GraphicsModule.java @@ -267,7 +267,7 @@ public abstract class GraphicsModule extends BackendModule { /** - * Get backend-flavoured deferred texture. This should support PNG images. + * Get backend-flavoured deferred PNG texture. * * @param path path to texture * @return the deferred font @@ -276,7 +276,7 @@ public abstract class GraphicsModule extends BackendModule { /** - * Get backend-flavoured deferred font. This should support TTF fonts. + * Get backend-flavoured deferred TTF font. * * @param path path to font, or font name in the system * @return the deferred font @@ -284,6 +284,25 @@ public abstract class GraphicsModule extends BackendModule { public abstract DeferredFont createFontResource(String path); + /** + * Get backend-flavoured deferred TTF font. + * + * @param path path to font, or font name in the system + * @param chars chars that should be supported + * @param size font size used when rendering the font to a backing texture. + * For bitmap fonts, the actual font height. + * @return the deferred font + */ + public DeferredFont createFont(String path, String chars, double size) + { + final DeferredFont font = createFontResource(path); + font.setChars(chars); + font.setSize(size); + + return font; + } + + /** * Set target fps (for syncing in endFrame() call).
* With vsync enabled, the target fps may not be met. @@ -416,4 +435,10 @@ public abstract class GraphicsModule extends BackendModule { * @return screen height */ public abstract int getHeight(); + + + /** + * Create the display (window) based on current settings. + */ + public abstract void createDisplay(); } diff --git a/src/mightypork/gamecore/input/KeyStroke.java b/src/mightypork/gamecore/input/KeyStroke.java index ba5d603..fd94428 100644 --- a/src/mightypork/gamecore/input/KeyStroke.java +++ b/src/mightypork/gamecore/input/KeyStroke.java @@ -60,7 +60,7 @@ public class KeyStroke { */ public boolean isDown() { - return key.isDown() && (Keys.getActiveMod() == mod); + return key.isDown() && (Keys.getActiveMods() == mod); } diff --git a/src/mightypork/gamecore/input/Keys.java b/src/mightypork/gamecore/input/Keys.java index 09fc7b5..7949dca 100644 --- a/src/mightypork/gamecore/input/Keys.java +++ b/src/mightypork/gamecore/input/Keys.java @@ -346,7 +346,7 @@ public class Keys { * * @return active mod mask (mod bits ored) */ - public static int getActiveMod() + public static int getActiveMods() { int mods = 0; diff --git a/src/mightypork/gamecore/resources/loading/AsyncResourceLoader.java b/src/mightypork/gamecore/resources/loading/AsyncResourceLoader.java index a801e96..2976c51 100644 --- a/src/mightypork/gamecore/resources/loading/AsyncResourceLoader.java +++ b/src/mightypork/gamecore/resources/loading/AsyncResourceLoader.java @@ -20,14 +20,14 @@ import mightypork.utils.logging.Log; * @author Ondřej Hruška (MightyPork) */ public class AsyncResourceLoader extends Thread implements ResourceLoader, Destroyable { - + private final ExecutorService exs = Executors.newFixedThreadPool(2); - + private final LinkedBlockingQueue toLoad = new LinkedBlockingQueue<>(); private volatile boolean stopped; private volatile boolean mainLoopQueuing = true; - - + + /** * Create a resource loader. */ @@ -35,8 +35,8 @@ public class AsyncResourceLoader extends Thread implements ResourceLoader, Destr { super("Deferred loader"); } - - + + @Override public synchronized void init() { @@ -44,8 +44,8 @@ public class AsyncResourceLoader extends Thread implements ResourceLoader, Destr setDaemon(true); super.start(); } - - + + /** * True to queue resources that must load in rendering context to main loop. * May cause lag at the beginning, but results in smoother performance later @@ -57,23 +57,23 @@ public class AsyncResourceLoader extends Thread implements ResourceLoader, Destr { mainLoopQueuing = yes; } - - + + @Override public void loadResource(final DeferredResource resource) { if (resource.isLoaded()) return; - + // textures & fonts needs to be loaded in main thread if (Reflect.hasAnnotation(resource, MustLoadInRenderingContext.class)) { - + if (!mainLoopQueuing) { // just let it be } else { Log.f3("(loader) Delegating to main thread: " + Support.str(resource)); - + App.bus().send(new MainLoopRequest(new Runnable() { - + @Override public void run() { @@ -81,31 +81,31 @@ public class AsyncResourceLoader extends Thread implements ResourceLoader, Destr } }, false)); } - + return; } - + toLoad.add(resource); } - - + + @Override public void run() { Log.f3("Asynchronous resource loader started."); - + while (!stopped) { - + try { final DeferredResource def = toLoad.take(); if (def == null) continue; - + if (!def.isLoaded()) { - + Log.f3("(loader) Scheduling... " + Support.str(def)); - + exs.submit(new Runnable() { - + @Override public void run() { @@ -115,15 +115,15 @@ public class AsyncResourceLoader extends Thread implements ResourceLoader, Destr } }); } - + } catch (final InterruptedException ignored) { // } - + } } - - + + // apparently, destroy method exists on thread :/ @SuppressWarnings("deprecation") @Override @@ -133,5 +133,5 @@ public class AsyncResourceLoader extends Thread implements ResourceLoader, Destr stopped = true; exs.shutdownNow(); } - + }