Support for main loop and confignsaving on exit.

master
Ondřej Hruška 10 years ago
parent af8535620b
commit dbc32c4d0a
  1. 266
      src/junk/AppInitOptions.java
  2. 369
      src/junk/BaseApp.java
  3. 39
      src/mightypork/gamecore/core/App.java
  4. 128
      src/mightypork/gamecore/core/DeltaMainLoop.java
  5. 116
      src/mightypork/gamecore/core/MainLoop.java
  6. 11
      src/mightypork/gamecore/core/config/Config.java
  7. 40
      src/mightypork/gamecore/core/init/InitTaskMainLoop.java
  8. 1
      src/mightypork/gamecore/graphics/fonts/FontStyle.java

@ -1,133 +1,133 @@
package junk; //package junk;
//
//
import java.io.File; //import java.io.File;
import java.util.ArrayList; //import java.util.ArrayList;
import java.util.List; //import java.util.List;
import java.util.logging.Level; //import java.util.logging.Level;
//
import mightypork.gamecore.core.AppBackend; //import mightypork.gamecore.core.AppBackend;
import mightypork.gamecore.resources.ResourceInitializer; //import mightypork.gamecore.resources.ResourceInitializer;
import mightypork.gamecore.resources.loading.AsyncResourceLoader; //import mightypork.gamecore.resources.loading.AsyncResourceLoader;
import mightypork.gamecore.resources.loading.ResourceLoader; //import mightypork.gamecore.resources.loading.ResourceLoader;
//
//
/** ///**
* Init options holder class // * Init options holder class
*/ // */
public class AppInitOptions { //public class AppInitOptions {
//
String logDir = "log"; // String logDir = "log";
String logFilePrefix = "runtime"; // String logFilePrefix = "runtime";
//
String screenshotDir = "screenshots"; // String screenshotDir = "screenshots";
//
boolean busLogging = false; // boolean busLogging = false;
//
String configFile = "settings.cfg"; // String configFile = "settings.cfg";
String configComment = "Main config file"; // String configComment = "Main config file";
//
final List<ResourceInitializer> resourceLists = new ArrayList<>(); // final List<ResourceInitializer> resourceLists = new ArrayList<>();
final List<KeySetup> keyLists = new ArrayList<>(); // final List<KeySetup> keyLists = new ArrayList<>();
final List<ConfigSetup> configLists = new ArrayList<>(); // final List<ConfigSetup> configLists = new ArrayList<>();
//
ResourceLoader resourceLoader = new AsyncResourceLoader(); // ResourceLoader resourceLoader = new AsyncResourceLoader();
Level logLevel = Level.ALL; // Level logLevel = Level.ALL;
public boolean sigleInstance = true; // public boolean sigleInstance = true;
Level logSoutLevel = Level.ALL; // Level logSoutLevel = Level.ALL;
//
//
public void setConfigFile(String filename, String comment) // public void setConfigFile(String filename, String comment)
{ // {
configFile = filename; // configFile = filename;
configComment = comment; // configComment = comment;
} // }
//
//
public void addConfig(ConfigSetup cfg) // public void addConfig(ConfigSetup cfg)
{ // {
configLists.add(cfg); // configLists.add(cfg);
} // }
//
//
public void addKeys(KeySetup keys) // public void addKeys(KeySetup keys)
{ // {
keyLists.add(keys); // keyLists.add(keys);
} // }
//
//
public void addResources(ResourceInitializer res) // public void addResources(ResourceInitializer res)
{ // {
resourceLists.add(res); // resourceLists.add(res);
} // }
//
//
public void setBackend(AppBackend backend) // public void setBackend(AppBackend backend)
{ // {
this.backend = backend; // this.backend = backend;
} // }
//
//
/** // /**
* Set whether to run in single instance mode, or allow multiple instances.<br> // * Set whether to run in single instance mode, or allow multiple instances.<br>
* Multiple instances running can cause various collisions (eg. when writing // * Multiple instances running can cause various collisions (eg. when writing
* config file or logging). // * config file or logging).
* // *
* @param sigleInstance true to allow only one instance // * @param sigleInstance true to allow only one instance
*/ // */
public void setSigleInstance(boolean sigleInstance) // public void setSigleInstance(boolean sigleInstance)
{ // {
this.sigleInstance = sigleInstance; // this.sigleInstance = sigleInstance;
} // }
//
//
/** // /**
* Set working directory path. If not exists, it will be created. // * Set working directory path. If not exists, it will be created.
* // *
* @param workdir work dir path // * @param workdir work dir path
*/ // */
public void setWorkdir(File workdir) // public void setWorkdir(File workdir)
{ // {
this.workdir = workdir; // this.workdir = workdir;
} // }
//
//
public void setBusLogging(boolean yes) // public void setBusLogging(boolean yes)
{ // {
busLogging = yes; // busLogging = yes;
} // }
//
//
public void setLogOptions(String logDir, String filePrefix, int archivedCount, Level logLevel) // public void setLogOptions(String logDir, String filePrefix, int archivedCount, Level logLevel)
{ // {
this.logDir = logDir; // this.logDir = logDir;
this.logFilePrefix = filePrefix; // this.logFilePrefix = filePrefix;
this.logArchiveCount = archivedCount; // this.logArchiveCount = archivedCount;
this.logLevel = logLevel; // this.logLevel = logLevel;
} // }
//
//
public void setResourceLoader(ResourceLoader resLoader) // public void setResourceLoader(ResourceLoader resLoader)
{ // {
resourceLoader = resLoader; // resourceLoader = resLoader;
} // }
//
//
public void setScreenshotDir(String path) // public void setScreenshotDir(String path)
{ // {
this.screenshotDir = path; // this.screenshotDir = path;
} // }
//
//
public void setLockFile(String lockFile) // public void setLockFile(String lockFile)
{ // {
this.lockFile = lockFile; // this.lockFile = lockFile;
} // }
//
//
public void setLogLevel(Level logLevel, Level soutLevel) // public void setLogLevel(Level logLevel, Level soutLevel)
{ // {
this.logLevel = logLevel; // this.logLevel = logLevel;
this.logSoutLevel = soutLevel; // this.logSoutLevel = soutLevel;
} // }
} //}

@ -1,184 +1,185 @@
package junk; //package junk;
//
//
import java.lang.Thread.UncaughtExceptionHandler; //import java.lang.Thread.UncaughtExceptionHandler;
//
import mightypork.gamecore.core.App; //import mightypork.gamecore.core.App;
import mightypork.gamecore.core.AppBackend; //import mightypork.gamecore.core.AppBackend;
import mightypork.gamecore.core.MainLoop; //import mightypork.gamecore.core.MainLoop;
import mightypork.gamecore.core.config.Config; //import mightypork.gamecore.core.DeltaMainLoop;
import mightypork.gamecore.gui.screens.ScreenRegistry; //import mightypork.gamecore.core.config.Config;
import mightypork.gamecore.gui.screens.impl.CrossfadeOverlay; //import mightypork.gamecore.gui.screens.ScreenRegistry;
import mightypork.gamecore.resources.Res; //import mightypork.gamecore.gui.screens.impl.CrossfadeOverlay;
import mightypork.gamecore.resources.ResourceInitializer; //import mightypork.gamecore.resources.Res;
import mightypork.utils.files.WorkDir; //import mightypork.gamecore.resources.ResourceInitializer;
import mightypork.utils.logging.Log; //import mightypork.utils.files.WorkDir;
//import mightypork.utils.logging.Log;
//
/** //
* Basic screen-based game with subsystems.<br> ///**
* This class takes care of the initialization sequence. // * Basic screen-based game with subsystems.<br>
* // * This class takes care of the initialization sequence.
* @author Ondřej Hruška (MightyPork) // *
*/ // * @author Ondřej Hruška (MightyPork)
public abstract class BaseApp extends App implements UncaughtExceptionHandler { // */
//public abstract class BaseApp extends App implements UncaughtExceptionHandler {
// modules //
private MainLoop gameLoop; // // modules
private ScreenRegistry screenRegistry; // private MainLoop gameLoop;
// private ScreenRegistry screenRegistry;
private boolean started = false; //
private boolean lockObtained = false; // private boolean started = false;
// private boolean lockObtained = false;
// init opt holder //
private final AppInitOptions opt = new AppInitOptions(); // // init opt holder
// private final AppInitOptions opt = new AppInitOptions();
//
/** //
* Get init options // /**
* // * Get init options
* @return opt holder // *
*/ // * @return opt holder
public AppInitOptions getInitOptions() // */
{ // public AppInitOptions getInitOptions()
if (started) { // {
throw new IllegalStateException("Cannot alter init options after starting the App."); // if (started) {
} // throw new IllegalStateException("Cannot alter init options after starting the App.");
// }
return opt; //
} // return opt;
// }
//
public BaseApp(AppBackend backend) //
{ // public BaseApp(AppBackend backend)
super(backend); // {
} // super(backend);
// }
//
/** //
* Start the application // /**
*/ // * Start the application
@Override // */
public final void start() // @Override
{ // public final void start()
initialize(); // {
// initialize();
Log.i("Starting main loop..."); //
// Log.i("Starting main loop...");
// open first screen !!! //
started = true; // // open first screen !!!
gameLoop.start(); // started = true;
} // gameLoop.start();
// }
//
/** //
* Init the app // /**
*/ // * Init the app
protected void initialize() // */
{ // protected void initialize()
WorkDir.setBaseDir(opt.workdir); // {
// WorkDir.setBaseDir(opt.workdir);
if (opt.sigleInstance) initLock(); //
lockObtained = true; // if (opt.sigleInstance) initLock();
// lockObtained = true;
for (final RouteSetup rs : opt.routeLists) { //
WorkDir.registerRoutes(rs); // for (final RouteSetup rs : opt.routeLists) {
} // WorkDir.registerRoutes(rs);
WorkDir.addPath("_screenshot_dir", opt.screenshotDir); // }
// WorkDir.addPath("_screenshot_dir", opt.screenshotDir);
// apply configurations //
Config.init(WorkDir.getFile(opt.configFile), opt.configComment); // // apply configurations
// Config.init(WorkDir.getFile(opt.configFile), opt.configComment);
// add keys to config //
for (final KeySetup l : opt.keyLists) { // // add keys to config
Config.registerKeys(l); // for (final KeySetup l : opt.keyLists) {
} // Config.registerKeys(l);
// }
// add options to config //
for (final ConfigSetup c : opt.configLists) { // // add options to config
Config.registerOptions(c); // for (final ConfigSetup c : opt.configLists) {
} // Config.registerOptions(c);
Config.load(); // }
// Config.load();
/* //
* Display // /*
*/ // * Display
Log.f2("Initializing Display System..."); // */
initDisplay(gfx()); // Log.f2("Initializing Display System...");
// initDisplay(gfx());
/* //
* Audio // /*
*/ // * Audio
Log.f2("Initializing Sound System..."); // */
soundSystem = new SoundSystem(this); // Log.f2("Initializing Sound System...");
initSoundSystem(soundSystem); // soundSystem = new SoundSystem(this);
// initSoundSystem(soundSystem);
/* //
* Input // /*
*/ // * Input
Log.f2("Initializing Input System..."); // */
inputSystem = new LwjglInputModule(this); // Log.f2("Initializing Input System...");
initInputSystem(inputSystem); // inputSystem = new LwjglInputModule(this);
// initInputSystem(inputSystem);
/* //
* Prepare main loop // /*
*/ // * Prepare main loop
Log.f1("Creating Screen Registry and Game Loop..."); // */
screenRegistry = new ScreenRegistry(this); // Log.f1("Creating Screen Registry and Game Loop...");
gameLoop = createMainLoop(); // screenRegistry = new ScreenRegistry(this);
gameLoop.setRootRenderable(screenRegistry); // gameLoop = createMainLoop();
// gameLoop.setRootRenderable(screenRegistry);
/* //
* Load resources // /*
* // * Load resources
* Resources should be registered to registries, and AsyncResourceLoader will load them. // *
*/ // * Resources should be registered to registries, and AsyncResourceLoader will load them.
Log.f1("Loading resources..."); // */
if (opt.resourceLoader != null) { // Log.f1("Loading resources...");
opt.resourceLoader.setBaseDir(this); // if (opt.resourceLoader != null) {
} // opt.resourceLoader.setBaseDir(this);
// }
Res.setBaseDir(this); //
// Res.setBaseDir(this);
for (final ResourceInitializer rl : opt.resourceLists) { //
Res.load(rl); // for (final ResourceInitializer rl : opt.resourceLists) {
} // Res.load(rl);
// }
/* //
* Screen registry // /*
* // * Screen registry
* Must be after resources, because screens can request them during instantiation. // *
*/ // * Must be after resources, because screens can request them during instantiation.
Log.f2("Registering screens..."); // */
initScreens(screenRegistry); // Log.f2("Registering screens...");
} // initScreens(screenRegistry);
// }
//
/** //
* Register game screens to the registry. // /**
* // * Register game screens to the registry.
* @param screens // *
*/ // * @param screens
protected void initScreens(ScreenRegistry screens) // */
{ // protected void initScreens(ScreenRegistry screens)
screens.addOverlay(new CrossfadeOverlay(this)); // {
} // screens.addOverlay(new CrossfadeOverlay(this));
// }
//
/** //
* Create game loop instance // /**
* // * Create game loop instance
* @return the game loop. // *
*/ // * @return the game loop.
protected MainLoop createMainLoop() // */
{ // protected MainLoop createMainLoop()
return new MainLoop(this); // {
} // return new DeltaMainLoop(this);
// }
//
protected void beforeShutdown() //
{ // protected void beforeShutdown()
// ??? // {
if (lockObtained) Config.save(); // // ???
} // if (lockObtained) Config.save();
} // }
//}

@ -31,12 +31,16 @@ public class App extends BusNode {
private final AppBackend backend; private final AppBackend backend;
private final EventBus eventBus = new EventBus(); private final EventBus eventBus = new EventBus();
private boolean started = false; private boolean started = false;
private boolean inited = false;
/** List of installed App plugins */ /** List of installed App plugins */
protected final DelegatingList plugins = new DelegatingList(); protected final DelegatingList plugins = new DelegatingList();
/** List of initializers */ /** List of initializers */
protected final List<InitTask> initializers = new ArrayList<>(); protected final List<InitTask> initializers = new ArrayList<>();
/** The used main loop instance */
protected MainLoop mainLoop;
/** /**
* Create an app with given backend. * Create an app with given backend.
@ -100,6 +104,17 @@ public class App extends BusNode {
} }
/**
* Set the main loop implementation
*
* @param loop main loop impl
*/
public void setMainLoop(MainLoop loop)
{
this.mainLoop = loop;
}
/** /**
* Get current backend * Get current backend
* *
@ -123,11 +138,31 @@ public class App extends BusNode {
} }
started = true; 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.start();
}
private void init()
{
if (inited) {
throw new IllegalStateException("Already inited.");
}
inited = true;
// pre-init hook, just in case anyone wanted to have one. // pre-init hook, just in case anyone wanted to have one.
Log.f2("Calling pre-init hook..."); Log.f2("Calling pre-init hook...");
preInit(); preInit();
Log.i("=== Starting initialization sequence ==="); Log.f2("Running init tasks...");
// sort initializers by order. // sort initializers by order.
final List<InitTask> orderedInitializers = InitTask.inOrder(initializers); final List<InitTask> orderedInitializers = InitTask.inOrder(initializers);
@ -149,8 +184,6 @@ public class App extends BusNode {
initTask.after(); initTask.after();
} }
Log.i("=== Initialization sequence completed ===");
// user can now start the main loop etc. // user can now start the main loop etc.
Log.f2("Calling post-init hook..."); Log.f2("Calling post-init hook...");
postInit(); postInit();

@ -0,0 +1,128 @@
package mightypork.gamecore.core;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import mightypork.gamecore.graphics.Renderable;
import mightypork.utils.annotations.Stub;
import mightypork.utils.eventbus.clients.BusNode;
import mightypork.utils.eventbus.events.UpdateEvent;
import mightypork.utils.logging.Log;
import mightypork.utils.math.timing.Profiler;
import mightypork.utils.math.timing.TimerDelta;
/**
* Delta-timed game loop with task queue etc.
*
* @author Ondřej Hruška (MightyPork)
*/
public class DeltaMainLoop extends BusNode implements MainLoop {
/**
* Max time spent on main loop tasks per cycle (s)
*/
protected double MAX_TIME_TASKS = 1 / 30D;
/**
* Max delta time (s) per frame.<br>
* If delta is larger than this, it's clamped to it.
*/
protected double MAX_DELTA = 1 / 20D;
private final Deque<Runnable> tasks = new ConcurrentLinkedDeque<>();
private TimerDelta timer;
private Renderable rootRenderable;
private volatile boolean running = true;
@Override
public void setRootRenderable(Renderable rootRenderable)
{
this.rootRenderable = rootRenderable;
}
@Override
public void start()
{
timer = new TimerDelta();
while (running) {
App.gfx().beginFrame();
double delta = timer.getDelta();
if (delta > MAX_DELTA) {
Log.f3("(timing) Clamping delta: was " + delta + " s, MAX_DELTA = " + MAX_DELTA + " s");
delta = MAX_DELTA;
}
// dispatch update event
App.bus().sendDirect(new UpdateEvent(delta));
// run main loop tasks
Runnable r;
final long t = Profiler.begin();
while ((r = tasks.poll()) != null) {
Log.f3(" * Main loop task.");
r.run();
if (Profiler.end(t) > MAX_TIME_TASKS) {
Log.f3("! Time's up, postponing task to next cycle.");
break;
}
}
beforeRender();
if (rootRenderable != null) {
rootRenderable.render();
}
afterRender();
App.gfx().endFrame();
}
}
/**
* Called before render
*/
@Stub
protected void beforeRender()
{
//
}
/**
* Called after render
*/
@Stub
protected void afterRender()
{
//
}
@Override
public void destroy()
{
running = false;
}
@Override
public synchronized void queueTask(Runnable task, boolean skipQueue)
{
if (skipQueue) {
tasks.addFirst(task);
} else {
tasks.addLast(task);
}
}
}

@ -1,43 +1,17 @@
package mightypork.gamecore.core; package mightypork.gamecore.core;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import mightypork.gamecore.graphics.Renderable; import mightypork.gamecore.graphics.Renderable;
import mightypork.gamecore.gui.screens.ScreenRegistry; import mightypork.gamecore.gui.screens.ScreenRegistry;
import mightypork.utils.annotations.Stub;
import mightypork.utils.eventbus.clients.BusNode;
import mightypork.utils.eventbus.events.UpdateEvent;
import mightypork.utils.interfaces.Destroyable; import mightypork.utils.interfaces.Destroyable;
import mightypork.utils.logging.Log;
import mightypork.utils.math.timing.Profiler;
import mightypork.utils.math.timing.TimerDelta;
/** /**
* Delta-timed game loop with task queue etc. * A main loop of the app.
* *
* @author Ondřej Hruška (MightyPork) * @author Ondřej Hruška (MightyPork)
*/ */
public class MainLoop extends BusNode implements Destroyable { public interface MainLoop extends Destroyable {
/**
* Max time spent on main loop tasks per cycle (s)
*/
protected double MAX_TIME_TASKS = 1 / 30D;
/**
* Max delta time (s) per frame.<br>
* If delta is larger than this, it's clamped to it.
*/
protected double MAX_DELTA = 1 / 20D;
private final Deque<Runnable> tasks = new ConcurrentLinkedDeque<>();
private TimerDelta timer;
private Renderable rootRenderable;
private volatile boolean running = true;
/** /**
* Set primary renderable * Set primary renderable
@ -45,99 +19,27 @@ public class MainLoop extends BusNode implements Destroyable {
* @param rootRenderable main {@link Renderable}, typically a * @param rootRenderable main {@link Renderable}, typically a
* {@link ScreenRegistry} * {@link ScreenRegistry}
*/ */
public void setRootRenderable(Renderable rootRenderable) public abstract void setRootRenderable(Renderable rootRenderable);
{
this.rootRenderable = rootRenderable;
}
/** /**
* Start the loop * Start the loop. The loop should be terminated when the destroy() method
* is called.
*/ */
public void start() public abstract void start();
{
timer = new TimerDelta();
while (running) {
App.gfx().beginFrame();
double delta = timer.getDelta();
if (delta > MAX_DELTA) {
Log.f3("(timing) Clamping delta: was " + delta + " s, MAX_DELTA = " + MAX_DELTA + " s");
delta = MAX_DELTA;
}
// dispatch update event
App.bus().sendDirect(new UpdateEvent(delta));
// run main loop tasks
Runnable r;
final long t = Profiler.begin();
while ((r = tasks.poll()) != null) {
Log.f3(" * Main loop task.");
r.run();
if (Profiler.end(t) > MAX_TIME_TASKS) {
Log.f3("! Time's up, postponing task to next cycle.");
break;
}
}
beforeRender();
if (rootRenderable != null) {
rootRenderable.render();
}
afterRender();
App.gfx().endFrame();
}
}
/**
* Called before render
*/
@Stub
protected void beforeRender()
{
//
}
/**
* Called after render
*/
@Stub
protected void afterRender()
{
//
}
@Override @Override
public void destroy() public abstract void destroy();
{
running = false;
}
/** /**
* Add a task to queue to be executed in the main loop (in rendering * Add a task to queue to be executed in the main loop (in rendering
* context) * context). This may be eg. loading textures.
* *
* @param task task * @param task task
* @param skipQueue true to skip the queue * @param skipQueue true to skip the queue
*/ */
public synchronized void queueTask(Runnable task, boolean skipQueue) public abstract void queueTask(Runnable task, boolean skipQueue);
{
if (skipQueue) {
tasks.addFirst(task);
} else {
tasks.addLast(task);
}
}
} }

@ -4,6 +4,8 @@ package mightypork.gamecore.core.config;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import mightypork.gamecore.core.events.ShutdownEvent;
import mightypork.gamecore.core.events.ShutdownListener;
import mightypork.gamecore.input.Key; import mightypork.gamecore.input.Key;
import mightypork.gamecore.input.KeyStroke; import mightypork.gamecore.input.KeyStroke;
import mightypork.utils.config.propmgr.Property; import mightypork.utils.config.propmgr.Property;
@ -19,7 +21,7 @@ import mightypork.utils.logging.Log;
* *
* @author Ondřej Hruška (MightyPork) * @author Ondřej Hruška (MightyPork)
*/ */
public class Config { public class Config implements ShutdownListener {
/** Array of configs registered for the app */ /** Array of configs registered for the app */
protected static Map<String, Config> configs = new HashMap<>(); protected static Map<String, Config> configs = new HashMap<>();
@ -265,4 +267,11 @@ public class Config {
kp.getValue().setTo(key, mod); kp.getValue().setTo(key, mod);
} }
@Override
public void onShutdown(ShutdownEvent event)
{
save(); // save changes done to the config
}
} }

@ -0,0 +1,40 @@
package mightypork.gamecore.core.init;
import mightypork.gamecore.core.MainLoop;
/**
* Task to add a resource loader.<br>
* By default the async resource loader is used
*
* @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";
}
}

@ -1,5 +1,6 @@
package mightypork.gamecore.graphics.fonts; package mightypork.gamecore.graphics.fonts;
/** /**
* Font style enum * Font style enum
*/ */

Loading…
Cancel
Save