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

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

@ -31,12 +31,16 @@ public class App extends BusNode {
private final AppBackend backend;
private final EventBus eventBus = new EventBus();
private boolean started = false;
private boolean inited = false;
/** List of installed App plugins */
protected final DelegatingList plugins = new DelegatingList();
/** List of initializers */
protected final List<InitTask> initializers = new ArrayList<>();
/** The used main loop instance */
protected MainLoop mainLoop;
/**
* 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
*
@ -123,11 +138,31 @@ public class App extends BusNode {
}
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.
Log.f2("Calling pre-init hook...");
preInit();
Log.i("=== Starting initialization sequence ===");
Log.f2("Running init tasks...");
// sort initializers by order.
final List<InitTask> orderedInitializers = InitTask.inOrder(initializers);
@ -149,8 +184,6 @@ public class App extends BusNode {
initTask.after();
}
Log.i("=== Initialization sequence completed ===");
// user can now start the main loop etc.
Log.f2("Calling post-init hook...");
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;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import mightypork.gamecore.graphics.Renderable;
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.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)
*/
public class MainLoop extends BusNode implements 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;
public interface MainLoop extends Destroyable {
/**
* Set primary renderable
@ -45,99 +19,27 @@ public class MainLoop extends BusNode implements Destroyable {
* @param rootRenderable main {@link Renderable}, typically a
* {@link ScreenRegistry}
*/
public void setRootRenderable(Renderable rootRenderable)
{
this.rootRenderable = rootRenderable;
}
/**
* Start the loop
*/
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()
{
//
}
public abstract void setRootRenderable(Renderable rootRenderable);
/**
* Called after render
* Start the loop. The loop should be terminated when the destroy() method
* is called.
*/
@Stub
protected void afterRender()
{
//
}
public abstract void start();
@Override
public void destroy()
{
running = false;
}
public abstract void destroy();
/**
* 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 skipQueue true to skip the queue
*/
public synchronized void queueTask(Runnable task, boolean skipQueue)
{
if (skipQueue) {
tasks.addFirst(task);
} else {
tasks.addLast(task);
}
}
public abstract void queueTask(Runnable task, boolean skipQueue);
}

@ -4,6 +4,8 @@ package mightypork.gamecore.core.config;
import java.util.HashMap;
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.KeyStroke;
import mightypork.utils.config.propmgr.Property;
@ -19,7 +21,7 @@ import mightypork.utils.logging.Log;
*
* @author Ondřej Hruška (MightyPork)
*/
public class Config {
public class Config implements ShutdownListener {
/** Array of configs registered for the app */
protected static Map<String, Config> configs = new HashMap<>();
@ -265,4 +267,11 @@ public class Config {
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;
/**
* Font style enum
*/

Loading…
Cancel
Save