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