package me.hammerle.snuviengine.api; import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; import static org.lwjgl.glfw.Callbacks.*; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.system.MemoryUtil.*; public abstract class Engine { private static final String VERSION = "0.0.1"; public static final int TILE_SIZE = 16; public static final float SCALE = 2.0f; private long window; private long fpsLimit = -1; private long sleep = 0; private long nanosPerTick = 10_000_000; private final int maxTicksPerFrame = 20; private final Timer fps = new Timer(60); private final Timer tps = new Timer(1_000_000_000l / nanosPerTick); public Engine() { } public final void run() { initGLFW(); loop(); glfwFreeCallbacks(window); glfwDestroyWindow(window); glfwTerminate(); GLFWErrorCallback error = glfwSetErrorCallback(null); if(error != null) { error.free(); } } public final void stop() { glfwSetWindowShouldClose(window, true); } private void initGLFW() { GLFWErrorCallback.createPrint(System.err).set(); if(!glfwInit()) { throw new IllegalStateException("Unable to initialize GLFW"); } glfwDefaultWindowHints(); glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); window = glfwCreateWindow(Shader.getViewWidth(), Shader.getViewHeight(), "SnuviEngine " + VERSION, NULL, NULL); if(window == NULL) { throw new RuntimeException("Failed to create the GLFW window"); } glfwSetKeyCallback(window, (w, key, scancode, action, mods) -> { if(action == GLFW_RELEASE) { KeyHandler.onKeyUpEvent(key); } else if(action == GLFW_PRESS) { KeyHandler.onKeyDownEvent(key); } }); glfwSetFramebufferSizeCallback(window, (w, fwidth, fheight) -> { glViewport(0, 0, fwidth, fheight); Shader.setViewPort(fwidth, fheight); }); //glfwSetWindowAspectRatio(window, 16, 9); glfwMakeContextCurrent(window); glfwSwapInterval(1); glfwShowWindow(window); } private void sleep(long nanos) { if(nanos < 0) { sleep += nanos; return; } if(sleep < 0) { sleep += nanos / 4; nanos = (nanos * 3) / 4; } long end = System.nanoTime() + nanos - 10000; try { Thread.sleep(nanos / 1_000_000); } catch(InterruptedException ex) { } int i = 0; while(System.nanoTime() < end) { i++; } } private void loop() { GL.createCapabilities(); Shader.init(); init(); fps.update(); tps.update(); long lag = 0; while(!glfwWindowShouldClose(window)) { glfwSwapBuffers(window); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if(fpsLimit > 0) { sleep(fpsLimit - fps.getCurrentTime()); } fps.update(); lag += fps.getTime(); int ticksPerFrame = 0; while(lag >= nanosPerTick) { lag -= nanosPerTick; tps.update(); KeyHandler.tick(); tick(); ticksPerFrame++; if(ticksPerFrame >= maxTicksPerFrame) { long skip = lag / nanosPerTick; lag -= skip * nanosPerTick; if(skip > 0) { System.out.println("skipped " + skip + " game ticks " + lag); } break; } } Shader.doTasks(); renderTick((float) lag / nanosPerTick); tps.draw(); fps.draw(); glfwPollEvents(); } } public final void setNanosPerTick(long nanos) { nanosPerTick = nanos; tps.setExpectedValue(1_000_000_000l / nanos); } public final long getNanosPerTick() { return nanosPerTick; } public final double getTps() { return tps.getCallsPerSecond(); } public final void setRenderTps(boolean active) { tps.setActive(active); } public final double getFps() { return fps.getCallsPerSecond(); } public final void setMaxFps(int max) { fpsLimit = 1_000_000_000 / max; } public final void setRenderFps(boolean active) { fps.setActive(active); } public abstract void init(); public abstract void tick(); public abstract void renderTick(float lag); }