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;
}
}