493 lines
15 KiB
Java
493 lines
15 KiB
Java
package electrosphere.engine;
|
|
|
|
import static org.lwjgl.glfw.GLFW.glfwWindowShouldClose;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import org.graalvm.polyglot.HostAccess.Export;
|
|
import org.ode4j.ode.OdeHelper;
|
|
|
|
import electrosphere.audio.AudioEngine;
|
|
import electrosphere.audio.VirtualAudioSourceManager;
|
|
import electrosphere.client.ui.menu.debug.ImGuiWindowMacros;
|
|
import electrosphere.controls.ControlHandler;
|
|
import electrosphere.controls.ControlHandler.ControlsState;
|
|
import electrosphere.engine.cli.CLIParser;
|
|
import electrosphere.engine.loadingthreads.LoadingThread;
|
|
import electrosphere.engine.loadingthreads.LoadingThread.LoadingThreadType;
|
|
import electrosphere.engine.signal.SynchronousSignalHandling;
|
|
import electrosphere.engine.time.Timekeeper;
|
|
import electrosphere.logger.LoggerInterface;
|
|
import electrosphere.renderer.RenderingEngine;
|
|
import electrosphere.server.MainServerFunctions;
|
|
|
|
|
|
/**
|
|
* The main class
|
|
*/
|
|
public class Main {
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Pauses simulation
|
|
*/
|
|
public static final int FRAMESTEP_PAUSE = 0;
|
|
|
|
/**
|
|
* Simulates a single frame
|
|
*/
|
|
public static final int FRAMESTEP_SINGLE = 1;
|
|
|
|
/**
|
|
* Toggles automatic simulation
|
|
*/
|
|
public static final int FRAMESTEP_AUTO = 2;
|
|
|
|
|
|
|
|
/**
|
|
* Tracks whether the application is running or not
|
|
*/
|
|
public static boolean running = true;
|
|
|
|
/**
|
|
* Tracks if ode has been initialized or not
|
|
*/
|
|
static boolean initOde = false;
|
|
|
|
/**
|
|
* target amount of time per frame
|
|
*/
|
|
public static float targetFrameRate = 60.0f;
|
|
/**
|
|
* Target period per frame
|
|
*/
|
|
static float targetFramePeriod = 1.0f/targetFrameRate;
|
|
|
|
/**
|
|
* Framestep tracking variable
|
|
*/
|
|
static int framestep = 2;
|
|
|
|
|
|
/**
|
|
* The initial method of the application
|
|
* @param args CLI args
|
|
*/
|
|
public static void main(String args[]){
|
|
|
|
//
|
|
//
|
|
// I N I T I A L I Z A T I O N
|
|
//
|
|
//
|
|
|
|
Main.startUp(args);
|
|
|
|
Main.mainLoop();
|
|
|
|
}
|
|
|
|
/**
|
|
* Starts up the engine
|
|
*/
|
|
public static void startUp(){
|
|
Main.startUp(new String[]{
|
|
"Renderer"
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Starts up the engine
|
|
* @param args The command line arguments
|
|
*/
|
|
public static void startUp(String args[]){
|
|
//parse command line arguments
|
|
CLIParser.parseCLIArgs(args);
|
|
|
|
//init global variables
|
|
Globals.initGlobals();
|
|
|
|
//init scripting engine
|
|
if(Globals.RUN_SCRIPTS){
|
|
Globals.threadManager.start(new LoadingThread(LoadingThreadType.SCRIPT_ENGINE));
|
|
}
|
|
|
|
//controls
|
|
if(Globals.RUN_CLIENT){
|
|
initControlHandler();
|
|
}
|
|
|
|
//init ODE
|
|
if(!initOde){
|
|
OdeHelper.initODE();
|
|
initOde = true;
|
|
}
|
|
|
|
//create the drawing context
|
|
if(Globals.RUN_CLIENT && !Globals.HEADLESS){
|
|
//create opengl context
|
|
Globals.renderingEngine = new RenderingEngine();
|
|
Globals.renderingEngine.createOpenglContext();
|
|
Globals.initDefaultGraphicalResources();
|
|
ImGuiWindowMacros.initImGuiWindows();
|
|
|
|
//inits the controls state of the control handler
|
|
Globals.controlHandler.hintUpdateControlState(ControlsState.TITLE_MENU);
|
|
|
|
//start initial asset loading
|
|
Globals.threadManager.start(new LoadingThread(LoadingThreadType.INIT_ASSETS));
|
|
}
|
|
|
|
//Sets a hook that fires when the engine process stops
|
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
if(LoggerInterface.loggerEngine != null){
|
|
LoggerInterface.loggerEngine.INFO("Shutdown hook!");
|
|
}
|
|
}));
|
|
|
|
//create the audio context
|
|
Globals.audioEngine = new AudioEngine();
|
|
if(Globals.RUN_CLIENT && !Globals.HEADLESS && Globals.RUN_AUDIO){
|
|
Globals.virtualAudioSourceManager = new VirtualAudioSourceManager();
|
|
Globals.audioEngine.init();
|
|
Globals.audioEngine.listAllDevices();
|
|
Globals.initDefaultAudioResources();
|
|
// Globals.audioEngine.setGain(0.1f);
|
|
}
|
|
|
|
//init timekeeper
|
|
Globals.timekeeper.init(targetFramePeriod);
|
|
|
|
//fire off a loading thread for the title menus/screen
|
|
LoggerInterface.loggerStartup.INFO("Fire off loading thread");
|
|
if(Globals.RUN_DEMO){
|
|
LoadingThread serverThread = new LoadingThread(LoadingThreadType.DEMO_MENU);
|
|
Globals.threadManager.start(serverThread);
|
|
} else if(Globals.RUN_CLIENT){
|
|
LoadingThread serverThread = new LoadingThread(LoadingThreadType.TITLE_MENU);
|
|
Globals.threadManager.start(serverThread);
|
|
} else {
|
|
throw new IllegalStateException("Need to add handling for only running server again");
|
|
}
|
|
|
|
//recapture the screen for rendering
|
|
if(Globals.RUN_CLIENT && !Globals.HEADLESS){
|
|
LoggerInterface.loggerStartup.INFO("Recapture screen");
|
|
Globals.controlHandler.setRecapture(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runs the main loop indefinitely. Blocks the thread this is called in.
|
|
*/
|
|
public static void mainLoop(){
|
|
mainLoop(0);
|
|
shutdown();
|
|
}
|
|
|
|
/**
|
|
* Runs the main loop for a specified number of frames.
|
|
* @param maxFrames The number of frames to run for. If 0, will run indefinitely.
|
|
*/
|
|
public static void mainLoop(long maxFrames){
|
|
|
|
//resets running flag to that we can repeatedly loop (ie in tests)
|
|
running = true;
|
|
|
|
//main loop
|
|
while (running) {
|
|
try {
|
|
|
|
Globals.profiler.beginRootCpuSample("frame");
|
|
LoggerInterface.loggerEngine.DEBUG_LOOP("Begin Main Loop Frame");
|
|
|
|
//
|
|
//Update timekeeper, thread manager, and process all main thread signals
|
|
//
|
|
Globals.timekeeper.update();
|
|
Globals.threadManager.update();
|
|
|
|
|
|
|
|
///
|
|
/// A S S E T M A N A G E R S T U F F
|
|
///
|
|
if(Globals.RUN_CLIENT){
|
|
Globals.profiler.beginCpuSample("Load Assets");
|
|
LoggerInterface.loggerEngine.DEBUG_LOOP("Begin load assets");
|
|
Globals.assetManager.loadAssetsInQueue();
|
|
LoggerInterface.loggerEngine.DEBUG_LOOP("Begin delete assets");
|
|
Globals.assetManager.handleDeleteQueue();
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
|
|
|
|
|
|
|
|
///
|
|
/// C L I E N T N E T W O R K I N G S T U F F
|
|
///
|
|
//Why is this its own function? Just to get the networking code out of main()
|
|
if(Globals.clientConnection != null){
|
|
Globals.profiler.beginCpuSample("Client networking");
|
|
LoggerInterface.loggerEngine.DEBUG_LOOP("Begin parse client messages");
|
|
Globals.clientConnection.parseMessagesSynchronous();
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
|
|
|
|
|
|
///
|
|
/// I N P U T C O N T R O L S
|
|
///
|
|
//Poll controls
|
|
if(Globals.RUN_CLIENT){
|
|
Globals.profiler.beginCpuSample("Poll Controls");
|
|
LoggerInterface.loggerEngine.DEBUG_LOOP("Begin recapture screen");
|
|
Globals.controlHandler.pollControls();
|
|
RenderingEngine.recaptureIfNecessary();
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
|
|
|
|
///
|
|
/// S Y N C H R O N O U S S I G N A L H A N D L I N G
|
|
///
|
|
SynchronousSignalHandling.runMainThreadSignalHandlers();
|
|
|
|
///
|
|
/// E N G I N E S E R V I C E S
|
|
///
|
|
if(Globals.fileWatcherService != null){
|
|
Globals.fileWatcherService.poll();
|
|
}
|
|
if(Globals.RUN_SCRIPTS && Globals.scriptEngine != null){
|
|
Globals.scriptEngine.scanScriptDir();
|
|
}
|
|
|
|
|
|
///
|
|
///
|
|
/// M A I N S I M U L A T I O N I N N E R L O O P
|
|
///
|
|
|
|
int simFrameHardcapCounter = 0;
|
|
while(Globals.timekeeper.pullFromAccumulator() && framestep > 0 && simFrameHardcapCounter < Timekeeper.SIM_FRAME_HARDCAP){
|
|
|
|
//do not simulate extra frames if we're already behind schedule
|
|
if(Globals.timekeeper.getMostRecentRawFrametime() > Globals.timekeeper.getSimFrameTime() && simFrameHardcapCounter > 0){
|
|
break;
|
|
}
|
|
|
|
//sim frame hard cap counter increment
|
|
simFrameHardcapCounter++;
|
|
//handle framestep
|
|
if(framestep == 1){
|
|
framestep = 0;
|
|
}
|
|
|
|
|
|
|
|
///
|
|
/// C L I E N T S I M U L A T I O N S T U F F
|
|
///
|
|
LoggerInterface.loggerEngine.DEBUG_LOOP("Begin client simulation");
|
|
if(Globals.clientSimulation != null){
|
|
Globals.profiler.beginCpuSample("Client simulation");
|
|
Globals.clientSimulation.simulate();
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///
|
|
/// S E R V E R M A I N R O U T I N E S
|
|
///
|
|
Globals.profiler.beginCpuSample("Main Server Functions");
|
|
MainServerFunctions.simulate();
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// M A I N A U D I O F U N C T I O N
|
|
//
|
|
Globals.profiler.beginCpuSample("audio engine update");
|
|
if(Globals.audioEngine != null && Globals.audioEngine.initialized() && Globals.virtualAudioSourceManager != null){
|
|
Globals.audioEngine.update();
|
|
Globals.virtualAudioSourceManager.update((float)Globals.timekeeper.getSimFrameTime());
|
|
}
|
|
Globals.profiler.endCpuSample();
|
|
|
|
|
|
|
|
|
|
///
|
|
/// M A I N R E N D E R F U N C T I O N
|
|
///
|
|
LoggerInterface.loggerEngine.DEBUG_LOOP("Begin rendering call");
|
|
if(Globals.RUN_CLIENT && !Globals.HEADLESS){
|
|
Globals.profiler.beginCpuSample("render");
|
|
Globals.renderingEngine.drawScreen();
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
|
|
|
|
|
|
|
|
///
|
|
/// G A R B A G E C H E C K
|
|
///
|
|
Globals.profiler.beginCpuSample("gc");
|
|
if(Globals.EXPLICIT_GC && Globals.timekeeper.getNumberOfRenderFramesElapsed() % Globals.GC_FRAME_FREQUENCY == 0){
|
|
System.gc();
|
|
}
|
|
Globals.profiler.endCpuSample();
|
|
|
|
|
|
|
|
|
|
|
|
///
|
|
/// S H U T D O W N C H E C K
|
|
///
|
|
if(Globals.HEADLESS){
|
|
if(Globals.ENGINE_SHUTDOWN_FLAG){
|
|
running = false;
|
|
}
|
|
} else {
|
|
if(Globals.ENGINE_SHUTDOWN_FLAG || (Globals.RUN_CLIENT && glfwWindowShouldClose(Globals.window))){
|
|
running = false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///
|
|
/// C L E A N U P T I M E V A R I A B L E S
|
|
///
|
|
if(Globals.EXPLICIT_SLEEP && Globals.timekeeper.getMostRecentRawFrametime() < 0.01f){
|
|
Globals.profiler.beginCpuSample("sleep");
|
|
if(Globals.timekeeper.getMostRecentRawFrametime() < targetFramePeriod){
|
|
Main.sleep((int)(1000.0 * (targetFramePeriod - Globals.timekeeper.getMostRecentRawFrametime())));
|
|
} else {
|
|
Main.sleep(1);
|
|
}
|
|
Globals.profiler.endCpuSample();
|
|
}
|
|
Globals.timekeeper.numberOfRenderedFrames++;
|
|
if(maxFrames > 0 && Globals.timekeeper.numberOfRenderedFrames > maxFrames){
|
|
running = false;
|
|
}
|
|
|
|
|
|
///
|
|
/// F R A M E T I M E T R A C K I NG
|
|
///
|
|
ImGuiWindowMacros.addGlobalFramerateDatapoint("totalframerate", Globals.timekeeper.getMostRecentRawFrametime());
|
|
|
|
|
|
///
|
|
/// E N D M A I N L O O P
|
|
///
|
|
LoggerInterface.loggerEngine.DEBUG_LOOP("End Main Loop Frame");
|
|
Globals.profiler.endCpuSample();
|
|
|
|
} catch (NullPointerException ex){
|
|
LoggerInterface.loggerEngine.ERROR(ex);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Shuts down the engine
|
|
*/
|
|
public static void shutdown(){
|
|
if(LoggerInterface.loggerEngine != null){
|
|
LoggerInterface.loggerEngine.INFO("ENGINE SHUTDOWN");
|
|
}
|
|
//
|
|
// S H U T D O W N
|
|
//
|
|
//Terminate the program.
|
|
if(Globals.renderingEngine != null){
|
|
Globals.renderingEngine.destroy();
|
|
}
|
|
//used to signal threads to stop
|
|
if(Globals.threadManager != null){
|
|
Globals.threadManager.close();
|
|
}
|
|
//shut down audio engine
|
|
if(Globals.audioEngine != null && Globals.audioEngine.initialized()){
|
|
Globals.audioEngine.shutdown();
|
|
}
|
|
//if netmonitor is running, close
|
|
if(Globals.netMonitor != null){
|
|
Globals.netMonitor.close();
|
|
}
|
|
//shutdown profiler
|
|
if(Globals.profiler != null){
|
|
Globals.profiler.destroy();
|
|
}
|
|
//shutdown ode
|
|
if(initOde){
|
|
OdeHelper.closeODE();
|
|
initOde = false;
|
|
}
|
|
//
|
|
//Destroy services
|
|
if(Globals.serviceManager != null){
|
|
Globals.serviceManager.destroy();
|
|
}
|
|
//reset globals for good measure (making sure no long-running threads can re-inject entities into scenes)
|
|
Globals.resetGlobals();
|
|
}
|
|
|
|
static void sleep(int i) {
|
|
try {
|
|
TimeUnit.MILLISECONDS.sleep(i);
|
|
} catch (InterruptedException ex) {
|
|
System.out.println("Sleep somehow interrupted?!");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void initControlHandler(){
|
|
LoggerInterface.loggerStartup.INFO("Initialize control handler");
|
|
Globals.controlHandler = ControlHandler.generateExampleControlsMap();
|
|
Globals.controlHandler.setCallbacks();
|
|
}
|
|
|
|
/**
|
|
* Sets the framestep state (2 to resume automatic, 1 to make single step)
|
|
* @param framestep 2 - automatic framestep, 1 - single step, 0 - no step
|
|
*/
|
|
@Export
|
|
public static void setFramestep(int framestep){
|
|
Main.framestep = framestep;
|
|
}
|
|
|
|
|
|
|
|
}
|