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 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();

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

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

@ -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");

@ -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);

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

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

Loading…
Cancel
Save