Thread shutdown is now more stable, extensive logging of engine startup and shut down, Icon to be displayed in the software, path must be in resources but can be configured in properties file

This commit is contained in:
Harrison Corlett 2026-01-23 09:26:29 +00:00
parent 14282e0b5d
commit afbc46bffb
17 changed files with 122 additions and 12 deletions

View file

@ -1,5 +1,6 @@
package net.halbear.Executable;
import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import net.halbear.Terrain4J.EngineCore.Main.GameCore;
import net.halbear.Terrain4J.EngineCore.Main.PrimaryRuntime;
import org.tinylog.Logger;
@ -7,7 +8,7 @@ import org.tinylog.Logger;
public class Init {
public static void main(String[] args){
Logger.info("\nLaunching New Terrain4J Powered Software!");
var GameEngine = new PrimaryRuntime("Terrain4J", new GameCore());
var GameEngine = new PrimaryRuntime(EngineConfig.getInstance().GetSoftwareName(), new GameCore());
Logger.info("Launched Terrain4J Engine");
GameEngine.run();
}

View file

@ -0,0 +1,49 @@
package net.halbear.Terrain4J.EngineCore.Display;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static org.lwjgl.stb.STBImage.stbi_load;
public class GLFWImageParser {
private ByteBuffer image;
private int width, height;
GLFWImageParser(int width, int height, ByteBuffer image) {
this.image = image;
this.height = height;
this.width = width;
}
public static GLFWImageParser LoadImage(String path) {
ByteBuffer image;
int width, height;
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer comp = stack.mallocInt(1);
IntBuffer w = stack.mallocInt(1);
IntBuffer h = stack.mallocInt(1);
image = stbi_load(path, w, h, comp, 4);
if (image == null) {
System.out.println("Could not load or find image inputted.");
}
width = w.get();
height = h.get();
}
return new GLFWImageParser(width, height, image);
}
public ByteBuffer GetImage() {
return image;
}
public int GetWidth() {
return width;
}
public int GetHeight() {
return height;
}
}

View file

@ -3,10 +3,13 @@ package net.halbear.Terrain4J.EngineCore.Display;
import net.halbear.Terrain4J.EngineCore.Input.KeyboardInput;
import net.halbear.Terrain4J.EngineCore.Input.MouseListener;
import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import org.lwjgl.glfw.GLFWImage;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.system.MemoryUtil;
import org.tinylog.Logger;
import java.io.File;
import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.glfw.GLFWVulkan.glfwVulkanSupported;
@ -48,6 +51,24 @@ public class Window {
height = h;
});
File IconLocation = new File("src/main/resources" + EngineConfig.getInstance().FetchIcon());
GLFWImage Icon = GLFWImage.malloc();
GLFWImage.Buffer IconBuffer = GLFWImage.malloc(1);
String IconPath = "src/main/java/net/halbear/Terrain4J/EngineCore/EngineSpecificAssets/terrain4JlogoFullBlocks.png";
if (IconLocation.exists()){
IconPath =IconLocation.getPath();
Logger.debug("Loading Icon From Location [{}]",IconPath);
} else Logger.warn("Icon specified in config at Location [{}] does not exist, resorting to Engine Icon", IconLocation.getPath());
Icon.set(GLFWImageParser.LoadImage(IconPath).GetWidth(),
GLFWImageParser.LoadImage(IconPath).GetHeight(),
GLFWImageParser.LoadImage(IconPath).GetImage());
IconBuffer.put(0,Icon);
glfwSetWindowIcon(handle, IconBuffer);
IconBuffer.free(); //free the buffer once icon is set so when the program closes there isn't memory still floating around
Logger.debug("Freeing icon Buffer");
glfwMakeContextCurrent(handle);
if (EngineConfig.VSYNC) glfwSwapInterval(1);
else glfwSwapInterval(0);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -33,6 +33,8 @@ public class EngineConfig {
private boolean vkValidated;
private String PhysicalDeviceName;
private int RequestedImages;
private String IconPath = "/WindowResources/Icon/";
private String IconName = "ProgramIcon.png";
public int GetRequestedImages(){
return RequestedImages;
@ -79,6 +81,8 @@ public class EngineConfig {
}
}
if(SuccessfulLoad) {
IconName = (EngineConfigVar.getOrDefault("Software_Icon", "ProgramIcon.png").toString());
IconPath = (EngineConfigVar.getOrDefault("Software_Icon_Path", "/WindowResources/Icon/").toString());
CapMainToTickRate = Boolean.parseBoolean(EngineConfigVar.getOrDefault("cap_main_to_tickrate", "false").toString());
MainTickRate = Integer.parseInt(EngineConfigVar.getOrDefault("main_thread_tickrate", "120").toString());
FastTickRate = Integer.parseInt(EngineConfigVar.getOrDefault("fast_thread_tickrate", "240").toString());
@ -95,6 +99,8 @@ public class EngineConfig {
}
else if(!ConfigFile.exists()){
try {
EngineConfigVar.setProperty("Software_Icon","ProgramIcon.png");
EngineConfigVar.setProperty("Software_Icon_Path","/WindowResources/Icon/");
EngineConfigVar.setProperty("cap_main_to_tickrate","false");
EngineConfigVar.setProperty("main_thread_tickrate","120");
EngineConfigVar.setProperty("fast_thread_tickrate","240");
@ -145,4 +151,10 @@ public class EngineConfig {
public String GetPhysicalDeviceName(){
return PhysicalDeviceName;
}
public String GetSoftwareName(){
return SOFTWARE_TITLE;
}
public String FetchIcon(){
return IconPath + IconName;
}
}

View file

@ -6,6 +6,7 @@ import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.PhysicalDe
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen.Surface;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen.SwapChain;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.VulkanInstance;
import org.tinylog.Logger;
public class VulkanContext {
public static int VulkanLayersOpen = 0;
@ -31,6 +32,8 @@ public class VulkanContext {
device.cleanup();
PhysDevice.cleanup();
Instance.cleanup();
Logger.debug("Render Thread Finished ending Vulkan Processes\nVulkan Layers Open:[{}]", VulkanContext.VulkanLayersOpen < 0 ? "All processes closed and vulkan instance removed" : VulkanContext.VulkanLayersOpen);
VulkanLayersOpen--;
}
public Device GetDevice(){
return device;

View file

@ -19,12 +19,15 @@ public class PrimaryRuntime {
private static Window window;
private static EngineInstance engineInstance;
public static int ThreadsOpen = 0;
public static boolean CanClose = false;
public static long ShutDownStart;
public PrimaryRuntime(String windowTitle, GameLogic appLogic) {
window = new Window(windowTitle);
engineInstance = new EngineInstance(window, new Scene(window));
PrimaryThread = new MainThread(engineInstance, appLogic);
DrawingThread = new RenderThread(window, engineInstance);
Thread.currentThread().setName("Terrain4J Primary Runtime Thread");
}
public void UpdateTickRate(){
@ -42,7 +45,7 @@ public class PrimaryRuntime {
TickRate = (double) 1000000000 / (double) EngineConfig.getInstance().getTickRate();
long FrameAccuracy = EngineConfig.getInstance().getAccuracy();
Logger.debug("Primary Thread Started");
while (!window.shouldClose()){
while (!window.shouldClose() && running){
long now = System.nanoTime();
cycleDiff = now - InitialTime;
FrameDiff += cycleDiff;
@ -71,14 +74,11 @@ public class PrimaryRuntime {
}
InitialTime = now;
}
ShutDownStart = System.nanoTime();
Logger.debug("Closing Software of name [{}]",EngineConfig.getInstance().GetSoftwareName());
PrimaryThread.KillThread();
DrawingThread.KillThread();
cleanup();
Logger.debug("Waiting to close Primary Thread, making sure vulkan has shut down properly first and Engine Threads are closed\nThreads Open:[{}]\nVulkan Layers Open:[{}]",ThreadsOpen, VulkanContext.VulkanLayersOpen);
while(ThreadsOpen > 0 || VulkanContext.VulkanLayersOpen > 0){
}
Logger.debug("Closing Primary Thread");
}
@ -91,5 +91,15 @@ public class PrimaryRuntime {
private void cleanup() {
engineInstance.cleanup();
window.cleanup();
Logger.debug("Process still running: [{}]",Thread.getAllStackTraces().keySet().toString());
if (ThreadsOpen > 0 || VulkanContext.VulkanLayersOpen > -1) {
Logger.debug("Waiting to close Primary Thread, making sure vulkan has shut down properly first and Engine Threads are closed\nThreads Open:[{}]\nVulkan Layers Open:[{}]", ThreadsOpen, VulkanContext.VulkanLayersOpen < 0 ? "All processes closed and vulkan instance removed" : VulkanContext.VulkanLayersOpen);
while ((ThreadsOpen > 0 || VulkanContext.VulkanLayersOpen > 0) && VulkanContext.VulkanLayersOpen > -1 && !CanClose) {
}
}
Logger.debug("Closing Primary Thread, Thread: [{}]",Thread.currentThread().toString());
double ShutDownTime = Math.floor(((double)(System.nanoTime() - ShutDownStart)/1000000.0)*1000.0)/1000.0;
Logger.info("Successful stable Shut Down of Terrain4J in [{}] MilliSeconds", ShutDownTime);
}
}

View file

@ -23,7 +23,8 @@ public class MainThread implements Runnable {
}
public void cleanup(){
gameLogic.cleanup();
Logger.debug("Closing Main Thread");
PrimaryRuntime.ThreadsOpen--;
Logger.debug("Closing Main Thread, Thread: [{}]",Thread.currentThread().toString());
}
public Thread FetchMainThread(){
return mainThread;
@ -37,6 +38,7 @@ public class MainThread implements Runnable {
Logger.debug("Starting Main Thread");
UpdateTickRateThreshold();
mainThread = new Thread(this);
mainThread.setName("Terrain4J Main Engine Thread");
}
public void UpdateTickRateThreshold(){
CappedToTickRate = EngineConfig.getInstance().GetMainTickCap();
@ -86,6 +88,5 @@ public class MainThread implements Runnable {
InitialTime = now;
}
cleanup();
PrimaryRuntime.ThreadsOpen--;
}
}

View file

@ -4,6 +4,7 @@ import net.halbear.Terrain4J.EngineCore.Display.Render;
import net.halbear.Terrain4J.EngineCore.Display.Window;
import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance;
import net.halbear.Terrain4J.EngineCore.Logic.VulkanContext;
import net.halbear.Terrain4J.EngineCore.Main.PrimaryRuntime;
import org.tinylog.Logger;
@ -18,6 +19,7 @@ public class RenderThread implements Runnable {
//this.window = window;
render = new Render(engineInstance);
renderThread = new Thread(this);
renderThread.setName("Terrain4J Vulkan Render Thread");
}
public void KillThread(){
@ -31,7 +33,9 @@ public class RenderThread implements Runnable {
}
private void cleanup() {
render.cleanup();
Logger.debug("Closing Render Thread");
PrimaryRuntime.ThreadsOpen--;
Logger.debug("Closing Render Thread, Thread: [{}]",Thread.currentThread().toString());
if (PrimaryRuntime.ThreadsOpen <= 0 && VulkanContext.VulkanLayersOpen < 0) PrimaryRuntime.CanClose = true;
}
@Override
public void run() {
@ -62,6 +66,5 @@ public class RenderThread implements Runnable {
}
//PrimaryRuntime.CloseRuntime();
cleanup();
PrimaryRuntime.ThreadsOpen--;
}
}

View file

@ -3,6 +3,7 @@ package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.GPUSynchr
import net.halbear.Terrain4J.EngineCore.Logic.VulkanContext;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.VkFenceCreateInfo;
import org.tinylog.Logger;
import java.nio.LongBuffer;
@ -27,6 +28,7 @@ public class Fence {
}
public void cleanup(VulkanContext vulkanContext){
Logger.debug("Destroying Vulkan Fence");
vkDestroyFence(vulkanContext.GetDevice().FetchVulkanDevice(), VulkanFence, null);
}

View file

@ -3,6 +3,7 @@ package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.GPUSynchr
import net.halbear.Terrain4J.EngineCore.Logic.VulkanContext;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.VkSemaphoreCreateInfo;
import org.tinylog.Logger;
import java.nio.LongBuffer;
@ -22,6 +23,7 @@ public class Semaphore {
}
public void cleanup(VulkanContext vulkanContext){
Logger.debug("Destroying Vulkan Semaphore");
vkDestroySemaphore(vulkanContext.GetDevice().FetchVulkanDevice(), VulkanSemaphore, null);
}
public long GetSemaphore(){

View file

@ -3,6 +3,7 @@ package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.VkImageViewCreateInfo;
import org.tinylog.Logger;
import java.nio.LongBuffer;
@ -39,6 +40,7 @@ public class ImageView {
}
public void cleanup(Device device){
Logger.debug("Destroying Image View");
vkDestroyImageView(device.FetchVulkanDevice(), VulkanImageView, null);
}

View file

@ -15,4 +15,6 @@ vsync=true
#This is the name of the GPU you want the engine to use
PhysicalDeviceName=
#Title that shows up on app window
Software_Title=Terrain4J Game Engine
Software_Title=Terrain4J Game Engine
Software_Icon=ProgramIcon.png
Software_Icon_Path=/WindowResources/Icon/

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -17,4 +17,6 @@ RequestedImages=3
PhysicalDeviceName=
#Title that shows up on app window
Software_Title=Terrain4J Game Engine
Software_Icon=ProgramIcon.png
Software_Icon_Path=/WindowResources/Icon/