Factored out BaseApp.

v5stable
Ondřej Hruška 10 years ago
parent e4e7aa7eb3
commit e03d547fab
  1. 276
      src/mightypork/gamecore/control/BaseApp.java
  2. 24
      src/mightypork/gamecore/control/GameLoop.java
  3. 211
      src/mightypork/rogue/App.java
  4. 6
      src/mightypork/rogue/MainLoop.java
  5. 2
      src/mightypork/rogue/Paths.java
  6. 6
      src/mightypork/rogue/Res.java
  7. 2
      src/mightypork/rogue/util/SlickLogRedirector.java
  8. 2
      src/mightypork/utils/files/InstanceLock.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.<br>
* 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.<br>
* 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);
}
}

@ -21,23 +21,27 @@ public abstract class GameLoop extends AppModule implements MainLoopTaskRequest.
private final Queue<Runnable> taskQueue = new ConcurrentLinkedQueue<>(); private final Queue<Runnable> taskQueue = new ConcurrentLinkedQueue<>();
private TimerDelta timer; private TimerDelta timer;
private final Renderable mainRenderable; private Renderable rootRenderable;
private boolean running = true; private boolean running = true;
/** /**
* @param app {@link AppAccess} instance * @param app {@link AppAccess} instance
*/
public GameLoop(AppAccess app) {
super(app);
}
/**
* Set primary renderable
*
* @param rootRenderable main {@link Renderable}, typically a * @param rootRenderable main {@link Renderable}, typically a
* {@link ScreenRegistry} * {@link ScreenRegistry}
*/ */
public GameLoop(AppAccess app, Renderable rootRenderable) { public void setRootRenderable(Renderable rootRenderable)
super(app); {
this.rootRenderable = rootRenderable;
if (rootRenderable == null) {
throw new NullPointerException("Master renderable must not be null.");
}
mainRenderable = rootRenderable;
} }
@ -57,7 +61,7 @@ public abstract class GameLoop extends AppModule implements MainLoopTaskRequest.
beforeRender(); beforeRender();
mainRenderable.render(); if (rootRenderable != null) rootRenderable.render();
afterRender(); afterRender();

@ -1,19 +1,14 @@
package mightypork.rogue; package mightypork.rogue;
import java.io.File;
import java.util.Locale; import java.util.Locale;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JOptionPane;
import mightypork.gamecore.SlickLogRedirector;
import mightypork.gamecore.audio.SoundSystem; import mightypork.gamecore.audio.SoundSystem;
import mightypork.gamecore.control.AppAccess; import mightypork.gamecore.control.BaseApp;
import mightypork.gamecore.control.GameLoop; import mightypork.gamecore.control.GameLoop;
import mightypork.gamecore.control.bus.EventBus; 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.gui.screens.ScreenRegistry;
import mightypork.gamecore.input.InputSystem; import mightypork.gamecore.input.InputSystem;
import mightypork.gamecore.input.KeyStroke; 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_bouncyboxes.ScreenTestBouncy;
import mightypork.rogue.screens.test_cat_sound.ScreenTestCat; import mightypork.rogue.screens.test_cat_sound.ScreenTestCat;
import mightypork.rogue.screens.test_font.ScreenTestFont; import mightypork.rogue.screens.test_font.ScreenTestFont;
import mightypork.utils.files.InstanceLock;
import mightypork.utils.logging.Log; import mightypork.utils.logging.Log;
import mightypork.utils.logging.LogInstance; import mightypork.utils.logging.LogInstance;
@ -34,18 +28,10 @@ import mightypork.utils.logging.LogInstance;
* *
* @author MightyPork * @author MightyPork
*/ */
public class App implements AppAccess { public class App extends BaseApp {
/** instance pointer */ /** instance pointer */
private static App inst; private static BaseApp inst;
// modules
private InputSystem inputSystem;
private DisplaySystem displaySystem;
private SoundSystem soundSystem;
private EventBus eventBus;
private GameLoop mainLoop;
private ScreenRegistry screens;
/** /**
@ -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 * Handle a crash
* *
@ -102,132 +74,43 @@ public class App implements AppAccess {
@Override @Override
public void shutdown() protected void preInit()
{ {
Log.i("Shutting down subsystems..."); // to get dot instead of comma in floats
Locale.setDefault(Locale.ENGLISH);
if (getEventBus() != null) {
getEventBus().send(new DestroyEvent());
getEventBus().destroy();
}
Log.i("Terminating...");
System.exit(0);
} }
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); final LogInstance log = Log.create("runtime", Paths.LOGS, 10);
log.setFileLevel(Level.WARNING); log.setFileLevel(Level.WARNING);
log.setSysoutLevel(Level.ALL); log.setSysoutLevel(Level.ALL);
log.enable(Config.LOGGING_ENABLED); log.enable(Config.LOGGING_ENABLED);
log.enableSysout(Config.LOG_TO_STDOUT); log.enableSysout(Config.LOG_TO_STDOUT);
org.newdawn.slick.util.Log.setLogSystem(new SlickLogRedirector(log)); return 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();
} }
private void initScreens() @Override
protected void initDisplay(DisplaySystem display)
{ {
Log.f3("Registering game screens..."); display.createMainWindow(Const.WINDOW_W, Const.WINDOW_H, true, Config.START_IN_FS, Const.TITLEBAR);
display.setTargetFps(Const.FPS_RENDER);
screens.add(new ScreenTestBouncy(this));
screens.add(new ScreenTestCat(this));
screens.add(new ScreenTestFont(this));
screens.showScreen("test.bouncy");
} }
private void initChannels() @Override
protected void initSoundSystem(SoundSystem audio)
{ {
Log.f3("Registering channels..."); audio.setMasterVolume(1);
// 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);
} }
private void setupGlobalKeystrokes() @Override
protected void initKeystrokes(InputSystem input)
{ {
Log.f3("Setting up hot keys...");
// Go fullscreen // Go fullscreen
getInput().bindKeyStroke(new KeyStroke(Keys.KEY_F11), new Runnable() { 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; return new MainLoop(this);
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 sound system of the running instance
*/
@Override @Override
public SoundSystem getSoundSystem() protected void initResources()
{ {
return soundSystem; Res.load(this);
} }
/**
* @return input system of the running instance
*/
@Override @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 @Override
public DisplaySystem getDisplay() protected File getLockFile()
{ {
return displaySystem; return Paths.LOCK;
} }
/**
* @return event bus
*/
@Override @Override
public EventBus getEventBus() protected void initChannels(EventBus bus)
{ {
return eventBus; super.initChannels(bus);
// custom channels
bus.addChannel(ActionRequest.class, ActionRequest.Listener.class);
} }
} }

@ -1,8 +1,8 @@
package mightypork.rogue; package mightypork.rogue;
import mightypork.gamecore.control.BaseApp;
import mightypork.gamecore.control.GameLoop; import mightypork.gamecore.control.GameLoop;
import mightypork.gamecore.gui.renderers.Renderable;
import mightypork.gamecore.input.Action; import mightypork.gamecore.input.Action;
import mightypork.rogue.events.ActionRequest; import mightypork.rogue.events.ActionRequest;
import mightypork.rogue.events.ActionRequest.RequestType; import mightypork.rogue.events.ActionRequest.RequestType;
@ -11,8 +11,8 @@ import mightypork.rogue.util.Utils;
public class MainLoop extends GameLoop implements ActionRequest.Listener { public class MainLoop extends GameLoop implements ActionRequest.Listener {
public MainLoop(App app, Renderable masterRenderable) { public MainLoop(BaseApp app) {
super(app, masterRenderable); super(app);
} }

@ -9,7 +9,7 @@ import mightypork.utils.files.OsUtils;
public class Paths { public class Paths {
private static final String APPDIR_NAME = "rogue"; private static final String APPDIR_NAME = "rogue";
public static final File WORKDIR = OsUtils.getWorkDir(APPDIR_NAME); public static final File WORKDIR = OsUtils.getWorkDir(APPDIR_NAME);
public static final File LOGS = OsUtils.getWorkDir(APPDIR_NAME, "logs"); public static final File LOGS = OsUtils.getWorkDir(APPDIR_NAME, "logs");
public static final File SCREENSHOTS = OsUtils.getWorkDir(APPDIR_NAME, "screenshots"); public static final File SCREENSHOTS = OsUtils.getWorkDir(APPDIR_NAME, "screenshots");

@ -5,7 +5,7 @@ import mightypork.gamecore.audio.SoundBank;
import mightypork.gamecore.audio.players.EffectPlayer; import mightypork.gamecore.audio.players.EffectPlayer;
import mightypork.gamecore.audio.players.LoopPlayer; import mightypork.gamecore.audio.players.LoopPlayer;
import mightypork.gamecore.control.AppAccess; 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;
import mightypork.gamecore.render.fonts.DeferredFont.FontStyle; import mightypork.gamecore.render.fonts.DeferredFont.FontStyle;
import mightypork.gamecore.render.fonts.FontBank; import mightypork.gamecore.render.fonts.FontBank;
@ -37,13 +37,11 @@ public class Res {
* *
* @param app app access * @param app app access
*/ */
public static void load(App app) public static void load(BaseApp app)
{ {
if (initialized) return; if (initialized) return;
initialized = true; initialized = true;
AsyncResourceLoader.launch(app);
textures = new TextureBank(app); textures = new TextureBank(app);
sounds = new SoundBank(app); sounds = new SoundBank(app);
fonts = new FontBank(app); fonts = new FontBank(app);

@ -1,4 +1,4 @@
package mightypork.gamecore; package mightypork.rogue.util;
import mightypork.utils.logging.LogInstance; import mightypork.utils.logging.LogInstance;

@ -43,7 +43,7 @@ public class InstanceLock {
} }
return false; return false;
} catch (IOException e) { } catch (final IOException e) {
return false; return false;
} }
} }

Loading…
Cancel
Save