From e03d547fab5a8952dbc648c35805bac117dd81c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Wed, 9 Apr 2014 01:28:32 +0200 Subject: [PATCH] Factored out BaseApp. --- src/mightypork/gamecore/control/BaseApp.java | 276 ++++++++++++++++++ src/mightypork/gamecore/control/GameLoop.java | 24 +- src/mightypork/rogue/App.java | 211 +++---------- src/mightypork/rogue/MainLoop.java | 6 +- src/mightypork/rogue/Paths.java | 2 +- src/mightypork/rogue/Res.java | 6 +- .../util}/SlickLogRedirector.java | 2 +- src/mightypork/utils/files/InstanceLock.java | 2 +- 8 files changed, 335 insertions(+), 194 deletions(-) create mode 100644 src/mightypork/gamecore/control/BaseApp.java rename src/mightypork/{gamecore => rogue/util}/SlickLogRedirector.java (95%) diff --git a/src/mightypork/gamecore/control/BaseApp.java b/src/mightypork/gamecore/control/BaseApp.java new file mode 100644 index 0000000..505acd0 --- /dev/null +++ b/src/mightypork/gamecore/control/BaseApp.java @@ -0,0 +1,276 @@ +package mightypork.gamecore.control; + + +import java.io.File; + +import javax.swing.JOptionPane; + +import mightypork.gamecore.audio.SoundSystem; +import mightypork.gamecore.control.bus.EventBus; +import mightypork.gamecore.control.bus.events.*; +import mightypork.gamecore.control.interf.Destroyable; +import mightypork.gamecore.control.interf.NoImpl; +import mightypork.gamecore.control.interf.Updateable; +import mightypork.gamecore.gui.screens.ScreenRegistry; +import mightypork.gamecore.input.InputSystem; +import mightypork.gamecore.loading.AsyncResourceLoader; +import mightypork.gamecore.render.DisplaySystem; +import mightypork.rogue.util.SlickLogRedirector; +import mightypork.utils.files.InstanceLock; +import mightypork.utils.logging.Log; +import mightypork.utils.logging.LogInstance; + + +/** + * Basic screen-based game with subsystems.
+ * This class takes care of the initialization sequence. + * + * @author MightyPork + */ +public abstract class BaseApp implements AppAccess { + + // modules + private InputSystem inputSystem; + private DisplaySystem displaySystem; + private SoundSystem soundSystem; + private EventBus eventBus; + private GameLoop gameLoop; + private ScreenRegistry screenRegistry; + + + /** + * Start the application + */ + public void start() + { + Log.i("Commencing initialization sequence..."); + + initialize(); + + Log.i("Starting main loop..."); + + // open first screen + gameLoop.start(); + } + + + protected void initialize() + { + preInit(); + + /* + * Lock working directory + */ + initLock(); + + /* + * Setup logging + */ + final LogInstance log = createLog(); + org.newdawn.slick.util.Log.setLogSystem(new SlickLogRedirector(log)); + + // only here it makes sense to log. + Log.f1("Initializing subsystems..."); + + /* + * Event bus + */ + Log.f2("Starting Event Bus..."); + eventBus = new EventBus(); + + Log.f3("Registering channels..."); + initChannels(eventBus); + + /* + * Display + */ + Log.f2("Initializing Display System..."); + displaySystem = new DisplaySystem(this); + initDisplay(displaySystem); + + /* + * Audio + */ + Log.f2("Initializing Sound System..."); + soundSystem = new SoundSystem(this); + initSoundSystem(soundSystem); + + /* + * Input + */ + Log.f2("Initializing Input System..."); + inputSystem = new InputSystem(this); + initKeystrokes(inputSystem); + + /* + * Prepare main loop + */ + Log.f1("Creating Screen Registry and Game Loop..."); + screenRegistry = new ScreenRegistry(this); + gameLoop = createLoop(); + gameLoop.setRootRenderable(screenRegistry); + + /* + * Load resources + * + * Resources should be registered to banks, and AsyncResourceLoader will load them. + */ + Log.f1("Loading resources..."); + AsyncResourceLoader.launch(this); + initResources(); + + /* + * Screen registry + * + * Must be after resources, because screens can request them during instantiation. + */ + Log.f2("Registering screens..."); + initScreens(screenRegistry); + + postInit(); + Log.i("Initialized sequence completed."); + } + + + @NoImpl + protected void preInit() + { + } + + + @NoImpl + protected void postInit() + { + } + + + protected abstract LogInstance createLog(); + + + protected abstract void initDisplay(DisplaySystem display); + + + protected abstract void initSoundSystem(SoundSystem audio); + + + protected abstract void initKeystrokes(InputSystem input); + + + protected abstract void initResources(); + + + protected abstract void initScreens(ScreenRegistry screens); + + + protected abstract GameLoop createLoop(); + + + protected void initChannels(EventBus bus) + { + // framework events + bus.addChannel(DestroyEvent.class, Destroyable.class); + bus.addChannel(UpdateEvent.class, Updateable.class); + + // input events + bus.addChannel(ScreenChangeEvent.class, ScreenChangeEvent.Listener.class); + bus.addChannel(KeyEvent.class, KeyEvent.Listener.class); + bus.addChannel(MouseMotionEvent.class, MouseMotionEvent.Listener.class); + bus.addChannel(MouseButtonEvent.class, MouseButtonEvent.Listener.class); + + // control events + bus.addChannel(ScreenRequestEvent.class, ScreenRequestEvent.Listener.class); + bus.addChannel(ResourceLoadRequest.class, ResourceLoadRequest.Listener.class); + bus.addChannel(MainLoopTaskRequest.class, MainLoopTaskRequest.Listener.class); + } + + + /** + * Try to obtain lock. + */ + private void initLock() + { + final File lockFile = getLockFile(); + + if (lockFile == null) { + // lock off + return; + } + + if (!InstanceLock.onFile(lockFile)) { + onLockError(); + return; + } + } + + + /** + * Triggered when lock cannot be obtained.
+ * App should terminate gracefully. + */ + protected void onLockError() + { + System.err.println("Could not obtain lock file.\nOnly one instance can run at a time."); + + //@formatter:off + JOptionPane.showMessageDialog( + null, + "Another instance is already running.", + "Lock Error", + JOptionPane.ERROR_MESSAGE + ); + //@formatter:on + + shutdown(); + } + + + /** + * Get lock file path; Used to enforce single-instance policy. + * + * @return lock file, or null to disable lock. + */ + protected abstract File getLockFile(); + + + @Override + public final SoundSystem getSoundSystem() + { + return soundSystem; + } + + + @Override + public final InputSystem getInput() + { + return inputSystem; + } + + + @Override + public final DisplaySystem getDisplay() + { + return displaySystem; + } + + + @Override + public final EventBus getEventBus() + { + return eventBus; + } + + + @Override + public void shutdown() + { + Log.i("Shutting down subsystems..."); + + if (getEventBus() != null) { + getEventBus().send(new DestroyEvent()); + getEventBus().destroy(); + } + + Log.i("Terminating..."); + System.exit(0); + } +} diff --git a/src/mightypork/gamecore/control/GameLoop.java b/src/mightypork/gamecore/control/GameLoop.java index c625b6c..0fd8df5 100644 --- a/src/mightypork/gamecore/control/GameLoop.java +++ b/src/mightypork/gamecore/control/GameLoop.java @@ -21,23 +21,27 @@ public abstract class GameLoop extends AppModule implements MainLoopTaskRequest. private final Queue taskQueue = new ConcurrentLinkedQueue<>(); private TimerDelta timer; - private final Renderable mainRenderable; + private Renderable rootRenderable; private boolean running = true; /** * @param app {@link AppAccess} instance + */ + public GameLoop(AppAccess app) { + super(app); + } + + + /** + * Set primary renderable + * * @param rootRenderable main {@link Renderable}, typically a * {@link ScreenRegistry} */ - public GameLoop(AppAccess app, Renderable rootRenderable) { - super(app); - - if (rootRenderable == null) { - throw new NullPointerException("Master renderable must not be null."); - } - - mainRenderable = rootRenderable; + public void setRootRenderable(Renderable rootRenderable) + { + this.rootRenderable = rootRenderable; } @@ -57,7 +61,7 @@ public abstract class GameLoop extends AppModule implements MainLoopTaskRequest. beforeRender(); - mainRenderable.render(); + if (rootRenderable != null) rootRenderable.render(); afterRender(); diff --git a/src/mightypork/rogue/App.java b/src/mightypork/rogue/App.java index 190bfc7..15f6e73 100644 --- a/src/mightypork/rogue/App.java +++ b/src/mightypork/rogue/App.java @@ -1,19 +1,14 @@ package mightypork.rogue; +import java.io.File; import java.util.Locale; import java.util.logging.Level; -import javax.swing.JOptionPane; - -import mightypork.gamecore.SlickLogRedirector; import mightypork.gamecore.audio.SoundSystem; -import mightypork.gamecore.control.AppAccess; +import mightypork.gamecore.control.BaseApp; import mightypork.gamecore.control.GameLoop; import mightypork.gamecore.control.bus.EventBus; -import mightypork.gamecore.control.bus.events.*; -import mightypork.gamecore.control.interf.Destroyable; -import mightypork.gamecore.control.interf.Updateable; import mightypork.gamecore.gui.screens.ScreenRegistry; import mightypork.gamecore.input.InputSystem; import mightypork.gamecore.input.KeyStroke; @@ -24,7 +19,6 @@ import mightypork.rogue.events.ActionRequest.RequestType; import mightypork.rogue.screens.test_bouncyboxes.ScreenTestBouncy; import mightypork.rogue.screens.test_cat_sound.ScreenTestCat; import mightypork.rogue.screens.test_font.ScreenTestFont; -import mightypork.utils.files.InstanceLock; import mightypork.utils.logging.Log; import mightypork.utils.logging.LogInstance; @@ -34,18 +28,10 @@ import mightypork.utils.logging.LogInstance; * * @author MightyPork */ -public class App implements AppAccess { +public class App extends BaseApp { /** instance pointer */ - private static App inst; - - // modules - private InputSystem inputSystem; - private DisplaySystem displaySystem; - private SoundSystem soundSystem; - private EventBus eventBus; - private GameLoop mainLoop; - private ScreenRegistry screens; + private static BaseApp inst; /** @@ -69,20 +55,6 @@ public class App implements AppAccess { } - /** - * Start the application - */ - private void start() - { - initialize(); - - Log.i("Starting main loop..."); - - // open first screen - mainLoop.start(); - } - - /** * Handle a crash * @@ -102,132 +74,43 @@ public class App implements AppAccess { @Override - public void shutdown() + protected void preInit() { - Log.i("Shutting down subsystems..."); - - if (getEventBus() != null) { - getEventBus().send(new DestroyEvent()); - getEventBus().destroy(); - } - - Log.i("Terminating..."); - System.exit(0); + // to get dot instead of comma in floats + Locale.setDefault(Locale.ENGLISH); } - public void initialize() + @Override + protected LogInstance createLog() { - // to get dot instead of comma in floats - Locale.setDefault(Locale.ENGLISH); - - /* - * Lock working directory - */ - initLock(); - - /* - * Setup logging - */ final LogInstance log = Log.create("runtime", Paths.LOGS, 10); log.setFileLevel(Level.WARNING); log.setSysoutLevel(Level.ALL); log.enable(Config.LOGGING_ENABLED); log.enableSysout(Config.LOG_TO_STDOUT); - org.newdawn.slick.util.Log.setLogSystem(new SlickLogRedirector(log)); - - Log.f1("Initializing subsystems..."); - - /* - * Event bus - */ - Log.f2("Initializing Event Bus..."); - eventBus = new EventBus(); - eventBus.detailedLogging = true; - initChannels(); - - /* - * Display - */ - Log.f2("Initializing Display System..."); - displaySystem = new DisplaySystem(this); - displaySystem.createMainWindow(Const.WINDOW_W, Const.WINDOW_H, true, Config.START_IN_FS, Const.TITLEBAR); - displaySystem.setTargetFps(Const.FPS_RENDER); - - /* - * Audio - */ - Log.f2("Initializing Sound System..."); - soundSystem = new SoundSystem(this); - soundSystem.setMasterVolume(1); - - /* - * Input - */ - Log.f2("Initializing Input System..."); - inputSystem = new InputSystem(this); - setupGlobalKeystrokes(); - - /* - * Prepare main loop - */ - Log.f1("Preparing game systems..."); - screens = new ScreenRegistry(this); - mainLoop = new MainLoop(this, screens); - - /* - * Load resources - */ - Log.f1("Loading resources..."); - - Res.load(this); - - /* - * Screen registry - */ - Log.f2("Initializing screens..."); - initScreens(); + return log; } - private void initScreens() + @Override + protected void initDisplay(DisplaySystem display) { - Log.f3("Registering game screens..."); - - screens.add(new ScreenTestBouncy(this)); - screens.add(new ScreenTestCat(this)); - screens.add(new ScreenTestFont(this)); - - screens.showScreen("test.bouncy"); + display.createMainWindow(Const.WINDOW_W, Const.WINDOW_H, true, Config.START_IN_FS, Const.TITLEBAR); + display.setTargetFps(Const.FPS_RENDER); } - private void initChannels() + @Override + protected void initSoundSystem(SoundSystem audio) { - Log.f3("Registering channels..."); - - // framework events - getEventBus().addChannel(DestroyEvent.class, Destroyable.class); - getEventBus().addChannel(UpdateEvent.class, Updateable.class); - - // input events - getEventBus().addChannel(ScreenChangeEvent.class, ScreenChangeEvent.Listener.class); - getEventBus().addChannel(KeyEvent.class, KeyEvent.Listener.class); - getEventBus().addChannel(MouseMotionEvent.class, MouseMotionEvent.Listener.class); - getEventBus().addChannel(MouseButtonEvent.class, MouseButtonEvent.Listener.class); - - // control events - getEventBus().addChannel(ScreenRequestEvent.class, ScreenRequestEvent.Listener.class); - getEventBus().addChannel(ResourceLoadRequest.class, ResourceLoadRequest.Listener.class); - getEventBus().addChannel(ActionRequest.class, ActionRequest.Listener.class); - getEventBus().addChannel(MainLoopTaskRequest.class, MainLoopTaskRequest.Listener.class); + audio.setMasterVolume(1); } - private void setupGlobalKeystrokes() + @Override + protected void initKeystrokes(InputSystem input) { - Log.f3("Setting up hot keys..."); - // Go fullscreen getInput().bindKeyStroke(new KeyStroke(Keys.KEY_F11), new Runnable() { @@ -260,65 +143,45 @@ public class App implements AppAccess { } - private void initLock() + @Override + protected GameLoop createLoop() { - if (!Config.SINGLE_INSTANCE) return; - - if (!InstanceLock.onFile(Paths.LOCK)) { - System.err.println("Could not obtain lock.\nOnly one instance can run at a time."); - - //@formatter:off - JOptionPane.showMessageDialog( - null, - "Another instance is already running.", - "Instance error", - JOptionPane.ERROR_MESSAGE - ); - //@formatter:on - - shutdown(); - return; - } + return new MainLoop(this); } - /** - * @return sound system of the running instance - */ @Override - public SoundSystem getSoundSystem() + protected void initResources() { - return soundSystem; + Res.load(this); } - /** - * @return input system of the running instance - */ @Override - public InputSystem getInput() + protected void initScreens(ScreenRegistry screens) { - return inputSystem; + screens.add(new ScreenTestBouncy(this)); + screens.add(new ScreenTestCat(this)); + screens.add(new ScreenTestFont(this)); + + screens.showScreen("test.bouncy"); } - /** - * @return display system of the running instance - */ @Override - public DisplaySystem getDisplay() + protected File getLockFile() { - return displaySystem; + return Paths.LOCK; } - /** - * @return event bus - */ @Override - public EventBus getEventBus() + protected void initChannels(EventBus bus) { - return eventBus; + super.initChannels(bus); + + // custom channels + bus.addChannel(ActionRequest.class, ActionRequest.Listener.class); } } diff --git a/src/mightypork/rogue/MainLoop.java b/src/mightypork/rogue/MainLoop.java index 99c124f..cdfe1a1 100644 --- a/src/mightypork/rogue/MainLoop.java +++ b/src/mightypork/rogue/MainLoop.java @@ -1,8 +1,8 @@ package mightypork.rogue; +import mightypork.gamecore.control.BaseApp; import mightypork.gamecore.control.GameLoop; -import mightypork.gamecore.gui.renderers.Renderable; import mightypork.gamecore.input.Action; import mightypork.rogue.events.ActionRequest; import mightypork.rogue.events.ActionRequest.RequestType; @@ -11,8 +11,8 @@ import mightypork.rogue.util.Utils; public class MainLoop extends GameLoop implements ActionRequest.Listener { - public MainLoop(App app, Renderable masterRenderable) { - super(app, masterRenderable); + public MainLoop(BaseApp app) { + super(app); } diff --git a/src/mightypork/rogue/Paths.java b/src/mightypork/rogue/Paths.java index c25efab..849cf0d 100644 --- a/src/mightypork/rogue/Paths.java +++ b/src/mightypork/rogue/Paths.java @@ -9,7 +9,7 @@ import mightypork.utils.files.OsUtils; public class Paths { private static final String APPDIR_NAME = "rogue"; - + public static final File WORKDIR = OsUtils.getWorkDir(APPDIR_NAME); public static final File LOGS = OsUtils.getWorkDir(APPDIR_NAME, "logs"); public static final File SCREENSHOTS = OsUtils.getWorkDir(APPDIR_NAME, "screenshots"); diff --git a/src/mightypork/rogue/Res.java b/src/mightypork/rogue/Res.java index 3f7d371..085f403 100644 --- a/src/mightypork/rogue/Res.java +++ b/src/mightypork/rogue/Res.java @@ -5,7 +5,7 @@ import mightypork.gamecore.audio.SoundBank; import mightypork.gamecore.audio.players.EffectPlayer; import mightypork.gamecore.audio.players.LoopPlayer; import mightypork.gamecore.control.AppAccess; -import mightypork.gamecore.loading.AsyncResourceLoader; +import mightypork.gamecore.control.BaseApp; import mightypork.gamecore.render.fonts.DeferredFont; import mightypork.gamecore.render.fonts.DeferredFont.FontStyle; import mightypork.gamecore.render.fonts.FontBank; @@ -37,13 +37,11 @@ public class Res { * * @param app app access */ - public static void load(App app) + public static void load(BaseApp app) { if (initialized) return; initialized = true; - AsyncResourceLoader.launch(app); - textures = new TextureBank(app); sounds = new SoundBank(app); fonts = new FontBank(app); diff --git a/src/mightypork/gamecore/SlickLogRedirector.java b/src/mightypork/rogue/util/SlickLogRedirector.java similarity index 95% rename from src/mightypork/gamecore/SlickLogRedirector.java rename to src/mightypork/rogue/util/SlickLogRedirector.java index db95755..bfbd343 100644 --- a/src/mightypork/gamecore/SlickLogRedirector.java +++ b/src/mightypork/rogue/util/SlickLogRedirector.java @@ -1,4 +1,4 @@ -package mightypork.gamecore; +package mightypork.rogue.util; import mightypork.utils.logging.LogInstance; diff --git a/src/mightypork/utils/files/InstanceLock.java b/src/mightypork/utils/files/InstanceLock.java index 4b63c92..6eabfcd 100644 --- a/src/mightypork/utils/files/InstanceLock.java +++ b/src/mightypork/utils/files/InstanceLock.java @@ -43,7 +43,7 @@ public class InstanceLock { } return false; - } catch (IOException e) { + } catch (final IOException e) { return false; } }