Renderer/src/main/java/electrosphere/engine/Main.java
austin 3a34dc28ce
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good
cleanup work
2025-04-29 13:50:30 -04:00

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