Working on connecting the dots with a7 stuff
This commit is contained in:
@@ -19,12 +19,9 @@
|
||||
|
||||
package com.seibel.lod.core.api.internal;
|
||||
|
||||
import com.seibel.lod.core.ModInfo;
|
||||
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
|
||||
import com.seibel.lod.core.enums.config.VerticalQuality;
|
||||
import com.seibel.lod.core.objects.lod.LodWorld;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* This stores objects and variables that
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2022 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.api.internal.a7;
|
||||
|
||||
import com.seibel.lod.core.Config;
|
||||
import com.seibel.lod.core.ModInfo;
|
||||
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.enums.rendering.RendererType;
|
||||
import com.seibel.lod.core.handlers.LodDimensionFinder;
|
||||
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
|
||||
import com.seibel.lod.core.logging.ConfigBasedLogger;
|
||||
import com.seibel.lod.core.logging.ConfigBasedSpamLogger;
|
||||
import com.seibel.lod.core.logging.SpamReducedLogger;
|
||||
import com.seibel.lod.core.objects.a7.DHLevel;
|
||||
import com.seibel.lod.core.objects.a7.DHWorld;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
import com.seibel.lod.core.render.GLProxy;
|
||||
import com.seibel.lod.core.render.LodRenderer;
|
||||
import com.seibel.lod.core.render.RenderSystemTest;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This holds the methods that should be called
|
||||
* by the host mod loader (Fabric, Forge, etc.).
|
||||
* Specifically for the client.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2022-4-27
|
||||
*/
|
||||
public class ClientApi
|
||||
{
|
||||
public static final Logger LOGGER = LogManager.getLogger(ClientApi.class.getSimpleName());
|
||||
public static boolean prefLoggerEnabled = false;
|
||||
|
||||
public static final ClientApi INSTANCE = new ClientApi();
|
||||
public static final LodBufferBuilderFactory lodBufferBuilderFactory = new LodBufferBuilderFactory();
|
||||
public static LodRenderer renderer = new LodRenderer(lodBufferBuilderFactory);
|
||||
public static RenderSystemTest testRenderer = new RenderSystemTest();
|
||||
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
|
||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
|
||||
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
|
||||
private static final ServerApi EVENT_API = ServerApi.INSTANCE;
|
||||
|
||||
public static final boolean ENABLE_LAG_SPIKE_LOGGING = false;
|
||||
public static final long LAG_SPIKE_THRESHOLD_NS = TimeUnit.NANOSECONDS.convert(16, TimeUnit.MILLISECONDS);
|
||||
|
||||
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
|
||||
|
||||
public static LodDimensionFinder DIMENSION_FINDER = new LodDimensionFinder();;
|
||||
|
||||
public static class LagSpikeCatcher {
|
||||
long timer = System.nanoTime();
|
||||
public LagSpikeCatcher() {}
|
||||
public void end(String source) {
|
||||
if (!ENABLE_LAG_SPIKE_LOGGING) return;
|
||||
timer = System.nanoTime() - timer;
|
||||
if (timer > LAG_SPIKE_THRESHOLD_NS) {
|
||||
LOGGER.info("LagSpikeCatcher: "+source+" took "+Duration.ofNanos(timer)+"!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* there is some setup that should only happen once,
|
||||
* once this is true that setup has completed
|
||||
*/
|
||||
private boolean firstTimeSetupComplete = false;
|
||||
private boolean configOverrideReminderPrinted = false;
|
||||
|
||||
public boolean rendererDisabledBecauseOfExceptions = false;
|
||||
private ClientApi()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static void logToChat(Level logLevel, String str) {
|
||||
String prefix = "["+ModInfo.READABLE_NAME+"] ";
|
||||
if (logLevel == Level.ERROR) {
|
||||
prefix += "\u00A74";
|
||||
} else if (logLevel == Level.WARN) {
|
||||
prefix += "\u00A76";
|
||||
} else if (logLevel == Level.INFO) {
|
||||
prefix += "\u00A7f";
|
||||
} else if (logLevel == Level.DEBUG) {
|
||||
prefix += "\u00A77";
|
||||
} else if (logLevel == Level.TRACE) {
|
||||
prefix += "\u00A78";
|
||||
} else {
|
||||
prefix += "\u00A7f";
|
||||
}
|
||||
prefix += "\u00A7l\u00A7u";
|
||||
prefix += logLevel.name();
|
||||
prefix += ":\u00A7r ";
|
||||
if (MC != null) MC.sendChatMessage(prefix + str);
|
||||
}
|
||||
|
||||
public void clientChunkLoadEvent(IChunkWrapper chunk, IWorldWrapper world)
|
||||
{
|
||||
//TODO: Implement
|
||||
}
|
||||
public void clientChunkSaveEvent(IChunkWrapper chunk, IWorldWrapper world)
|
||||
{
|
||||
//TODO: Implement
|
||||
}
|
||||
|
||||
public void clientLevelUnloadEvent(IWorldWrapper world)
|
||||
{
|
||||
if (SharedApi.currentServer != null) return;
|
||||
if (SharedApi.currentWorld != null) {
|
||||
SharedApi.currentWorld.unloadLevel(world);
|
||||
}
|
||||
}
|
||||
public void clientLevelLoadEvent(IWorldWrapper world)
|
||||
{
|
||||
if (SharedApi.currentServer != null) return;
|
||||
if (SharedApi.currentWorld != null) {
|
||||
SharedApi.currentWorld.getOrLoadLevel(world);
|
||||
}
|
||||
}
|
||||
|
||||
private long lastFlush = 0;
|
||||
|
||||
public void preRender() {
|
||||
IProfilerWrapper profiler = MC.getProfiler();
|
||||
profiler.pop(); // get out of "terrain"
|
||||
profiler.push("DH-PreRender");
|
||||
boolean doFlush = System.nanoTime() - lastFlush >= SPAM_LOGGER_FLUSH_NS;
|
||||
if (doFlush) {
|
||||
lastFlush = System.nanoTime();
|
||||
SpamReducedLogger.flushAll();
|
||||
}
|
||||
ConfigBasedLogger.updateAll();
|
||||
ConfigBasedSpamLogger.updateAll(doFlush);
|
||||
// only run the first time setup once
|
||||
if (!firstTimeSetupComplete) firstFrameSetup();
|
||||
if (ModInfo.IS_DEV_BUILD)
|
||||
{
|
||||
// config overrides should only be used in the developer builds
|
||||
applyDeveloperConfigOverrides();
|
||||
}
|
||||
|
||||
if (SharedApi.currentServer == null && SharedApi.currentWorld != null) {
|
||||
// In single player.
|
||||
SharedApi.currentWorld.asyncTick();
|
||||
}
|
||||
//FIXME: Is it always 'terrain' that is the previous thing in the profiler?
|
||||
profiler.push("terrain"); // go back into "terrain"
|
||||
}
|
||||
|
||||
public void renderLods(IWorldWrapper world, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
|
||||
{
|
||||
IProfilerWrapper profiler = MC.getProfiler();
|
||||
profiler.pop(); // get out of "terrain"
|
||||
profiler.push("DH-RenderLevel");
|
||||
try {
|
||||
if (!MC.playerExists()) return;
|
||||
if (world == null) return;
|
||||
DHWorld dhWorld = SharedApi.currentWorld;
|
||||
if (dhWorld == null) return;
|
||||
DHLevel level = (SharedApi.currentServer == null) ? dhWorld.getOrLoadLevel(world) : dhWorld.getLevel(world);
|
||||
if (level == null) return;
|
||||
|
||||
if (prefLoggerEnabled) {
|
||||
level.dumpRamUsage();
|
||||
}
|
||||
|
||||
if (SharedApi.currentServer == null) {
|
||||
// In multiplayer, without access to the server-side stuff. So we need to do some extra work.
|
||||
level.asyncTick();
|
||||
}
|
||||
|
||||
if (Config.Client.Advanced.Debugging.rendererType.get() == RendererType.DEFAULT) {
|
||||
if (MC_RENDER.playerHasBlindnessEffect()) {
|
||||
// if the player is blind, don't render LODs,
|
||||
// and don't change minecraft's fog
|
||||
// which blindness relies on.
|
||||
return;
|
||||
}
|
||||
if (MC_RENDER.getLightmapWrapper() == null)
|
||||
return;
|
||||
profiler.push("Render-Lods");
|
||||
if (!rendererDisabledBecauseOfExceptions) {
|
||||
try {
|
||||
level.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler);
|
||||
} catch (RuntimeException e) {
|
||||
rendererDisabledBecauseOfExceptions = true;
|
||||
LOGGER.error("Renderer thrown an uncaught exception: ", e);
|
||||
try {
|
||||
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons"
|
||||
+ " renderer has encountered an exception!");
|
||||
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues.");
|
||||
MC.sendChatMessage("\u00A74Exception detail: " + e.toString());
|
||||
} catch (RuntimeException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
profiler.pop(); // "Render-Lods"
|
||||
} else if (Config.Client.Advanced.Debugging.rendererType.get() == RendererType.DEBUG) {
|
||||
profiler.push("Render-Test");
|
||||
try {
|
||||
ClientApi.testRenderer.render();
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.error("Renderer thrown an uncaught exception: ", e);
|
||||
try {
|
||||
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons"
|
||||
+ " renderer has encountered an exception!");
|
||||
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues.");
|
||||
MC.sendChatMessage("\u00A74Exception detail: " + e.toString());
|
||||
} catch (RuntimeException ignored) {
|
||||
}
|
||||
}
|
||||
profiler.pop(); // end LODTestRendering
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("client level rendering uncaught exception: ", e);
|
||||
} finally {
|
||||
profiler.pop(); // end LOD
|
||||
profiler.push("terrain"); // go back into "terrain"
|
||||
}
|
||||
}
|
||||
|
||||
/** used in a development environment to change settings on the fly */
|
||||
private void applyDeveloperConfigOverrides()
|
||||
{
|
||||
// remind the user that the config override is active
|
||||
if (!configOverrideReminderPrinted)
|
||||
{
|
||||
MC.sendChatMessage(ModInfo.READABLE_NAME + " experimental build " + ModInfo.VERSION);
|
||||
MC.sendChatMessage("You are running an unsupported version of the mod!");
|
||||
MC.sendChatMessage("Here be dragons!");
|
||||
|
||||
configOverrideReminderPrinted = true;
|
||||
}
|
||||
}
|
||||
|
||||
//=================//
|
||||
// DEBUG USE //
|
||||
//=================//
|
||||
|
||||
// Trigger once on key press, with CLIENT PLAYER.
|
||||
public void keyPressedEvent(int glfwKey)
|
||||
{
|
||||
if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get())
|
||||
return;
|
||||
|
||||
if (glfwKey == GLFW.GLFW_KEY_F8)
|
||||
{
|
||||
Config.Client.Advanced.Debugging.debugMode.set(DebugMode.next(Config.Client.Advanced.Debugging.debugMode.get()));
|
||||
MC.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugMode.get());
|
||||
}
|
||||
if (glfwKey == GLFW.GLFW_KEY_F6)
|
||||
{
|
||||
Config.Client.Advanced.Debugging.rendererType.set(RendererType.next(Config.Client.Advanced.Debugging.rendererType.get()));
|
||||
MC.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererType.get());
|
||||
}
|
||||
if (glfwKey == GLFW.GLFW_KEY_P)
|
||||
{
|
||||
prefLoggerEnabled = !prefLoggerEnabled;
|
||||
MC.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
//=================//
|
||||
// Lod maintenance //
|
||||
//=================//
|
||||
|
||||
// FIXME: I need a onLastFrameCleanup() callback in Render Thread... Which calls renderer.cleanup()
|
||||
|
||||
/** This event is called once during the first frame Minecraft renders in the world. */
|
||||
public void firstFrameSetup()
|
||||
{
|
||||
// make sure the GLProxy is created before the LodBufferBuilder needs it
|
||||
GLProxy.getInstance();
|
||||
|
||||
firstTimeSetupComplete = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2022 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.api.internal.a7;
|
||||
|
||||
import com.seibel.lod.core.api.internal.InternalApiShared;
|
||||
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
|
||||
import com.seibel.lod.core.builders.worldGeneration.BatchGenerator;
|
||||
import com.seibel.lod.core.enums.WorldType;
|
||||
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.objects.DHChunkPos;
|
||||
import com.seibel.lod.core.objects.DHRegionPos;
|
||||
import com.seibel.lod.core.objects.a7.DHWorld;
|
||||
import com.seibel.lod.core.objects.a7.Server;
|
||||
import com.seibel.lod.core.objects.lod.LodDimension;
|
||||
import com.seibel.lod.core.render.GLProxy;
|
||||
import com.seibel.lod.core.render.LodRenderer;
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
/**
|
||||
* This holds the methods that should be called by the host mod loader (Fabric,
|
||||
* Forge, etc.). Specifically server and client events.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2021-11-12
|
||||
*/
|
||||
public class ServerApi
|
||||
{
|
||||
public static final boolean ENABLE_STACK_DUMP_LOGGING = false;
|
||||
public static final ServerApi INSTANCE = new ServerApi();
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
|
||||
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
|
||||
|
||||
private boolean isCurrentlyOnSinglePlayerServer = false;
|
||||
|
||||
private ServerApi()
|
||||
{
|
||||
}
|
||||
|
||||
// =============//
|
||||
// tick events //
|
||||
// =============//
|
||||
|
||||
private int lastWorldGenTickDelta = 0;
|
||||
public void serverTickEvent()
|
||||
{
|
||||
lastWorldGenTickDelta--;
|
||||
if (SharedApi.currentWorld != null && lastWorldGenTickDelta <= 0) {
|
||||
lastWorldGenTickDelta = 20;
|
||||
DHWorld dhWorld = SharedApi.currentWorld;
|
||||
dhWorld.tick();
|
||||
}
|
||||
}
|
||||
|
||||
public void serverWorldLoadEvent() {
|
||||
Server server = new Server();
|
||||
SharedApi.currentServer = server;
|
||||
SharedApi.currentWorld = new DHWorld();
|
||||
}
|
||||
|
||||
public void serverWorldUnloadEvent() {
|
||||
SharedApi.currentWorld.close();
|
||||
SharedApi.currentWorld = null;
|
||||
SharedApi.currentServer = null;
|
||||
}
|
||||
|
||||
public void serverLevelLoadEvent(IWorldWrapper world) {
|
||||
SharedApi.currentWorld.getOrLoadLevel(world);
|
||||
}
|
||||
public void serverLevelUnloadEvent(IWorldWrapper world) {
|
||||
SharedApi.currentWorld.unloadLevel(world);
|
||||
}
|
||||
public void serverSaveEvent() {
|
||||
SharedApi.currentWorld.save();
|
||||
}
|
||||
|
||||
public void chunkSaveEvent(IChunkWrapper chunk, IWorldWrapper world) {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.seibel.lod.core.api.internal.a7;
|
||||
|
||||
import com.seibel.lod.core.objects.a7.DHWorld;
|
||||
import com.seibel.lod.core.objects.a7.Server;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
|
||||
public class SharedApi {
|
||||
public static DHWorld currentWorld;
|
||||
public static Server currentServer;
|
||||
public static IMinecraftSharedWrapper MC;
|
||||
public static IMinecraftClientWrapper MC_CLIENT;
|
||||
|
||||
public static void onServerStart() {
|
||||
if (MC.isServerJar()) {
|
||||
ServerApi.INSTANCE.serverWorldLoadEvent();
|
||||
} else if (MC_CLIENT.hasSinglePlayerServer()) {
|
||||
ServerApi.INSTANCE.serverWorldLoadEvent();
|
||||
} // else do nothing
|
||||
}
|
||||
|
||||
public static void onServerStop() {
|
||||
if (MC.isServerJar()) {
|
||||
ServerApi.INSTANCE.serverWorldUnloadEvent();
|
||||
} else if (MC_CLIENT.hasSinglePlayerServer()) {
|
||||
ServerApi.INSTANCE.serverWorldUnloadEvent();
|
||||
} // else do nothing
|
||||
}
|
||||
|
||||
public static void onLevelLoad(IWorldWrapper world) {
|
||||
if (MC.isServerJar()) {
|
||||
ServerApi.INSTANCE.serverLevelLoadEvent(world);
|
||||
} else if (MC_CLIENT.hasSinglePlayerServer()) {
|
||||
ServerApi.INSTANCE.serverLevelLoadEvent(world);
|
||||
} else {
|
||||
ClientApi.INSTANCE.clientLevelLoadEvent(world);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onLevelUnload(IWorldWrapper world) {
|
||||
if (MC.isServerJar()) {
|
||||
ServerApi.INSTANCE.serverLevelUnloadEvent(world);
|
||||
} else if (MC_CLIENT.hasSinglePlayerServer()) {
|
||||
ServerApi.INSTANCE.serverLevelUnloadEvent(world);
|
||||
} else {
|
||||
ClientApi.INSTANCE.clientLevelUnloadEvent(world);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,24 +51,37 @@ public enum DebugMode
|
||||
/** Only draw overlapping LOD quads, and draws in wireframe. */
|
||||
SHOW_OVERLAPPING_QUADS_WIREFRAME;
|
||||
|
||||
/** used when cycling through the different modes */
|
||||
private DebugMode next;
|
||||
|
||||
static
|
||||
{
|
||||
OFF.next = SHOW_WIREFRAME;
|
||||
SHOW_WIREFRAME.next = SHOW_DETAIL;
|
||||
SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME;
|
||||
SHOW_DETAIL_WIREFRAME.next = SHOW_GENMODE;
|
||||
SHOW_GENMODE.next = SHOW_GENMODE_WIREFRAME;
|
||||
SHOW_GENMODE_WIREFRAME.next = SHOW_OVERLAPPING_QUADS;
|
||||
SHOW_OVERLAPPING_QUADS.next = SHOW_OVERLAPPING_QUADS_WIREFRAME;
|
||||
SHOW_OVERLAPPING_QUADS_WIREFRAME.next = OFF;
|
||||
}
|
||||
|
||||
/** returns the next debug mode */
|
||||
// Deprecated: use DebugMode.next() instead
|
||||
@Deprecated
|
||||
public DebugMode getNext()
|
||||
{
|
||||
return this.next;
|
||||
return next(this);
|
||||
}
|
||||
|
||||
public static DebugMode next(DebugMode type) {
|
||||
switch (type) {
|
||||
case OFF: return SHOW_WIREFRAME;
|
||||
case SHOW_WIREFRAME: return SHOW_DETAIL;
|
||||
case SHOW_DETAIL: return SHOW_DETAIL_WIREFRAME;
|
||||
case SHOW_DETAIL_WIREFRAME: return SHOW_GENMODE;
|
||||
case SHOW_GENMODE: return SHOW_GENMODE_WIREFRAME;
|
||||
case SHOW_GENMODE_WIREFRAME: return SHOW_OVERLAPPING_QUADS;
|
||||
case SHOW_OVERLAPPING_QUADS: return SHOW_OVERLAPPING_QUADS_WIREFRAME;
|
||||
default: return OFF;
|
||||
}
|
||||
}
|
||||
|
||||
public static DebugMode previous(DebugMode type) {
|
||||
switch (type) {
|
||||
case OFF: return SHOW_OVERLAPPING_QUADS_WIREFRAME;
|
||||
case SHOW_OVERLAPPING_QUADS_WIREFRAME: return SHOW_OVERLAPPING_QUADS;
|
||||
case SHOW_OVERLAPPING_QUADS: return SHOW_GENMODE_WIREFRAME;
|
||||
case SHOW_GENMODE_WIREFRAME: return SHOW_GENMODE;
|
||||
case SHOW_GENMODE: return SHOW_DETAIL_WIREFRAME;
|
||||
case SHOW_DETAIL_WIREFRAME: return SHOW_DETAIL;
|
||||
case SHOW_DETAIL: return SHOW_WIREFRAME;
|
||||
default: return OFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
package com.seibel.lod.core.objects.a7;
|
||||
|
||||
import com.seibel.lod.core.api.internal.InternalApiShared;
|
||||
import com.seibel.lod.core.api.internal.a7.ClientApi;
|
||||
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
|
||||
import com.seibel.lod.core.objects.a7.data.DataFileHandler;
|
||||
import com.seibel.lod.core.objects.a7.pos.DhBlockPos2D;
|
||||
import com.seibel.lod.core.objects.a7.render.RenderBufferHandler;
|
||||
import com.seibel.lod.core.render.LodRenderProgram;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
import com.seibel.lod.core.render.a7LodRenderer;
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.core.util.EventLoop;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class DHLevel extends LodQuadTree {
|
||||
public class DHLevel extends LodQuadTree implements Closeable {
|
||||
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
|
||||
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
|
||||
public final File saveFolder; // Could be null, for no saving
|
||||
@@ -23,11 +30,16 @@ public class DHLevel extends LodQuadTree {
|
||||
public final ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHLevelTickerThread", 2);
|
||||
private final AtomicBoolean isRunning = new AtomicBoolean(false);
|
||||
public final IWorldWrapper level;
|
||||
public a7LodRenderer renderer;
|
||||
public final DHWorld world;
|
||||
|
||||
public DHLevel(File saveFolder, IWorldWrapper level) {
|
||||
public EventLoop eventLoop;
|
||||
|
||||
public DHLevel(DHWorld world, File saveFolder, IWorldWrapper level) {
|
||||
super(CONFIG.client().graphics().quality().getLodChunkRenderDistance()*16,
|
||||
MC.getPlayerBlockPos().x,
|
||||
MC.getPlayerBlockPos().z);
|
||||
this.world = world;
|
||||
this.saveFolder = saveFolder;
|
||||
if (saveFolder != null) {
|
||||
dataFileHandler = new DataFileHandler(saveFolder, this);
|
||||
@@ -36,6 +48,7 @@ public class DHLevel extends LodQuadTree {
|
||||
}
|
||||
renderBufferHandler = new RenderBufferHandler(this);
|
||||
this.level = level;
|
||||
eventLoop = new EventLoop(world.dhTickerThread, this::tick);
|
||||
}
|
||||
|
||||
// Should be called by server tick thread, or called by render thread but only 20 times per second, or less?
|
||||
@@ -64,11 +77,60 @@ public class DHLevel extends LodQuadTree {
|
||||
return dataFileHandler;
|
||||
}
|
||||
|
||||
public void render(LodRenderProgram renderContext) {
|
||||
renderBufferHandler.render(renderContext);
|
||||
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) {
|
||||
if (renderer == null) {
|
||||
renderer = new a7LodRenderer(this);
|
||||
}
|
||||
renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler);
|
||||
}
|
||||
|
||||
public int getMinY() {
|
||||
return level.getMinHeight();
|
||||
}
|
||||
public void dumpRamUsage() {
|
||||
//TODO
|
||||
}
|
||||
public void asyncTick() {
|
||||
eventLoop.tick();
|
||||
}
|
||||
public void close() {
|
||||
eventLoop.halt();
|
||||
if (dataFileHandler != null) {
|
||||
dataFileHandler.close();
|
||||
}
|
||||
}
|
||||
public void saveFlush() {
|
||||
if (dataFileHandler != null) {
|
||||
dataFileHandler.save();
|
||||
}
|
||||
}
|
||||
|
||||
public void viewDistanceChangedEvent()
|
||||
{
|
||||
// calculate how wide the dimension(s) should be in regions
|
||||
int chunksWide;
|
||||
if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
|
||||
chunksWide = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(),
|
||||
LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
|
||||
else
|
||||
chunksWide = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 2 + 1;
|
||||
|
||||
int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
|
||||
// make sure we have an odd number of regions
|
||||
newWidth += (newWidth & 1) == 0 ? 1 : 0;
|
||||
|
||||
// do the dimensions need to change in size?
|
||||
if (InternalApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
|
||||
{
|
||||
// update the dimensions to fit the new width
|
||||
InternalApiShared.lodWorld.resizeDimensionRegionWidth(newWidth);
|
||||
InternalApiShared.lodBuilder.defaultDimensionWidthInRegions = newWidth;
|
||||
ClientApi.renderer.setupBuffers();
|
||||
|
||||
recalculateWidths = false;
|
||||
// LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: "
|
||||
// + newWidth );
|
||||
}
|
||||
DetailDistanceUtil.updateSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
package com.seibel.lod.core.objects.a7;
|
||||
|
||||
import com.seibel.lod.core.handlers.LodDimensionFinder;
|
||||
import com.seibel.lod.core.Config;
|
||||
import com.seibel.lod.core.api.internal.InternalApiShared;
|
||||
import com.seibel.lod.core.api.internal.a7.ClientApi;
|
||||
import com.seibel.lod.core.objects.a7.io.DHFolderHandler;
|
||||
import com.seibel.lod.core.objects.a7.io.LevelToFileMatcher;
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.core.util.EventLoop;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class DHWorld {
|
||||
public class DHWorld implements Closeable {
|
||||
private final File saveDir;
|
||||
private final HashMap<IWorldWrapper, DHLevel> levels;
|
||||
|
||||
private LevelToFileMatcher levelToFileMatcher = null;
|
||||
|
||||
public ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHTickerThread", 2);
|
||||
|
||||
public EventLoop eventLoop = new EventLoop(dhTickerThread, this::tick);
|
||||
|
||||
public DHWorld() {
|
||||
saveDir = DHFolderHandler.getCurrentWorldFolder();
|
||||
levels = new HashMap<>();
|
||||
}
|
||||
|
||||
public DHLevel getLevel(IWorldWrapper wrapper) {
|
||||
public DHLevel getOrLoadLevel(IWorldWrapper wrapper) {
|
||||
if (!levels.containsKey(wrapper)) {
|
||||
if (levelToFileMatcher == null || levelToFileMatcher.getTargetWorld() != wrapper) {
|
||||
levelToFileMatcher = new LevelToFileMatcher(saveDir, wrapper);
|
||||
@@ -34,4 +45,48 @@ public class DHWorld {
|
||||
}
|
||||
} else return levels.get(wrapper);
|
||||
}
|
||||
|
||||
public DHLevel getLevel(IWorldWrapper wrapper) {
|
||||
return levels.get(wrapper);
|
||||
}
|
||||
|
||||
public void unloadLevel(IWorldWrapper wrapper) {
|
||||
if (levels.containsKey(wrapper)) {
|
||||
levels.get(wrapper).close();
|
||||
levels.remove(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
int newViewDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * 16;
|
||||
Iterator<DHLevel> iterator = levels.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
DHLevel level = iterator.next();
|
||||
if (level.viewDistance != newViewDistance) {
|
||||
level.close();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
DetailDistanceUtil.updateSettings();
|
||||
}
|
||||
public void doWorldGen() {
|
||||
}
|
||||
public void asyncTick() {
|
||||
eventLoop.tick();
|
||||
}
|
||||
|
||||
public void save() {
|
||||
for (DHLevel level : levels.values()) {
|
||||
level.saveFlush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
eventLoop.halt();
|
||||
for (DHLevel level : levels.values()) {
|
||||
level.close();
|
||||
}
|
||||
levels.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ public abstract class LodQuadTree {
|
||||
}
|
||||
|
||||
final SectionDetailLayer[] sectionDetailLayers;
|
||||
public final int viewDistance;
|
||||
|
||||
/**
|
||||
* Constructor of the quadTree
|
||||
@@ -106,6 +107,7 @@ public abstract class LodQuadTree {
|
||||
*/
|
||||
public LodQuadTree(int viewDistance, int initialPlayerX, int initialPlayerZ) {
|
||||
assertContainerTypeConfigCorrect();
|
||||
this.viewDistance = viewDistance;
|
||||
|
||||
{ // Calculate the max section detail
|
||||
byte maxDetailLevel = getMaxDetailInRange(viewDistance * Math.sqrt(2));
|
||||
|
||||
@@ -107,7 +107,17 @@ public class DataFile {
|
||||
return path.exists() && path.isFile() && path.canRead() && path.canWrite();
|
||||
}
|
||||
|
||||
public void save(DHLevel level) throws IOException {
|
||||
public void saveIfNeeded(DHLevel level, boolean freeMemory) {
|
||||
if (loadedData == null) return;
|
||||
if (!verifyPath()) return;
|
||||
try {
|
||||
save(level, freeMemory);
|
||||
} catch (IOException e) {
|
||||
//FIXME: Log and review this handling
|
||||
}
|
||||
}
|
||||
|
||||
public void save(DHLevel level, boolean freeMemory) throws IOException {
|
||||
if (loadedData == null) throw new IllegalStateException("No data loaded");
|
||||
if (!verifyPath()) throw new IOException("File path became invalid");
|
||||
DataSourceSaver saver;
|
||||
@@ -149,7 +159,15 @@ public class DataFile {
|
||||
|
||||
dataLevel = newDataLevel;
|
||||
loader = saver;
|
||||
if (freeMemory) {
|
||||
loadedData = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void close(DHLevel level) {
|
||||
if (loadedData != null) {
|
||||
saveIfNeeded(level, true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,17 @@ import com.seibel.lod.core.objects.a7.render.RenderDataSource;
|
||||
import com.seibel.lod.core.objects.a7.render.RenderDataSourceLoader;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.rmi.server.ExportException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DataFileHandler implements RenderDataProvider {
|
||||
public class DataFileHandler implements RenderDataProvider, Closeable {
|
||||
public static final List<OldFileConverter> CONVERTERS = new ArrayList<>();
|
||||
public static final String FILE_EXTENSION = ".lod";
|
||||
|
||||
@@ -122,7 +124,7 @@ public class DataFileHandler implements RenderDataProvider {
|
||||
DataFile dataFile = new DataFile(newFile, saver, dataSource);
|
||||
dataFiles.put(pos, dataFile);
|
||||
try {
|
||||
dataFile.save(level);
|
||||
dataFile.save(level, false);
|
||||
} catch (Exception e) {
|
||||
dataFiles.remove(pos, dataFile);
|
||||
//TODO: Log error
|
||||
@@ -141,4 +143,17 @@ public class DataFileHandler implements RenderDataProvider {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
IO_MANAGER.shutdown();
|
||||
try {
|
||||
IO_MANAGER.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
dataFiles.values().forEach((f) -> f.close(level));
|
||||
}
|
||||
|
||||
public void save() {
|
||||
//TODO: Make it free memory that is not needed
|
||||
dataFiles.values().forEach(f -> f.saveIfNeeded(level, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.seibel.lod.core.objects.Pos2D;
|
||||
import com.seibel.lod.core.objects.a7.LodQuadTree;
|
||||
import com.seibel.lod.core.objects.a7.LodSection;
|
||||
import com.seibel.lod.core.objects.a7.pos.DhSectionPos;
|
||||
import com.seibel.lod.core.objects.a7.render.RenderBuffer;
|
||||
import com.seibel.lod.core.render.LodRenderProgram;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.gridList.MovableGridRingList;
|
||||
@@ -142,4 +141,5 @@ public class RenderBufferHandler {
|
||||
public void close() {
|
||||
renderBufferNodes.clear(RenderBufferNode::close);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2022 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.render;
|
||||
|
||||
import com.seibel.lod.core.Config;
|
||||
import com.seibel.lod.core.api.internal.InternalApiShared;
|
||||
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.enums.rendering.FogColorMode;
|
||||
import com.seibel.lod.core.enums.rendering.FogDistance;
|
||||
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
|
||||
import com.seibel.lod.core.logging.ConfigBasedLogger;
|
||||
import com.seibel.lod.core.logging.ConfigBasedSpamLogger;
|
||||
import com.seibel.lod.core.objects.BoolType;
|
||||
import com.seibel.lod.core.objects.DHBlockPos;
|
||||
import com.seibel.lod.core.objects.Pos2D;
|
||||
import com.seibel.lod.core.objects.a7.DHLevel;
|
||||
import com.seibel.lod.core.objects.lod.LodDimension;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
import com.seibel.lod.core.objects.math.Vec3d;
|
||||
import com.seibel.lod.core.objects.math.Vec3f;
|
||||
import com.seibel.lod.core.objects.opengl.RenderRegion;
|
||||
import com.seibel.lod.core.render.objects.GLState;
|
||||
import com.seibel.lod.core.render.objects.QuadElementBuffer;
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.gridList.EdgeDistanceBooleanGrid;
|
||||
import com.seibel.lod.core.util.gridList.MovableGridRingList;
|
||||
import com.seibel.lod.core.util.gridList.PosArrayGridList;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.misc.ILightMapWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import java.awt.*;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This is where all the magic happens. <br>
|
||||
* This is where LODs are draw to the world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-12-2021
|
||||
*/
|
||||
public class a7LodRenderer
|
||||
{
|
||||
public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(a7LodRenderer.class),
|
||||
() -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get());
|
||||
|
||||
public static ConfigBasedSpamLogger tickLogger = new ConfigBasedSpamLogger(LogManager.getLogger(a7LodRenderer.class),
|
||||
() -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get(),1);
|
||||
public static final boolean ENABLE_DRAW_LAG_SPIKE_LOGGING = false;
|
||||
public static final boolean ENABLE_DUMP_GL_STATE = true;
|
||||
public static final long DRAW_LAG_SPIKE_THRESHOLD_NS = TimeUnit.NANOSECONDS.convert(20, TimeUnit.MILLISECONDS);
|
||||
|
||||
public static final boolean ENABLE_IBO = true;
|
||||
public static class LagSpikeCatcher {
|
||||
long timer = System.nanoTime();
|
||||
public LagSpikeCatcher() {}
|
||||
public void end(String source) {
|
||||
if (!ENABLE_DRAW_LAG_SPIKE_LOGGING) return;
|
||||
timer = System.nanoTime() - timer;
|
||||
if (timer> DRAW_LAG_SPIKE_THRESHOLD_NS) { //4 ms
|
||||
EVENT_LOGGER.debug("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
|
||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
|
||||
|
||||
public DebugMode previousDebugMode = null;
|
||||
public final DHLevel level;
|
||||
|
||||
// The shader program
|
||||
LodRenderProgram shaderProgram = null;
|
||||
public QuadElementBuffer quadIBO = null;
|
||||
|
||||
public a7LodRenderer(DHLevel level)
|
||||
{
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public void drawLODs(Mat4f baseModelViewMatrix, Mat4f baseProjectionMatrix, float partialTicks, IProfilerWrapper profiler)
|
||||
{
|
||||
//=================================//
|
||||
// determine if LODs should render //
|
||||
//=================================//
|
||||
if (MC_RENDER.playerHasBlindnessEffect())
|
||||
{
|
||||
// if the player is blind, don't render LODs,
|
||||
// and don't change minecraft's fog
|
||||
// which blindness relies on.
|
||||
return;
|
||||
}
|
||||
if (MC_RENDER.getLightmapWrapper() == null)
|
||||
return;
|
||||
|
||||
// get MC's shader program
|
||||
// Save all MC render state
|
||||
LagSpikeCatcher drawSaveGLState = new LagSpikeCatcher();
|
||||
GLState currentState = new GLState();
|
||||
if (ENABLE_DUMP_GL_STATE) {
|
||||
tickLogger.debug("Saving GL state: {}", currentState);
|
||||
}
|
||||
drawSaveGLState.end("drawSaveGLState");
|
||||
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
if (canVanillaFogBeDisabled && CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
|
||||
if (!MC_RENDER.tryDisableVanillaFog())
|
||||
canVanillaFogBeDisabled = false;
|
||||
|
||||
// TODO move the buffer regeneration logic into its own class (probably called in the client api instead)
|
||||
// starting here...
|
||||
LagSpikeCatcher updateStatus = new LagSpikeCatcher();
|
||||
updateRegenStatus(lodDim, partialTicks);
|
||||
updateStatus.end("LodDrawSetup:UpdateStatus");
|
||||
|
||||
|
||||
// FIXME: Currently, we check for last Lod Dimension so that we can trigger a cleanup() if dimension has changed
|
||||
// The better thing to do is to call cleanup() on leaving dimensions in the EventApi, but only for client-side.
|
||||
if (markToCleanup) {
|
||||
LagSpikeCatcher drawObjectClenup = new LagSpikeCatcher();
|
||||
markToCleanup = false;
|
||||
cleanup(); // This will unset the isSetupComplete, causing a setup() call.
|
||||
drawObjectClenup.end("drawObjectClenup");
|
||||
}
|
||||
|
||||
//=================//
|
||||
// create the LODs //
|
||||
//=================//
|
||||
|
||||
// only regenerate the LODs if:
|
||||
// 1. we want to regenerate LODs
|
||||
// 2. we aren't already regenerating the LODs
|
||||
// 3. we aren't waiting for the build and draw buffers to swap
|
||||
// (this is to prevent thread conflicts)
|
||||
LagSpikeCatcher swapBuffer = new LagSpikeCatcher();
|
||||
if (partialRegen || fullRegen) {
|
||||
if (lodBufferBuilderFactory.updateAndSwapLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(),
|
||||
MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), fullRegen)) {
|
||||
// the regen process has been started,
|
||||
// it will be done when lodBufferBuilder.newBuffersAvailable() is true
|
||||
fullRegen = false;
|
||||
partialRegen = false;
|
||||
}
|
||||
}
|
||||
swapBuffer.end("SwapBuffer");
|
||||
// Get the front buffers to draw
|
||||
MovableGridRingList<RenderRegion> regions = lodBufferBuilderFactory.getRenderRegions();
|
||||
|
||||
if (regions == null) {
|
||||
// There is no vbos, which means nothing needs to be drawn. So skip rendering
|
||||
return;
|
||||
}
|
||||
|
||||
//===================//
|
||||
// draw params setup //
|
||||
//===================//
|
||||
|
||||
profiler.push("LOD draw setup");
|
||||
LagSpikeCatcher drawSetup = new LagSpikeCatcher();
|
||||
/*---------Set GL State--------*/
|
||||
// Make sure to unbind current VBO so we don't mess up vanilla settings
|
||||
LagSpikeCatcher drawGLSetup = new LagSpikeCatcher();
|
||||
LagSpikeCatcher drawBindBuff = new LagSpikeCatcher();
|
||||
//GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer());
|
||||
GL32.glViewport(0,0, MC_RENDER.getTargetFrameBufferViewportWidth(), MC_RENDER.getTargetFrameBufferViewportHeight());
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
|
||||
drawBindBuff.end("drawBindBuff");
|
||||
// set the required open GL settings
|
||||
LagSpikeCatcher drawSetPolygon = new LagSpikeCatcher();
|
||||
if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME
|
||||
|| CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_GENMODE_WIREFRAME
|
||||
|| CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_WIREFRAME
|
||||
|| CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_OVERLAPPING_QUADS_WIREFRAME) {
|
||||
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
|
||||
//GL32.glDisable(GL32.GL_CULL_FACE);
|
||||
}
|
||||
else {
|
||||
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
|
||||
GL32.glEnable(GL32.GL_CULL_FACE);
|
||||
}
|
||||
drawSetPolygon.end("drawSetPolygon");
|
||||
LagSpikeCatcher drawEnableDepth = new LagSpikeCatcher();
|
||||
GL32.glEnable(GL32.GL_DEPTH_TEST);
|
||||
// GL32.glDisable(GL32.GL_DEPTH_TEST);
|
||||
GL32.glDepthFunc(GL32.GL_LESS);
|
||||
drawEnableDepth.end("drawEnableDepth");
|
||||
drawGLSetup.end("drawGLSetup");
|
||||
// enable transparent rendering
|
||||
// GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA);
|
||||
// GL32.glEnable(GL32.GL_BLEND);
|
||||
GL32.glDisable(GL32.GL_BLEND);
|
||||
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
/*---------Bind required objects--------*/
|
||||
// Setup LodRenderProgram and the LightmapTexture if it has not yet been done
|
||||
// also binds LightmapTexture, VAO, and ShaderProgram
|
||||
if (!isSetupComplete) {
|
||||
LagSpikeCatcher drawObjectSetup = new LagSpikeCatcher();
|
||||
setup();
|
||||
drawObjectSetup.end("drawObjectSetup");
|
||||
} else {
|
||||
LagSpikeCatcher drawShaderBind = new LagSpikeCatcher();
|
||||
LodFogConfig newConfig = shaderProgram.isShaderUsable();
|
||||
if (newConfig != null) {
|
||||
shaderProgram.free();
|
||||
shaderProgram = new LodRenderProgram(newConfig);
|
||||
}
|
||||
shaderProgram.bind();
|
||||
drawShaderBind.end("drawShaderBind");
|
||||
}
|
||||
LagSpikeCatcher drawSetActiveTexture = new LagSpikeCatcher();
|
||||
GL32.glActiveTexture(GL32.GL_TEXTURE0);
|
||||
drawSetActiveTexture.end("drawSetActiveTexture");
|
||||
LagSpikeCatcher drawCalculateParams = new LagSpikeCatcher();
|
||||
//LightmapTexture lightmapTexture = new LightmapTexture();
|
||||
|
||||
/*---------Get required data--------*/
|
||||
// Get the matrixs for rendering
|
||||
int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
|
||||
int farPlaneBlockDistance;
|
||||
// required for setupFog and setupProjectionMatrix
|
||||
if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
|
||||
farPlaneBlockDistance = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH;
|
||||
else
|
||||
farPlaneBlockDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * LodUtil.CHUNK_WIDTH;
|
||||
drawCalculateParams.end("drawCalculateParams");
|
||||
|
||||
Mat4f combinedMatrix = createCombinedMatrix(baseProjectionMatrix, baseModelViewMatrix,
|
||||
vanillaBlockRenderedDistance, farPlaneBlockDistance, partialTicks);
|
||||
|
||||
/*---------Fill uniform data--------*/
|
||||
LagSpikeCatcher drawFillData = new LagSpikeCatcher();
|
||||
// Fill the uniform data. Note: GL33.GL_TEXTURE0 == texture bindpoint 0
|
||||
shaderProgram.fillUniformData(combinedMatrix,
|
||||
MC_RENDER.isFogStateSpecial() ? getSpecialFogColor(partialTicks) : getFogColor(partialTicks),
|
||||
0, MC.getWrappedClientWorld().getHeight(), MC.getWrappedClientWorld().getMinHeight(), farPlaneBlockDistance,
|
||||
vanillaBlockRenderedDistance, MC_RENDER.isFogStateSpecial());
|
||||
|
||||
// Note: Since lightmapTexture is changing every frame, it's faster to recreate it than to reuse the old one.
|
||||
LagSpikeCatcher drawFillLightmap = new LagSpikeCatcher();
|
||||
ILightMapWrapper lightmap = MC_RENDER.getLightmapWrapper();
|
||||
lightmap.bind();
|
||||
|
||||
if (ENABLE_IBO) quadIBO.bind();
|
||||
|
||||
//lightmapTexture.fillData(MC_RENDER.getLightmapTextureWidth(), MC_RENDER.getLightmapTextureHeight(), MC_RENDER.getLightmapPixels());
|
||||
drawFillLightmap.end("drawFillLightmap");
|
||||
drawFillData.end("DrawFillData");
|
||||
//GL32.glEnable( GL32.GL_POLYGON_OFFSET_FILL );
|
||||
//GL32.glPolygonOffset( 1f, 1f );
|
||||
|
||||
//===========//
|
||||
// rendering //
|
||||
//===========//
|
||||
drawSetup.end("LodDrawSetup");
|
||||
profiler.popPush("LOD draw");
|
||||
LagSpikeCatcher draw = new LagSpikeCatcher();
|
||||
|
||||
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
|
||||
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
|
||||
DHBlockPos cameraBlockPos = MC_RENDER.getCameraBlockPosition();
|
||||
Vec3f cameraDir = MC_RENDER.getLookAtVector();
|
||||
int drawCount = 0;
|
||||
|
||||
{
|
||||
int ox,oy,dx,dy;
|
||||
ox = oy = dx = 0;
|
||||
dy = -1;
|
||||
int len = regions.getSize();
|
||||
int maxI = len*len;
|
||||
int halfLen = len/2;
|
||||
for(int i =0; i < maxI; i++){
|
||||
if ((-halfLen <= ox) && (ox <= halfLen) && (-halfLen <= oy) && (oy <= halfLen)){
|
||||
Pos2D pos = regions.getCenter();
|
||||
int regionX = ox+pos.x;
|
||||
int regionZ = oy+pos.y;
|
||||
{
|
||||
RenderRegion region = regions.get(regionX, regionZ);
|
||||
if (region == null) continue;
|
||||
if (region.render(lodDim, cameraPos, cameraBlockPos, cameraDir,
|
||||
!cullingDisabled, shaderProgram)) drawCount++;
|
||||
}
|
||||
}
|
||||
if( (ox == oy) || ((ox < 0) && (ox == -oy)) || ((ox > 0) && (ox == 1-oy))){
|
||||
int temp = dx;
|
||||
dx = -dy;
|
||||
dy = temp;
|
||||
}
|
||||
ox += dx;
|
||||
oy += dy;
|
||||
}
|
||||
}
|
||||
//if (drawCall==0)
|
||||
// tickLogger.info("DrawCall Count: {}", drawCount);
|
||||
|
||||
//================//
|
||||
// render cleanup //
|
||||
//================//
|
||||
draw.end("LodDraw");
|
||||
profiler.popPush("LOD cleanup");
|
||||
LagSpikeCatcher drawCleanup = new LagSpikeCatcher();
|
||||
lightmap.unbind();
|
||||
if (ENABLE_IBO) quadIBO.unbind();
|
||||
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
shaderProgram.unbind();
|
||||
//lightmapTexture.free();
|
||||
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
currentState.restore();
|
||||
drawCleanup.end("LodDrawCleanup");
|
||||
|
||||
// end of internal LOD profiling
|
||||
profiler.pop();
|
||||
tickLogger.incLogTries();
|
||||
}
|
||||
|
||||
//=================//
|
||||
// Setup Functions //
|
||||
//=================//
|
||||
|
||||
/** Setup all render objects - REQUIRES to be in render thread */
|
||||
private void setup() {
|
||||
if (isSetupComplete) {
|
||||
EVENT_LOGGER.warn("Renderer setup called but it has already completed setup!");
|
||||
return;
|
||||
}
|
||||
if (!GLProxy.hasInstance()) {
|
||||
EVENT_LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
EVENT_LOGGER.info("Setting up renderer");
|
||||
isSetupComplete = true;
|
||||
shaderProgram = new LodRenderProgram(LodFogConfig.generateFogConfig());
|
||||
if (ENABLE_IBO) {
|
||||
quadIBO = new QuadElementBuffer();
|
||||
quadIBO.reserve(LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER);
|
||||
}
|
||||
EVENT_LOGGER.info("Renderer setup complete");
|
||||
}
|
||||
|
||||
/** Create all buffers that will be used. */
|
||||
public void setupBuffers()
|
||||
{
|
||||
lodBufferBuilderFactory.triggerReset();
|
||||
}
|
||||
|
||||
private Color getFogColor(float partialTicks)
|
||||
{
|
||||
Color fogColor;
|
||||
|
||||
if (CONFIG.client().graphics().fogQuality().getFogColorMode() == FogColorMode.USE_SKY_COLOR)
|
||||
fogColor = MC_RENDER.getSkyColor();
|
||||
else
|
||||
fogColor = MC_RENDER.getFogColor(partialTicks);
|
||||
|
||||
return fogColor;
|
||||
}
|
||||
private Color getSpecialFogColor(float partialTicks)
|
||||
{
|
||||
return MC_RENDER.getSpecialFogColor(partialTicks);
|
||||
}
|
||||
|
||||
private static float calculateNearClipPlane(float distance, float partialTicks) {
|
||||
double fov = MC_RENDER.getFov(partialTicks);
|
||||
double aspectRatio = (double)MC_RENDER.getScreenWidth()/MC_RENDER.getScreenHeight();
|
||||
return (float) (distance
|
||||
/ Math.sqrt(1d + LodUtil.pow2(Math.tan(fov/180d*Math.PI/2d))
|
||||
* (LodUtil.pow2(aspectRatio) + 1d)));
|
||||
}
|
||||
|
||||
/**
|
||||
* create and return a new projection matrix based on MC's projection matrix
|
||||
* @param projMat this is Minecraft's current projection matrix
|
||||
* @param modelMat this is Minecraft's current model matrix
|
||||
* @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance
|
||||
*/
|
||||
private static Mat4f createCombinedMatrix(Mat4f projMat, Mat4f modelMat, float vanillaBlockRenderedDistance,
|
||||
int farPlaneBlockDistance, float partialTicks)
|
||||
{
|
||||
//Create a copy of the current matrix, so the current matrix isn't modified.
|
||||
Mat4f lodProj = projMat.copy();
|
||||
|
||||
float nearClipPlane;
|
||||
if (CONFIG.client().advanced().getLodOnlyMode()) {
|
||||
nearClipPlane = 0.1f;
|
||||
} else if (CONFIG.client().graphics().advancedGraphics().getUseExtendedNearClipPlane()) {
|
||||
nearClipPlane = Math.min((vanillaBlockRenderedDistance-16f),8f*16f);
|
||||
} else {
|
||||
nearClipPlane = 16f;
|
||||
}
|
||||
|
||||
//Set new far and near clip plane values.
|
||||
lodProj.setClipPlanes(
|
||||
calculateNearClipPlane(nearClipPlane, partialTicks),
|
||||
(float)((farPlaneBlockDistance+LodUtil.REGION_WIDTH) * Math.sqrt(2)));
|
||||
|
||||
lodProj.multiply(modelMat);
|
||||
|
||||
return lodProj;
|
||||
}
|
||||
|
||||
//======================//
|
||||
// Cleanup Functions //
|
||||
//======================//
|
||||
|
||||
/** cleanup and free all render objects. REQUIRES to be in render thread
|
||||
* (Many objects are Native, outside of JVM, and need manual cleanup) */
|
||||
private void cleanup() {
|
||||
if (!isSetupComplete) {
|
||||
EVENT_LOGGER.warn("Renderer cleanup called but Renderer has not completed setup!");
|
||||
return;
|
||||
}
|
||||
if (!GLProxy.hasInstance()) {
|
||||
EVENT_LOGGER.warn("Renderer Cleanup called but the GLProxy has never been inited!");
|
||||
return;
|
||||
}
|
||||
isSetupComplete = false;
|
||||
EVENT_LOGGER.info("Renderer Cleanup Started");
|
||||
shaderProgram.free();
|
||||
if (quadIBO != null) quadIBO.destroy(false);
|
||||
EVENT_LOGGER.info("Renderer Cleanup Complete");
|
||||
}
|
||||
|
||||
/** Calls the BufferBuilder's destroyBuffers method. */
|
||||
public void destroyBuffers()
|
||||
{
|
||||
lodBufferBuilderFactory.destroyBuffers();
|
||||
}
|
||||
|
||||
//======================//
|
||||
// Other Misc Functions //
|
||||
//======================//
|
||||
|
||||
/**
|
||||
* If this is called then the next time "drawLODs" is called
|
||||
* the LODs will be regenerated; the same as if the player moved.
|
||||
*/
|
||||
public void regenerateLODsNextFrame()
|
||||
{
|
||||
fullRegen = true;
|
||||
}
|
||||
|
||||
// returns whether anything changed
|
||||
private boolean updateVanillaRenderedChunks(LodDimension lodDim) {
|
||||
// if the player is high enough, draw all LODs
|
||||
IWorldWrapper world = MC.getWrappedClientWorld();
|
||||
if (lastUpdatedPos.getY() > world.getHeight()-world.getMinHeight() ||
|
||||
CONFIG.client().advanced().getLodOnlyMode()) {
|
||||
if (vanillaChunks != null) {
|
||||
vanillaChunks = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LagSpikeCatcher getChunks = new LagSpikeCatcher();
|
||||
EdgeDistanceBooleanGrid edgeGrid = LodUtil.readVanillaRenderedChunks(lodDim);
|
||||
if (edgeGrid == null) {
|
||||
if (vanillaChunks != null) {
|
||||
vanillaChunks = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
getChunks.end("LodDrawSetup:UpdateStatus:UpdateVanillaChunks:getChunks");
|
||||
PosArrayGridList<BoolType> grid = new PosArrayGridList<>(edgeGrid.gridSize, edgeGrid.getOffsetX(), edgeGrid.getOffsetY());
|
||||
|
||||
int overdrawOffset = LodUtil.computeOverdrawOffset(lodDim);
|
||||
edgeGrid.flagAllWithDistance(grid, (i) -> (i >= overdrawOffset));
|
||||
vanillaChunks = grid;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateRegenStatus(LodDimension lodDim, float partialTicks) {
|
||||
short chunkRenderDistance = (short) MC_RENDER.getRenderDistance();
|
||||
long newTime = System.currentTimeMillis();
|
||||
DHBlockPos newPos = MC.getPlayerBlockPos();
|
||||
boolean shouldUpdateChunks = false;
|
||||
boolean tryPartialGen = false;
|
||||
boolean tryFullGen = false;
|
||||
|
||||
// check if the view distance or config changed
|
||||
if (InternalApiShared.previousLodRenderDistance != CONFIG.client().graphics().quality().getLodChunkRenderDistance()
|
||||
|| chunkRenderDistance != prevRenderDistance
|
||||
|| prevFogDistance != CONFIG.client().graphics().fogQuality().getFogDistance())
|
||||
{
|
||||
DetailDistanceUtil.updateSettings(); // FIXME: This should NOT be here!
|
||||
prevFogDistance = CONFIG.client().graphics().fogQuality().getFogDistance();
|
||||
prevRenderDistance = chunkRenderDistance;
|
||||
tryFullGen = true;
|
||||
} else if (CONFIG.client().advanced().debugging().getDebugMode() != previousDebugMode)
|
||||
{ // did the user change the debug setting?
|
||||
previousDebugMode = CONFIG.client().advanced().debugging().getDebugMode();
|
||||
tryFullGen = true;
|
||||
}
|
||||
|
||||
// check if the player has moved
|
||||
if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout) {
|
||||
if (lastUpdatedPos == null
|
||||
|| Math.abs(newPos.getX() - lastUpdatedPos.getX()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16
|
||||
|| Math.abs(newPos.getZ() - lastUpdatedPos.getZ()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16)
|
||||
{
|
||||
shouldUpdateChunks = true;
|
||||
}
|
||||
prevPlayerPosTime = newTime;
|
||||
}
|
||||
|
||||
// check if the vanilla rendered chunks changed
|
||||
if (newTime - prevVanillaChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().renderedChunkTimeout)
|
||||
{
|
||||
shouldUpdateChunks = true;
|
||||
prevVanillaChunkTime = newTime;
|
||||
}
|
||||
|
||||
// check if there is any newly generated terrain to show
|
||||
if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout)
|
||||
{
|
||||
tryPartialGen = true;
|
||||
prevChunkTime = newTime;
|
||||
}
|
||||
|
||||
shouldUpdateChunks |= tryFullGen;
|
||||
if (shouldUpdateChunks) {
|
||||
lastUpdatedPos = newPos;
|
||||
tryPartialGen |= updateVanillaRenderedChunks(lodDim);
|
||||
}
|
||||
|
||||
if (tryFullGen) {
|
||||
fullRegen = true;
|
||||
} else if (tryPartialGen) {
|
||||
partialRegen = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.seibel.lod.core.util;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class EventLoop {
|
||||
private final ExecutorService executorService;
|
||||
private final Runnable runnable;
|
||||
private CompletableFuture<Void> future;
|
||||
public EventLoop(ExecutorService executorService, Runnable runnable) {
|
||||
this.executorService = executorService;
|
||||
this.runnable = runnable;
|
||||
}
|
||||
public void tick() {
|
||||
if (future != null && future.isDone()) {
|
||||
try {
|
||||
future.join();
|
||||
} catch (Exception ignored) {} finally {future = null;}
|
||||
}
|
||||
if (future == null) {
|
||||
future = CompletableFuture.runAsync(runnable, executorService);
|
||||
}
|
||||
}
|
||||
public void halt() {
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
public boolean isRunning() {
|
||||
return future != null && !future.isDone();
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.seibel.lod.core.wrapperInterfaces.minecraft;
|
||||
|
||||
public interface IMinecraftSharedWrapper {
|
||||
boolean isServerJar();
|
||||
|
||||
|
||||
}
|
||||
@@ -27,6 +27,10 @@ import com.seibel.lod.core.handlers.dependencyInjection.IBindable;
|
||||
*/
|
||||
public interface IProfilerWrapper extends IBindable
|
||||
{
|
||||
// Note to self:
|
||||
// if "unspecified" shows up in the pie chart, it is
|
||||
// possibly because the amount of time between sections
|
||||
// is too small for the profiler to measures
|
||||
void push(String newSection);
|
||||
|
||||
void popPush(String newSection);
|
||||
|
||||
Reference in New Issue
Block a user