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.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.time.Timekeeper; import electrosphere.game.server.world.MacroData; import electrosphere.logger.LoggerInterface; import electrosphere.menu.debug.ImGuiWindowMacros; import electrosphere.renderer.RenderingEngine; import electrosphere.server.MainServerFunctions; import electrosphere.server.simulation.MacroSimulation; /** * The main class */ public class Main { 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; static float targetFramePeriod = 1.0f/targetFrameRate; //framestep variable static int framestep = 2; public static void main(String args[]){ // // // I N I T I A L I Z A T I O N // // startUp(args); mainLoop(); } /** * Starts up the engine */ public static void startUp(){ 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; } //world gen testing //gen terrain if(Globals.RUN_SIMULATION_ONLY){ //gen world Globals.macroData = MacroData.generateWorld(0); Globals.macroData.describeWorld(); boolean run = true; Globals.macroSimulation = new MacroSimulation(); while(run){ Globals.macroSimulation.simulate(); try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } } } // if(1==1){ // Globals.audioEngine = new AudioEngine(); // Globals.audioEngine.init(); // Globals.assetManager.addAudioPathToQueue("/Audio/MenuStartup.ogg"); // Globals.assetManager.loadAssetsInQueue(); // Globals.audioEngine.setGain(0.1f); // AudioSource startupSound = AudioUtils.playAudioAtLocation("/Audio/MenuStartup.ogg", new Vector3f(3,0,0)); // if(startupSound != null){ // while(startupSound.isPlaying()){ // try { // TimeUnit.MILLISECONDS.sleep(10); // } catch (InterruptedException ex) { // ex.printStackTrace(); // } // } // } // System.exit(0); // } //debug: create terrain/world viewer // TerrainViewer.runViewer(); //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!"); } })); //uncomment to test loading a model into engine // if(1==1){ // //Models/creatures/person2/person2_1.glb // ///Models/creatures/viewmodel.glb // Globals.assetManager.addModelPathToQueue("/Models/creatures/person2/person2_1.glb"); // Globals.assetManager.loadAssetsInQueue(); // electrosphere.renderer.model.Model model = Globals.assetManager.fetchModel("/Models/creatures/person2/person2_1.glb"); // // for(electrosphere.renderer.anim.Animation anim : model.getAnimations()){ // // if(anim.name.equals("Armature|Idle1")){ // // System.out.println(anim.duration); // // for(electrosphere.renderer.anim.AnimChannel channel : anim.channels){ // // if(channel.getNodeID().equals("Torso")){ // // channel.fullDescribeChannel(); // // } // // // System.out.println("CHannel: " + channel.getNodeID()); // // } // // break; // // } // // } // model.describeHighLevel(); // // model.animations.get(0).fullDescribeAnimation(); // // model.describeHighLevel(); // System.exit(0); // } //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); } // RenderUtils.recaptureScreen(); } /** * 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(); 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(); } /// /// /// 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){ //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 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){ 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 /// Globals.profiler.beginCpuSample("sleep"); if(Globals.timekeeper.getMostRecentRawFrametime() < targetFramePeriod){ sleep((int)(1000.0 * (targetFramePeriod - Globals.timekeeper.getMostRecentRawFrametime()))); } else { sleep(1); } Globals.profiler.endCpuSample(); Globals.timekeeper.numberOfRenderedFrames++; if(maxFrames > 0 && Globals.timekeeper.numberOfRenderedFrames > maxFrames){ running = false; } /// /// 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("Main frame uncaught NPE", ex); //after a while, jvm will stop reporting stack traces with errors //need to explicitly kill the vm if you want to see the stack trace if(Globals.ENGINE_DEBUG){ System.exit(1); } } } } /** * 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; } }