Compare commits

...

49 Commits

Author SHA1 Message Date
James Seibel b82a59ecbc Speed up shutdown and reduce logging 2025-11-14 07:46:02 -06:00
James Seibel 6fe0df7d0f Don't duplicate adjacent data 2025-11-13 07:18:09 -06:00
James Seibel b9746381eb Add varint encoding for full data
Closes Merge !93
Thanks Ryan Hitchman!
2025-11-12 07:21:54 -06:00
James Seibel 6eb24ecde1 re-add GPU upload config including "none" 2025-11-10 07:33:03 -06:00
James Seibel 767753c004 add logging to infinite repo unit test 2025-11-10 06:56:24 -06:00
James Seibel 97442f8833 Fix config min/max validation default setup 2025-11-08 19:11:56 -06:00
James Seibel 62359e3dde remove LOD load pref logging 2025-11-08 19:08:30 -06:00
James Seibel b5199cfa87 Optimize ColumnBox building 2025-11-08 18:08:02 -06:00
James Seibel f0acc73c56 Add compass Index to Edirection 2025-11-08 17:48:30 -06:00
James Seibel f9dfc38bf1 Separate BlockBiomeWrapperPair from FullDataPointIdMap 2025-11-08 17:47:50 -06:00
James Seibel 5c5d39738e minor reformating 2025-11-08 17:44:08 -06:00
James Seibel 27fb629c22 default unsafe UI values to config option 2025-11-08 17:41:07 -06:00
James Seibel c374bf7ca8 test 2025-11-08 08:14:03 -06:00
James Seibel 7e04b12e37 Optimize PrefRecorder slightly 2025-11-07 07:41:59 -06:00
James Seibel 67637dbf10 detail level renaming 2025-11-06 21:50:43 -06:00
James Seibel 6456651d27 Handle non-adjacent data conversion 2025-11-06 21:28:25 -06:00
James Seibel 9343854b4a Clean up data source getters 2025-11-06 07:42:58 -06:00
James Seibel 5fd8ed840f Add adjacent data to FullDataDTO for faster loading 2025-11-06 07:35:23 -06:00
James Seibel 4d4d8fd8e9 Split up full data source provider into multiple classes 2025-11-04 07:46:06 -06:00
James Seibel bf05965015 remove IDataSource 2025-11-02 07:20:07 -06:00
James Seibel 47569f2b3c minor dataSourceHandler refactor 2025-11-01 16:33:07 -04:00
James Seibel 0567195f73 minor datasource renaming 2025-11-01 16:27:54 -04:00
James Seibel e355366ffc Clean up EDhDirection 2025-11-01 09:06:53 -04:00
James Seibel 3681d50eb2 minor comment cleanup 2025-11-01 08:42:25 -04:00
James Seibel 2a49fdee7f Add experimental loading option and perfRecorder 2025-10-28 07:46:53 -05:00
James Seibel f39e06b6dc remove unused interrupt check 2025-10-28 07:36:28 -05:00
James Seibel 0d5c454dd4 remove unused ColumnQuadView methods 2025-10-28 07:24:24 -05:00
James Seibel 1b447fdc98 Fix logger builder doubling DH name 2025-10-28 07:23:58 -05:00
James Seibel d84ba05380 minor style reformatting 2025-10-27 06:52:36 -05:00
James Seibel 3e7f160fcd Merge Fade apply shaders 2025-10-25 11:54:32 -05:00
James Seibel dcaf334828 use same fade apply frag shader 2025-10-25 11:39:27 -05:00
James Seibel 789306ccff Add far clip fading 2025-10-25 11:06:19 -05:00
James Seibel e33fa3cb5e Rename fade renderer -> Vanilla Fade renderer 2025-10-25 09:36:11 -05:00
James Seibel 8f99117066 Fix iris not setting face culling in the MC state manager 2025-10-25 08:38:29 -05:00
James Seibel 2136c0fe83 framebuffer name consistency fix 2025-10-25 08:37:11 -05:00
James Seibel 7a6cffe19d Move getKeyedLevelDimensionName() to implementation 2025-10-23 07:17:46 -05:00
James Seibel 06bef93c82 run occlusion culling whenever saving a LOD
Also run culling for every column in an LOD, which improves compression by about 20%
- Thanks Scaevolus
2025-10-22 07:25:04 -05:00
James Seibel 939e45ce62 minor RenderBufferHandler optimization and bugfix 2025-10-19 16:40:57 -05:00
James Seibel 7f958269e4 Fix not reloading LODs on horizontal quality change 2025-10-19 16:16:35 -05:00
James Seibel 07e3091d13 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2025-10-19 16:06:24 -05:00
James Seibel f7ece2b02e Clean up LodRendering logic 2025-10-19 16:06:00 -05:00
s809 bd796c2ce0 Fix handling of empty server keys 2025-10-19 22:58:07 +05:00
s809 4e6be35da9 Merge branch 'feature/server-keys' 2025-10-19 22:57:50 +05:00
James Seibel 0e0e1e1b0f Make LodRenderer a singleton 2025-10-18 11:42:36 -05:00
James Seibel f4ab101403 Dh and level wrapper refactoring and commenting 2025-10-17 07:21:16 -05:00
James Seibel 0902d3f0f5 merge loggers and add logger builder 2025-10-15 17:37:08 -05:00
James Seibel 75c2758fd5 up version number 2.3.6 -> 2.3.7 2025-10-13 18:03:19 -05:00
s809 034ec7d656 Bump protocol version 2025-08-16 21:01:45 +05:00
s809 fb5e15a2f1 Add a server keys feature 2025-08-16 20:59:28 +05:00
187 changed files with 6627 additions and 5713 deletions
@@ -22,7 +22,8 @@ package com.seibel.distanthorizons.api.enums.config;
/** /**
* UNCOMPRESSED <br> * UNCOMPRESSED <br>
* LZ4 <br> * LZ4 <br>
* XZ <br><br> * Z_STD <br>
* LZMA2 <br><br>
* *
* Note: speed and compression ratios are examples * Note: speed and compression ratios are examples
* and should only be used for estimated comparisons. * and should only be used for estimated comparisons.
@@ -44,6 +44,10 @@ public enum EDhApiGpuUploadMethod
/** Fast rendering but may stutter when uploading. */ /** Fast rendering but may stutter when uploading. */
SUB_DATA(false, false), SUB_DATA(false, false),
/** Don't upload, only should be used for debugging */
@Deprecated // TODO remove before release
NONE(false, false),
/** /**
* May end up storing buffers in System memory. <br> * May end up storing buffers in System memory. <br>
* Fast rending if in GPU memory, slow if in system memory, <br> * Fast rending if in GPU memory, slow if in system memory, <br>
@@ -0,0 +1,53 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.distanthorizons.api.enums.config;
import org.apache.logging.log4j.Level;
/**
* ALL
* DEBUG
* INFO
* WARN
* ERROR
* DISABLED
*
* @since API 5.0.0
* @version 2024-4-6
*/
public enum EDhApiLoggerLevel
{
// ordered from most to least broad
ALL(Level.ALL),
DEBUG(Level.DEBUG),
INFO(Level.INFO),
WARN(Level.WARN),
ERROR(Level.ERROR),
DISABLED(Level.OFF),
;
public final Level level;
EDhApiLoggerLevel(Level level)
{ this.level = level; }
}
@@ -1,55 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.distanthorizons.api.enums.config;
import org.apache.logging.log4j.Level;
/**
* @since API 2.0.0
* @version 2024-4-6
*/
public enum EDhApiLoggerMode
{
DISABLED(Level.OFF, Level.OFF),
LOG_ALL_TO_FILE(Level.ALL, Level.OFF),
LOG_ERROR_TO_CHAT(Level.ALL, Level.ERROR),
LOG_WARNING_TO_CHAT(Level.ALL, Level.WARN),
LOG_INFO_TO_CHAT(Level.ALL, Level.INFO),
LOG_DEBUG_TO_CHAT(Level.ALL, Level.DEBUG),
LOG_ALL_TO_CHAT(Level.ALL, Level.ALL),
LOG_ERROR_TO_CHAT_AND_FILE(Level.ERROR, Level.ERROR),
LOG_WARNING_TO_CHAT_AND_FILE(Level.WARN, Level.WARN),
LOG_INFO_TO_CHAT_AND_FILE(Level.INFO, Level.INFO),
LOG_DEBUG_TO_CHAT_AND_FILE(Level.DEBUG, Level.DEBUG),
LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.WARN),
LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.ERROR),
LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE(Level.ERROR, Level.WARN),
;
public final Level levelForFile;
public final Level levelForChat;
EDhApiLoggerMode(Level levelForFile, Level levelForChat)
{
this.levelForFile = levelForFile;
this.levelForChat = levelForChat;
}
}
@@ -64,10 +64,7 @@ public enum EDhApiMaxHorizontalResolution
/** How wide each LOD DataPoint is */ /** How wide each LOD DataPoint is */
public final int dataPointWidth; public final int dataPointWidth;
/** /** This is the same as detailLevel in LodQuadTreeNode */
* This is the same as detailLevel in LodQuadTreeNode,
* lowest is 0 highest is 9
*/
public final byte detailLevel; public final byte detailLevel;
/* Start/End X/Z give the block positions /* Start/End X/Z give the block positions
@@ -33,7 +33,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
* @since API 2.0.0 * @since API 2.0.0
* @deprecated Replaced by {@link DhApiBeforeColorDepthTextureCreatedEvent} since this event's name isn't obvious when it fires. * @deprecated Replaced by {@link DhApiBeforeColorDepthTextureCreatedEvent} since this event's name isn't obvious when it fires.
*/ */
@Deprecated @Deprecated // internal notes: this method must be kept around due to Iris using it and we don't want to break old Iris support
public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam> public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam>
{ {
/** Fired before Distant Horizons creates. */ /** Fired before Distant Horizons creates. */
@@ -67,7 +67,6 @@ public class DhApiRenderParam implements IDhApiEventParam
// constructors // // constructors //
//==============// //==============//
public DhApiRenderParam(DhApiRenderParam parent) public DhApiRenderParam(DhApiRenderParam parent)
{ {
this( this(
@@ -86,7 +85,7 @@ public class DhApiRenderParam implements IDhApiEventParam
DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix, DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix,
DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix, DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix,
int worldYOffset int worldYOffset
) )
{ {
this.renderPass = renderPass; this.renderPass = renderPass;
@@ -112,9 +111,8 @@ public class DhApiRenderParam implements IDhApiEventParam
//================// //================//
@Override @Override
public DhApiRenderParam copy() public DhApiRenderParam copy() { return new DhApiRenderParam(this); }
{
return new DhApiRenderParam(this);
}
} }
@@ -31,21 +31,21 @@ public final class ModInfo
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial"; public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */ /** Incremented every time any packets are added, changed or removed, with a few exceptions. */
public static final int PROTOCOL_VERSION = 11; public static final int PROTOCOL_VERSION = 12;
public static final String WRAPPER_PACKET_PATH = "message"; public static final String WRAPPER_PACKET_PATH = "message";
/** The internal mod name */ /** The internal mod name */
public static final String NAME = "DistantHorizons"; public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */ /** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons"; public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.3.6-b"; public static final String VERSION = "2.3.7-b-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */ /** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
/** This version should only be updated when breaking changes are introduced to the DH API */ /** This version should only be updated when breaking changes are introduced to the DH API */
public static final int API_MAJOR_VERSION = 4; public static final int API_MAJOR_VERSION = 5;
/** This version should be updated whenever new methods are added to the DH API */ /** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 1; public static final int API_MINOR_VERSION = 0;
/** This version should be updated whenever non-breaking fixes are added to the DH API */ /** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATCH_VERSION = 0; public static final int API_PATCH_VERSION = 0;
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core;
import com.github.luben.zstd.ZstdOutputStream; import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory; import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater; import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
@@ -33,7 +34,7 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import net.jpountz.lz4.LZ4FrameOutputStream; import net.jpountz.lz4.LZ4FrameOutputStream;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.sqlite.SQLiteJDBCLoader; import org.sqlite.SQLiteJDBCLoader;
import org.sqlite.util.OSInfo; import org.sqlite.util.OSInfo;
import org.tukaani.xz.XZOutputStream; import org.tukaani.xz.XZOutputStream;
@@ -44,8 +45,7 @@ import java.io.File;
/** Handles first time Core setup. */ /** Handles first time Core setup. */
public class Initializer public class Initializer
{ {
private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + Initializer.class.getSimpleName()); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static void init() public static void init()
@@ -2,10 +2,10 @@ package com.seibel.distanthorizons.core.api.external.methods.data;
import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataCache; import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataCache;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.LongSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
@@ -15,7 +15,7 @@ public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
private final Object modificationLock = new Object(); private final Object modificationLock = new Object();
private Long2ReferenceOpenHashMap<SoftReference<FullDataSourceV2>> posToFullDataRef = new Long2ReferenceOpenHashMap<>(); private Long2ReferenceOpenHashMap<SoftReference<FullDataSourceV2>> posToFullDataRef = new Long2ReferenceOpenHashMap<>();
private static final Logger LOGGER = LogManager.getLogger(DhApiTerrainDataCache.class.getSimpleName()); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -31,6 +31,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
@@ -48,8 +49,7 @@ import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3i; import com.seibel.distanthorizons.core.util.math.Vec3i;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.LogManager; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.awt.*; import java.awt.*;
@@ -64,7 +64,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{ {
public static DhApiTerrainDataRepo INSTANCE = new DhApiTerrainDataRepo(); public static DhApiTerrainDataRepo INSTANCE = new DhApiTerrainDataRepo();
private static final Logger LOGGER = LogManager.getLogger(DhApiTerrainDataRepo.class.getSimpleName()); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
// debugging values // debugging values
private static volatile boolean debugThreadRunning = false; private static volatile boolean debugThreadRunning = false;
@@ -258,7 +258,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
//===============================// //===============================//
FullDataPointIdMap mapping = dataSource.mapping; FullDataPointIdMap mapping = dataSource.mapping;
LongArrayList dataColumn = dataSource.get(relativePos.x, relativePos.z); LongArrayList dataColumn = dataSource.getColumnAtRelPos(relativePos.x, relativePos.z);
if (dataColumn != null) if (dataColumn != null)
{ {
int dataColumnIndexCount = dataColumn.size(); int dataColumnIndexCount = dataColumn.size();
@@ -23,18 +23,19 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode; import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.api.internal.rendering.RenderState;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure; import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry; import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.renderer.FadeRenderer; import com.seibel.distanthorizons.core.render.renderer.VanillaFadeRenderer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.RenderParams;
import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
@@ -43,13 +44,8 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel; import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.logging.SpamReducedLogger;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.renderer.TestRenderer; import com.seibel.distanthorizons.core.render.renderer.TestRenderer;
import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientServerWorld; import com.seibel.distanthorizons.core.world.DhClientServerWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld; import com.seibel.distanthorizons.core.world.DhClientWorld;
@@ -58,15 +54,14 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.apache.logging.log4j.LogManager; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/** /**
* This holds the methods that should be called * This holds the methods that should be called
@@ -75,7 +70,7 @@ import java.util.concurrent.TimeUnit;
*/ */
public class ClientApi public class ClientApi
{ {
private static final Logger LOGGER = LogManager.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static boolean prefLoggerEnabled = false; public static boolean prefLoggerEnabled = false;
@@ -85,8 +80,6 @@ public class ClientApi
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
/** this includes the is dev build message and low allocated memory warning */ /** this includes the is dev build message and low allocated memory warning */
private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000; private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000;
@@ -98,14 +91,13 @@ public class ClientApi
* *
* Only downside is making sure each variable is populated before rendering. * Only downside is making sure each variable is populated before rendering.
*/ */
public static final RenderState RENDER_STATE = new RenderState(); public static final DhRenderState RENDER_STATE = new DhRenderState();
private boolean isDevBuildMessagePrinted = false; private boolean isDevBuildMessagePrinted = false;
private boolean lowMemoryWarningPrinted = false; private boolean lowMemoryWarningPrinted = false;
private boolean highVanillaRenderDistanceWarningPrinted = false; private boolean highVanillaRenderDistanceWarningPrinted = false;
/** when the last static */
private long lastStaticWarningMessageSentMsTime = 0L; private long lastStaticWarningMessageSentMsTime = 0L;
private final Queue<String> chatMessageQueueForNextFrame = new LinkedBlockingQueue<>(); private final Queue<String> chatMessageQueueForNextFrame = new LinkedBlockingQueue<>();
@@ -113,8 +105,6 @@ public class ClientApi
public boolean rendererDisabledBecauseOfExceptions = false; public boolean rendererDisabledBecauseOfExceptions = false;
private long lastFlushNanoTime = 0;
private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi(this::clientLevelLoadEvent, this::clientLevelUnloadEvent); private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi(this::clientLevelLoadEvent, this::clientLevelUnloadEvent);
/** Delay loading the first level to give the server some time to respond with level to actually load */ /** Delay loading the first level to give the server some time to respond with level to actually load */
@@ -126,8 +116,8 @@ public class ClientApi
/** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */ /** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */
public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>(); public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>();
/** re-set every frame during the opaque rendering stage */ @Nullable
private boolean renderingCancelledForThisFrame; public String lastRenderParamValidationMessage = null;
@@ -332,10 +322,11 @@ public class ClientApi
//===============// //============//
// render events // // clint tick //
//===============// //============//
@Deprecated
public void clientTickEvent() public void clientTickEvent()
{ {
IProfilerWrapper profiler = MC_CLIENT.getProfiler(); IProfilerWrapper profiler = MC_CLIENT.getProfiler();
@@ -343,16 +334,7 @@ public class ClientApi
try try
{ {
boolean doFlush = System.nanoTime() - this.lastFlushNanoTime >= SPAM_LOGGER_FLUSH_NS; IDhClientWorld clientWorld = SharedApi.tryGetDhClientWorld();
if (doFlush)
{
this.lastFlushNanoTime = System.nanoTime();
SpamReducedLogger.flushAll();
}
ConfigBasedLogger.updateAll();
ConfigBasedSpamLogger.updateAll(doFlush);
IDhClientWorld clientWorld = SharedApi.getIDhClientWorld();
if (clientWorld != null) if (clientWorld != null)
{ {
clientWorld.clientTick(); clientWorld.clientTick();
@@ -395,9 +377,9 @@ public class ClientApi
//===========// //===============//
// rendering // // LOD rendering //
//===========// //===============//
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */ /** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
public void renderLods() { this.renderLodLayer(false); } public void renderLods() { this.renderLodLayer(false); }
@@ -410,18 +392,9 @@ public class ClientApi
private void renderLodLayer(boolean renderingDeferredLayer) private void renderLodLayer(boolean renderingDeferredLayer)
{ {
// A global render state variable is used since MC has split up their //=========//
// render prep and actual rendering into different threads/methods
// this is annoying since it's possible to start a render with only
// partially complete info, but there isn't a better option at the moment
IClientLevelWrapper levelWrapper = RENDER_STATE.clientLevelWrapper;
Mat4f mcModelViewMatrix = RENDER_STATE.mcModelViewMatrix;
Mat4f mcProjectionMatrix = RENDER_STATE.mcProjectionMatrix;
float partialTicks = RENDER_STATE.frameTime;
// logging // // logging //
//=========//
this.sendQueuedChatMessages(); this.sendQueuedChatMessages();
@@ -431,7 +404,33 @@ public class ClientApi
// render parameter setup // //=====================//
// render thread tasks //
//=====================//
// only run these tasks once per frame
if (!renderingDeferredLayer)
{
profiler.push("DH render thread tasks");
try
{
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
GLProxy.getInstance().runRenderThreadTasks();
}
catch (Exception e)
{
LOGGER.error("Unexpected issue running render thread tasks, error: [" + e.getMessage() + "].", e);
}
profiler.pop();
}
//=================//
// parameter setup //
//=================//
EDhApiRenderPass renderPass; EDhApiRenderPass renderPass;
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering()) if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
@@ -450,86 +449,67 @@ public class ClientApi
renderPass = EDhApiRenderPass.OPAQUE_AND_TRANSPARENT; renderPass = EDhApiRenderPass.OPAQUE_AND_TRANSPARENT;
} }
DhApiRenderParam renderEventParam = // A global render state variable is used since MC has split up their
new DhApiRenderParam( // render prep and actual rendering into different threads/methods
renderPass, // this is annoying since it's possible to start a render with only
partialTicks, // partially complete info, but there isn't a better option at the moment
RenderUtil.getNearClipPlaneDistanceInBlocks(partialTicks), RenderUtil.getFarClipPlaneDistanceInBlocks(), RenderParams renderParams =
mcProjectionMatrix, mcModelViewMatrix, new RenderParams(
RenderUtil.createLodProjectionMatrix(mcProjectionMatrix, partialTicks), RenderUtil.createLodModelViewMatrix(mcModelViewMatrix), renderPass,
levelWrapper.getMinHeight() RENDER_STATE.frameTime,
); RENDER_STATE.mcProjectionMatrix, RENDER_STATE.mcModelViewMatrix,
RENDER_STATE.clientLevelWrapper
);
//Mat4f mcCombined = mcModelViewMatrix.copy(); //============//
//mcCombined.multiply(mcProjectionMatrix); // validation //
// //============//
//com.seibel.distanthorizons.api.objects.math.DhApiMat4f dhCombined = renderEventParam.dhModelViewMatrix.copy();
//dhCombined.multiply(renderEventParam.dhProjectionMatrix); // TODO write this message to the F3 menu so people can see when a different mod screws with the lightmap
// String validationMessage = renderParams.getValidationErrorMessage();
//LOGGER.info("\n\n" + if (validationMessage != null)
// "API\n" + {
// "Mc MVM: \n" + mcModelViewMatrix.toString() + "\n" + this.lastRenderParamValidationMessage = validationMessage;
// "Mc Proj: \n" + mcProjectionMatrix + "\n" + return;
// "Mc Combined:\n" + mcCombined.toString() + "\n" + }
// "\n" + else
// "DH MVM: \n" + renderEventParam.dhModelViewMatrix.toString() + "\n" + {
// "DH Proj: \n" + renderEventParam.dhProjectionMatrix + "\n" + this.lastRenderParamValidationMessage = null;
// "DH Combined:\n" + mcCombined.toString() }
//);
if (this.rendererDisabledBecauseOfExceptions)
{
// re-enable rendering if the user toggles DH rendering
if (!Config.Client.quickEnableRendering.get())
{
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
this.rendererDisabledBecauseOfExceptions = false;
Config.Client.quickEnableRendering.set(true);
}
return;
}
// render validation // //===========//
// rendering //
//===========//
try try
{ {
// TODO write this message to the F3 menu so people can see when a different mod screws with the lightmap
String reasonLodsCannotRender = RenderUtil.shouldLodsRender(levelWrapper, renderEventParam);
if (reasonLodsCannotRender != null)
{
return;
}
IDhClientWorld dhClientWorld = SharedApi.getIDhClientWorld();
if (dhClientWorld == null)
{
return;
}
IDhClientLevel level = (IDhClientLevel) dhClientWorld.getLevel(levelWrapper);
if (level == null)
{
return;
}
if (this.rendererDisabledBecauseOfExceptions)
{
// re-enable rendering if the user toggles DH rendering
if (!Config.Client.quickEnableRendering.get())
{
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
this.rendererDisabledBecauseOfExceptions = false;
Config.Client.quickEnableRendering.set(true);
}
return;
}
// render pass // // render pass //
if (!renderingDeferredLayer) if (!renderingDeferredLayer)
{ {
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT) if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
{ {
this.renderingCancelledForThisFrame = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderEventParam); boolean renderingCancelledForThisFrame = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderParams);
if (!this.renderingCancelledForThisFrame) if (!renderingCancelledForThisFrame)
{ {
level.render(renderEventParam, profiler); LodRenderer.INSTANCE.render(renderParams, profiler);
} }
if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering()) if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering())
@@ -546,10 +526,10 @@ public class ClientApi
} }
else else
{ {
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderEventParam); boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderParams);
if (!renderingCancelled) if (!renderingCancelled)
{ {
level.renderDeferred(renderEventParam, profiler); LodRenderer.INSTANCE.renderDeferred(renderParams, profiler);
} }
@@ -569,24 +549,19 @@ public class ClientApi
MC_CLIENT.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering."); MC_CLIENT.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
MC_CLIENT.sendChatMessage("\u00A74Error: " + e); MC_CLIENT.sendChatMessage("\u00A74Error: " + e);
} }
finally
{
try
{
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
GLProxy.getInstance().runRenderThreadTasks();
}
catch (Exception e)
{
LOGGER.error("Unexpected issue running render thread tasks.", e);
}
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain" profiler.pop(); // end LOD
} profiler.push("terrain"); // go back into "terrain"
} }
//================//
// fade rendering //
//================//
/** /**
* The first fade pass. * The first fade pass.
* Called after MC finishes rendering the opaque passes. * Called after MC finishes rendering the opaque passes.
@@ -600,7 +575,7 @@ public class ClientApi
// don't fade when Iris shaders are active, otherwise the rendering can get weird // don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering()) && !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{ {
FadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper); VanillaFadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper);
} }
} }
/** /**
@@ -624,14 +599,13 @@ public class ClientApi
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering(); && !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
if (renderFade) if (renderFade)
{ {
FadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper); VanillaFadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper);
} }
} }
} }
//=================// //=================//
// DEBUG USE // // DEBUG USE //
//=================// //=================//
@@ -4,13 +4,13 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager; import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel; import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.apache.logging.log4j.LogManager;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -22,8 +22,10 @@ import java.util.function.Consumer;
*/ */
public class ClientPluginChannelApi public class ClientPluginChannelApi
{ {
private static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), private static final DhLogger LOGGER = new DhLoggerBuilder()
() -> Config.Common.Logging.logNetworkEvent.get()); .fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class); private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
@@ -75,7 +77,12 @@ public class ClientPluginChannelApi
private void onLevelInitMessage(LevelInitMessage msg) private void onLevelInitMessage(LevelInitMessage msg)
{ {
if (!msg.levelKey.matches(LevelInitMessage.VALIDATION_REGEX)) if (!msg.serverKey.isEmpty() && !msg.serverKey.matches(LevelInitMessage.SERVER_KEY_REGEX))
{
throw new IllegalArgumentException("Server sent invalid server key.");
}
if (!msg.levelKey.matches(LevelInitMessage.LEVEL_KEY_REGEX))
{ {
throw new IllegalArgumentException("Server sent invalid level key."); throw new IllegalArgumentException("Server sent invalid level key.");
} }
@@ -105,10 +112,12 @@ public class ClientPluginChannelApi
this.levelUnloadHandler.accept(clientLevel); this.levelUnloadHandler.accept(clientLevel);
} }
if (existingKeyedClientLevel == null || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey)) if (existingKeyedClientLevel == null
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
|| !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{ {
LOGGER.info("Loading level with key: [" + msg.levelKey + "]."); LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.levelKey); IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey);
this.levelLoadHandler.accept(keyedLevel); this.levelLoadHandler.accept(keyedLevel);
} }
}); });
@@ -30,7 +30,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** /**
@@ -41,7 +41,7 @@ public class ServerApi
{ {
public static final ServerApi INSTANCE = new ServerApi(); public static final ServerApi INSTANCE = new ServerApi();
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -61,7 +61,7 @@ public class ServerApi
{ {
try try
{ {
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld(); IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
if (serverWorld != null) if (serverWorld != null)
{ {
serverWorld.serverTick(); serverWorld.serverTick();
@@ -152,7 +152,7 @@ public class ServerApi
return; return;
} }
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld(); IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
LOGGER.info("Player ["+player.getName()+"] joined."); LOGGER.info("Player ["+player.getName()+"] joined.");
if (serverWorld != null) if (serverWorld != null)
{ {
@@ -166,7 +166,7 @@ public class ServerApi
return; return;
} }
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld(); IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
LOGGER.info("Player ["+player.getName()+"] disconnected."); LOGGER.info("Player ["+player.getName()+"] disconnected.");
if (serverWorld != null) if (serverWorld != null)
{ {
@@ -180,7 +180,7 @@ public class ServerApi
return; return;
} }
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld(); IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
LOGGER.info("Player ["+player.getName()+"] changed level: ["+originLevel.getKeyedLevelDimensionName()+"] -> ["+destinationLevel.getKeyedLevelDimensionName()+"]."); LOGGER.info("Player ["+player.getName()+"] changed level: ["+originLevel.getKeyedLevelDimensionName()+"] -> ["+destinationLevel.getKeyedLevelDimensionName()+"].");
if (serverWorld != null) if (serverWorld != null)
{ {
@@ -200,7 +200,7 @@ public class ServerApi
return; return;
} }
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld(); IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
if (serverWorld != null) if (serverWorld != null)
{ {
serverWorld.getServerPlayerStateManager().handlePluginMessage(player, message); serverWorld.getServerPlayerStateManager().handlePluginMessage(player, message);
@@ -47,7 +47,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSha
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
@@ -58,7 +58,7 @@ public class SharedApi
{ {
public static final SharedApi INSTANCE = new SharedApi(); public static final SharedApi INSTANCE = new SharedApi();
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** will be null on the server-side */ /** will be null on the server-side */
@Nullable @Nullable
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
@@ -153,12 +153,14 @@ public class SharedApi
@Nullable @Nullable
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; } public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */
public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld instanceof DhClientServerWorld) ? (DhClientServerWorld) currentWorld : null; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientWorld} or {@link DhClientServerWorld} */ /** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientWorld} or {@link DhClientServerWorld} */
public static IDhClientWorld getIDhClientWorld() { return (currentWorld instanceof IDhClientWorld) ? (IDhClientWorld) currentWorld : null; } @Nullable
public static IDhClientWorld tryGetDhClientWorld() { return (currentWorld instanceof IDhClientWorld) ? (IDhClientWorld) currentWorld : null; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhServerWorld} or {@link DhClientServerWorld} */ /** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhServerWorld} or {@link DhClientServerWorld} */
public static IDhServerWorld getIDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; } @Nullable
public static IDhServerWorld tryGetDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; }
@@ -491,6 +493,7 @@ public class SharedApi
IChunkWrapper chunkWrapper = updateData.chunkWrapper; IChunkWrapper chunkWrapper = updateData.chunkWrapper;
IDhLevel dhLevel = updateData.dhLevel; IDhLevel dhLevel = updateData.dhLevel;
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
// having a list of the nearby chunks is needed for lighting and beacon generation // having a list of the nearby chunks is needed for lighting and beacon generation
@Nullable ArrayList<IChunkWrapper> nearbyChunkList = updateData.neighborChunkList; @Nullable ArrayList<IChunkWrapper> nearbyChunkList = updateData.neighborChunkList;
@@ -504,7 +507,7 @@ public class SharedApi
try try
{ {
// sky lighting is populated later at the data source level // sky lighting is populated later at the data source level
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, levelWrapper.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList); dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
@@ -6,11 +6,11 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.world.EWorldEnvironment; import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
public class ChunkUpdateQueueManager public class ChunkUpdateQueueManager
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public final ChunkPosQueue updateQueue; public final ChunkPosQueue updateQueue;
@@ -9,7 +9,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
* *
* @see ClientApi * @see ClientApi
*/ */
public class RenderState public class DhRenderState
{ {
public Mat4f mcModelViewMatrix = null; public Mat4f mcModelViewMatrix = null;
public Mat4f mcProjectionMatrix = null; public Mat4f mcProjectionMatrix = null;
@@ -36,7 +36,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.lwjgl.util.tinyfd.TinyFileDialogs; import org.lwjgl.util.tinyfd.TinyFileDialogs;
import java.awt.*; import java.awt.*;
@@ -54,7 +54,7 @@ import java.util.List;
@SuppressWarnings("ConcatenationWithEmptyString") @SuppressWarnings("ConcatenationWithEmptyString")
public class Config public class Config
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static ConfigCategory client = new ConfigCategory.Builder().set(Client.class).build(); public static ConfigCategory client = new ConfigCategory.Builder().set(Client.class).build();
@@ -193,6 +193,7 @@ public class Config
+ "This indicates how quickly LODs decrease in quality the further away they are. \n" + "This indicates how quickly LODs decrease in quality the further away they are. \n"
+ "Higher settings will render higher quality fake chunks farther away, \n" + "Higher settings will render higher quality fake chunks farther away, \n"
+ "but will increase memory and GPU usage.") + "but will increase memory and GPU usage.")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<EDhApiMaxHorizontalResolution> maxHorizontalResolution = new ConfigEntry.Builder<EDhApiMaxHorizontalResolution>() public static ConfigEntry<EDhApiMaxHorizontalResolution> maxHorizontalResolution = new ConfigEntry.Builder<EDhApiMaxHorizontalResolution>()
@@ -311,6 +312,14 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Boolean> dhFadeFarClipPlane = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "Should DH fade out before reaching the far clip plane? \n"
+ "This is helpful to prevent DH clouds from cutting off in the distance. \n"
+ "")
.build();
public static ConfigEntry<Double> brightnessMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats) public static ConfigEntry<Double> brightnessMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
.set(1.0) .set(1.0)
.comment("" .comment(""
@@ -830,6 +839,19 @@ public class Config
+ "will be set to 0 (disabled).") + "will be set to 0 (disabled).")
.addListener(WorldCurvatureConfigEventHandler.INSTANCE) .addListener(WorldCurvatureConfigEventHandler.INSTANCE)
.build(); .build();
// TODO should be replaced with a better long-term solution
@Deprecated
public static ConfigEntry<Boolean> onlyLoadCenterLods = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "For internal testing:\n"
+ "Skips loading adjacent LODs to significantly reduce load times (~5x)\n"
+ "but causes lighting on LOD borders to appear as full-bright\n"
+ "and other graphical bugs.\n"
+ "")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build();
} }
} }
@@ -1061,11 +1083,10 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Boolean> validateBufferIdsBeforeRendering = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<EDhApiGpuUploadMethod> glUploadMode = new ConfigEntry.Builder<EDhApiGpuUploadMethod>()
.set(false) .set(EDhApiGpuUploadMethod.AUTO)
.comment("" .comment(""
+ "Massively reduces FPS. \n" + "\n"
+ "Should only be used if mysterious EXCEPTION_ACCESS_VIOLATION crashes are happening in DH's rendering code for troubleshooting. \n"
+ "") + "")
.build(); .build();
@@ -1527,49 +1548,74 @@ public class Config
{ {
public static ConfigUIComment loggingHeader = new ConfigUIComment.Builder().setParentConfigClass(Logging.class).build(); public static ConfigUIComment loggingHeader = new ConfigUIComment.Builder().setParentConfigClass(Logging.class).build();
// TODO add change all option
// TODO default to error chat and info file public static ConfigEntry<EDhApiLoggerLevel> globalFileMaxLevel = new ConfigEntry.Builder<EDhApiLoggerLevel>()
public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() .setChatCommandName("logging.globalFileMaxLevel")
.set(EDhApiLoggerLevel.INFO)
.comment(""
+ ""
+ "")
.build();
public static ConfigEntry<EDhApiLoggerLevel> globalChatMaxLevel = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.setChatCommandName("logging.globalChatMaxLevel")
.set(EDhApiLoggerLevel.ERROR)
.comment(""
+ ""
+ "")
.build();
public static ConfigUISpacer globalLoggingSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<EDhApiLoggerLevel> logWorldGenEventToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.setChatCommandName("logging.logWorldGenEvent") .setChatCommandName("logging.logWorldGenEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerLevel.INFO)
.comment("" .comment(""
+ "If enabled, the mod will log information about the world generation process. \n" + "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerLevel> logWorldGenPerformanceToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.setChatCommandName("logging.logWorldGenPerformance") .setChatCommandName("logging.logWorldGenPerformance")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerLevel.INFO)
.comment("" .comment(""
+ "If enabled, the mod will log performance about the world generation process. \n" + "If enabled, the mod will log performance about the world generation process. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerLevel> logWorldGenChunkLoadEventToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.setChatCommandName("logging.logWorldGenLoadEvent") .setChatCommandName("logging.logWorldGenLoadEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerLevel.INFO)
.comment("" .comment(""
+ "If enabled, the mod will log information about the world generation process. \n" + "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logRendererBufferEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerLevel> logRendererEventToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerLevel.INFO)
.comment("" .comment(""
+ "If enabled, the mod will log information about the renderer buffer process. \n" + "If enabled, the mod will log information about the renderer setup, cleanup, and any issues it may encounter. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logRendererGLEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerLevel> logRendererGLEventToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerLevel.INFO)
.comment("" .comment(""
+ "If enabled, the mod will log information about the renderer OpenGL process. \n" + "If enabled, the mod will log information about the renderer OpenGL process. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerLevel> logRendererGLEventToChat = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.set(EDhApiLoggerLevel.ERROR)
.comment(""
+ "If enabled, the mod will log information about the renderer OpenGL process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerLevel> logNetworkEventToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.setChatCommandName("logging.logNetworkEvent") .setChatCommandName("logging.logNetworkEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE) .set(EDhApiLoggerLevel.INFO)
.comment("" .comment(""
+ "If enabled, the mod will log information about network operations. \n" + "If enabled, the mod will log information about network operations. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
@@ -1649,6 +1695,28 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Integer> serverId = new ConfigEntry.Builder<Integer>()
.set(new Random().nextInt())
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.comment(""
+ "DO NOT CHANGE UNLESS YOU KNOW WHAT YOU'RE DOING.\n"
+ "Autogenerated ID used to prevent multiple independent servers from accidentally\n"
+ "writing over each other's LODs when the same serverKey is set on both.\n"
+ "")
.build();
public static ConfigEntry<String> serverKey = new ConfigEntry.Builder<String>()
.setChatCommandName("levelKeys.serverKey")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.set("")
.comment(""
+ "Custom server key used which can be used to always reuse the same LOD data folder,\n"
+ "for cases when the server doesn't have a static IP for some reason.\n"
+ "If this value is empty, the client itself decides which folder name to use.\n"
+ "Requires rejoining the server to apply after changing.\n"
+ "")
.build();
public static ConfigEntry<String> levelKeyPrefix = new ConfigEntry.Builder<String>() public static ConfigEntry<String> levelKeyPrefix = new ConfigEntry.Builder<String>()
.setChatCommandName("levelKeys.prefix") .setChatCommandName("levelKeys.prefix")
.set("") .set("")
@@ -26,7 +26,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.file.Path; import java.nio.file.Path;
@@ -44,7 +44,7 @@ import java.util.*;
*/ */
public class ConfigHandler public class ConfigHandler
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class); private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
@@ -123,6 +123,7 @@ public class ConfigHandler
this.initNestedClass(Config.class, ""); // Init root category this.initNestedClass(Config.class, ""); // Init root category
this.configFileHandler.loadFromFile(); this.configFileHandler.loadFromFile();
this.runMinMaxValidation = !Config.Client.Advanced.Debugging.allowUnsafeValues.get();
this.isLoaded = true; this.isLoaded = true;
LOGGER.info("[" + ModInfo.NAME + "] Config initialised"); LOGGER.info("[" + ModInfo.NAME + "] Config initialised");
@@ -24,18 +24,19 @@ import com.seibel.distanthorizons.core.config.ConfigPresetOptions;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener; import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase; import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.config.IConfigGui; import com.seibel.distanthorizons.core.wrapperInterfaces.config.IConfigGui;
import com.seibel.distanthorizons.coreapi.util.StringUtil; import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<?>> implements IConfigListener public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<?>> implements IConfigListener
{ {
private static final Logger LOGGER = LogManager.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final long MS_DELAY_BEFORE_APPLYING_PRESET = 3_000; private static final long MS_DELAY_BEFORE_APPLYING_PRESET = 3_000;
@Nullable @Nullable
@@ -139,7 +140,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
LOGGER.info("changing preset to: " + newPresetEnum); LOGGER.debug("changing preset to: [" + newPresetEnum + "].");
this.changingPreset = true; this.changingPreset = true;
// update the controlled config values // update the controlled config values
@@ -151,7 +152,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
this.setUiOnlyConfigValues(); this.setUiOnlyConfigValues();
this.changingPreset = false; this.changingPreset = false;
LOGGER.info("preset active: " + newPresetEnum); LOGGER.debug("preset active: [" + newPresetEnum + "].");
} }
/** /**
@@ -29,8 +29,8 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.ConfigPresetOptions; import com.seibel.distanthorizons.core.config.ConfigPresetOptions;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase; import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import org.apache.logging.log4j.LogManager; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.*; import java.util.*;
@@ -39,7 +39,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
{ {
public static final RenderQualityPresetConfigEventHandler INSTANCE = new RenderQualityPresetConfigEventHandler(); public static final RenderQualityPresetConfigEventHandler INSTANCE = new RenderQualityPresetConfigEventHandler();
private static final Logger LOGGER = LogManager.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final ConfigPresetOptions<EDhApiQualityPreset, EDhApiMaxHorizontalResolution> drawResolution = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution, private final ConfigPresetOptions<EDhApiQualityPreset, EDhApiMaxHorizontalResolution> drawResolution = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution,
@@ -96,6 +96,15 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, EDhApiMcRenderingFadeMode.DOUBLE_PASS); this.put(EDhApiQualityPreset.HIGH, EDhApiMcRenderingFadeMode.DOUBLE_PASS);
this.put(EDhApiQualityPreset.EXTREME, EDhApiMcRenderingFadeMode.DOUBLE_PASS); this.put(EDhApiQualityPreset.EXTREME, EDhApiMcRenderingFadeMode.DOUBLE_PASS);
}}); }});
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> dhFadeFarClipPlane = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane,
new HashMap<EDhApiQualityPreset, Boolean>()
{{
this.put(EDhApiQualityPreset.MINIMUM, false);
this.put(EDhApiQualityPreset.LOW, false);
this.put(EDhApiQualityPreset.MEDIUM, true);
this.put(EDhApiQualityPreset.HIGH, true);
this.put(EDhApiQualityPreset.EXTREME, true);
}});
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> dhDither = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.ditherDhFade, private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> dhDither = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.ditherDhFade,
new HashMap<EDhApiQualityPreset, Boolean>() new HashMap<EDhApiQualityPreset, Boolean>()
{{ {{
@@ -139,6 +148,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.configList.add(this.horizontalQuality); this.configList.add(this.horizontalQuality);
this.configList.add(this.transparency); this.configList.add(this.transparency);
this.configList.add(this.ssaoEnabled); this.configList.add(this.ssaoEnabled);
this.configList.add(this.dhFadeFarClipPlane);
this.configList.add(this.vanillaFade); this.configList.add(this.vanillaFade);
this.configList.add(this.dhDither); this.configList.add(this.dhDither);
this.configList.add(this.caveCulling); this.configList.add(this.caveCulling);
@@ -24,9 +24,10 @@ import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.ConfigPresetOptions; import com.seibel.distanthorizons.core.config.ConfigPresetOptions;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase; import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@@ -37,7 +38,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
{ {
public static final ThreadPresetConfigEventHandler INSTANCE = new ThreadPresetConfigEventHandler(); public static final ThreadPresetConfigEventHandler INSTANCE = new ThreadPresetConfigEventHandler();
private static final Logger LOGGER = LogManager.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static int getDefaultThreadCount() { return getThreadCountByPercent(0.5); } public static int getDefaultThreadCount() { return getThreadCountByPercent(0.5); }
@@ -28,7 +28,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@@ -43,13 +43,11 @@ import java.util.concurrent.locks.ReentrantLock;
*/ */
public class ConfigFileHandler public class ConfigFileHandler
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public final Path configPath; public final Path configPath;
private final Logger logger;
/** This is the object for night-config */ /** This is the object for night-config */
private final CommentedFileConfig nightConfig; private final CommentedFileConfig nightConfig;
@@ -64,7 +62,6 @@ public class ConfigFileHandler
public ConfigFileHandler(Path configPath) public ConfigFileHandler(Path configPath)
{ {
this.logger = LogManager.getLogger(this.getClass().getSimpleName() + ", " + ModInfo.ID);
this.configPath = configPath; this.configPath = configPath;
this.nightConfig = CommentedFileConfig this.nightConfig = CommentedFileConfig
@@ -157,18 +154,18 @@ public class ConfigFileHandler
} }
else if (currentCfgVersion > ModInfo.CONFIG_FILE_VERSION) else if (currentCfgVersion > ModInfo.CONFIG_FILE_VERSION)
{ {
this.logger.warn("Found config version [" + currentCfgVersion + "] which is newer than current mods config version of [" + ModInfo.CONFIG_FILE_VERSION + "]. You may have downgraded the mod and items may have been moved, you have been warned"); LOGGER.warn("Found config version [" + currentCfgVersion + "] which is newer than current mods config version of [" + ModInfo.CONFIG_FILE_VERSION + "]. You may have downgraded the mod and items may have been moved, you have been warned");
} }
else // if (currentCfgVersion < configBase.configVersion) else // if (currentCfgVersion < configBase.configVersion)
{ {
this.logger.warn(ModInfo.NAME + " config is of an older version, currently there is no config updater... so resetting config"); LOGGER.warn(ModInfo.NAME + " config is of an older version, currently there is no config updater... so resetting config");
try try
{ {
Files.delete(this.configPath); Files.delete(this.configPath);
} }
catch (Exception e) catch (Exception e)
{ {
this.logger.error(e); LOGGER.error("Unable to delete outdated config file at: ["+this.configPath+"], error: ["+e.getMessage()+"].", e);
} }
} }
@@ -243,7 +240,7 @@ public class ConfigFileHandler
else if (entry.getTrueValue() == null) else if (entry.getTrueValue() == null)
{ {
// TODO when can this happen? // TODO when can this happen?
throw new IllegalArgumentException("Entry [" + entry.getNameAndCategory() + "] is null, this may be a problem with [" + ModInfo.NAME + "]. Please contact the authors."); throw new IllegalArgumentException("BlockBiomeWrapperPair [" + entry.getNameAndCategory() + "] is null, this may be a problem with [" + ModInfo.NAME + "]. Please contact the authors.");
} }
workConfig.set(entry.getNameAndCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue())); workConfig.set(entry.getNameAndCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue()));
@@ -281,7 +278,7 @@ public class ConfigFileHandler
Object convertedValue = ConfigTypeConverters.attemptToConvertFromString(expectedValueClass, value); Object convertedValue = ConfigTypeConverters.attemptToConvertFromString(expectedValueClass, value);
if (!convertedValue.getClass().equals(expectedValueClass)) if (!convertedValue.getClass().equals(expectedValueClass))
{ {
this.logger.error("Unable to convert config value ["+value+"] from ["+(value != null ? value.getClass() : "NULL")+"] to ["+expectedValueClass+"] for config ["+entry.name+"], " + LOGGER.error("Unable to convert config value ["+value+"] from ["+(value != null ? value.getClass() : "NULL")+"] to ["+expectedValueClass+"] for config ["+entry.name+"], " +
"the default config value will be used instead ["+entry.getDefaultValue()+"]. " + "the default config value will be used instead ["+entry.getDefaultValue()+"]. " +
"Make sure a converter is defined in ["+ConfigTypeConverters.class.getSimpleName()+"]."); "Make sure a converter is defined in ["+ConfigTypeConverters.class.getSimpleName()+"].");
convertedValue = entry.getDefaultValue(); convertedValue = entry.getDefaultValue();
@@ -290,13 +287,13 @@ public class ConfigFileHandler
if (entry.getTrueValue() == null) if (entry.getTrueValue() == null)
{ {
this.logger.warn("Entry [" + entry.getNameAndCategory() + "] returned as null from the config. Using default value."); LOGGER.warn("BlockBiomeWrapperPair [" + entry.getNameAndCategory() + "] returned as null from the config. Using default value.");
entry.setWithoutFiringEvents(entry.getDefaultValue()); entry.setWithoutFiringEvents(entry.getDefaultValue());
} }
} }
catch (Exception e) catch (Exception e)
{ {
this.logger.warn("Entry [" + entry.getNameAndCategory() + "] had an invalid value when loading the config. Using default value."); LOGGER.warn("BlockBiomeWrapperPair [" + entry.getNameAndCategory() + "] had an invalid value when loading the config. Using default value.");
entry.setWithoutFiringEvents(entry.getDefaultValue()); entry.setWithoutFiringEvents(entry.getDefaultValue());
} }
} }
@@ -350,18 +347,20 @@ public class ConfigFileHandler
} }
catch (Exception e) catch (Exception e)
{ {
this.logger.warn("Loading file failed because of this expectation:\n" + e); LOGGER.warn("Loading file failed because of this expectation:\n" + e);
reCreateFile(this.configPath); reCreateFile(this.configPath);
nightConfig.load(); nightConfig.load();
} }
} }
catch (Exception ex) catch (Exception e)
{ {
System.out.println("Creating file failed"); LOGGER.error("File creation failed at ["+this.configPath+"], error: ["+e.getMessage()+"].", e);
this.logger.error(ex);
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Loading file and resetting config file failed at path [" + this.configPath + "]. Please check the file is ok and you have the permissions", ex); // TODO is there a reason this is lazily gotten?
IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
mc.crashMinecraft("Loading file and resetting config file failed at path [" + this.configPath + "]. Please check the file is ok and you have the permissions", e);
} }
} }
@@ -23,7 +23,7 @@ import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.io.ParsingMode; import com.electronwill.nightconfig.core.io.ParsingMode;
import com.electronwill.nightconfig.json.JsonFormat; import com.electronwill.nightconfig.json.JsonFormat;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -36,7 +36,7 @@ import java.util.Map;
*/ */
public class ConfigTypeConverters public class ConfigTypeConverters
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
// Once you've made a converter add it to here where the first value is the type you want to convert and the 2nd value is the converter // Once you've made a converter add it to here where the first value is the type you want to convert and the 2nd value is the converter
public static final Map<Class<?>, ConverterBase> convertObjects = new HashMap<Class<?>, ConverterBase>() public static final Map<Class<?>, ConverterBase> convertObjects = new HashMap<Class<?>, ConverterBase>()
@@ -23,7 +23,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.types.enums.EConfigCommentTextPosition; import com.seibel.distanthorizons.core.config.types.enums.EConfigCommentTextPosition;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance; import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -34,7 +34,7 @@ import org.jetbrains.annotations.Nullable;
*/ */
public class ConfigUIComment extends AbstractConfigBase<String> public class ConfigUIComment extends AbstractConfigBase<String>
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public String parentConfigPath = null; public String parentConfigPath = null;
@@ -0,0 +1,151 @@
package com.seibel.distanthorizons.core.dataObjects;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import java.util.concurrent.ConcurrentHashMap;
/**
* A pooled compound key between the biome and blockState. <br>
* These objects are pooled since we will need this compound key
* many times.
*
* @see FullDataPointIdMap
* @see IBlockStateWrapper
* @see IBiomeWrapper
*/
public class BlockBiomeWrapperPair
{
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
/** two levels are present so we don't need to use a key object */
private static final ConcurrentHashMap<IBlockStateWrapper, ConcurrentHashMap<IBiomeWrapper, BlockBiomeWrapperPair>> CACHED_PAIR_BY_BIOME_BY_BLOCK = new ConcurrentHashMap<>();
public final IBiomeWrapper biome;
public final IBlockStateWrapper blockState;
private int hashCode = 0;
private boolean hashGenerated = false;
private String serialString = null;
//=============//
// constructor //
//=============//
public static BlockBiomeWrapperPair get(IBlockStateWrapper blockState, IBiomeWrapper biome)
{
// check for existing entry
ConcurrentHashMap<IBiomeWrapper, BlockBiomeWrapperPair> pairByBiomeWrapper = CACHED_PAIR_BY_BIOME_BY_BLOCK.get(blockState);
if (pairByBiomeWrapper != null)
{
BlockBiomeWrapperPair pair = pairByBiomeWrapper.get(biome);
if (pair != null)
{
return pair;
}
}
// Lazily create the inner map and new BlockBiomeWrapperPair
return CACHED_PAIR_BY_BIOME_BY_BLOCK
.computeIfAbsent(blockState, newBlockState -> new ConcurrentHashMap<>())
.computeIfAbsent(biome, newBiome -> new BlockBiomeWrapperPair(biome, blockState));
}
private BlockBiomeWrapperPair(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
this.biome = biome;
this.blockState = blockState;
}
//===========//
// overrides //
//===========//
/**
* Reminder: this hash code won't always be unique, collisions can occur;
* because of that this hash shouldn't be the only unique identifier for this object.
*/
@Override
public int hashCode()
{
// cache the hash code to improve speed
if (!this.hashGenerated)
{
this.hashCode = generateHashCode(this);
this.hashGenerated = true;
}
return this.hashCode;
}
private static int generateHashCode(BlockBiomeWrapperPair pair) { return generateHashCode(pair.biome, pair.blockState); }
private static int generateHashCode(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
final int prime = 31;
int result = 1;
// the biome and blockstate hashcode should be already calculated by the time
// we get here, so this operation should be very fast
result = prime * result + (biome == null ? 0 : biome.hashCode());
result = prime * result + (blockState == null ? 0 : blockState.hashCode());
return result;
}
@Override
public boolean equals(Object otherObj)
{
if (otherObj == this)
{
return true;
}
if (!(otherObj instanceof BlockBiomeWrapperPair))
{
return false;
}
BlockBiomeWrapperPair other = (BlockBiomeWrapperPair) otherObj;
return other.biome.getSerialString().equals(this.biome.getSerialString())
&& other.blockState.getSerialString().equals(this.blockState.getSerialString());
}
@Override
public String toString() { return this.serialize(); }
//=================//
// (de)serializing //
//=================//
public String serialize()
{
if (this.serialString == null)
{
this.serialString = this.biome.getSerialString() + FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING + this.blockState.getSerialString();
}
return this.serialString;
}
public static BlockBiomeWrapperPair deserialize(String str, ILevelWrapper levelWrapper) throws DataCorruptedException
{
int separatorIndex = str.indexOf(FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING);
if (separatorIndex == -1)
{
throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry ["+str+"], unable to find separator.");
}
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapperOrGetDefault(str.substring(0, separatorIndex), levelWrapper);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault(str.substring(separatorIndex+FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING.length()), levelWrapper);
return BlockBiomeWrapperPair.get(blockState, biome);
}
}
@@ -19,7 +19,8 @@
package com.seibel.distanthorizons.core.dataObjects.fullData; package com.seibel.distanthorizons.core.dataObjects.fullData;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dataObjects.BlockBiomeWrapperPair;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
@@ -27,10 +28,8 @@ import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStrea
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.LogManager; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.Logger;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
@@ -51,7 +50,7 @@ import java.util.concurrent.ConcurrentHashMap;
*/ */
public class FullDataPointIdMap public class FullDataPointIdMap
{ {
private static final Logger LOGGER = LogManager.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** /**
* Should only be enabled when debugging. * Should only be enabled when debugging.
* Has the system check if any duplicate Entries were read/written * Has the system check if any duplicate Entries were read/written
@@ -59,15 +58,15 @@ public class FullDataPointIdMap
*/ */
private static final boolean RUN_SERIALIZATION_DUPLICATE_VALIDATION = false; private static final boolean RUN_SERIALIZATION_DUPLICATE_VALIDATION = false;
/** Distant Horizons - Block State Wrapper */ /** Distant Horizons - Block State Wrapper */
private static final String BLOCK_STATE_SEPARATOR_STRING = "_DH-BSW_"; public static final String BLOCK_STATE_SEPARATOR_STRING = "_DH-BSW_";
/** should only be used for debugging */ /** should only be used for debugging */
private long pos; private long pos;
/** The index should be the same as the Entry's ID */ /** The index should be the same as the BlockBiomeWrapperPair's ID */
private final ArrayList<Entry> entryList = new ArrayList<>(); private final ArrayList<BlockBiomeWrapperPair> blockBiomePairList = new ArrayList<>();
private final ConcurrentHashMap<Entry, Integer> idMap = new ConcurrentHashMap<>(); private final ConcurrentHashMap<BlockBiomeWrapperPair, Integer> idMap = new ConcurrentHashMap<>();
private int cachedHashCode = 0; private int cachedHashCode = 0;
@@ -89,28 +88,28 @@ public class FullDataPointIdMap
public IBiomeWrapper getBiomeWrapper(int id) throws IndexOutOfBoundsException { return this.getEntry(id).biome; } public IBiomeWrapper getBiomeWrapper(int id) throws IndexOutOfBoundsException { return this.getEntry(id).biome; }
/** @see FullDataPointIdMap#getEntry(int) */ /** @see FullDataPointIdMap#getEntry(int) */
public IBlockStateWrapper getBlockStateWrapper(int id) throws IndexOutOfBoundsException { return this.getEntry(id).blockState; } public IBlockStateWrapper getBlockStateWrapper(int id) throws IndexOutOfBoundsException { return this.getEntry(id).blockState; }
/** @throws IndexOutOfBoundsException if the given ID isn't in the {@link FullDataPointIdMap#entryList} */ /** @throws IndexOutOfBoundsException if the given ID isn't in the {@link FullDataPointIdMap#blockBiomePairList} */
private Entry getEntry(int id) throws IndexOutOfBoundsException private BlockBiomeWrapperPair getEntry(int id) throws IndexOutOfBoundsException
{ {
Entry entry; BlockBiomeWrapperPair pair;
try try
{ {
entry = this.entryList.get(id); pair = this.blockBiomePairList.get(id);
} }
catch (IndexOutOfBoundsException e) catch (IndexOutOfBoundsException e)
{ {
throw new IndexOutOfBoundsException("FullData ID Map out of sync for pos: "+this.pos+". ID: ["+id+"] greater than the number of known ID's: ["+this.entryList.size()+"]."); throw new IndexOutOfBoundsException("FullData ID Map out of sync for pos: "+DhSectionPos.toString(this.pos)+". ID: ["+id+"] greater than the number of known ID's: ["+this.blockBiomePairList.size()+"].");
} }
return entry; return pair;
} }
/** @return -1 if the list is empty */ /** @return -1 if the list is empty */
public int getMaxValidId() { return this.entryList.size() - 1; } public int getMaxValidId() { return this.blockBiomePairList.size() - 1; }
public int size() { return this.entryList.size(); } public int size() { return this.blockBiomePairList.size(); }
public boolean isEmpty() { return this.entryList.isEmpty(); } public boolean isEmpty() { return this.blockBiomePairList.isEmpty(); }
public long getPos() { return this.pos; } public long getPos() { return this.pos; }
@@ -124,11 +123,11 @@ public class FullDataPointIdMap
* If an entry with the given values already exists nothing will * If an entry with the given values already exists nothing will
* be added but the existing item's ID will still be returned. * be added but the existing item's ID will still be returned.
*/ */
public int addIfNotPresentAndGetId(IBiomeWrapper biome, IBlockStateWrapper blockState) { return this.addIfNotPresentAndGetId(Entry.getEntry(biome, blockState)); } public int addIfNotPresentAndGetId(IBiomeWrapper biome, IBlockStateWrapper blockState) { return this.addIfNotPresentAndGetId(BlockBiomeWrapperPair.get(blockState, biome)); }
private int addIfNotPresentAndGetId(Entry biomeBlockStateEntry) private int addIfNotPresentAndGetId(BlockBiomeWrapperPair pair)
{ {
// try getting the existing ID // try getting the existing ID
Integer nullableId = this.idMap.get(biomeBlockStateEntry); Integer nullableId = this.idMap.get(pair);
if (nullableId != null) if (nullableId != null)
{ {
return nullableId; return nullableId;
@@ -136,7 +135,7 @@ public class FullDataPointIdMap
// create the new ID // create the new ID
return this.idMap.compute(biomeBlockStateEntry, (Entry newBiomeBlockStateEntry, Integer currentId) -> return this.idMap.compute(pair, (BlockBiomeWrapperPair newPair, Integer currentId) ->
{ {
if (currentId != null) if (currentId != null)
{ {
@@ -145,8 +144,8 @@ public class FullDataPointIdMap
// Add the new ID // Add the new ID
currentId = this.entryList.size(); currentId = this.blockBiomePairList.size();
this.entryList.add(biomeBlockStateEntry); this.blockBiomePairList.add(newPair);
// invalidate the cached hash code // invalidate the cached hash code
this.cachedHashCode = 0; this.cachedHashCode = 0;
@@ -156,7 +155,7 @@ public class FullDataPointIdMap
} }
/** /**
* Adds every {@link Entry} from inputMap into this map. <br> * Adds every {@link BlockBiomeWrapperPair} from inputMap into this map. <br>
* Allows duplicate entries. <br><br> * Allows duplicate entries. <br><br>
* *
* Allowing duplicate entries should be done if a datasource is just being read in and * Allowing duplicate entries should be done if a datasource is just being read in and
@@ -166,19 +165,19 @@ public class FullDataPointIdMap
*/ */
public void addAll(FullDataPointIdMap inputMap) public void addAll(FullDataPointIdMap inputMap)
{ {
ArrayList<Entry> entriesToMerge = inputMap.entryList; ArrayList<BlockBiomeWrapperPair> pairsToMerge = inputMap.blockBiomePairList;
for (int i = 0; i < entriesToMerge.size(); i++) for (int i = 0; i < pairsToMerge.size(); i++)
{ {
Entry entity = entriesToMerge.get(i); BlockBiomeWrapperPair pair = pairsToMerge.get(i);
this.add(entity); this.add(pair);
} }
} }
/** allows for adding duplicate {@link Entry} */ /** allows for adding duplicate {@link BlockBiomeWrapperPair} */
private void add(Entry biomeBlockStateEntry) private void add(BlockBiomeWrapperPair pair)
{ {
int id = this.entryList.size(); int id = this.blockBiomePairList.size();
this.entryList.add(biomeBlockStateEntry); this.blockBiomePairList.add(pair);
this.idMap.put(biomeBlockStateEntry, id); this.idMap.put(pair, id);
// invalidate the cached hash code // invalidate the cached hash code
this.cachedHashCode = 0; this.cachedHashCode = 0;
@@ -195,23 +194,23 @@ public class FullDataPointIdMap
*/ */
public int[] mergeAndReturnRemappedEntityIds(FullDataPointIdMap inputMap) public int[] mergeAndReturnRemappedEntityIds(FullDataPointIdMap inputMap)
{ {
ArrayList<Entry> entriesToMerge = inputMap.entryList; ArrayList<BlockBiomeWrapperPair> entriesToMerge = inputMap.blockBiomePairList;
int[] remappedEntryIds = new int[entriesToMerge.size()]; int[] remappedPairIds = new int[entriesToMerge.size()];
for (int i = 0; i < entriesToMerge.size(); i++) for (int i = 0; i < entriesToMerge.size(); i++)
{ {
Entry entity = entriesToMerge.get(i); BlockBiomeWrapperPair entity = entriesToMerge.get(i);
int id = this.addIfNotPresentAndGetId(entity); int id = this.addIfNotPresentAndGetId(entity);
remappedEntryIds[i] = id; remappedPairIds[i] = id;
} }
return remappedEntryIds; return remappedPairIds;
} }
/** Should only be used if this map is going to be reused, otherwise bad things will happen. */ /** Should only be used if this map is going to be reused, otherwise bad things will happen. */
public void clear(long pos) public void clear(long pos)
{ {
this.pos = pos; this.pos = pos;
this.entryList.clear(); this.blockBiomePairList.clear();
this.idMap.clear(); this.idMap.clear();
this.cachedHashCode = 0; this.cachedHashCode = 0;
} }
@@ -225,27 +224,27 @@ public class FullDataPointIdMap
/** Serializes all contained entries into the given stream, formatted in UTF */ /** Serializes all contained entries into the given stream, formatted in UTF */
public void serialize(DhDataOutputStream outputStream) throws IOException public void serialize(DhDataOutputStream outputStream) throws IOException
{ {
outputStream.writeInt(this.entryList.size()); outputStream.writeInt(this.blockBiomePairList.size());
// only used when debugging // only used when debugging
HashMap<String, FullDataPointIdMap.Entry> dataPointEntryBySerialization = new HashMap<>(); HashMap<String, BlockBiomeWrapperPair> dataPointEntryBySerialization = new HashMap<>();
for (Entry entry : this.entryList) for (BlockBiomeWrapperPair pair : this.blockBiomePairList)
{ {
String entryString = entry.serialize(); String entryString = pair.serialize();
outputStream.writeUTF(entryString); outputStream.writeUTF(entryString);
if (RUN_SERIALIZATION_DUPLICATE_VALIDATION) if (RUN_SERIALIZATION_DUPLICATE_VALIDATION)
{ {
if (dataPointEntryBySerialization.containsKey(entryString)) if (dataPointEntryBySerialization.containsKey(entryString))
{ {
LOGGER.error("Duplicate serialized entry found with serial: " + entryString); LOGGER.error("Duplicate serialized pair found with serial: " + entryString);
} }
if (dataPointEntryBySerialization.containsValue(entry)) if (dataPointEntryBySerialization.containsValue(pair))
{ {
LOGGER.error("Duplicate serialized entry found with value: " + entry.serialize()); LOGGER.error("Duplicate serialized pair found with value: " + pair.serialize());
} }
dataPointEntryBySerialization.put(entryString, entry); dataPointEntryBySerialization.put(entryString, pair);
} }
} }
} }
@@ -261,7 +260,7 @@ public class FullDataPointIdMap
// only used when debugging // only used when debugging
HashMap<String, FullDataPointIdMap.Entry> dataPointEntryBySerialization = new HashMap<>(); HashMap<String, BlockBiomeWrapperPair> dataPointEntryBySerialization = new HashMap<>();
FullDataPointIdMap newMap = new FullDataPointIdMap(pos); FullDataPointIdMap newMap = new FullDataPointIdMap(pos);
for (int i = 0; i < entityCount; i++) for (int i = 0; i < entityCount; i++)
@@ -274,8 +273,8 @@ public class FullDataPointIdMap
String entryString = inputStream.readUTF(); String entryString = inputStream.readUTF();
Entry newEntry = Entry.deserialize(entryString, levelWrapper); BlockBiomeWrapperPair newPair = BlockBiomeWrapperPair.deserialize(entryString, levelWrapper);
newMap.entryList.add(newEntry); newMap.blockBiomePairList.add(newPair);
if (RUN_SERIALIZATION_DUPLICATE_VALIDATION) if (RUN_SERIALIZATION_DUPLICATE_VALIDATION)
{ {
@@ -283,11 +282,11 @@ public class FullDataPointIdMap
{ {
LOGGER.error("Duplicate deserialized entry found with serial: " + entryString); LOGGER.error("Duplicate deserialized entry found with serial: " + entryString);
} }
if (dataPointEntryBySerialization.containsValue(newEntry)) if (dataPointEntryBySerialization.containsValue(newPair))
{ {
LOGGER.error("Duplicate deserialized entry found with value: " + newEntry.serialize()); LOGGER.error("Duplicate deserialized entry found with value: " + newPair.serialize());
} }
dataPointEntryBySerialization.put(entryString, newEntry); dataPointEntryBySerialization.put(entryString, newPair);
} }
} }
@@ -333,149 +332,13 @@ public class FullDataPointIdMap
private void generateHashCode() private void generateHashCode()
{ {
int result = DhSectionPos.hashCode(this.pos); int result = DhSectionPos.hashCode(this.pos);
for (int i = 0; i < this.entryList.size(); i++) for (int i = 0; i < this.blockBiomePairList.size(); i++)
{ {
result = 31 * result + this.entryList.hashCode(); result = 31 * result + this.blockBiomePairList.hashCode();
} }
this.cachedHashCode = result; this.cachedHashCode = result;
} }
//==============//
// helper class //
//==============//
private static final class Entry
{
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
/** two levels are present so we don't need to use a key object */
private static final ConcurrentHashMap<IBiomeWrapper, ConcurrentHashMap<IBlockStateWrapper, Entry>> ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER = new ConcurrentHashMap<>();
public final IBiomeWrapper biome;
public final IBlockStateWrapper blockState;
private int hashCode = 0;
private boolean hashGenerated = false;
private String serialString = null;
//=============//
// constructor //
//=============//
public static Entry getEntry(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
// check for existing entry
ConcurrentHashMap<IBlockStateWrapper, Entry> entryByBlockState = ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER.get(biome);
if (entryByBlockState != null)
{
Entry entry = entryByBlockState.get(blockState);
if (entry != null)
{
return entry;
}
}
// Lazily create the inner map and new Entry
return ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER
.computeIfAbsent(biome, newBiome -> new ConcurrentHashMap<>())
.computeIfAbsent(blockState, newBlockState -> new Entry(biome, blockState));
}
private Entry(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
this.biome = biome;
this.blockState = blockState;
}
//===========//
// overrides //
//===========//
/**
* Reminder: this hash code won't always be unique, collisions can occur;
* because of that this hash shouldn't be the only unique identifier for this object.
*/
@Override
public int hashCode()
{
// cache the hash code to improve speed
if (!this.hashGenerated)
{
this.hashCode = generateHashCode(this);
this.hashGenerated = true;
}
return this.hashCode;
}
private static int generateHashCode(Entry entry) { return generateHashCode(entry.biome, entry.blockState); }
private static int generateHashCode(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
final int prime = 31;
int result = 1;
// the biome and blockstate hashcode should be already calculated by the time
// we get here, so this operation should be very fast
result = prime * result + (biome == null ? 0 : biome.hashCode());
result = prime * result + (blockState == null ? 0 : blockState.hashCode());
return result;
}
@Override
public boolean equals(Object otherObj)
{
if (otherObj == this)
{
return true;
}
if (!(otherObj instanceof Entry))
{
return false;
}
Entry other = (Entry) otherObj;
return other.biome.getSerialString().equals(this.biome.getSerialString())
&& other.blockState.getSerialString().equals(this.blockState.getSerialString());
}
@Override
public String toString() { return this.serialize(); }
//=================//
// (de)serializing //
//=================//
public String serialize()
{
if (this.serialString == null)
{
this.serialString = this.biome.getSerialString() + BLOCK_STATE_SEPARATOR_STRING + this.blockState.getSerialString();
}
return this.serialString;
}
public static Entry deserialize(String str, ILevelWrapper levelWrapper) throws DataCorruptedException
{
int separatorIndex = str.indexOf(BLOCK_STATE_SEPARATOR_STRING);
if (separatorIndex == -1)
{
throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry ["+str+"], unable to find separator.");
}
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapperOrGetDefault(str.substring(0, separatorIndex), levelWrapper);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault(str.substring(separatorIndex+BLOCK_STATE_SEPARATOR_STRING.length()), levelWrapper);
return Entry.getEntry(biome, blockState);
}
}
} }
@@ -20,8 +20,8 @@
package com.seibel.distanthorizons.core.dataObjects.fullData.sources; package com.seibel.distanthorizons.core.dataObjects.fullData.sources;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.file.IDataSource;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
@@ -33,7 +33,6 @@ import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStre
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import java.io.*; import java.io.*;
import java.util.Arrays; import java.util.Arrays;
@@ -47,9 +46,9 @@ import java.util.Arrays;
* @see FullDataPointUtil * @see FullDataPointUtil
* @see FullDataSourceV2 * @see FullDataSourceV2
*/ */
public class FullDataSourceV1 implements IDataSource<IDhLevel> public class FullDataSourceV1
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static final byte SECTION_SIZE_OFFSET = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; public static final byte SECTION_SIZE_OFFSET = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
/** measured in dataPoints */ /** measured in dataPoints */
@@ -94,28 +93,13 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
} }
//======//
// data //
//======//
@Deprecated
@Override
public boolean update(FullDataSourceV2 dataSource, IDhLevel level) { throw new UnsupportedOperationException("Deprecated"); }
//=====================// //=====================//
// setters and getters // // setters and getters //
//=====================// //=====================//
@Override
public Long getKey() { return this.pos; } public Long getKey() { return this.pos; }
@Override
public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); } public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); }
@Override
public long getPos() { return this.pos; } public long getPos() { return this.pos; }
public void resizeDataStructuresForRepopulation(long pos) public void resizeDataStructuresForRepopulation(long pos)
@@ -124,7 +108,6 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
this.pos = pos; this.pos = pos;
} }
@Override
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - SECTION_SIZE_OFFSET); } public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - SECTION_SIZE_OFFSET); }
public boolean isEmpty() { return this.isEmpty; } public boolean isEmpty() { return this.isEmpty; }
@@ -197,7 +180,7 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
{ {
outputStream.writeInt(this.getDataDetailLevel()); outputStream.writeInt(this.getDataDetailLevel());
outputStream.writeInt(WIDTH); outputStream.writeInt(WIDTH);
outputStream.writeInt(level.getMinY()); outputStream.writeInt(level.getLevelWrapper().getMinHeight());
outputStream.writeByte(this.worldGenStep.value); outputStream.writeByte(this.worldGenStep.value);
} }
@@ -206,19 +189,19 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
int dataDetail = inputStream.readInt(); int dataDetail = inputStream.readInt();
if (dataDetail != dto.dataDetailLevel) if (dataDetail != dto.dataDetailLevel)
{ {
throw new IOException(LodUtil.formatLog("Data level mismatch. Expected: ["+dto.dataDetailLevel+"], found ["+dataDetail+"].")); throw new IOException("Data level mismatch. Expected: ["+dto.dataDetailLevel+"], found ["+dataDetail+"].");
} }
int width = inputStream.readInt(); int width = inputStream.readInt();
if (width != WIDTH) if (width != WIDTH)
{ {
throw new IOException(LodUtil.formatLog("Section width mismatch: " + width + " != " + WIDTH + " (Currently only 1 section width is supported)")); throw new IOException("Section width mismatch: [" + width + "] != [" + WIDTH + "] (Currently only 1 section width is supported)");
} }
int minY = inputStream.readInt(); int minY = inputStream.readInt();
if (minY != level.getMinY()) if (minY != level.getLevelWrapper().getMinHeight())
{ {
LOGGER.warn("Data minY mismatch: " + minY + " != " + level.getMinY() + ". Will ignore data's y level"); LOGGER.warn("Data minY mismatch: [" + minY + "] != [" + level.getLevelWrapper().getMinHeight() + "]. Will ignore data's y level");
} }
byte worldGenByte = inputStream.readByte(); byte worldGenByte = inputStream.readByte();
@@ -377,15 +360,6 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); } public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); }
//==================//
// override methods //
//==================//
@Override
public void close()
{ /* not currently needed */ }
//================// //================//
// helper classes // // helper classes //
@@ -23,11 +23,13 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint; import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource; import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataOcclusionCuller;
import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder; import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder;
import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.IDataSource; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList; import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
@@ -35,6 +37,7 @@ import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.sql.dto.util.FullDataMinMaxPosUtil;
import com.seibel.distanthorizons.core.util.*; import com.seibel.distanthorizons.core.util.*;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -42,10 +45,9 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -58,9 +60,9 @@ import java.util.List;
*/ */
public class FullDataSourceV2 public class FullDataSourceV2
extends AbstractPhantomArrayList extends AbstractPhantomArrayList
implements IDataSource<IDhLevel>, IDhApiFullDataSource implements IDhApiFullDataSource
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** useful for debugging, but can slow down update operations quite a bit due to being called so often. */ /** useful for debugging, but can slow down update operations quite a bit due to being called so often. */
private static final boolean RUN_UPDATE_DEV_VALIDATION = false; private static final boolean RUN_UPDATE_DEV_VALIDATION = false;
/** /**
@@ -75,8 +77,6 @@ public class FullDataSourceV2
/** how many chunks wide this datasource is at detail level 0. */ /** how many chunks wide this datasource is at detail level 0. */
public static final int NUMB_OF_CHUNKS_WIDE = WIDTH / LodUtil.CHUNK_WIDTH; public static final int NUMB_OF_CHUNKS_WIDE = WIDTH / LodUtil.CHUNK_WIDTH;
public static final byte DATA_FORMAT_VERSION = 1;
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("FullDataV2"); public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("FullDataV2");
@@ -84,10 +84,6 @@ public class FullDataSourceV2
private int cachedHashCode = 0; private int cachedHashCode = 0;
private final long pos; private final long pos;
@Override
public Long getKey() { return this.pos; }
@Override
public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); }
public final FullDataPointIdMap mapping; public final FullDataPointIdMap mapping;
@@ -113,9 +109,7 @@ public class FullDataSourceV2
/** /**
* stored x/z, y <br> * stored x/z, y <br>
* The y data should be sorted from top to bottom <br> * The y data should be sorted from top to bottom
* TODO that ordering feels weird, it'd be nice to reverse that order, unfortunately
* there's something in the render data logic that expects this order so we can't change it right now
*/ */
public final LongArrayList[] dataPoints; public final LongArrayList[] dataPoints;
@@ -225,7 +219,7 @@ public class FullDataSourceV2
private FullDataSourceV2( private FullDataSourceV2(
long pos, long pos,
FullDataPointIdMap mapping, @Nullable LongArrayList[] data, FullDataPointIdMap mapping, @Nullable LongArrayList[] data,
@Nullable byte[] columnGenerationSteps, @Nullable byte[] columnWorldCompressionMode, byte @Nullable [] columnGenerationSteps, byte @Nullable [] columnWorldCompressionMode,
boolean empty) boolean empty)
{ {
super(ARRAY_LIST_POOL, 2, 0, WIDTH * WIDTH); super(ARRAY_LIST_POOL, 2, 0, WIDTH * WIDTH);
@@ -282,18 +276,30 @@ public class FullDataSourceV2
//======// //=========//
// getters // // getters //
//======// //=========//
public LongArrayList get(int relX, int relZ) throws IndexOutOfBoundsException public LongArrayList getColumnAtRelPos(int relX, int relZ) throws IndexOutOfBoundsException
{ return this.dataPoints[relativePosToIndex(relX, relZ)]; } { return this.dataPoints[relativePosToIndex(relX, relZ)]; }
@Nullable
public LongArrayList tryGetColumnAtRelPos(int relX, int relZ)
{
int index = tryGetRelativePosToIndex(relX, relZ);
if (index == -1)
{
return null;
}
return this.dataPoints[index];
}
/** /**
* returns {@link FullDataPointUtil#EMPTY_DATA_POINT} if the given {@link DhBlockPos} * returns {@link FullDataPointUtil#EMPTY_DATA_POINT} if the given {@link DhBlockPos}
* is outside this data source's boundaries. * is outside this data source's boundaries.
*/ */
public long getAtBlockPos(DhBlockPos blockPos) public long getDataPointAtBlockPos(DhBlockPos blockPos)
{ {
DhLodPos requestedPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPos.getX(), blockPos.getZ()); DhLodPos requestedPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPos.getX(), blockPos.getZ());
@@ -317,7 +323,7 @@ public class FullDataSourceV2
DhLodPos relativePos = requestedPos.getDhSectionRelativePositionForDetailLevel(requestDetailLevel); DhLodPos relativePos = requestedPos.getDhSectionRelativePositionForDetailLevel(requestDetailLevel);
// get the data column // get the data column
LongArrayList dataColumn = this.get(relativePos.x, relativePos.z); LongArrayList dataColumn = this.getColumnAtRelPos(relativePos.x, relativePos.z);
if (dataColumn == null) if (dataColumn == null)
{ {
return FullDataPointUtil.EMPTY_DATA_POINT; return FullDataPointUtil.EMPTY_DATA_POINT;
@@ -361,9 +367,7 @@ public class FullDataSourceV2
// updating // // updating //
//==========// //==========//
@Override public boolean updateFromChunk(@NotNull FullDataSourceV2 inputDataSource)
public boolean update(@NotNull FullDataSourceV2 inputDataSource, @Nullable IDhLevel level) { return this.update(inputDataSource); }
public boolean update(@NotNull FullDataSourceV2 inputDataSource)
{ {
// don't try updating if the input is empty // don't try updating if the input is empty
if (inputDataSource.mapping.isEmpty()) if (inputDataSource.mapping.isEmpty())
@@ -391,7 +395,7 @@ public class FullDataSourceV2
// copy over application flag if either are set to continue propagating // copy over application flag if either are set to continue propagating
(BoolUtil.falseIfNull(this.applyToParent) || BoolUtil.falseIfNull(inputDataSource.applyToParent)) (BoolUtil.falseIfNull(this.applyToParent) || BoolUtil.falseIfNull(inputDataSource.applyToParent))
// don't propagate past the top of the tree // don't propagate past the top of the tree
&& (DhSectionPos.getDetailLevel(this.pos) < AbstractDataSourceHandler.TOP_SECTION_DETAIL_LEVEL); && (DhSectionPos.getDetailLevel(this.pos) < FullDataSourceProviderV2.ROOT_SECTION_DETAIL_LEVEL);
} }
// null check to prevent setting a flag we don't want to save in the DB // null check to prevent setting a flag we don't want to save in the DB
@@ -400,7 +404,7 @@ public class FullDataSourceV2
this.applyToChildren = this.applyToChildren =
(BoolUtil.falseIfNull(this.applyToChildren) || BoolUtil.falseIfNull(inputDataSource.applyToChildren)) (BoolUtil.falseIfNull(this.applyToChildren) || BoolUtil.falseIfNull(inputDataSource.applyToChildren))
// don't propagate past the bottom of the tree // don't propagate past the bottom of the tree
&& (DhSectionPos.getDetailLevel(this.pos) > AbstractDataSourceHandler.MIN_SECTION_DETAIL_LEVEL); && (DhSectionPos.getDetailLevel(this.pos) > FullDataSourceProviderV2.LEAF_SECTION_DETAIL_LEVEL);
} }
} }
else if (inputDetailLevel + 1 == thisDetailLevel) else if (inputDetailLevel + 1 == thisDetailLevel)
@@ -411,7 +415,7 @@ public class FullDataSourceV2
this.applyToParent = this.applyToParent =
dataChanged dataChanged
&& (BoolUtil.falseIfNull(this.applyToParent) || BoolUtil.falseIfNull(inputDataSource.applyToParent)) && (BoolUtil.falseIfNull(this.applyToParent) || BoolUtil.falseIfNull(inputDataSource.applyToParent))
&& (DhSectionPos.getDetailLevel(this.pos) < AbstractDataSourceHandler.TOP_SECTION_DETAIL_LEVEL); && (DhSectionPos.getDetailLevel(this.pos) < FullDataSourceProviderV2.ROOT_SECTION_DETAIL_LEVEL);
} }
else if (inputDetailLevel - 1 == thisDetailLevel) else if (inputDetailLevel - 1 == thisDetailLevel)
@@ -423,7 +427,7 @@ public class FullDataSourceV2
this.applyToChildren = this.applyToChildren =
dataChanged dataChanged
&& (BoolUtil.falseIfNull(this.applyToChildren) || BoolUtil.falseIfNull(inputDataSource.applyToChildren)) && (BoolUtil.falseIfNull(this.applyToChildren) || BoolUtil.falseIfNull(inputDataSource.applyToChildren))
&& (DhSectionPos.getDetailLevel(this.pos) > AbstractDataSourceHandler.MIN_SECTION_DETAIL_LEVEL); && (DhSectionPos.getDetailLevel(this.pos) > FullDataSourceProviderV2.LEAF_SECTION_DETAIL_LEVEL);
} }
else else
{ {
@@ -434,8 +438,31 @@ public class FullDataSourceV2
throw new UnsupportedOperationException("Unsupported data source update. Expected input detail level of ["+(thisDetailLevel-1)+"], ["+thisDetailLevel+"], or ["+(thisDetailLevel+1)+"], received detail level ["+inputDetailLevel+"]."); throw new UnsupportedOperationException("Unsupported data source update. Expected input detail level of ["+(thisDetailLevel-1)+"], ["+thisDetailLevel+"], or ["+(thisDetailLevel+1)+"], received detail level ["+inputDetailLevel+"].");
} }
if (dataChanged) if (dataChanged)
{ {
EDhApiWorldCompressionMode worldCompressionMode = Config.Common.LodBuilding.worldCompression.get();
boolean cullHiddenBlocks = (worldCompressionMode != EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
if (cullHiddenBlocks)
{
for (int x = 0; x < WIDTH; x++)
{
for (int z = 0; z < WIDTH; z++)
{
LongArrayList dataColumn = this.getColumnAtRelPos(x, z);
if (dataColumn != null
&& dataColumn.size() > 1)
{
FullDataOcclusionCuller.cullHiddenDatapointsInColumn(this, x, z);
}
}
}
}
// update the hash code // update the hash code
this.generateHashCode(); this.generateHashCode();
} }
@@ -824,7 +851,7 @@ public class FullDataSourceV2
catch (DataCorruptedException e) catch (DataCorruptedException e)
{ {
// shouldn't happen, (especially if validation is disabled) but just in case // shouldn't happen, (especially if validation is disabled) but just in case
LOGGER.warn("Skipping corrupt datapoint for pos "+inputDataSource.pos+" at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"]."); LOGGER.warn("Skipping corrupt datapoint for pos ["+DhSectionPos.toString(inputDataSource.pos)+"] at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
} }
} }
@@ -846,7 +873,7 @@ public class FullDataSourceV2
catch (DataCorruptedException e) catch (DataCorruptedException e)
{ {
// shouldn't happen, (especially if validation is disabled) but just in case // shouldn't happen, (especially if validation is disabled) but just in case
LOGGER.warn("Skipping corrupt datapoint for pos "+inputDataSource.pos+" at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"]."); LOGGER.warn("Skipping corrupt datapoint for pos ["+DhSectionPos.toString(inputDataSource.pos)+"] at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
} }
} }
@@ -1067,6 +1094,38 @@ public class FullDataSourceV2
//===================//
// adjacent clearing //
//===================//
/** Removes any non-adjacent data from the given direction. */
public void clearAllNonAdjData(EDhDirection direction)
{
long encodedMinMaxPos = FullDataMinMaxPosUtil.getEncodedMinMaxPos(direction);
int minX = FullDataMinMaxPosUtil.getAdjMinX(encodedMinMaxPos);
int maxX = FullDataMinMaxPosUtil.getAdjMaxX(encodedMinMaxPos);
int minZ = FullDataMinMaxPosUtil.getAdjMinZ(encodedMinMaxPos);
int maxZ = FullDataMinMaxPosUtil.getAdjMaxZ(encodedMinMaxPos);
for (int relX = 0; relX < FullDataSourceV2.WIDTH; relX++)
{
for (int relZ = 0; relZ < FullDataSourceV2.WIDTH; relZ++)
{
// skip non-adjacent data
if (relX >= minX && relX < maxX
&& relZ >= minZ && relZ < maxZ)
{
continue;
}
LongArrayList dataColumn = this.getColumnAtRelPos(relX, relZ);
dataColumn.clear();
dataColumn.add(FullDataPointUtil.EMPTY_DATA_POINT);
}
}
}
//================// //================//
// helper methods // // helper methods //
//================// //================//
@@ -1074,18 +1133,35 @@ public class FullDataSourceV2
/** /**
* Usually this should just be used internally, but there may be instances * Usually this should just be used internally, but there may be instances
* where the raw data arrays are available without the data source object. * where the raw data arrays are available without the data source object.
*
* @return -1 if given an out-of-bounds relative position
*/ */
public static int relativePosToIndex(int relX, int relZ) throws IndexOutOfBoundsException public static int tryGetRelativePosToIndex(int relX, int relZ)
{ {
if (relX < 0 || relZ < 0 || if (relX < 0 || relZ < 0
relX > WIDTH || relZ > WIDTH) || relX >= WIDTH || relZ >= WIDTH)
{ {
throw new IndexOutOfBoundsException("Relative data source positions must be between [0] and ["+WIDTH+"] (inclusive) the relative pos: ["+relX+","+relZ+"] is outside of those boundaries."); return -1;
} }
return (relX * WIDTH) + relZ; return (relX * WIDTH) + relZ;
} }
/**
* Usually this should just be used internally, but there may be instances
* where the raw data arrays are available without the data source object.
*/
public static int relativePosToIndex(int relX, int relZ) throws IndexOutOfBoundsException
{
int index = tryGetRelativePosToIndex(relX, relZ);
if (index < 0)
{
throw new IndexOutOfBoundsException("Relative data source positions must be between [0] (inclusive) and ["+WIDTH+"] (exclusive) the relative pos: ["+relX+","+relZ+"] is outside those boundaries.");
}
return index;
}
/** /**
* Throws an exception if the given * Throws an exception if the given
* full data column array is in the wrong order * full data column array is in the wrong order
@@ -1150,18 +1226,10 @@ public class FullDataSourceV2
// setters and getters // // setters and getters //
//=====================// //=====================//
@Override
public long getPos() { return this.pos; } public long getPos() { return this.pos; }
@Override
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); } public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); }
public EDhApiWorldGenerationStep getWorldGenStepAtRelativePos(int relX, int relZ)
{
int index = relativePosToIndex(relX, relZ);
return EDhApiWorldGenerationStep.fromValue(this.columnGenerationSteps.getByte(index));
}
public void setSingleColumn(LongArrayList longArray, int relX, int relZ, EDhApiWorldGenerationStep worldGenStep, EDhApiWorldCompressionMode worldCompressionMode) public void setSingleColumn(LongArrayList longArray, int relX, int relZ, EDhApiWorldGenerationStep worldGenStep, EDhApiWorldCompressionMode worldCompressionMode)
{ {
int index = relativePosToIndex(relX, relZ); int index = relativePosToIndex(relX, relZ);
@@ -1225,7 +1293,7 @@ public class FullDataSourceV2
@Override @Override
public List<DhApiTerrainDataPoint> getApiDataPointColumn(int relX, int relZ) throws IndexOutOfBoundsException public List<DhApiTerrainDataPoint> getApiDataPointColumn(int relX, int relZ) throws IndexOutOfBoundsException
{ {
LongArrayList dataColumn = this.get(relX, relZ); LongArrayList dataColumn = this.getColumnAtRelPos(relX, relZ);
ArrayList<DhApiTerrainDataPoint> apiList = new ArrayList<>(); ArrayList<DhApiTerrainDataPoint> apiList = new ArrayList<>();
for (int i = 0; i < dataColumn.size(); i++) for (int i = 0; i < dataColumn.size(); i++)
@@ -1,96 +0,0 @@
package com.seibel.distanthorizons.core.dataObjects.render;
import com.google.common.cache.Cache;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
/**
* Wrapper for {@link ColumnRenderSource} that handles reference counting
* and cache tracking.
*/
public class CachedColumnRenderSource implements AutoCloseable
{
/** an externally handled future that will complete once the {@link CachedColumnRenderSource#columnRenderSource} has finished loading */
public final CompletableFuture<CachedColumnRenderSource> loadFuture;
/** will be null initially, should be non-null once the corresponding load future is done */
@Nullable
public ColumnRenderSource columnRenderSource = null;
private final AtomicInteger referenceCount;
private final Cache<Long, CachedColumnRenderSource> cachedRenderSourceByPos;
private final ReentrantLock getterLock;
//=============//
// constructor //
//=============//
public CachedColumnRenderSource(
@NotNull CompletableFuture<CachedColumnRenderSource> loadFuture,
@NotNull ReentrantLock getterLock,
@NotNull Cache<Long, CachedColumnRenderSource> cachedRenderSourceByPos)
{
this.loadFuture = loadFuture;
this.getterLock = getterLock;
this.referenceCount = new AtomicInteger(1);
this.cachedRenderSourceByPos = cachedRenderSourceByPos;
}
//====================//
// reference counting //
//====================//
public void markInUse() { this.referenceCount.getAndIncrement(); }
//================//
// base overrides //
//================//
/**
* Will be called multiple times,
* however it will only close the underlying data once
* all references have closed.
*/
@Override
public void close() throws IllegalStateException
{
try
{
// lock to prevent other threads for accessing the cache if we invalidate it
this.getterLock.lock();
// should only happen if something goes wrong up-stream
if (this.columnRenderSource == null)
{
return;
}
// only close once everyone is done with this datasource
int refCount = this.referenceCount.decrementAndGet();
if (refCount == 0)
{
this.cachedRenderSourceByPos.invalidate(this.columnRenderSource.pos);
this.columnRenderSource.close();
}
else if (refCount < 0)
{
throw new IllegalStateException("Render source ["+this.columnRenderSource.pos+"] reference count incorrect. Object already closed.");
}
}
finally
{
this.getterLock.unlock();
}
}
}
@@ -23,16 +23,12 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList; import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnQuadView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnQuadView;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.concurrent.atomic.AtomicLong;
/** /**
* Stores the render data used to generate OpenGL buffers. * Stores the render data used to generate OpenGL buffers.
@@ -41,12 +37,10 @@ import java.util.concurrent.atomic.AtomicLong;
*/ */
public class ColumnRenderSource extends AbstractPhantomArrayList public class ColumnRenderSource extends AbstractPhantomArrayList
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static final boolean DO_SAFETY_CHECKS = ModInfo.IS_DEV_BUILD; /** measured in data columns */
public static final byte SECTION_SIZE_OFFSET = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; public static final int WIDTH = 64;
/** width of this data in columns */
public static final int SECTION_SIZE = BitShiftUtil.powerOfTwo(SECTION_SIZE_OFFSET); // 64
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Render Source"); public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Render Source");
@@ -63,8 +57,6 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
private boolean isEmpty = true; private boolean isEmpty = true;
public AtomicLong localVersion = new AtomicLong(0); // used to track changes to the data source, so that buffers can be updated when necessary
//==============// //==============//
@@ -88,9 +80,9 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
this.verticalDataCount = maxVerticalSize; this.verticalDataCount = maxVerticalSize;
this.renderDataContainer = this.pooledArraysCheckout.getLongArray(0, SECTION_SIZE * SECTION_SIZE * this.verticalDataCount); this.renderDataContainer = this.pooledArraysCheckout.getLongArray(0, WIDTH * WIDTH * this.verticalDataCount);
this.debugSourceFlags = new DebugSourceFlag[SECTION_SIZE * SECTION_SIZE]; this.debugSourceFlags = new DebugSourceFlag[WIDTH * WIDTH];
} }
@@ -99,19 +91,19 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
// datapoint manipulation // // datapoint manipulation //
//========================// //========================//
public long getDataPoint(int posX, int posZ, int verticalIndex) { return this.renderDataContainer.getLong(posX * SECTION_SIZE * this.verticalDataCount + posZ * this.verticalDataCount + verticalIndex); } public long getDataPoint(int posX, int posZ, int verticalIndex) { return this.renderDataContainer.getLong(posX * WIDTH * this.verticalDataCount + posZ * this.verticalDataCount + verticalIndex); }
public ColumnArrayView getVerticalDataPointView(int posX, int posZ) public ColumnArrayView getVerticalDataPointView(int posX, int posZ)
{ {
int offset = posX * SECTION_SIZE * this.verticalDataCount + posZ * this.verticalDataCount; int offset = posX * WIDTH * this.verticalDataCount + posZ * this.verticalDataCount;
// don't allow returning views that are outside this render source's bounds // don't allow returning views that are outside this render source's bounds
if (offset >= this.renderDataContainer.size()) if (offset >= this.renderDataContainer.size())
{ {
return null; return null;
} }
else if (posX < 0 || posX >= SECTION_SIZE else if (posX < 0 || posX >= WIDTH
|| posZ < 0 || posZ >= SECTION_SIZE) || posZ < 0 || posZ >= WIDTH)
{ {
return null; return null;
} }
@@ -120,8 +112,8 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
offset, this.verticalDataCount); offset, this.verticalDataCount);
} }
public ColumnQuadView getFullQuadView() { return this.getQuadViewOverRange(0, 0, SECTION_SIZE, SECTION_SIZE); } public ColumnQuadView getFullQuadView() { return this.getQuadViewOverRange(0, 0, WIDTH, WIDTH); }
public ColumnQuadView getQuadViewOverRange(int quadX, int quadZ, int quadXSize, int quadZSize) { return new ColumnQuadView(this.renderDataContainer, SECTION_SIZE, this.verticalDataCount, quadX, quadZ, quadXSize, quadZSize); } public ColumnQuadView getQuadViewOverRange(int quadX, int quadZ, int quadXSize, int quadZSize) { return new ColumnQuadView(this.renderDataContainer, WIDTH, this.verticalDataCount, quadX, quadZ, quadXSize, quadZSize); }
@@ -131,9 +123,8 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
public Long getPos() { return this.pos; } public Long getPos() { return this.pos; }
public Long getKey() { return this.pos; } public Long getKey() { return this.pos; }
public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); }
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - SECTION_SIZE_OFFSET); } public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); }
public boolean isEmpty() { return this.isEmpty; } public boolean isEmpty() { return this.isEmpty; }
public void markNotEmpty() { this.isEmpty = false; } public void markNotEmpty() { this.isEmpty = false; }
@@ -147,15 +138,15 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
} }
for (int x = 0; x < SECTION_SIZE; x++) for (int x = 0; x < WIDTH; x++)
{ {
for (int z = 0; z < SECTION_SIZE; z++) for (int z = 0; z < WIDTH; z++)
{ {
ColumnArrayView columnArrayView = this.getVerticalDataPointView(x,z); ColumnArrayView columnArrayView = this.getVerticalDataPointView(x,z);
for (int i = 0; i < columnArrayView.size; i++) for (int i = 0; i < columnArrayView.size; i++)
{ {
long dataPoint = columnArrayView.get(i); long dataPoint = columnArrayView.get(i);
if (!RenderDataPointUtil.isVoid(dataPoint)) if (!RenderDataPointUtil.hasZeroHeight(dataPoint))
{ {
return true; return true;
} }
@@ -179,12 +170,12 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
{ {
for (int z = zStart; z < zStart + zWidth; z++) for (int z = zStart; z < zStart + zWidth; z++)
{ {
this.debugSourceFlags[x * SECTION_SIZE + z] = flag; this.debugSourceFlags[x * WIDTH + z] = flag;
} }
} }
} }
public DebugSourceFlag debugGetFlag(int ox, int oz) { return this.debugSourceFlags[ox * SECTION_SIZE + oz]; } public DebugSourceFlag debugGetFlag(int ox, int oz) { return this.debugSourceFlags[ox * WIDTH + oz]; }
@@ -35,7 +35,7 @@ public final class BufferQuad
public static final int NORMAL_MAX_QUAD_WIDTH = 2048; public static final int NORMAL_MAX_QUAD_WIDTH = 2048;
/** /**
* The maximum number of blocks wide a quad can be * The maximum number of blocks wide a quad can be
* when {@link Config.Client.Advanced.Graphics.AdvancedGraphics#earthCurveRatio earthCurveRatio} * when {@link Config.Client.Advanced.Graphics.Experimental#earthCurveRatio earthCurveRatio}
* is enabled. * is enabled.
*/ */
public static final int MAX_QUAD_WIDTH_FOR_EARTH_CURVATURE = LodUtil.CHUNK_WIDTH; public static final int MAX_QUAD_WIDTH_FOR_EARTH_CURVATURE = LodUtil.CHUNK_WIDTH;
@@ -99,7 +99,7 @@ public final class BufferQuad
if (compareDirection == BufferMergeDirectionEnum.EastWest) if (compareDirection == BufferMergeDirectionEnum.EastWest)
{ {
switch (this.direction.getAxis()) switch (this.direction.axis)
{ {
case X: case X:
return threeDimensionalCompare(this.x, this.y, this.z, quad.x, quad.y, quad.z); return threeDimensionalCompare(this.x, this.y, this.z, quad.x, quad.y, quad.z);
@@ -109,12 +109,12 @@ public final class BufferQuad
return threeDimensionalCompare(this.z, this.y, this.x, quad.z, quad.y, quad.x); return threeDimensionalCompare(this.z, this.y, this.x, quad.z, quad.y, quad.x);
default: default:
throw new IllegalArgumentException("Invalid Axis enum: " + this.direction.getAxis()); throw new IllegalArgumentException("Invalid Axis enum: [" + this.direction.axis + "].");
} }
} }
else else
{ {
switch (this.direction.getAxis()) switch (this.direction.axis)
{ {
case X: case X:
return threeDimensionalCompare(this.x, this.z, this.y, quad.x, quad.z, quad.y); return threeDimensionalCompare(this.x, this.z, this.y, quad.x, quad.z, quad.y);
@@ -124,7 +124,7 @@ public final class BufferQuad
return threeDimensionalCompare(this.z, this.x, this.y, quad.z, quad.x, quad.y); return threeDimensionalCompare(this.z, this.x, this.y, quad.z, quad.x, quad.y);
default: default:
throw new IllegalArgumentException("Invalid Axis enum: " + this.direction.getAxis()); throw new IllegalArgumentException("Invalid Axis enum: [" + this.direction.axis + "].");
} }
} }
} }
@@ -169,7 +169,7 @@ public final class BufferQuad
short thisParallelCompareStartPos; // edge parallel to the merge direction short thisParallelCompareStartPos; // edge parallel to the merge direction
short otherPerpendicularCompareStartPos; short otherPerpendicularCompareStartPos;
short otherParallelCompareStartPos; short otherParallelCompareStartPos;
switch (this.direction.getAxis()) switch (this.direction.axis)
{ {
default: // shouldn't normally happen, just here to make the compiler happy default: // shouldn't normally happen, just here to make the compiler happy
case X: case X:
@@ -23,38 +23,27 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public class ColumnBox public class ColumnBox
{ {
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
/** /**
* if the skylight has this value that means * if the skylight has this value that means
* no data is expected * that block position is covered/occluded by an adjacent block/column.
*/ */
private static final byte SKYLIGHT_EMPTY = -1; private static final byte SKYLIGHT_COVERED = -1;
/**
* if the skylight has this value that means
* that block position is covered/occuled by an adjacent block/column.
*/
private static final byte SKYLIGHT_COVERED = -2;
private static final ThreadLocal<byte[]> THREAD_LOCAL_SKY_LIGHT_ARRAY = ThreadLocal.withInitial(() ->
{
byte[] array = new byte[RenderDataPointUtil.MAX_WORLD_Y_SIZE];
Arrays.fill(array, SKYLIGHT_EMPTY);
return array;
});
@@ -63,8 +52,8 @@ public class ColumnBox
//=========// //=========//
public static void addBoxQuadsToBuilder( public static void addBoxQuadsToBuilder(
LodQuadBuilder builder, IDhClientLevel clientLevel, LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IDhClientLevel clientLevel,
short xSize, short ySize, short zSize, short width, short yHeight,
short minX, short minY, short minZ, short minX, short minY, short minZ,
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight, int color, byte irisBlockMaterialId, byte skyLight, byte blockLight,
long topData, long bottomData, ColumnArrayView[] adjData, boolean[] isAdjDataSameDetailLevel) long topData, long bottomData, ColumnArrayView[] adjData, boolean[] isAdjDataSameDetailLevel)
@@ -73,23 +62,26 @@ public class ColumnBox
// variable setup // // variable setup //
//================// //================//
short maxX = (short) (minX + xSize); short maxX = (short) (minX + width);
short maxY = (short) (minY + ySize); short maxY = (short) (minY + yHeight);
short maxZ = (short) (minZ + zSize); short maxZ = (short) (minZ + width);
byte skyLightTop = skyLight; byte skyLightTop = skyLight;
byte skyLightBot = RenderDataPointUtil.doesDataPointExist(bottomData) ? RenderDataPointUtil.getLightSky(bottomData) : 0; byte skyLightBot = RenderDataPointUtil.doesDataPointExist(bottomData) ? RenderDataPointUtil.getLightSky(bottomData) : 0;
boolean isTransparent = ColorUtil.getAlpha(color) < 255 && LodRenderer.transparencyEnabled; boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
boolean fakeOceanFloor = Config.Client.Advanced.Graphics.Quality.transparency.get().fakeTransparencyEnabled;
boolean isTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled;
boolean overVoid = !RenderDataPointUtil.doesDataPointExist(bottomData); boolean overVoid = !RenderDataPointUtil.doesDataPointExist(bottomData);
boolean isTopTransparent = RenderDataPointUtil.getAlpha(topData) < 255 && LodRenderer.transparencyEnabled; boolean isTopTransparent = RenderDataPointUtil.getAlpha(topData) < 255 && transparencyEnabled;
boolean isBottomTransparent = RenderDataPointUtil.getAlpha(bottomData) < 255 && LodRenderer.transparencyEnabled; boolean isBottomTransparent = RenderDataPointUtil.getAlpha(bottomData) < 255 && transparencyEnabled;
// defaulting to a value far below what we can normally render means we // defaulting to a value far below what we can normally render means we
// don't need to have an additional "is cave culling enabled" check // don't need to have an additional "is cave culling enabled" check
int caveCullingMaxY = Integer.MIN_VALUE; int caveCullingMaxY = Integer.MIN_VALUE;
if (Config.Client.Advanced.Graphics.Culling.enableCaveCulling.get()) if (Config.Client.Advanced.Graphics.Culling.enableCaveCulling.get())
{ {
caveCullingMaxY = Config.Client.Advanced.Graphics.Culling.caveCullingHeight.get() - clientLevel.getMinY(); caveCullingMaxY = Config.Client.Advanced.Graphics.Culling.caveCullingHeight.get() - clientLevel.getLevelWrapper().getMinHeight();
} }
@@ -103,20 +95,20 @@ public class ColumnBox
// fake ocean transparency // fake ocean transparency
if (LodRenderer.transparencyEnabled && LodRenderer.fakeOceanFloor) if (transparencyEnabled && fakeOceanFloor)
{ {
if (!isTransparent && isTopTransparent && RenderDataPointUtil.doesDataPointExist(topData)) if (!isTransparent && isTopTransparent && RenderDataPointUtil.doesDataPointExist(topData))
{ {
skyLightTop = (byte) MathUtil.clamp(0, 15 - (RenderDataPointUtil.getYMax(topData) - minY), 15); skyLightTop = (byte) MathUtil.clamp(0, 15 - (RenderDataPointUtil.getYMax(topData) - minY), 15);
ySize = (short) (RenderDataPointUtil.getYMax(topData) - minY - 1); yHeight = (short) (RenderDataPointUtil.getYMax(topData) - minY - 1);
} }
else if (isTransparent && !isBottomTransparent && RenderDataPointUtil.doesDataPointExist(bottomData)) else if (isTransparent && !isBottomTransparent && RenderDataPointUtil.doesDataPointExist(bottomData))
{ {
minY = (short) (minY + ySize - 1); minY = (short) (minY + yHeight - 1);
ySize = 1; yHeight = 1;
} }
maxY = (short) (minY + ySize); maxY = (short) (minY + yHeight);
} }
@@ -125,16 +117,26 @@ public class ColumnBox
// add top and bottom faces // // add top and bottom faces //
//==========================// //==========================//
boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData) && (RenderDataPointUtil.getYMin(topData) == maxY) && !isTopTransparent; // top face
if (!skipTop)
{ {
builder.addQuadUp(minX, maxY, minZ, xSize, zSize, ColorUtil.applyShade(color, MC.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight); boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData)
&& (RenderDataPointUtil.getYMin(topData) == maxY)
&& !isTopTransparent;
if (!skipTop)
{
builder.addQuadUp(minX, maxY, minZ, width, width, ColorUtil.applyShade(color, MC.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight);
}
} }
boolean skipBottom = RenderDataPointUtil.doesDataPointExist(bottomData) && (RenderDataPointUtil.getYMax(bottomData) == minY) && !isBottomTransparent; // bottom face
if (!skipBottom)
{ {
builder.addQuadDown(minX, minY, minZ, xSize, zSize, ColorUtil.applyShade(color, MC.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); boolean skipBottom = RenderDataPointUtil.doesDataPointExist(bottomData)
&& (RenderDataPointUtil.getYMax(bottomData) == minY)
&& !isBottomTransparent;
if (!skipBottom)
{
builder.addQuadDown(minX, minY, minZ, width, width, ColorUtil.applyShade(color, MC.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight);
}
} }
@@ -145,84 +147,119 @@ public class ColumnBox
// NORTH face // NORTH face
{ {
ColumnArrayView adjCol = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2? ColumnArrayView adjCol = adjData[EDhDirection.NORTH.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.ordinal() - 2]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.compassIndex];
// if the adjacent column is null that generally means the adjacent area hasn't been generated yet // if the adjacent column is null that generally means the adjacent area hasn't been generated yet
if (adjCol == null) if (adjCol == null)
{ {
// Add an adjacent face if this is opaque face or transparent over the void. // Add an adjacent face if this is opaque face or transparent over the void.
if (!isTransparent || overVoid) if (!isTransparent || overVoid)
{ {
builder.addQuadAdj(EDhDirection.NORTH, minX, minY, minZ, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(
EDhDirection.NORTH,
minX, minY, minZ,
width, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, minX, minY, minZ, xSize, ySize, makeAdjVerticalQuad(
builder, phantomArrayCheckout,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH,
minX, minY, minZ, width, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
// SOUTH face // SOUTH face
{ {
ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.ordinal() - 2]; ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.ordinal() - 2]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.compassIndex];
if (adjCol == null) if (adjCol == null)
{ {
if (!isTransparent || overVoid) if (!isTransparent || overVoid)
{ {
builder.addQuadAdj(EDhDirection.SOUTH, minX, minY, maxZ, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(
EDhDirection.SOUTH,
minX, minY, maxZ,
width, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, minX, minY, maxZ, xSize, ySize, makeAdjVerticalQuad(
builder, phantomArrayCheckout,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH,
minX, minY, maxZ, width, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
// WEST face // WEST face
{ {
ColumnArrayView adjCol = adjData[EDhDirection.WEST.ordinal() - 2]; ColumnArrayView adjCol = adjData[EDhDirection.WEST.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.ordinal() - 2]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.compassIndex];
if (adjCol == null) if (adjCol == null)
{ {
if (!isTransparent || overVoid) if (!isTransparent || overVoid)
{ {
builder.addQuadAdj(EDhDirection.WEST, minX, minY, minZ, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(
EDhDirection.WEST,
minX, minY, minZ,
width, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, minX, minY, minZ, zSize, ySize, makeAdjVerticalQuad(
builder, phantomArrayCheckout,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST,
minX, minY, minZ, width, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
// EAST face // EAST face
{ {
ColumnArrayView adjCol = adjData[EDhDirection.EAST.ordinal() - 2]; ColumnArrayView adjCol = adjData[EDhDirection.EAST.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.ordinal() - 2]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.compassIndex];
if (adjCol == null) if (adjCol == null)
{ {
if (!isTransparent || overVoid) if (!isTransparent || overVoid)
{ {
builder.addQuadAdj(EDhDirection.EAST, maxX, minY, minZ, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(
EDhDirection.EAST,
maxX, minY, minZ,
width, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, maxX, minY, minZ, zSize, ySize, makeAdjVerticalQuad(
builder, phantomArrayCheckout,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST,
maxX, minY, minZ, width, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
} }
private static void makeAdjVerticalQuad( private static void makeAdjVerticalQuad(
LodQuadBuilder builder, @NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction, LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout,
@NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
short x, short yMin, short z, short horizontalWidth, short ySize, short x, short yMin, short z, short horizontalWidth, short ySize,
int color, byte irisBlockMaterialId, byte blockLight) int color, byte irisBlockMaterialId, byte blockLight)
{ {
// pooled arrays
LongArrayList segments = phantomArrayCheckout.getLongArray(0, 0);
LongArrayList newSegments = phantomArrayCheckout.getLongArray(1, 0);
//==================// //==================//
// create face with // // create face with //
// no adjacent data // // no adjacent data //
@@ -230,173 +267,164 @@ public class ColumnBox
color = ColorUtil.applyShade(color, MC.getShade(direction)); color = ColorUtil.applyShade(color, MC.getShade(direction));
// if there isn't any data adjacent to this LOD, if (adjColumnView.size == 0
// just add the full vertical quad || RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0)))
if (adjColumnView.size == 0 || RenderDataPointUtil.isVoid(adjColumnView.get(0)))
{ {
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
return; return;
} }
//===========================// //=================================//
// Determine face visibility // // determine face visibility/light //
// based on it's neighbors // //=================================//
//===========================//
short yMax = (short) (yMin + ySize); // min is inclusive, max is exclusive boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
byte[] skyLightAtInputPos = THREAD_LOCAL_SKY_LIGHT_ARRAY.get(); boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled;
short yMax = (short) (yMin + ySize);
try
int adjCount = adjColumnView.size();
// Start with the entire range at max light
segments.add(YSegmentUtil.encode(yMin, yMax, LodUtil.MAX_MC_LIGHT));
// Process each adjacent datapoint and split segments as needed
for (int adjIndex = 0; adjIndex < adjCount; adjIndex++)
{ {
// set the initial sky-lights for this face, long adjPoint = adjColumnView.get(adjIndex);
// if nothing overlaps or overhangs the face should have max sky light short adjMinY = RenderDataPointUtil.getYMin(adjPoint);
Arrays.fill(skyLightAtInputPos, yMin, yMax, LodUtil.MAX_MC_LIGHT); short adjMaxY = RenderDataPointUtil.getYMax(adjPoint);
// iterate top down // skip empty adjacent points
int adjCount = adjColumnView.size(); // or points below this one
for (int adjIndex = 0; adjIndex < adjCount; adjIndex++) if (!RenderDataPointUtil.doesDataPointExist(adjPoint)
|| RenderDataPointUtil.hasZeroHeight(adjPoint)
|| yMax <= adjMinY)
{ {
long adjPoint = adjColumnView.get(adjIndex); continue;
short adjMinY = RenderDataPointUtil.getYMin(adjPoint);
short adjMaxY = RenderDataPointUtil.getYMax(adjPoint);
// skip empty adjacent datapoints
if (!RenderDataPointUtil.doesDataPointExist(adjPoint)
|| RenderDataPointUtil.isVoid(adjPoint))
{
continue;
}
// skip this adjacent datapoint if it's above the input datapoint (since it can't affect the input data point)
if (yMax <= adjMinY)
{
continue;
}
long adjAbovePoint = (adjIndex != 0) ? adjColumnView.get(adjIndex - 1) : RenderDataPointUtil.EMPTY_DATA;
long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA;
// if the adjacent data point is over the void
// don't consider it as transparent
boolean adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBelowPoint);
boolean adjTransparent = !adjOverVoid && RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled;
//=================================//
// set sky light based on adjacent //
//=================================//
// set light based on overlapping adjacent
if (!adjTransparent)
{
// adj opaque
// mark positions adjacent is covering
byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint);
for (int i = adjMinY; i < adjMaxY; i++)
{
byte skyLightAtPos = skyLightAtInputPos[i];
// if the adjacent is a different detail level, we want to render adjacent opaque
// faces to try and reduce the chance of holes on detail level borders
boolean adjacentCoversThis =
// if the adjacent is the same detail level, no special handling is necessary
!adjacentIsSameDetailLevel
// if the adjacent face is underground we probably don't need it
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY
// check if this face is on a border
&&
(
(x == 0 && direction == EDhDirection.WEST)
|| (z == 0 && direction == EDhDirection.NORTH)
// TODO why does 256 represent a border? aren't LODs only 64 datapoints wide?
|| (x == 256 && direction == EDhDirection.EAST)
|| (z == 256 && direction == EDhDirection.SOUTH)
);
byte newSkyLightAtPos = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED;
skyLightAtInputPos[i] = (byte) Math.min(newSkyLightAtPos, skyLightAtPos);
}
}
else
{
// adjacent is transparent,
// use datapoint below adjacent for lighting
byte belowSkyLight = RenderDataPointUtil.getLightSky(adjBelowPoint);
for (int i = adjMinY; i < adjMaxY; i++)
{
byte skyLightAtPos = skyLightAtInputPos[i];
skyLightAtInputPos[i] = (byte) Math.min(belowSkyLight, skyLightAtPos);
}
}
// fill in sky light up to the next DP,
// this is done to handle overhangs
byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint);
int adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
for (int i = adjMaxY; i < adjAboveMinY; i++)
{
byte skyLightAtPos = skyLightAtInputPos[i];
skyLightAtInputPos[i] = (byte) Math.min(adjSkyLight, skyLightAtPos);
}
} }
long adjAbovePoint = (adjIndex != 0) ? adjColumnView.get(adjIndex - 1) : RenderDataPointUtil.EMPTY_DATA;
long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA;
//=======================// boolean adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBelowPoint);
// create vertical faces // boolean adjTransparent = !adjOverVoid
//=======================// && RenderDataPointUtil.getAlpha(adjPoint) < 255
&& transparencyEnabled;
boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && LodRenderer.transparencyEnabled; byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint);
byte lastSkyLight = skyLightAtInputPos[yMin]; byte lightToApply;
int quadBottomY = yMin;
int quadTopY = -1;
// walk up the sky lights and create a new face if (!adjTransparent)
// whenever the light changes to different valid value
for (int i = yMin; i < yMax; i++)
{ {
byte skyLight = skyLightAtInputPos[i]; // Adjacent is opaque
if (skyLight != lastSkyLight) boolean adjacentCoversThis =
{ !adjacentIsSameDetailLevel
// the sky light changed, create the in-progress face && RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY
tryAddVerticalFaceWithSkyLightToBuilder( &&
builder, direction, (
x, z, horizontalWidth, (x == 0 && direction == EDhDirection.WEST)
color, irisBlockMaterialId, blockLight, || (z == 0 && direction == EDhDirection.NORTH)
lastSkyLight, inputTransparent, quadTopY, quadBottomY || (x == 256 && direction == EDhDirection.EAST)
|| (z == 256 && direction == EDhDirection.SOUTH)
); );
lastSkyLight = skyLight; lightToApply = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED;
quadBottomY = i; }
} else
{
quadTopY = (i + 1); // Adjacent is transparent, use below light
lightToApply = RenderDataPointUtil.getLightSky(adjBelowPoint);
} }
// add the in-progress face if present
if (quadTopY != -1) // Apply light to the range [adjMinY, adjMaxY)
applyLightToRange(segments, newSegments, adjMinY, adjMaxY, lightToApply);
// Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight
short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
if (adjMaxY < adjAboveMinY)
{ {
tryAddVerticalFaceWithSkyLightToBuilder( applyLightToRange(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight);
builder, direction,
x, z, horizontalWidth,
color, irisBlockMaterialId, blockLight,
lastSkyLight, inputTransparent, quadTopY, quadBottomY
);
} }
} }
finally
//=======================//
// Create vertical faces //
// from segments //
//=======================//
for (int i = 0; i < segments.size(); i++)
{ {
// clean up the array before the next thread uses it long segment = segments.getLong(i);
// (may be unnecessary since we only work between the yMin-yMax anyway, but is helpful for debugging) tryAddVerticalFaceWithSkyLightToBuilder(
Arrays.fill(skyLightAtInputPos, yMin, yMax, SKYLIGHT_EMPTY); builder, direction,
x, z, horizontalWidth,
color, irisBlockMaterialId, blockLight,
YSegmentUtil.getSkyLight(segment), inputTransparent, YSegmentUtil.getEndY(segment), YSegmentUtil.getStartY(segment)
);
} }
} }
/**
* Apply the new light value over the given y range,
* splitting segments as needed
* <p>
* source: claude.ai
*/
private static void applyLightToRange(
LongArrayList segments, LongArrayList newSegments,
short rangeStart, short rangeEnd,
byte newLight)
{
// clear the pooled array that the new segments will go into
newSegments.clear();
for (int i = 0; i < segments.size(); i++)
{
long seg = segments.getLong(i);
short endY = YSegmentUtil.getEndY(seg);
short startY = YSegmentUtil.getStartY(seg);
byte skyLight = YSegmentUtil.getSkyLight(seg);
// No overlap
if (endY <= rangeStart
|| startY >= rangeEnd)
{
newSegments.add(seg);
continue;
}
// Partial or complete overlap - need to split
// Part before the range
if (startY < rangeStart)
{
newSegments.add(YSegmentUtil.encode(startY, rangeStart, skyLight));
}
// Overlapping part - take minimum light
short overlapStart = (short)Math.max(startY, rangeStart);
short overlapEnd = (short)Math.min(endY, rangeEnd);
byte minLight = (byte) Math.min(newLight, skyLight);
newSegments.add(YSegmentUtil.encode(overlapStart, overlapEnd, minLight));
// Part after the range
if (endY > rangeEnd)
{
newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight));
}
}
segments.clear();
segments.addAll(newSegments);
}
private static void tryAddVerticalFaceWithSkyLightToBuilder( private static void tryAddVerticalFaceWithSkyLightToBuilder(
LodQuadBuilder builder, EDhDirection direction, LodQuadBuilder builder, EDhDirection direction,
short x, short z, short horizontalWidth, short x, short z, short horizontalWidth,
@@ -405,22 +433,72 @@ public class ColumnBox
) )
{ {
// invalid positions will have a negative skylight // invalid positions will have a negative skylight
if (lastSkyLight >= 0) if (lastSkyLight < 0)
{ {
// Don't add transparent vertical faces return;
// unless the adjacent position is empty.
// This is done to prevent walls between water blocks in the ocean.
if (!inputTransparent
|| (lastSkyLight == LodUtil.MAX_MC_LIGHT))
{
// don't add negative/empty height faces
short height = (short) (quadTopY - quadBottomY);
if (height > 0)
{
builder.addQuadAdj(direction, x, (short) quadBottomY, z, horizontalWidth, height, color, irisBlockMaterialId, lastSkyLight, blockLight);
}
}
} }
// Don't add transparent vertical faces
// unless the adjacent position is empty.
// This is done to prevent walls between water blocks in the ocean.
if (inputTransparent
&& (lastSkyLight != LodUtil.MAX_MC_LIGHT))
{
return;
}
// don't add negative/empty height faces
short height = (short) (quadTopY - quadBottomY);
if (height <= 0)
{
return;
}
builder.addQuadAdj(
direction,
x, (short) quadBottomY, z,
horizontalWidth, height,
color, irisBlockMaterialId, lastSkyLight, blockLight);
}
//================//
// helper classes //
//================//
/**
* encodes height/light data into a long
* to reduce object allocations.
*/
private static class YSegmentUtil
{
private static final int HEIGHT_WIDTH = Short.SIZE;
private static final int SKY_LIGHT_WIDTH = Byte.SIZE;
private static final int START_Y_MASK = (int) Math.pow(2, HEIGHT_WIDTH) - 1;
private static final int END_Y_MASK = (int) Math.pow(2, HEIGHT_WIDTH) - 1;
private static final int SKY_LIGHT_MASK = (int) Math.pow(2, SKY_LIGHT_WIDTH) - 1;
private static final int START_Y_OFFSET = 0;
private static final int END_Y_OFFSET = START_Y_OFFSET + HEIGHT_WIDTH;
private static final int SKY_LIGHT_OFFSET = END_Y_OFFSET + HEIGHT_WIDTH;
public static long encode(short startY, short endY, byte skyLight)
{
long data = 0L;
data |= (long) (startY & START_Y_MASK) << START_Y_OFFSET;
data |= (long) (endY & END_Y_MASK) << END_Y_OFFSET;
data |= (long) (skyLight & SKY_LIGHT_MASK) << SKY_LIGHT_OFFSET;
return data;
}
public static short getStartY(long data) { return (short) ((data >> START_Y_OFFSET) & START_Y_MASK); }
public static short getEndY(long data) { return (short) ((data >> END_Y_OFFSET) & END_Y_MASK); }
public static byte getSkyLight(long data) { return (byte) ((data >> SKY_LIGHT_OFFSET) & SKY_LIGHT_MASK); }
} }
@@ -25,20 +25,17 @@ import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -49,9 +46,9 @@ import java.util.concurrent.CompletableFuture;
*/ */
public class ColumnRenderBufferBuilder public class ColumnRenderBufferBuilder
{ {
public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(), private static final DhLogger LOGGER = new DhLoggerBuilder().build();
() -> Config.Common.Logging.logRendererBufferEvent.get());
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Column Buffer Builder");
@@ -60,14 +57,15 @@ public class ColumnRenderBufferBuilder
//==============// //==============//
/** @link adjData should be null for adjacent sections that cross detail level boundaries */ /** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<ColumnRenderBuffer> uploadBuffersAsync( public static CompletableFuture<LodBufferContainer> uploadBuffersAsync(
IDhClientLevel clientLevel, IDhClientLevel clientLevel,
long pos, long pos,
LodQuadBuilder quadBuilder LodQuadBuilder quadBuilder
) )
{ {
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(pos))); DhBlockPos minBlockPos = new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos));
CompletableFuture<ColumnRenderBuffer> uploadFuture = buffer.makeAndUploadBuffersAsync(quadBuilder, GLProxy.getInstance().getGpuUploadMethod()); LodBufferContainer bufferContainer = new LodBufferContainer(pos, minBlockPos);
CompletableFuture<LodBufferContainer> uploadFuture = bufferContainer.makeAndUploadBuffersAsync(quadBuilder);
uploadFuture.whenComplete((uploadedBuffer, exception) -> uploadFuture.whenComplete((uploadedBuffer, exception) ->
{ {
// clean up if not uploaded // clean up if not uploaded
@@ -109,208 +107,209 @@ public class ColumnRenderBufferBuilder
// build each column // // build each column //
//===================// //===================//
byte thisDetailLevel = renderSource.getDataDetailLevel(); // pooled arrays for ColumnBox use
for (int relX = 0; relX < ColumnRenderSource.SECTION_SIZE; relX++) try (PhantomArrayListCheckout phantomArrayCheckout = ARRAY_LIST_POOL.checkoutArrays(0, 0, 2))
{ {
for (int relZ = 0; relZ < ColumnRenderSource.SECTION_SIZE; relZ++) byte thisDetailLevel = renderSource.getDataDetailLevel();
for (int relX = 0; relX < ColumnRenderSource.WIDTH; relX++)
{ {
// stop the builder if requested for (int relZ = 0; relZ < ColumnRenderSource.WIDTH; relZ++)
UncheckedInterruptedException.throwIfInterrupted();
// ignore empty/null columns
ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(relX, relZ);
if (columnRenderData.size() == 0
|| !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0))
|| RenderDataPointUtil.isVoid(columnRenderData.get(0)))
{ {
continue; // ignore empty/null columns
} ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(relX, relZ);
if (columnRenderData.size() == 0
|| !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0))
|| RenderDataPointUtil.hasZeroHeight(columnRenderData.get(0)))
//=============//
// debug limit //
//=============//
// can be used to limit the buffer building to a specific relative position.
// useful for debugging a single column
if (columnBuilderDebugEnabled)
{
int wantedX = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugXRow.get();
if (wantedX >= 0 && relX != wantedX)
{ {
continue; continue;
} }
int wantedZ = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugZRow.get();
if (wantedZ >= 0 && relZ != wantedZ)
//=============//
// debug limit //
//=============//
// can be used to limit the buffer building to a specific relative position.
// useful for debugging a single column
if (columnBuilderDebugEnabled)
{ {
continue; int wantedX = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugXRow.get();
} if (wantedX >= 0 && relX != wantedX)
}
//==================================//
// get adjacent render data columns //
//==================================//
ColumnArrayView[] adjColumnViews = new ColumnArrayView[EDhDirection.ADJ_DIRECTIONS.length];
for (EDhDirection lodDirection : EDhDirection.ADJ_DIRECTIONS)
{
try
{
int xAdj = relX + lodDirection.getNormal().x;
int zAdj = relZ + lodDirection.getNormal().z;
boolean isCrossRenderSourceBoundary =
(xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) ||
(zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE);
ColumnRenderSource adjRenderSource;
byte adjDetailLevel;
//=========================//
// get the adjacent render //
// source if present //
//=========================//
if (!isCrossRenderSourceBoundary)
{ {
// the adjacent position is inside this same render source continue;
adjRenderSource = renderSource;
adjDetailLevel = thisDetailLevel;
} }
else int wantedZ = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugZRow.get();
{ if (wantedZ >= 0 && relZ != wantedZ)
// the adjacent position is outside this render source
// skip empty sections
adjRenderSource = adjRegions[lodDirection.ordinal() - 2];
if (adjRenderSource == null)
{
continue;
}
adjDetailLevel = adjRenderSource.getDataDetailLevel();
if (adjDetailLevel == thisDetailLevel)
{
// if the adjacent position is outside this render source,
// wrap the position around so it's inside the adjacent source
if (xAdj < 0)
{
xAdj += ColumnRenderSource.SECTION_SIZE;
}
if (xAdj >= ColumnRenderSource.SECTION_SIZE)
{
xAdj -= ColumnRenderSource.SECTION_SIZE;
}
if (zAdj < 0)
{
zAdj += ColumnRenderSource.SECTION_SIZE;
}
if (zAdj >= ColumnRenderSource.SECTION_SIZE)
{
zAdj -= ColumnRenderSource.SECTION_SIZE;
}
}
}
//========================//
// get the adjacent views //
//========================//
// the old logic handled additional cases, but they never appeared to fire,
// so just these two cases should be fine
boolean expectedDetailLevels = (adjDetailLevel == thisDetailLevel) || (adjDetailLevel > thisDetailLevel);
if (!expectedDetailLevels)
{
LodUtil.assertNotReach("Mismatch between adjacent detail level ["+adjDetailLevel+"] and this render source's detail level ["+thisDetailLevel+"]. Detail levels should be adj >= this.");
}
adjColumnViews[lodDirection.ordinal() - 2] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
}
catch (RuntimeException e)
{
EVENT_LOGGER.warn("Failed to get adj data for relative pos: [" + thisDetailLevel + ":" + relX + "," + relZ + "] at [" + lodDirection + "], Error: "+e.getMessage(), e);
}
} // for adjacent directions
//==========================//
// build this render column //
//==========================//
ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(relX, relZ);
// We render every vertical lod present in this position
// We only stop when we find a block that is void or non-existing block
for (int i = 0; i < columnRenderData.size(); i++)
{
// can be uncommented to limit which vertical LOD is generated
if (Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugEnable.get())
{
int wantedColumnIndex = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugColumnIndex.get();
if (wantedColumnIndex >= 0 && i != wantedColumnIndex)
{ {
continue; continue;
} }
} }
long data = columnRenderData.get(i);
// If the data is not render-able (Void or non-existing) we stop since there is
// no data left in this position //==================================//
if (RenderDataPointUtil.isVoid(data) || !RenderDataPointUtil.doesDataPointExist(data)) // get adjacent render data columns //
//==================================//
ColumnArrayView[] adjColumnViews = new ColumnArrayView[EDhDirection.CARDINAL_COMPASS.length];
for (EDhDirection direction : EDhDirection.CARDINAL_COMPASS)
{ {
break; try
{
int xAdj = relX + direction.normal.x;
int zAdj = relZ + direction.normal.z;
boolean isCrossRenderSourceBoundary =
(xAdj < 0 || xAdj >= ColumnRenderSource.WIDTH) ||
(zAdj < 0 || zAdj >= ColumnRenderSource.WIDTH);
ColumnRenderSource adjRenderSource;
byte adjDetailLevel;
//=========================//
// get the adjacent render //
// source if present //
//=========================//
if (!isCrossRenderSourceBoundary)
{
// the adjacent position is inside this same render source
adjRenderSource = renderSource;
adjDetailLevel = thisDetailLevel;
}
else
{
// the adjacent position is outside this render source
// skip empty sections
adjRenderSource = adjRegions[direction.compassIndex];
if (adjRenderSource == null)
{
continue;
}
adjDetailLevel = adjRenderSource.getDataDetailLevel();
if (adjDetailLevel == thisDetailLevel)
{
// if the adjacent position is outside this render source,
// wrap the position around so it's inside the adjacent source
if (xAdj < 0)
{
xAdj += ColumnRenderSource.WIDTH;
}
if (xAdj >= ColumnRenderSource.WIDTH)
{
xAdj -= ColumnRenderSource.WIDTH;
}
if (zAdj < 0)
{
zAdj += ColumnRenderSource.WIDTH;
}
if (zAdj >= ColumnRenderSource.WIDTH)
{
zAdj -= ColumnRenderSource.WIDTH;
}
}
}
//========================//
// get the adjacent views //
//========================//
// the old logic handled additional cases, but they never appeared to fire,
// so just these two cases should be fine
boolean expectedDetailLevels = (adjDetailLevel == thisDetailLevel) || (adjDetailLevel > thisDetailLevel);
if (!expectedDetailLevels)
{
LodUtil.assertNotReach("Mismatch between adjacent detail level ["+adjDetailLevel+"] and this render source's detail level ["+thisDetailLevel+"]. Detail levels should be adj >= this.");
}
adjColumnViews[direction.compassIndex] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
}
catch (RuntimeException e)
{
LOGGER.warn("Failed to get adj data for relative pos: [" + thisDetailLevel + ":" + relX + "," + relZ + "] at [" + direction + "], Error: [" + e.getMessage() + "].", e);
}
} // for adjacent directions
//==========================//
// build this render column //
//==========================//
ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(relX, relZ);
for (int i = 0; i < columnRenderData.size(); i++)
{
// can be uncommented to limit which vertical LOD is generated
if (Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugEnable.get())
{
int wantedColumnIndex = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugColumnIndex.get();
if (wantedColumnIndex >= 0
&& i != wantedColumnIndex)
{
continue;
}
}
long data = columnRenderData.get(i);
// If the data is not render-able (Void or non-existing) we stop since there is
// no data left in this position
if (RenderDataPointUtil.hasZeroHeight(data)
|| !RenderDataPointUtil.doesDataPointExist(data))
{
break;
}
long topDataPoint = (i - 1) >= 0 ? columnRenderData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA;
long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
addRenderDataPointToBuilder(
clientLevel, phantomArrayCheckout,
data, topDataPoint, bottomDataPoint,
adjColumnViews, isSameDetailLevel,
thisDetailLevel, relX, relZ,
quadBuilder, debugSourceFlag);
} }
long topDataPoint = (i - 1) >= 0 ? columnRenderData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA; }// for z
long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA; }// for x
}// phantom checkout
addLodToBuffer(
clientLevel,
data, topDataPoint, bottomDataPoint,
adjColumnViews, isSameDetailLevel,
thisDetailLevel, relX, relZ,
quadBuilder, debugSourceFlag);
}
}// for z
}// for x
quadBuilder.mergeQuads(); quadBuilder.mergeQuads();
} }
private static void addLodToBuffer( private static void addRenderDataPointToBuilder(
IDhClientLevel clientLevel, IDhClientLevel clientLevel, PhantomArrayListCheckout phantomArrayCheckout,
long data, long topData, long bottomData, long renderData, long topRenderData, long bottomRenderData,
ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel, ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel,
byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ, byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ,
LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource) LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource)
{ {
long sectionPos = DhSectionPos.encode(detailLevel, renderSourceOffsetPosX, renderSourceOffsetPosZ); long sectionPos = DhSectionPos.encode(detailLevel, renderSourceOffsetPosX, renderSourceOffsetPosZ);
short width = (short) BitShiftUtil.powerOfTwo(detailLevel); short blockWidth = (short) DhSectionPos.getDetailLevelWidthInBlocks(detailLevel);
short xMin = (short) DhSectionPos.getMinCornerBlockX(sectionPos); short blockMinX = (short) DhSectionPos.getMinCornerBlockX(sectionPos);
short yMin = RenderDataPointUtil.getYMin(data); short blockMinY = RenderDataPointUtil.getYMin(renderData);
short zMin = (short) DhSectionPos.getMinCornerBlockZ(sectionPos); short blockMinZ = (short) DhSectionPos.getMinCornerBlockZ(sectionPos);
short ySize = (short) (RenderDataPointUtil.getYMax(data) - yMin); short blockMaxY = (short) (RenderDataPointUtil.getYMax(renderData) - blockMinY);
if (ySize == 0) if (blockMaxY == 0)
{ {
return; return;
} }
else if (ySize < 0) else if (blockMaxY < 0)
{ {
throw new IllegalArgumentException("Negative y size for the data! Data: [" + RenderDataPointUtil.toString(data) + "]."); throw new IllegalArgumentException("Negative y size for the renderDataPoint! Data: [" + RenderDataPointUtil.toString(renderData) + "].");
} }
byte blockMaterialId = RenderDataPointUtil.getBlockMaterialId(data); byte blockMaterialId = RenderDataPointUtil.getBlockMaterialId(renderData);
@@ -325,11 +324,11 @@ public class ColumnRenderBufferBuilder
float brightnessMultiplier = Config.Client.Advanced.Graphics.Quality.brightnessMultiplier.get().floatValue(); float brightnessMultiplier = Config.Client.Advanced.Graphics.Quality.brightnessMultiplier.get().floatValue();
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0) if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0)
{ {
color = RenderDataPointUtil.getColor(data); color = RenderDataPointUtil.getColor(renderData);
} }
else else
{ {
float[] ahsv = ColorUtil.argbToAhsv(RenderDataPointUtil.getColor(data)); float[] ahsv = ColorUtil.argbToAhsv(RenderDataPointUtil.getColor(renderData));
color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier); color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier);
} }
break; break;
@@ -419,14 +418,14 @@ public class ColumnRenderBufferBuilder
} }
ColumnBox.addBoxQuadsToBuilder( ColumnBox.addBoxQuadsToBuilder(
quadBuilder, clientLevel, quadBuilder, phantomArrayCheckout, clientLevel,
width, ySize, width, blockWidth, blockMaxY,
xMin, yMin, zMin, blockMinX, blockMinY, blockMinZ,
color, color,
blockMaterialId, blockMaterialId,
RenderDataPointUtil.getLightSky(data), RenderDataPointUtil.getLightSky(renderData),
fullBright ? 15 : RenderDataPointUtil.getLightBlock(data), fullBright ? LodUtil.MAX_MC_LIGHT : RenderDataPointUtil.getLightBlock(renderData),
topData, bottomData, adjColumnViews, isSameDetailLevel); topRenderData, bottomRenderData, adjColumnViews, isSameDetailLevel);
} }
} }
@@ -19,16 +19,14 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer; import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.StatsMap; import com.seibel.distanthorizons.core.util.objects.StatsMap;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -40,9 +38,9 @@ import java.util.concurrent.*;
* *
* @see ColumnRenderBufferBuilder * @see ColumnRenderBufferBuilder
*/ */
public class ColumnRenderBuffer implements AutoCloseable public class LodBufferContainer implements AutoCloseable
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** number of bytes a single quad takes */ /** number of bytes a single quad takes */
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4; public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4;
@@ -52,15 +50,16 @@ public class ColumnRenderBuffer implements AutoCloseable
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE; public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
/** the position closest to minimum X/Z infinity and the level's lowest Y */
public final DhBlockPos blockPos; public final DhBlockPos minCornerBlockPos;
public final long pos;
public boolean buffersUploaded = false; public boolean buffersUploaded = false;
private GLVertexBuffer[] vbos; public GLVertexBuffer[] vbos;
private GLVertexBuffer[] vbosTransparent; public GLVertexBuffer[] vbosTransparent;
private CompletableFuture<ColumnRenderBuffer> uploadFuture = null; private CompletableFuture<LodBufferContainer> uploadFuture = null;
@@ -68,9 +67,10 @@ public class ColumnRenderBuffer implements AutoCloseable
// constructors // // constructors //
//==============// //==============//
public ColumnRenderBuffer(DhBlockPos blockPos) public LodBufferContainer(long pos, DhBlockPos minCornerBlockPos)
{ {
this.blockPos = blockPos; this.pos = pos;
this.minCornerBlockPos = minCornerBlockPos;
this.vbos = new GLVertexBuffer[0]; this.vbos = new GLVertexBuffer[0];
this.vbosTransparent = new GLVertexBuffer[0]; this.vbosTransparent = new GLVertexBuffer[0];
} }
@@ -82,10 +82,10 @@ public class ColumnRenderBuffer implements AutoCloseable
//==================// //==================//
/** Should be run on a DH thread. */ /** Should be run on a DH thread. */
public synchronized CompletableFuture<ColumnRenderBuffer> makeAndUploadBuffersAsync(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) public synchronized CompletableFuture<LodBufferContainer> makeAndUploadBuffersAsync(LodQuadBuilder builder)
{ {
// separate variable to prevent race condition when checking null // separate variable to prevent race condition when checking null
CompletableFuture<ColumnRenderBuffer> future = this.uploadFuture; CompletableFuture<LodBufferContainer> future = this.uploadFuture;
if (future != null) if (future != null)
{ {
// upload already in process // upload already in process
@@ -117,6 +117,8 @@ public class ColumnRenderBuffer implements AutoCloseable
throw new InterruptedException(); throw new InterruptedException();
} }
EDhApiGpuUploadMethod gpuUploadMethod = GLProxy.getInstance().getGpuUploadMethod();
// upload on the render thread // upload on the render thread
uploadBuffersDirect(this.vbos, opaqueBuffers, gpuUploadMethod); uploadBuffersDirect(this.vbos, opaqueBuffers, gpuUploadMethod);
uploadBuffersDirect(this.vbosTransparent, transparentBuffers, gpuUploadMethod); uploadBuffersDirect(this.vbosTransparent, transparentBuffers, gpuUploadMethod);
@@ -133,7 +135,7 @@ public class ColumnRenderBuffer implements AutoCloseable
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Unexpected issue uploading buffer ["+this.blockPos +"], error: ["+e.getMessage()+"].", e); LOGGER.error("Unexpected issue uploading buffer ["+this.minCornerBlockPos +"], error: ["+e.getMessage()+"].", e);
this.uploadFuture.completeExceptionally(e); this.uploadFuture.completeExceptionally(e);
this.uploadFuture = null; this.uploadFuture = null;
@@ -177,7 +179,9 @@ public class ColumnRenderBuffer implements AutoCloseable
} }
return newVbos; return newVbos;
} }
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, ArrayList<ByteBuffer> byteBuffers, EDhApiGpuUploadMethod method) throws InterruptedException private static void uploadBuffersDirect(
GLVertexBuffer[] vbos, ArrayList<ByteBuffer> byteBuffers,
EDhApiGpuUploadMethod uploadMethod) throws InterruptedException
{ {
int vboIndex = 0; int vboIndex = 0;
for (int i = 0; i < byteBuffers.size(); i++) for (int i = 0; i < byteBuffers.size(); i++)
@@ -191,7 +195,7 @@ public class ColumnRenderBuffer implements AutoCloseable
// get or create the VBO // get or create the VBO
if (vbos[vboIndex] == null) if (vbos[vboIndex] == null)
{ {
vbos[vboIndex] = new GLVertexBuffer(method.useBufferStorage); vbos[vboIndex] = new GLVertexBuffer(uploadMethod.useBufferStorage);
} }
GLVertexBuffer vbo = vbos[vboIndex]; GLVertexBuffer vbo = vbos[vboIndex];
@@ -202,13 +206,13 @@ public class ColumnRenderBuffer implements AutoCloseable
try try
{ {
vbo.bind(); vbo.bind();
vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER); vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), uploadMethod, FULL_SIZED_BUFFER);
} }
catch (Exception e) catch (Exception e)
{ {
vbos[vboIndex] = null; vbos[vboIndex] = null;
vbo.close(); vbo.close();
LOGGER.error("Failed to upload buffer: ", e); LOGGER.error("Failed to upload buffer. Error: ["+e.getMessage()+"].", e);
} }
vboIndex++; vboIndex++;
@@ -222,69 +226,6 @@ public class ColumnRenderBuffer implements AutoCloseable
//========//
// render //
//========//
/** @return true if something was rendered, false otherwise */
public boolean renderOpaque(LodRenderer renderContext, DhApiRenderParam renderEventParam)
{
boolean hasRendered = false;
renderContext.setModelViewMatrixOffset(this.blockPos, renderEventParam);
for (GLVertexBuffer vbo : this.vbos)
{
if (vbo == null)
{
continue;
}
if (vbo.getVertexCount() == 0)
{
continue;
}
hasRendered = true;
renderContext.drawVbo(vbo, this);
}
return hasRendered;
}
/** @return true if something was rendered, false otherwise */
public boolean renderTransparent(LodRenderer renderContext, DhApiRenderParam renderEventParam)
{
boolean hasRendered = false;
try
{
// can throw an IllegalStateException if the GL program was freed before it should've been
renderContext.setModelViewMatrixOffset(this.blockPos, renderEventParam);
for (GLVertexBuffer vbo : this.vbosTransparent)
{
if (vbo == null)
{
continue;
}
if (vbo.getVertexCount() == 0)
{
continue;
}
hasRendered = true;
renderContext.drawVbo(vbo, this);
}
}
catch (IllegalStateException e)
{
LOGGER.error("renderContext program doesn't exist for pos: "+this.blockPos, e);
}
return hasRendered;
}
//================// //================//
// helper methods // // helper methods //
//================// //================//
@@ -328,7 +269,7 @@ public class ColumnRenderBuffer implements AutoCloseable
if (vertexBuffer.getSize() == 0) if (vertexBuffer.getSize() == 0)
{ {
GLProxy.GL_LOGGER.warn("VBO with size 0"); GLProxy.LOGGER.warn("VBO with size 0");
} }
statsMap.incBytesStat("TotalUsage", vertexBuffer.getSize()); statsMap.incBytesStat("TotalUsage", vertexBuffer.getSize());
} }
@@ -20,9 +20,7 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import com.seibel.distanthorizons.api.enums.config.EDhApiGrassSideRendering; import com.seibel.distanthorizons.api.enums.config.EDhApiGrassSideRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial; import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
@@ -30,15 +28,12 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
/** /**
@@ -48,7 +43,7 @@ import org.lwjgl.system.MemoryUtil;
*/ */
public class LodQuadBuilder public class LodQuadBuilder
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -143,7 +138,8 @@ public class LodQuadBuilder
//===========// //===========//
public void addQuadAdj( public void addQuadAdj(
EDhDirection dir, short x, short y, short z, EDhDirection dir,
short x, short y, short z,
short widthEastWest, short widthNorthSouthOrUpDown, short widthEastWest, short widthNorthSouthOrUpDown,
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight) int color, byte irisBlockMaterialId, byte skyLight, byte blockLight)
{ {
@@ -154,11 +150,11 @@ public class LodQuadBuilder
BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir); BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir);
ArrayList<BufferQuad> quadList = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ? this.transparentQuads[dir.ordinal()] : this.opaqueQuads[dir.ordinal()]; ArrayList<BufferQuad> quadList = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ? this.transparentQuads[dir.ordinal()] : this.opaqueQuads[dir.ordinal()];
if (!quadList.isEmpty() && if (!quadList.isEmpty()
( && (
quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|| quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown)) || quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
) )
{ {
this.premergeCount++; this.premergeCount++;
return; return;
@@ -170,18 +166,23 @@ public class LodQuadBuilder
// XZ // XZ
public void addQuadUp(short minX, short maxY, short minZ, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) // TODO argument names are wrong public void addQuadUp(short minX, short maxY, short minZ, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) // TODO argument names are wrong
{ {
BufferQuad quad = new BufferQuad(minX, maxY, minZ, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255); boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255);
ArrayList<BufferQuad> quadList = isTransparent ? this.transparentQuads[EDhDirection.UP.ordinal()] : this.opaqueQuads[EDhDirection.UP.ordinal()]; ArrayList<BufferQuad> quadList = isTransparent
? this.transparentQuads[EDhDirection.UP.ordinal()]
: this.opaqueQuads[EDhDirection.UP.ordinal()];
BufferQuad quad = new BufferQuad(minX, maxY, minZ, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
quadList.add(quad); quadList.add(quad);
} }
public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte irisBlockMaterialId, byte skylight, byte blocklight)
{ {
ArrayList<BufferQuad> quadArray = (this.doTransparency && ColorUtil.getAlpha(color) < 255)
? this.transparentQuads[EDhDirection.DOWN.ordinal()]
: this.opaqueQuads[EDhDirection.DOWN.ordinal()];
BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN); BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN);
ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255) quadArray.add(quad);
? transparentQuads[EDhDirection.DOWN.ordinal()] : opaqueQuads[EDhDirection.DOWN.ordinal()];
qs.add(quad);
} }
@@ -285,7 +286,7 @@ public class LodQuadBuilder
// create a new buffer // create a new buffer
if (buffer == null || !buffer.hasRemaining()) if (buffer == null || !buffer.hasRemaining())
{ {
buffer = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER); buffer = MemoryUtil.memAlloc(LodBufferContainer.FULL_SIZED_BUFFER);
byteBufferList.add(buffer); byteBufferList.add(buffer);
} }
@@ -309,7 +310,7 @@ public class LodQuadBuilder
short widthEastWest = quad.widthEastWest; short widthEastWest = quad.widthEastWest;
short widthNorthSouth = quad.widthNorthSouthOrUpDown; short widthNorthSouth = quad.widthNorthSouthOrUpDown;
byte normalIndex = (byte) quad.direction.ordinal(); byte normalIndex = (byte) quad.direction.ordinal();
EDhDirection.Axis axis = quad.direction.getAxis(); EDhDirection.Axis axis = quad.direction.axis;
for (int i = 0; i < quadBase.length; i++) for (int i = 0; i < quadBase.length; i++)
{ {
short dx, dy, dz; short dx, dy, dz;
@@ -357,7 +358,7 @@ public class LodQuadBuilder
if (this.grassSideRenderingMode != EDhApiGrassSideRendering.AS_GRASS) if (this.grassSideRenderingMode != EDhApiGrassSideRendering.AS_GRASS)
{ {
// only change the vertex color if it's on the side or bottom // only change the vertex color if it's on the side or bottom
if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN) if (quad.direction.axis.isHorizontal() || quad.direction == EDhDirection.DOWN)
{ {
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
// if we want the color to fade, only apply the dirt color to the bottom vertices // if we want the color to fade, only apply the dirt color to the bottom vertices
@@ -456,7 +457,7 @@ public class LodQuadBuilder
} }
/** Returns how many GpuBuffers will be needed to render opaque quads in this builder. */ /** Returns how many GpuBuffers will be needed to render opaque quads in this builder. */
public int getCurrentNeededOpaqueVertexBufferCount() { return MathUtil.ceilDiv(this.getCurrentOpaqueQuadsCount(), ColumnRenderBuffer.MAX_QUADS_PER_BUFFER); } public int getCurrentNeededOpaqueVertexBufferCount() { return MathUtil.ceilDiv(this.getCurrentOpaqueQuadsCount(), LodBufferContainer.MAX_QUADS_PER_BUFFER); }
/** Returns how many GpuBuffers will be needed to render transparent quads in this builder. */ /** Returns how many GpuBuffers will be needed to render transparent quads in this builder. */
public int getCurrentNeededTransparentVertexBufferCount() public int getCurrentNeededTransparentVertexBufferCount()
{ {
@@ -465,7 +466,7 @@ public class LodQuadBuilder
return 0; return 0;
} }
return MathUtil.ceilDiv(this.getCurrentTransparentQuadsCount(), ColumnRenderBuffer.MAX_QUADS_PER_BUFFER); return MathUtil.ceilDiv(this.getCurrentTransparentQuadsCount(), LodBufferContainer.MAX_QUADS_PER_BUFFER);
} }
} }
@@ -101,9 +101,7 @@ public final class ColumnArrayView implements IColumnDataView
@Override @Override
public ColumnArrayView subView(int dataIndexStart, int dataCount) public ColumnArrayView subView(int dataIndexStart, int dataCount)
{ { return new ColumnArrayView(data, dataCount * verticalSize, offset + dataIndexStart * verticalSize, verticalSize); }
return new ColumnArrayView(data, dataCount * verticalSize, offset + dataIndexStart * verticalSize, verticalSize);
}
public void fill(long value) { Arrays.fill(data.elements(), offset, offset + size, value); } public void fill(long value) { Arrays.fill(data.elements(), offset, offset + size, value); }
@@ -185,21 +183,11 @@ public final class ColumnArrayView implements IColumnDataView
{ {
for (int i = 0; i < this.dataCount(); i++) for (int i = 0; i < this.dataCount(); i++)
{ {
RenderDataPointUtil.mergeMultiData(source.subView(i, 1), subView(i, 1)); RenderDataPointUtil.mergeMultiData(source.subView(i, 1), this.subView(i, 1));
} }
} }
} }
public void mergeMultiDataFrom(IColumnDataView source)
{
if (dataCount() != 1)
{
throw new IllegalArgumentException("output dataCount must be 1");
}
RenderDataPointUtil.mergeMultiData(source, this);
}
//================// //================//
@@ -210,15 +198,15 @@ public final class ColumnArrayView implements IColumnDataView
public String toString() public String toString()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("S:").append(size); sb.append("S:").append(this.size);
sb.append(" V:").append(verticalSize); sb.append(" V:").append(this.verticalSize);
sb.append(" O:").append(offset); sb.append(" O:").append(this.offset);
sb.append(" ["); sb.append(" [");
for (int i = 0; i < size; i++) for (int i = 0; i < this.size; i++)
{ {
sb.append(RenderDataPointUtil.toString(data.getLong(offset + i))); sb.append(RenderDataPointUtil.toString(this.data.getLong(this.offset + i)));
if (i < size - 1) if (i < this.size - 1)
{ {
sb.append(",\n"); sb.append(",\n");
} }
@@ -229,11 +217,7 @@ public final class ColumnArrayView implements IColumnDataView
} }
public int getDataHash() public int getDataHash() { return arrayHash(this.data, this.offset, this.size); }
{
return arrayHash(data, offset, size);
}
private static int arrayHash(LongArrayList a, int offset, int length) private static int arrayHash(LongArrayList a, int offset, int length)
{ {
if (a == null) if (a == null)
@@ -252,4 +236,6 @@ public final class ColumnArrayView implements IColumnDataView
return result; return result;
} }
} }
@@ -140,40 +140,4 @@ public class ColumnQuadView implements IColumnDataView
} }
} }
public void copyTo(ColumnQuadView target)
{
if (target.xSize != xSize || target.zSize != zSize)
throw new IllegalArgumentException("Target view must have same size as this view");
for (int x = 0; x < xSize; x++)
{
target.getRow(x).changeVerticalSizeFrom(getRow(x));
}
}
public void mergeMultiColumnFrom(ColumnQuadView source)
{
if (source.xSize == xSize && source.zSize == zSize)
{
source.copyTo(this);
return;
}
if (source.xSize < xSize || source.zSize < zSize)
throw new IllegalArgumentException("Source view must have same or larger size as this view");
int srcXPerTrgX = source.xSize / xSize;
int srcZPerTrgZ = source.zSize / zSize;
if (source.xSize % xSize != 0 || source.zSize % zSize != 0)
throw new IllegalArgumentException("Source view's size must be a multiple of this view's size");
for (int x = 0; x < xSize; x++)
{
for (int z = 0; z < zSize; z++)
{
ColumnQuadView srcBlock = source.subView(x * srcXPerTrgX, z * srcZPerTrgZ, srcXPerTrgX, srcZPerTrgZ);
get(x, z).mergeMultiDataFrom(srcBlock);
}
}
}
} }
@@ -0,0 +1,199 @@
package com.seibel.distanthorizons.core.dataObjects.transformers;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull;
public class FullDataOcclusionCuller
{
/**
* Mutates the given datasource so blocks that aren't visible
* (IE completely surrounded by other opaque blocks)
* are removed from the data column.
*
* @param dataSource
* @param relX relative X position in the datasource
* @param relZ relative Z position in the datasource
*/
public static void cullHiddenDatapointsInColumn(
FullDataSourceV2 dataSource,
int relX, int relZ
)
{
LongArrayList centerColumn = dataSource.getColumnAtRelPos(relX, relZ);
LongArrayList posXColumn = dataSource.tryGetColumnAtRelPos(relX + 1, relZ);
LongArrayList negXColumn = dataSource.tryGetColumnAtRelPos(relX - 1, relZ);
LongArrayList posZColumn = dataSource.tryGetColumnAtRelPos(relX, relZ + 1);
LongArrayList negZColumn = dataSource.tryGetColumnAtRelPos(relX, relZ - 1);
if (posXColumn == null || posXColumn.size() == 0
|| negXColumn == null || negXColumn.size() == 0
|| posZColumn == null || posZColumn.size() == 0
|| negZColumn == null || negZColumn.size() == 0)
{
// if any adjacent columns are empty then we can't
// cull this column, since at least one side will be open
// to air/void
return;
}
int centerIndex = centerColumn.size() - 1;
int posXIndex = (posXColumn.size() - 1);
int negXIndex = (negXColumn.size() - 1);
int posZIndex = (posZColumn.size() - 1);
int negZIndex = (negZColumn.size() - 1);
for (; centerIndex >= 0; centerIndex--)
{
long currentPoint = centerColumn.getLong(centerIndex);
// Translucent data points are not eligible to be culled.
if (isTranslucent(dataSource, currentPoint))
{
continue;
}
// the top segment should never be culled.
if (centerIndex == 0
|| isTranslucent(dataSource, centerColumn.getLong(centerIndex - 1)))
{
continue;
}
// the bottom segment can sometimes be culled.
// assume it will not be seen from below,
// because this would imply the player is in the void.
if (centerIndex + 1 < centerColumn.size()
&& isTranslucent(dataSource, centerColumn.getLong(centerIndex + 1)))
{
continue;
}
// the lowest/bedrock segment should not be culled
if (centerIndex + 1 == centerColumn.size())
{
continue;
}
posXIndex = checkOcclusion(dataSource, currentPoint, posXColumn, posXIndex);
if (posXIndex < 0)
{
posXIndex = ~posXIndex;
continue;
}
negXIndex = checkOcclusion(dataSource, currentPoint, negXColumn, negXIndex);
if (negXIndex < 0)
{
negXIndex = ~negXIndex;
continue;
}
posZIndex = checkOcclusion(dataSource, currentPoint, posZColumn, posZIndex);
if (posZIndex < 0)
{
posZIndex = ~posZIndex;
continue;
}
negZIndex = checkOcclusion(dataSource, currentPoint, negZColumn, negZIndex);
if (negZIndex < 0)
{
negZIndex = ~negZIndex;
continue;
}
// Current point is fully surrounded. remove it.
centerColumn.removeLong(centerIndex);
// Make the above data point cover the area that the current point used to occupy.
// The element that was at `centerIndex - 1` is still at that position even after removal of centerIndex.
long above = centerColumn.getLong(centerIndex - 1);
above = FullDataPointUtil.setBottomY(above, FullDataPointUtil.getBottomY(currentPoint));
above = FullDataPointUtil.setHeight(above, FullDataPointUtil.getHeight(currentPoint) + FullDataPointUtil.getHeight(above));
centerColumn.set(centerIndex - 1, above);
}
}
/**
checks if centerPoint is "covered" by opaque data points in adjacentColumn.
centerPoint counts as covered if, and only if, for all Y levels in its height range,
there exists an opaque data point in adjacentColumn which overlaps with that Y level.
@param source used to lookup blocks (and their opacities) based on their IDs.
@param centerPoint the point being checked to see if it's fully covered.
@param adjacentColumn the data points which might cover centerPoint.
@param adjacentIndex the starting index in adjacentColumn to start scanning at.
indices greater than adjacentIndex have already been checked and confirmed to
not overlap or only overlap partially with centerPoint's Y range.
@return if centerPoint is covered, returns the index of the segment which finishes covering it.
the start of the covering may be a smaller index. in this case, the returned index may be used
as the adjacentIndex provided to this method on the next iteration which yields a new centerPoint.
if centerPoint is NOT covered, returns the bitwise negation of the index of the
segment which did not cover it. this guarantees that the returned value is negative.
the caller should check for negative return values and manually un-negate them to proceed with the loop.
in other words, this function returns the index of the next adjacent data
point to use in the loop, AND a boolean indicating whether or not the
centerPoint is covered; both are packed into the same int, and returned.
*/
private static int checkOcclusion(@NotNull FullDataSourceV2 source, long centerPoint, @NotNull LongArrayList adjacentColumn, int adjacentIndex)
{
// check if this point is adjacent to an empty column
// if so it will always be shown
if (adjacentColumn.isEmpty())
{
return ~adjacentIndex;
}
else if (adjacentColumn.size() == 1
&& adjacentColumn.getLong(0) == FullDataPointUtil.EMPTY_DATA_POINT)
{
return ~adjacentIndex;
}
int bottomOfCenter = FullDataPointUtil.getBottomY(centerPoint);
int topOfCenter = bottomOfCenter + FullDataPointUtil.getHeight(centerPoint);
for (; adjacentIndex >= 0; adjacentIndex--)
{
long adjacentPoint = adjacentColumn.getLong(adjacentIndex);
int topOfAdjacent = FullDataPointUtil.getBottomY(adjacentPoint) + FullDataPointUtil.getHeight(adjacentPoint);
if (topOfAdjacent <= bottomOfCenter)
{
// the adjacent point is below the center point,
// check the next one
continue;
}
else if (isTranslucent(source, adjacentPoint))
{
// this point is adjacent to a transparent LOD and should be shown
return ~adjacentIndex;
}
else if (topOfAdjacent >= topOfCenter)
{
// the adjacent point covers the center point
return adjacentIndex;
}
}
// the Adjacent column ends before center column does,
// this point should be visible
return ~adjacentIndex;
}
private static boolean isTranslucent(FullDataSourceV2 source, long point)
{
int id = FullDataPointUtil.getId(point);
int opacity = source.mapping.getBlockStateWrapper(id).getOpacity();
return opacity < LodUtil.BLOCK_FULLY_OPAQUE;
}
}
@@ -31,6 +31,7 @@ import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.render.LodQuadTree;
import com.seibel.distanthorizons.core.util.*; import com.seibel.distanthorizons.core.util.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -40,7 +41,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.HashSet; import java.util.HashSet;
@@ -50,7 +51,7 @@ import java.util.HashSet;
*/ */
public class FullDataToRenderDataTransformer public class FullDataToRenderDataTransformer
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@@ -66,7 +67,8 @@ public class FullDataToRenderDataTransformer
//==============================// //==============================//
@Nullable @Nullable
public static ColumnRenderSource transformFullDataToRenderSource(@Nullable FullDataSourceV2 fullDataSource, @Nullable IClientLevelWrapper levelWrapper) public static ColumnRenderSource transformFullDataToRenderSource(
@Nullable FullDataSourceV2 fullDataSource, @Nullable IClientLevelWrapper levelWrapper)
{ {
if (fullDataSource == null) if (fullDataSource == null)
{ {
@@ -102,7 +104,8 @@ public class FullDataToRenderDataTransformer
* @throws InterruptedException Can be caused by interrupting the thread upstream. * @throws InterruptedException Can be caused by interrupting the thread upstream.
* Generally thrown if the method is running after the client leaves the current world. * Generally thrown if the method is running after the client leaves the current world.
*/ */
private static ColumnRenderSource transformCompleteFullDataToColumnData(IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource) throws InterruptedException private static ColumnRenderSource transformCompleteFullDataToColumnData(
IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource) throws InterruptedException
{ {
final long pos = fullDataSource.getPos(); final long pos = fullDataSource.getPos();
final byte dataDetail = fullDataSource.getDataDetailLevel(); final byte dataDetail = fullDataSource.getDataDetailLevel();
@@ -125,10 +128,8 @@ public class FullDataToRenderDataTransformer
{ {
for (int z = 0; z < FullDataSourceV2.WIDTH; z++) for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
{ {
throwIfThreadInterrupted();
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z); ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
LongArrayList dataColumn = fullDataSource.get(x, z); LongArrayList dataColumn = fullDataSource.getColumnAtRelPos(x, z);
updateOrReplaceRenderDataViewColumnWithFullDataColumn( updateOrReplaceRenderDataViewColumnWithFullDataColumn(
levelWrapper, fullDataSource, levelWrapper, fullDataSource,
@@ -138,7 +139,7 @@ public class FullDataToRenderDataTransformer
} }
} }
columnSource.fillDebugFlag(0, 0, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.DebugSourceFlag.FULL); columnSource.fillDebugFlag(0, 0, ColumnRenderSource.WIDTH, ColumnRenderSource.WIDTH, ColumnRenderSource.DebugSourceFlag.FULL);
return columnSource; return columnSource;
} }
@@ -173,6 +174,7 @@ public class FullDataToRenderDataTransformer
// expand the ColumnArrayView to fit the new larger max vertical size // expand the ColumnArrayView to fit the new larger max vertical size
ColumnArrayView newColumnArrayView = new ColumnArrayView(dataArrayList, fullDataLength, 0, fullDataLength); ColumnArrayView newColumnArrayView = new ColumnArrayView(dataArrayList, fullDataLength, 0, fullDataLength);
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, newColumnArrayView, fullDataColumn); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, newColumnArrayView, fullDataColumn);
columnArrayView.changeVerticalSizeFrom(newColumnArrayView); columnArrayView.changeVerticalSizeFrom(newColumnArrayView);
} }
finally finally
@@ -277,18 +279,18 @@ public class FullDataToRenderDataTransformer
if (caveBlock) if (caveBlock)
{ {
if (caveCullingEnabled if (caveCullingEnabled
// assume this data point is underground if it has no sky-light // assume this data point is underground if it has no sky-light
&& skyLight == LodUtil.MIN_MC_LIGHT && skyLight == LodUtil.MIN_MC_LIGHT
// ignore caves above a certain height to prevent floating islands from having walls underneath them // ignore caves above a certain height to prevent floating islands from having walls underneath them
&& topY < caveCullingMaxY && topY < caveCullingMaxY
// cave culling shouldn't happen when at the top of the world // cave culling shouldn't happen when at the top of the world
&& renderDataIndex != 0 && fullDataIndex != 0 && renderDataIndex != 0 && fullDataIndex != 0
// cave culling can't happen when at the bottom of the world // cave culling can't happen when at the bottom of the world
&& (fullDataIndex+1) < fullColumnData.size()) && (fullDataIndex + 1) < fullColumnData.size())
{ {
// we need to get the next sky/block lights because // we need to get the next sky/block lights because
// the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved. // the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved.
long nextFullData = fullColumnData.getLong(fullDataIndex+1); long nextFullData = fullColumnData.getLong(fullDataIndex + 1);
int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData); int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData);
if (nextSkyLight == LodUtil.MIN_MC_LIGHT if (nextSkyLight == LodUtil.MIN_MC_LIGHT
@@ -323,9 +325,9 @@ public class FullDataToRenderDataTransformer
//=======================// //=======================//
if (ignoreNonCollidingBlocks if (ignoreNonCollidingBlocks
&& !block.isSolid() && !block.isSolid()
&& !block.isLiquid() && !block.isLiquid()
&& block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE) && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE)
{ {
if (colorBelowWithAvoidedBlocks) if (colorBelowWithAvoidedBlocks)
{ {
@@ -335,7 +337,7 @@ public class FullDataToRenderDataTransformer
// this prevents issues if grass is transparent // this prevents issues if grass is transparent
if (ColorUtil.getAlpha(tempColor) != 0) if (ColorUtil.getAlpha(tempColor) != 0)
{ {
colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255); colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor, 255);
skylightToApplyToNextBlock = skyLight; skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight; blocklightToApplyToNextBlock = blockLight;
} }
@@ -368,7 +370,9 @@ public class FullDataToRenderDataTransformer
//=============================// //=============================//
// check if they share a top-bottom face and if they have same color // check if they share a top-bottom face and if they have same color
if (color == lastColor && bottomY + blockHeight == lastBottom && renderDataIndex > 0) if (color == lastColor
&& bottomY + blockHeight == lastBottom
&& renderDataIndex > 0)
{ {
//replace the previous block with new bottom //replace the previous block with new bottom
long columnData = renderColumnData.get(renderDataIndex - 1); long columnData = renderColumnData.get(renderDataIndex - 1);
@@ -396,21 +400,4 @@ public class FullDataToRenderDataTransformer
//================//
// helper methods //
//================//
/**
* Called in loops that may run for an extended period of time. <br>
* This is necessary to allow canceling these transformers since running
* them after the client has left a given world will throw exceptions.
*/
private static void throwIfThreadInterrupted() throws InterruptedException
{
if (Thread.interrupted())
{
throw new InterruptedException(FullDataToRenderDataTransformer.class.getSimpleName() + " task interrupted.");
}
}
} }
@@ -24,8 +24,6 @@ import java.util.List;
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode; import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkProcessingEvent; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkProcessingEvent;
import com.seibel.distanthorizons.api.objects.data.DhApiChunk; import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint; import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
@@ -46,12 +44,12 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class LodDataBuilder public class LodDataBuilder
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final IBlockStateWrapper AIR = WRAPPER_FACTORY.getAirBlockStateWrapper(); private static final IBlockStateWrapper AIR = WRAPPER_FACTORY.getAirBlockStateWrapper();
@@ -118,7 +116,6 @@ public class LodDataBuilder
//==========================// //==========================//
EDhApiWorldCompressionMode worldCompressionMode = Config.Common.LodBuilding.worldCompression.get(); EDhApiWorldCompressionMode worldCompressionMode = Config.Common.LodBuilding.worldCompression.get();
boolean ignoreHiddenBlocks = (worldCompressionMode != EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
try try
{ {
@@ -142,7 +139,7 @@ public class LodDataBuilder
int columnZ = relBlockZ + chunkOffsetZ; int columnZ = relBlockZ + chunkOffsetZ;
// Get column data // Get column data
LongArrayList longs = dataSource.get(columnX, columnZ); LongArrayList longs = dataSource.getColumnAtRelPos(columnX, columnZ);
if (longs == null) if (longs == null)
{ {
longs = new LongArrayList(dataCapacity); longs = new LongArrayList(dataCapacity);
@@ -257,11 +254,6 @@ public class LodDataBuilder
dataSource.setSingleColumn(longs, columnX, columnZ, EDhApiWorldGenerationStep.LIGHT, worldCompressionMode); dataSource.setSingleColumn(longs, columnX, columnZ, EDhApiWorldGenerationStep.LIGHT, worldCompressionMode);
} }
} }
if (ignoreHiddenBlocks)
{
cullHiddenBlocks(dataSource, chunkOffsetX, chunkOffsetZ);
}
} }
catch (DataCorruptedException e) catch (DataCorruptedException e)
{ {
@@ -273,153 +265,6 @@ public class LodDataBuilder
return dataSource; return dataSource;
} }
private static void cullHiddenBlocks(FullDataSourceV2 dataSource, int chunkOffsetX, int chunkOffsetZ)
{
for (int relZ = 1; relZ < LodUtil.CHUNK_WIDTH - 1; relZ++)
{
for (int relX = 1; relX < LodUtil.CHUNK_WIDTH - 1; relX++)
{
LongArrayList
centerColumn = dataSource.get(relX + chunkOffsetX, relZ + chunkOffsetZ),
posXColumn = dataSource.get(relX + chunkOffsetX + 1, relZ + chunkOffsetZ),
negXColumn = dataSource.get(relX + chunkOffsetX - 1, relZ + chunkOffsetZ),
posZColumn = dataSource.get(relX + chunkOffsetX, relZ + chunkOffsetZ + 1),
negZColumn = dataSource.get(relX + chunkOffsetX, relZ + chunkOffsetZ - 1);
int
centerIndex = centerColumn.size() - 1,
posXIndex = posXColumn.size() - 1,
negXIndex = negXColumn.size() - 1,
posZIndex = posZColumn.size() - 1,
negZIndex = negZColumn.size() - 1;
for (; centerIndex >= 0; centerIndex--)
{
long currentPoint = centerColumn.getLong(centerIndex);
// Translucent data points are not eligible to be culled.
if (isTranslucent(dataSource, currentPoint))
{
continue;
}
// the top segment should never be culled.
if (centerIndex == 0
|| isTranslucent(dataSource, centerColumn.getLong(centerIndex - 1))
)
{
continue;
}
// the bottom segment can sometimes be culled.
// assume it will not be seen from below,
// because this would imply the player is in the void.
if (centerIndex + 1 < centerColumn.size()
&& isTranslucent(dataSource, centerColumn.getLong(centerIndex + 1))
)
{
continue;
}
// the lowest/bedrock segment should not be culled
if (centerIndex + 1 == centerColumn.size())
{
continue;
}
posXIndex = checkOcclusion(dataSource, currentPoint, posXColumn, posXIndex);
if (posXIndex < 0)
{
posXIndex = ~posXIndex;
continue;
}
negXIndex = checkOcclusion(dataSource, currentPoint, negXColumn, negXIndex);
if (negXIndex < 0)
{
negXIndex = ~negXIndex;
continue;
}
posZIndex = checkOcclusion(dataSource, currentPoint, posZColumn, posZIndex);
if (posZIndex < 0)
{
posZIndex = ~posZIndex;
continue;
}
negZIndex = checkOcclusion(dataSource, currentPoint, negZColumn, negZIndex);
if (negZIndex < 0)
{
negZIndex = ~negZIndex;
continue;
}
// Current point is fully surrounded. remove it.
centerColumn.removeLong(centerIndex);
// Make the above data point cover the area that the current point used to occupy.
// The element that was at `centerIndex - 1` is still at that position even after removal of centerIndex.
long above = centerColumn.getLong(centerIndex - 1);
above = FullDataPointUtil.setBottomY(above, FullDataPointUtil.getBottomY(currentPoint));
above = FullDataPointUtil.setHeight(above, FullDataPointUtil.getHeight(currentPoint) + FullDataPointUtil.getHeight(above));
centerColumn.set(centerIndex - 1, above);
}
}
}
}
/**
checks if centerPoint is "covered" by opaque data points in adjacentColumn.
centerPoint counts as covered if, and only if, for all Y levels in its height range,
there exists an opaque data point in adjacentColumn which overlaps with that Y level.
@param source used to lookup blocks (and their opacities) based on their IDs.
@param centerPoint the point being checked to see if it's fully covered.
@param adjacentColumn the data points which might cover centerPoint.
@param adjacentIndex the starting index in adjacentColumn to start scanning at.
indices greater than adjacentIndex have already been checked and confirmed to
not overlap or only overlap partially with centerPoint's Y range.
@return if centerPoint is covered, returns the index of the segment which finishes covering it.
the start of the covering may be a smaller index. in this case, the returned index may be used
as the adjacentIndex provided to this method on the next iteration which yields a new centerPoint.
if centerPoint is NOT covered, returns the bitwise negation of the index of the
segment which did not cover it. this guarantees that the returned value is negative.
the caller should check for negative return values and manually un-negate them to proceed with the loop.
in other words, this function returns the index of the next adjacent data
point to use in the loop, AND a boolean indicating whether or not the
centerPoint is covered; both are packed into the same int, and returned.
*/
private static int checkOcclusion(FullDataSourceV2 source, long centerPoint, LongArrayList adjacentColumn, int adjacentIndex)
{
int bottomOfCenter = FullDataPointUtil.getBottomY(centerPoint);
int topOfCenter = bottomOfCenter + FullDataPointUtil.getHeight(centerPoint);
for (; adjacentIndex >= 0; adjacentIndex--)
{
long adjacentPoint = adjacentColumn.getLong(adjacentIndex);
int topOfAdjacent = FullDataPointUtil.getBottomY(adjacentPoint) + FullDataPointUtil.getHeight(adjacentPoint);
if (topOfAdjacent <= bottomOfCenter)
{
continue;
}
else if (isTranslucent(source, adjacentPoint))
{
return ~adjacentIndex;
}
else if (topOfAdjacent >= topOfCenter)
{
return adjacentIndex;
}
}
throw new LodUtil.AssertFailureException("Adjacent column ends before center column does.");
}
private static boolean isTranslucent(FullDataSourceV2 source, long point) {
return source.mapping.getBlockStateWrapper(FullDataPointUtil.getId(point)).getOpacity() < LodUtil.BLOCK_FULLY_OPAQUE;
}
/** @throws ClassCastException if an API user returns the wrong object type(s) */ /** @throws ClassCastException if an API user returns the wrong object type(s) */
@@ -23,7 +23,7 @@ import com.seibel.distanthorizons.coreapi.DependencyInjection.DependencyInjector
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModAccessor; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModAccessor;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
@@ -40,7 +40,7 @@ import java.lang.invoke.MethodHandles;
*/ */
public class ModAccessorInjector extends DependencyInjector<IModAccessor> public class ModAccessorInjector extends DependencyInjector<IModAccessor>
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static final ModAccessorInjector INSTANCE = new ModAccessorInjector(IModAccessor.class); public static final ModAccessorInjector INSTANCE = new ModAccessorInjector(IModAccessor.class);
@@ -19,496 +19,165 @@
package com.seibel.distanthorizons.core.enums; package com.seibel.distanthorizons.core.enums;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.seibel.distanthorizons.core.util.math.Vec3i; import com.seibel.distanthorizons.core.util.math.Vec3i;
/** /**
* An (almost) exact copy of Minecraft's
* Direction enum. <Br><Br>
*
* Up <Br> * Up <Br>
* Down <Br> * Down <Br>
* North <Br> * North <Br>
* South <Br> * South <Br>
* East <Br> * East <Br>
* West <Br> * West <Br>
*
* @author James Seibel
* @version 2021-11-13
*/ */
public enum EDhDirection public enum EDhDirection
{ {
/** negative Y */ /** negative Y */
DOWN(0, 1, -1, "down", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Y, new Vec3i(0, -1, 0)), DOWN("down", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Y, new Vec3i(0, -1, 0), -1),
/** positive Y */ /** positive Y */
UP(1, 0, -1, "up", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Y, new Vec3i(0, 1, 0)), UP("up", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Y, new Vec3i(0, 1, 0), -1),
/** negative Z */ /** negative Z */
NORTH(2, 3, 2, "north", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Z, new Vec3i(0, 0, -1)), NORTH("north", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Z, new Vec3i(0, 0, -1), 0),
/** positive Z */ /** positive Z */
SOUTH(3, 2, 0, "south", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Z, new Vec3i(0, 0, 1)), SOUTH("south", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Z, new Vec3i(0, 0, 1), 1),
/** negative X */ /** negative X */
WEST(4, 5, 1, "west", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.X, new Vec3i(-1, 0, 0)), WEST("west", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.X, new Vec3i(-1, 0, 0), 2),
/** positive X */ /** positive X */
EAST(5, 4, 3, "east", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.X, new Vec3i(1, 0, 0)); EAST("east", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.X, new Vec3i(1, 0, 0), 3);
/**
* Up, Down, West, East, North, South <br> /** Up, Down, West, East, North, South */
* Similar to {@link EDhDirection#OPPOSITE_DIRECTIONS}, just with a different order public static final EDhDirection[] ALL = new EDhDirection[] {
*/
public static final EDhDirection[] CARDINAL_DIRECTIONS = new EDhDirection[]{
EDhDirection.UP, EDhDirection.UP,
EDhDirection.DOWN, EDhDirection.DOWN,
EDhDirection.WEST, EDhDirection.WEST,
EDhDirection.EAST, EDhDirection.EAST,
EDhDirection.NORTH, EDhDirection.NORTH,
EDhDirection.SOUTH}; EDhDirection.SOUTH
};
/** /** North, South, East, West */
* Up, Down, South, North, East, West <br> public static final EDhDirection[] CARDINAL_COMPASS = new EDhDirection[] {
* Similar to {@link EDhDirection#CARDINAL_DIRECTIONS}, just with a different order
*/
public static final EDhDirection[] OPPOSITE_DIRECTIONS = new EDhDirection[]{
EDhDirection.UP,
EDhDirection.DOWN,
EDhDirection.SOUTH,
EDhDirection.NORTH,
EDhDirection.EAST,
EDhDirection.WEST};
/** North, South, East, West */ // TODO rename to state this is just X/Z or flat directions
public static final EDhDirection[] ADJ_DIRECTIONS = new EDhDirection[]{
EDhDirection.EAST, EDhDirection.EAST,
EDhDirection.WEST, EDhDirection.WEST,
EDhDirection.SOUTH, EDhDirection.SOUTH,
EDhDirection.NORTH}; EDhDirection.NORTH
};
// private final int data3d;
// private final int oppositeIndex;
// private final int data2d;
private final String name;
private final EDhDirection.Axis axis;
private final EDhDirection.AxisDirection axisDirection;
private final Vec3i normal;
private static final EDhDirection[] VALUES = values();
private static final Map<String, EDhDirection> BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(EDhDirection::getName, (p_199787_0_) -> public final String name;
public final EDhDirection.Axis axis;
public final EDhDirection.AxisDirection axisDirection;
public final Vec3i normal;
/** -1 if not a {@link EDhDirection#CARDINAL_COMPASS} direction */
public final int compassIndex;
//=============//
// constructor //
//=============//
EDhDirection(String name, EDhDirection.AxisDirection axisDirection, EDhDirection.Axis axis, Vec3i normal, int compassIndex)
{ {
return p_199787_0_; this.name = name;
})); this.axis = axis;
this.axisDirection = axisDirection;
// private static final LodDirection[] BY_3D_DATA = Arrays.stream(VALUES).sorted(Comparator.comparingInt((p_199790_0_) -> this.normal = normal;
// { this.compassIndex = compassIndex;
// return p_199790_0_.data3d;
// })).toArray((p_199788_0_) ->
// {
// return new LodDirection[p_199788_0_];
// });
//
// private static final LodDirection[] BY_2D_DATA = Arrays.stream(VALUES).filter((p_199786_0_) ->
// {
// return p_199786_0_.getAxis().isHorizontal();
// }).sorted(Comparator.comparingInt((p_199789_0_) ->
// {
// return p_199789_0_.data2d;
// })).toArray((p_199791_0_) ->
// {
// return new LodDirection[p_199791_0_];
// });
// private static final Long2ObjectMap<LodDirection> BY_NORMAL = Arrays.stream(VALUES).collect(Collectors.toMap((p_218385_0_) ->
// {
// return (new BlockPos(p_218385_0_.getNormal())).asLong();
// }, (p_218384_0_) ->
// {
// return p_218384_0_;
// }, (p_218386_0_, p_218386_1_) ->
// {
// throw new IllegalArgumentException("Duplicate keys");
// }, Long2ObjectOpenHashMap::new));
EDhDirection(int p_i46016_3_, int p_i46016_4_, int p_i46016_5_, String p_i46016_6_, EDhDirection.AxisDirection p_i46016_7_, EDhDirection.Axis p_i46016_8_, Vec3i p_i46016_9_)
{
// this.data3d = p_i46016_3_;
// this.data2d = p_i46016_5_;
// this.oppositeIndex = p_i46016_4_;
this.name = p_i46016_6_;
this.axis = p_i46016_8_;
this.axisDirection = p_i46016_7_;
this.normal = p_i46016_9_;
} }
//=========//
// methods //
//=========//
// public static LodDirection[] orderedByNearest(Entity p_196054_0_) public EDhDirection opposite()
// {
// float f = p_196054_0_.getViewXRot(1.0F) * ((float) Math.PI / 180F);
// float f1 = -p_196054_0_.getViewYRot(1.0F) * ((float) Math.PI / 180F);
// float f2 = MathHelper.sin(f);
// float f3 = MathHelper.cos(f);
// float f4 = MathHelper.sin(f1);
// float f5 = MathHelper.cos(f1);
// boolean flag = f4 > 0.0F;
// boolean flag1 = f2 < 0.0F;
// boolean flag2 = f5 > 0.0F;
// float f6 = flag ? f4 : -f4;
// float f7 = flag1 ? -f2 : f2;
// float f8 = flag2 ? f5 : -f5;
// float f9 = f6 * f3;
// float f10 = f8 * f3;
// LodDirection lodDirection = flag ? EAST : WEST;
// LodDirection direction1 = flag1 ? UP : DOWN;
// LodDirection direction2 = flag2 ? SOUTH : NORTH;
// if (f6 > f8)
// {
// if (f7 > f9)
// {
// return makeDirectionArray(direction1, lodDirection, direction2);
// }
// else
// {
// return f10 > f7 ? makeDirectionArray(lodDirection, direction2, direction1) : makeDirectionArray(lodDirection, direction1, direction2);
// }
// }
// else if (f7 > f10)
// {
// return makeDirectionArray(direction1, direction2, lodDirection);
// }
// else
// {
// return f9 > f7 ? makeDirectionArray(direction2, lodDirection, direction1) : makeDirectionArray(direction2, direction1, lodDirection);
// }
// }
// private static LodDirection[] makeDirectionArray(LodDirection p_196053_0_, LodDirection p_196053_1_, LodDirection p_196053_2_)
// {
// return new LodDirection[] { p_196053_0_, p_196053_1_, p_196053_2_, p_196053_2_.getOpposite(), p_196053_1_.getOpposite(), p_196053_0_.getOpposite() };
// }
// public static LodDirection rotate(Mat4f p_229385_0_, LodDirection p_229385_1_)
// {
// Vec3i Vec3i = p_229385_1_.getNormal();
// Vector4f vector4f = new Vector4f(Vec3i.getX(), Vec3i.getY(), Vec3i.getZ(), 0.0F);
// vector4f.transform(p_229385_0_);
// return getNearest(vector4f.x(), vector4f.y(), vector4f.z());
// }
// public Quaternion getRotation()
// {
// Quaternion quaternion = Vector3f.XP.rotationDegrees(90.0F);
// switch (this)
// {
// case DOWN:
// return Vector3f.XP.rotationDegrees(180.0F);
// case UP:
// return Quaternion.ONE.copy();
// case NORTH:
// quaternion.mul(Vector3f.ZP.rotationDegrees(180.0F));
// return quaternion;
// case SOUTH:
// return quaternion;
// case WEST:
// quaternion.mul(Vector3f.ZP.rotationDegrees(90.0F));
// return quaternion;
// case EAST:
// default:
// quaternion.mul(Vector3f.ZP.rotationDegrees(-90.0F));
// return quaternion;
// }
// }
// public int get3DDataValue()
// {
// return this.data3d;
// }
//
// public int get2DDataValue()
// {
// return this.data2d;
// }
public EDhDirection.AxisDirection getAxisDirection()
{ {
return this.axisDirection; switch(this)
}
// public LodDirection getOpposite()
// {
// return from3DDataValue(this.oppositeIndex);
// }
public EDhDirection getClockWise()
{
switch (this)
{ {
case UP:
return EDhDirection.DOWN;
case DOWN:
return EDhDirection.UP;
case NORTH: case NORTH:
return EAST; return EDhDirection.SOUTH;
case SOUTH: case SOUTH:
return WEST; return EDhDirection.NORTH;
case WEST:
return NORTH;
case EAST: case EAST:
return SOUTH; return EDhDirection.WEST;
default:
throw new IllegalStateException("Unable to get Y-rotated facing of " + this);
}
}
public EDhDirection getCounterClockWise()
{
switch (this)
{
case NORTH:
return WEST;
case SOUTH:
return EAST;
case WEST: case WEST:
return SOUTH; return EDhDirection.EAST;
case EAST:
return NORTH;
default: default:
throw new IllegalStateException("Unable to get CCW facing of " + this); throw new IllegalArgumentException();
} }
} }
public String getName()
@Override
public String toString() { return this.name; }
//================//
// helper classes //
//================//
/**
* X <br>
* Y <br>
* Z <br>
*/
public enum Axis
{ {
return this.name; X("x"),
} Y("y"),
Z("z");
public EDhDirection.Axis getAxis() public final String name;
{
return this.axis;
}
public static EDhDirection byName(String name)
{
return name == null ? null : BY_NAME.get(name.toLowerCase(Locale.ROOT));
}
// public static LodDirection from3DDataValue(int p_82600_0_) //=============//
// { // constructor //
// return BY_3D_DATA[MathHelper.abs(p_82600_0_ % BY_3D_DATA.length)]; //=============//
// }
//
// public static LodDirection from2DDataValue(int p_176731_0_)
// {
// return BY_2D_DATA[MathHelper.abs(p_176731_0_ % BY_2D_DATA.length)];
// }
// @Nullable Axis(String name) { this.name = name; }
// public static LodDirection fromNormal(int p_218383_0_, int p_218383_1_, int p_218383_2_)
// {
// return BY_NORMAL.get(BlockPos.asLong(p_218383_0_, p_218383_1_, p_218383_2_));
// }
// public static LodDirection fromYRot(double p_176733_0_)
// {
// return from2DDataValue(MathHelper.floor(p_176733_0_ / 90.0D + 0.5D) & 3);
// }
public static EDhDirection fromAxisAndDirection(EDhDirection.Axis p_211699_0_, EDhDirection.AxisDirection p_211699_1_)
{
switch (p_211699_0_)
{
case X:
return p_211699_1_ == EDhDirection.AxisDirection.POSITIVE ? EAST : WEST;
case Y:
return p_211699_1_ == EDhDirection.AxisDirection.POSITIVE ? UP : DOWN;
case Z:
default:
return p_211699_1_ == EDhDirection.AxisDirection.POSITIVE ? SOUTH : NORTH;
}
}
// public float toYRot() //=========//
// { // methods //
// return (this.data2d & 3) * 90; //=========//
// }
// public static LodDirection getRandom(Random p_239631_0_) public boolean isVertical() { return this == Y; }
// { public boolean isHorizontal() { return this == X || this == Z; }
// return Util.getRandom(VALUES, p_239631_0_);
// }
// public static LodDirection getNearest(double p_210769_0_, double p_210769_2_, double p_210769_4_)
// {
// return getNearest((float) p_210769_0_, (float) p_210769_2_, (float) p_210769_4_);
// }
// public static LodDirection getNearest(float p_176737_0_, float p_176737_1_, float p_176737_2_)
// {
// LodDirection lodDirection = NORTH;
// float f = Float.MIN_VALUE;
//
// for (LodDirection direction1 : VALUES)
// {
// float f1 = p_176737_0_ * direction1.normal.x + p_176737_1_ * direction1.normal.y + p_176737_2_ * direction1.normal.z;
// if (f1 > f)
// {
// f = f1;
// lodDirection = direction1;
// }
// }
//
// return lodDirection;
// }
public static EDhDirection get(EDhDirection.AxisDirection p_181076_0_, EDhDirection.Axis p_181076_1_)
{
for (EDhDirection lodDirection : VALUES)
{
if (lodDirection.getAxisDirection() == p_181076_0_ && lodDirection.getAxis() == p_181076_1_)
{
return lodDirection;
}
}
throw new IllegalArgumentException("No such direction: " + p_181076_0_ + " " + p_181076_1_);
}
public Vec3i getNormal()
{
return this.normal;
}
// public boolean isFacingAngle(float p_243532_1_)
// {
// float f = p_243532_1_ * ((float) Math.PI / 180F);
// float f1 = -MathHelper.sin(f);
// float f2 = MathHelper.cos(f);
// return this.normal.getX() * f1 + this.normal.getZ() * f2 > 0.0F;
// }
public enum Axis implements Predicate<EDhDirection>
{
X("x")
{
@Override
public int choose(int x, int y, int z)
{
return x;
}
@Override
public double choose(double x, double y, double z)
{
return x;
}
},
Y("y")
{
@Override
public int choose(int x, int y, int z)
{
return y;
}
@Override
public double choose(double x, double y, double z)
{
return y;
}
},
Z("z")
{
@Override
public int choose(int x, int y, int z)
{
return z;
}
@Override
public double choose(double x, double y, double z)
{
return z;
}
};
private static final EDhDirection.Axis[] VALUES = values();
private static final Map<String, EDhDirection.Axis> BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(EDhDirection.Axis::getName, (p_199785_0_) ->
{
return p_199785_0_;
}));
private final String name;
Axis(String name)
{
this.name = name;
}
public static EDhDirection.Axis byName(String name)
{
return BY_NAME.get(name.toLowerCase(Locale.ROOT));
}
public String getName()
{
return this.name;
}
public boolean isVertical()
{
return this == Y;
}
public boolean isHorizontal()
{
return this == X || this == Z;
}
@Override @Override
public String toString() public String toString() { return this.name; }
{
return this.name;
}
// public static LodDirection.Axis getRandom(Random p_239634_0_)
// {
// return Util.getRandom(VALUES, p_239634_0_);
// }
@Override
public boolean test(EDhDirection p_test_1_)
{
return p_test_1_ != null && p_test_1_.getAxis() == this;
}
// public LodDirection.Plane getPlane()
// {
// switch (this)
// {
// case X:
// case Z:
// return LodDirection.Plane.HORIZONTAL;
// case Y:
// return LodDirection.Plane.VERTICAL;
// default:
// throw new Error("Someone's been tampering with the universe!");
// }
// }
public abstract int choose(int p_196052_1_, int p_196052_2_, int p_196052_3_);
public abstract double choose(double p_196051_1_, double p_196051_3_, double p_196051_5_);
} }
/**
* POSITIVE <br>
* NEGATIVE <br>
*/
public enum AxisDirection public enum AxisDirection
{ {
POSITIVE(1, "Towards positive"), POSITIVE(1, "Towards positive"),
NEGATIVE(-1, "Towards negative"); NEGATIVE(-1, "Towards negative");
private final int step; public final int step;
private final String name; public final String name;
//=============//
// constructor //
//=============//
AxisDirection(int newStep, String newName) AxisDirection(int newStep, String newName)
{ {
@@ -516,77 +185,20 @@ public enum EDhDirection
this.name = newName; this.name = newName;
} }
public int getStep()
{
return this.step;
}
@Override
public String toString() //=========//
{ // methods //
return this.name; //=========//
}
public EDhDirection.AxisDirection opposite() public EDhDirection.AxisDirection opposite()
{ { return (this == POSITIVE) ? NEGATIVE : POSITIVE; }
return this == POSITIVE ? NEGATIVE : POSITIVE;
}
}
// public static enum Plane implements Iterable<LodDirection>, Predicate<LodDirection> @Override
// { public String toString() { return this.name; }
// HORIZONTAL(new LodDirection[] { LodDirection.NORTH, LodDirection.EAST, LodDirection.SOUTH, LodDirection.WEST }, new LodDirection.Axis[] { LodDirection.Axis.X, LodDirection.Axis.Z }),
// VERTICAL(new LodDirection[] { LodDirection.UP, LodDirection.DOWN }, new LodDirection.Axis[] { LodDirection.Axis.Y });
//
// private final LodDirection[] faces;
// private final LodDirection.Axis[] axis;
//
// private Plane(LodDirection[] p_i49393_3_, LodDirection.Axis[] p_i49393_4_)
// {
// this.faces = p_i49393_3_;
// this.axis = p_i49393_4_;
// }
//
// public LodDirection getRandomDirection(Random p_179518_1_)
// {
// return Util.getRandom(this.faces, p_179518_1_);
// }
//
// public LodDirection.Axis getRandomAxis(Random p_244803_1_)
// {
// return Util.getRandom(this.axis, p_244803_1_);
// }
//
// @Override
// public boolean test(@Nullable LodDirection p_test_1_)
// {
// return p_test_1_ != null && p_test_1_.getAxis().getPlane() == this;
// }
//
// @Override
// public Iterator<LodDirection> iterator()
// {
// return Iterators.forArray(this.faces);
// }
//
// public Stream<LodDirection> stream()
// {
// return Arrays.stream(this.faces);
// }
// }
public String getSerializedName()
{
return this.name;
}
@Override
public String toString()
{
return this.name;
} }
} }
@@ -1,369 +0,0 @@
package com.seibel.distanthorizons.core.file;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.threading.PositionalLockProvider;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
// TODO is there a reason this is separate from FullDataSourceProviderV2?
// We shouldn't need multiple data source handlers
public abstract class AbstractDataSourceHandler
<TDataSource extends IDataSource<TDhLevel>,
TDTO extends IBaseDTO<Long>,
TRepo extends AbstractDhRepo<Long, TDTO>,
TDhLevel extends IDhLevel>
implements AutoCloseable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final Set<String> CORRUPT_DATA_ERRORS_LOGGED = Collections.newSetFromMap(new ConcurrentHashMap<>());
/**
* The highest numerical detail level possible.
* Used when determining which positions to update.
*
* @see AbstractDataSourceHandler#MIN_SECTION_DETAIL_LEVEL
*/
public static final byte TOP_SECTION_DETAIL_LEVEL = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + LodUtil.REGION_DETAIL_LEVEL;
/**
* The lowest numerical detail level possible.
*
* @see AbstractDataSourceHandler#TOP_SECTION_DETAIL_LEVEL
*/
public static final byte MIN_SECTION_DETAIL_LEVEL = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
protected final PositionalLockProvider updateLockProvider = new PositionalLockProvider();
/**
* generally just used for debugging,
* keeps track of which positions are currently locked.
*/
public final Set<Long> lockedPosSet = ConcurrentHashMap.newKeySet();
public final ConcurrentHashMap<Long, AtomicInteger> queuedUpdateCountsByPos = new ConcurrentHashMap<>();
protected final ReentrantLock closeLock = new ReentrantLock();
protected volatile boolean isShutdown = false;
protected final TDhLevel level;
protected final File saveDir;
public final TRepo repo;
public final ArrayList<IDataSourceUpdateFunc<TDataSource>> dateSourceUpdateListeners = new ArrayList<>();
//=============//
// constructor //
//=============//
public AbstractDataSourceHandler(TDhLevel level, ISaveStructure saveStructure) { this(level, saveStructure, null); }
public AbstractDataSourceHandler(TDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride)
{
this.level = level;
this.saveDir = (saveDirOverride == null) ? saveStructure.getSaveFolder(level.getLevelWrapper()) : saveDirOverride;
this.repo = this.createRepo();
}
//==================//
// abstract methods //
//==================//
/** When this is called the parent folders should be created */
protected abstract TRepo createRepo();
protected abstract TDataSource createDataSourceFromDto(TDTO dto) throws InterruptedException, IOException, DataCorruptedException;
protected abstract TDTO createDtoFromDataSource(TDataSource dataSource);
protected abstract TDataSource makeEmptyDataSource(long pos);
//==============//
// data reading //
//==============//
/**
* Returns the {@link TDataSource} for the given section position. <Br>
* The returned data source may be null if repo is in the process of shutting down. <Br> <Br>
*
* This call is concurrent. I.e. it supports being called by multiple threads at the same time.
*/
public CompletableFuture<TDataSource> getAsync(long pos)
{
AbstractExecutorService executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor == null || executor.isTerminated())
{
return CompletableFuture.completedFuture(null);
}
try
{
return CompletableFuture.supplyAsync(() -> this.get(pos), executor);
}
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
return CompletableFuture.completedFuture(null);
}
}
/**
* Should only be used in internal file handler methods where we are already running on a file handler thread.
* Can return null if the repo is in the process of being shut down
* @see AbstractDataSourceHandler#getAsync(long)
*/
@Nullable
public TDataSource get(long pos)
{
TDataSource dataSource = null;
try(TDTO dto = this.repo.getByKey(pos))
{
if (dto != null)
{
try
{
// load from database
dataSource = this.createDataSourceFromDto(dto);
}
catch (DataCorruptedException e)
{
// there's a rare issue where the exception doesn't
// have a message, which can cause problems
String message = (e.getMessage() == null) ? e.getMessage() : "No Error message for exception ["+e.getClass().getSimpleName()+"]";
// Only log each message type once.
// This is done to prevent logging "No compression mode with the value [2]" 10,000 times
// if the user is migrating from a nightly build and used ZStd.
if (CORRUPT_DATA_ERRORS_LOGGED.add(message))
{
LOGGER.warn("Corrupted data found at pos [" + DhSectionPos.toString(pos) + "]. Data at position will be deleted so it can be re-generated to prevent issues. Future errors with this same message won't be logged. Error: [" + message + "].", e);
}
this.repo.deleteWithKey(pos);
}
}
else
{
dataSource = this.makeEmptyDataSource(pos);
}
}
catch (InterruptedException ignore) { }
catch (IOException e)
{
LOGGER.warn("File read Error for pos ["+ DhSectionPos.toString(pos)+"], error: "+e.getMessage(), e);
}
return dataSource;
}
//===============//
// data updating //
//===============//
/**
* Can be used if the same thread is already handling IO and/or LOD generation.
* Otherwise the async version {@link AbstractDataSourceHandler#updateDataSourceAsync(FullDataSourceV2)} may be a better choice.
*/
public void updateDataSource(@NotNull FullDataSourceV2 inputDataSource)
{ this.updateDataSourceAtPos(inputDataSource.getPos(), inputDataSource, true); }
/**
* Can be used if you don't want to lock the current thread
* Otherwise the sync version {@link AbstractDataSourceHandler#updateDataSource(FullDataSourceV2)} may be a better choice.
*/
public CompletableFuture<Void> updateDataSourceAsync(@NotNull FullDataSourceV2 inputDataSource)
{
AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor == null || executor.isTerminated())
{
return CompletableFuture.completedFuture(null);
}
try
{
this.markUpdateStart(inputDataSource.getPos());
return CompletableFuture.runAsync(() ->
{
try
{
this.updateDataSourceAtPos(inputDataSource.getPos(), inputDataSource, true);
}
catch (Exception e)
{
LOGGER.error("Unexpected error in async data source update at pos: ["+DhSectionPos.toString(inputDataSource.getPos())+"], error: ["+e.getMessage()+"].", e);
}
finally
{
this.markUpdateEnd(inputDataSource.getPos());
}
}, executor);
}
catch (RejectedExecutionException ignore)
{
// can happen if the executor was shutdown while this task was queued
this.markUpdateEnd(inputDataSource.getPos());
return CompletableFuture.completedFuture(null);
}
}
/**
* After this method returns the inputData will be written to file.
*
* @param updatePos the position to update
*/
protected void updateDataSourceAtPos(long updatePos, @NotNull FullDataSourceV2 inputData, boolean lockOnUpdatePos)
{
boolean methodLocked = false;
// a lock is necessary to prevent two threads from writing to the same position at once,
// if that happens only the second update will apply and the LOD will end up with hole(s)
ReentrantLock updateLock = this.updateLockProvider.getLock(updatePos);
try
{
if (lockOnUpdatePos)
{
methodLocked = true;
updateLock.lock();
this.lockedPosSet.add(updatePos);
}
// get or create the data source
try (TDataSource recipientDataSource = this.get(updatePos))
{
if (recipientDataSource != null)
{
boolean dataModified = recipientDataSource.update(inputData, this.level);
if (dataModified)
{
// save the updated data to the database
try (TDTO dto = this.createDtoFromDataSource(recipientDataSource))
{
this.repo.save(dto);
}
for (IDataSourceUpdateFunc<TDataSource> listener : this.dateSourceUpdateListeners)
{
if (listener != null)
{
listener.OnDataSourceUpdated(recipientDataSource);
}
}
}
}
}
}
catch (Exception e)
{
LOGGER.error("Error updating pos ["+DhSectionPos.toString(updatePos)+"], error: "+e.getMessage(), e);
}
finally
{
if (methodLocked)
{
updateLock.unlock();
this.lockedPosSet.remove(updatePos);
}
}
}
//================//
// helper methods //
//================//
/** used for debugging to track which positions are queued for updating */
private void markUpdateStart(long dataSourcePos)
{
this.queuedUpdateCountsByPos.compute(dataSourcePos, (pos, atomicCount) ->
{
if (atomicCount == null)
{
atomicCount = new AtomicInteger(0);
}
atomicCount.incrementAndGet();
return atomicCount;
});
}
/** used for debugging to track which positions are queued for updating */
private void markUpdateEnd(long dataSourcePos)
{
this.queuedUpdateCountsByPos.compute(dataSourcePos, (pos, atomicCount) ->
{
if (atomicCount != null && atomicCount.decrementAndGet() <= 0)
{
atomicCount = null;
}
return atomicCount;
});
}
//=========//
// cleanup //
//=========//
@Override
public void close()
{
try
{
this.closeLock.lock();
this.isShutdown = true;
// wait a moment so any queued saves can finish queuing,
// otherwise we might not see everything that needs saving and attempt to use a closed repo
Thread.sleep(200);
LOGGER.info("Closing [" + this.getClass().getSimpleName() + "] for level: [" + this.level + "].");
this.repo.close();
}
catch (InterruptedException ignore) { }
finally
{
this.closeLock.unlock();
}
}
//================//
// helper classes //
//================//
@FunctionalInterface
public interface IDataSourceUpdateFunc<TDataSource>
{
void OnDataSourceUpdated(TDataSource updatedFullDataSource);
}
}
@@ -1,36 +0,0 @@
package com.seibel.distanthorizons.core.file;
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
/**
* Base for all data sources. <br><br>
*
* AutoCloseable Can be implemented to allow for disposing of pooled data sources. <br><br>
*
* @param <TDhLevel> there are times when we need specifically a client level vs a more generic level
*/
public interface IDataSource<TDhLevel extends IDhLevel> extends IBaseDTO<Long>, AutoCloseable
{
long getPos();
/** @return true if the data was changed */
boolean update(FullDataSourceV2 chunkData, TDhLevel level);
//===========//
// meta data //
//===========//
/**
* Returns the detail level of the data contained by this data source.
* IE: 0 for block, 1 for 2x2 blocks, etc.
*
* @see EDhApiDetailLevel
*/
byte getDataDetailLevel();
}
@@ -5,7 +5,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.KeyedLockContainer; import com.seibel.distanthorizons.core.util.KeyedLockContainer;
import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.ThreadUtil;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@@ -21,7 +21,7 @@ import java.util.concurrent.locks.ReentrantLock;
*/ */
public class DelayedFullDataSourceSaveCache implements AutoCloseable public class DelayedFullDataSourceSaveCache implements AutoCloseable
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** /**
* a cache won't automatically clean itself unless we trigger it's clean method * a cache won't automatically clean itself unless we trigger it's clean method
@@ -104,7 +104,7 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
} }
// write the new data into memory // write the new data into memory
memoryDataSource.update(inputDataSource); memoryDataSource.updateFromChunk(inputDataSource);
// keep track of when the last time we saved something was // keep track of when the last time we saved something was
pair.updateLastWrittenTimestamp(); pair.updateLastWrittenTimestamp();
} }
@@ -1,813 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.distanthorizons.core.file.fullDatafile;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.text.NumberFormat;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
/**
* Handles reading/writing {@link FullDataSourceV2}
* to and from the database.
*/
public class FullDataSourceProviderV2
extends AbstractDataSourceHandler<FullDataSourceV2, FullDataSourceV2DTO, FullDataSourceV2Repo, IDhLevel>
implements IDebugRenderable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
protected static final int NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD = 5;
/** how many parent update tasks can be in the queue at once */
protected static int getMaxUpdateTaskCount() { return NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD* Config.Common.MultiThreading.numberOfThreads.get(); }
/** indicates how long the update queue thread should wait between queuing ticks */
protected static final int UPDATE_QUEUE_THREAD_DELAY_IN_MS = 250;
/** how many data sources should be pulled down for migration at once */
private static final int MIGRATION_BATCH_COUNT = NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD;
/**
* 5 minutes <br>
* This should be much longer than any update should take. This is just
* to make sure the thread doesn't get stuck.
*/
private static final int MIGRATION_MAX_UPDATE_TIMEOUT_IN_MS = 5 * 60 * 1_000;
/**
* Interrupting the migration thread pool doesn't work well and may corrupt the database
* vs gracefully shutting down the thread ourselves.
*/
protected final AtomicBoolean migrationThreadRunning = new AtomicBoolean(true);
protected final FullDataSourceProviderV1<IDhLevel> legacyFileHandler;
protected boolean migrationStartMessageQueued = false;
protected long legacyDeletionCount = -1;
protected long migrationCount = -1;
protected boolean migrationStoppedWithError = false;
/**
* Tracks which positions are currently being updated
* to prevent duplicate concurrent updates.
*/
public final Set<Long> updatingPosSet = ConcurrentHashMap.newKeySet();
// TODO only run thread if modifications happened recently
/**
* This isn't in {@link AbstractDataSourceHandler} since we only want to update
* the newest version of the full data, so if we have providers for either
* render data or old full data, we don't want to update them. <br><br>
*
* Will be null on the dedicated server since updates don't need to be propagated,
* only the highest detail level is needed.
*/
@Nullable
private final ThreadPoolExecutor updateQueueProcessor;
//=============//
// constructor //
//=============//
public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure) { this(level, saveStructure, null); }
public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride)
{
super(level, saveStructure, saveDirOverride);
this.legacyFileHandler = new FullDataSourceProviderV1<>(level, saveStructure, saveDirOverride);
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
String levelId = level.getLevelWrapper().getDhIdentifier();
// start migrating any legacy data sources present in the background
ThreadPoolExecutor executor = ThreadPoolUtil.getFullDataMigrationExecutor();
if (executor != null)
{
executor.execute(this::convertLegacyDataSources);
}
else
{
// shouldn't happen, but just in case
LOGGER.error("Unable to start migration for level: ["+levelId+"] due to missing executor.");
}
// update propagation doesn't need to be run on the server since only the highest detail level is needed
this.updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Parent Update Queue [" + levelId + "]");
this.updateQueueProcessor.execute(this::runUpdateQueue);
}
//====================//
// Abstract overrides //
//====================//
@Override
protected FullDataSourceV2Repo createRepo()
{
try
{
return new FullDataSourceV2Repo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, new File(this.saveDir.getPath() + File.separator + ISaveStructure.DATABASE_NAME));
}
catch (SQLException e)
{
// should only happen if there is an issue with the database (it's locked or the folder path is missing)
// or the database update failed
throw new RuntimeException(e);
}
}
@Override
protected FullDataSourceV2DTO createDtoFromDataSource(FullDataSourceV2 dataSource)
{
try
{
// when creating new data use the compressor currently selected in the config
EDhApiDataCompressionMode compressionModeEnum = Config.Common.LodBuilding.dataCompression.get();
return FullDataSourceV2DTO.CreateFromDataSource(dataSource, compressionModeEnum);
}
catch (IOException e)
{
LOGGER.warn("Unable to create DTO, error: "+e.getMessage(), e);
return null;
}
}
@Override
protected FullDataSourceV2 createDataSourceFromDto(FullDataSourceV2DTO dto) throws InterruptedException, IOException, DataCorruptedException
{ return dto.createDataSource(this.level.getLevelWrapper()); }
@Override
protected FullDataSourceV2 makeEmptyDataSource(long pos)
{ return FullDataSourceV2.createEmpty(pos); }
//================//
// parent updates //
//================//
private void runUpdateQueue()
{
while (!Thread.interrupted())
{
try
{
Thread.sleep(UPDATE_QUEUE_THREAD_DELAY_IN_MS);
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getUpdatePropagatorExecutor();
if (executor == null || executor.isTerminated())
{
continue;
}
// TODO it might be worth skipping this logic if no parent updates happened
// update positions closest to the player (if not on a server)
// to make world gen appear faster
DhBlockPos targetBlockPos = DhBlockPos.ZERO;
if (MC_CLIENT != null && MC_CLIENT.playerExists())
{
targetBlockPos = MC_CLIENT.getPlayerBlockPos();
}
this.runParentUpdates(executor, targetBlockPos);
if (Config.Common.LodBuilding.Experimental.upsampleLowerDetailLodsToFillHoles.get())
{
this.runChildUpdates(executor, targetBlockPos);
}
}
catch (InterruptedException ignored)
{
Thread.currentThread().interrupt();
}
catch (Exception e)
{
LOGGER.error("Unexpected error in the parent update queue thread. Error: " + e.getMessage(), e);
}
}
LOGGER.info("Update thread ["+Thread.currentThread().getName()+"] terminated.");
}
/** will always apply updates */
private void runParentUpdates(PriorityTaskPicker.Executor executor, DhBlockPos targetBlockPos)
{
int maxUpdateTaskCount = getMaxUpdateTaskCount();
// queue parent updates
if (executor.getQueueSize() < maxUpdateTaskCount
&& this.updatingPosSet.size() < maxUpdateTaskCount)
{
// get the positions that need to be applied to their parents
LongArrayList parentUpdatePosList = this.repo.getPositionsToUpdate(targetBlockPos.getX(), targetBlockPos.getZ(), maxUpdateTaskCount);
// combine updates together based on their parent
HashMap<Long, HashSet<Long>> updatePosByParentPos = new HashMap<>();
for (Long pos : parentUpdatePosList)
{
updatePosByParentPos.compute(DhSectionPos.getParentPos(pos), (parentPos, updatePosSet) ->
{
if (updatePosSet == null)
{
updatePosSet = new HashSet<>();
}
updatePosSet.add(pos);
return updatePosSet;
});
}
// queue the updates
for (Long parentUpdatePos : updatePosByParentPos.keySet())
{
// stop if there are already a bunch of updates queued
if (this.updatingPosSet.size() > maxUpdateTaskCount
|| executor.getQueueSize() > maxUpdateTaskCount
|| !this.updatingPosSet.add(parentUpdatePos))
{
break;
}
try
{
executor.execute(() ->
{
ReentrantLock parentWriteLock = this.updateLockProvider.getLock(parentUpdatePos);
boolean parentLocked = false;
try
{
//LOGGER.info("updating parent: "+parentUpdatePos);
// Locking the parent before the children should prevent deadlocks.
// TryLock is used instead of lock so this thread can handle a different update.
if (parentWriteLock.tryLock())
{
parentLocked = true;
this.lockedPosSet.add(parentUpdatePos);
try (FullDataSourceV2 parentDataSource = this.get(parentUpdatePos))
{
// will return null if the file handler is shutting down
if (parentDataSource != null)
{
// apply each child pos to the parent
for (Long childPos : updatePosByParentPos.get(parentUpdatePos))
{
ReentrantLock childReadLock = this.updateLockProvider.getLock(childPos);
try
{
childReadLock.lock();
this.lockedPosSet.add(childPos);
try (FullDataSourceV2 childDataSource = this.get(childPos))
{
// can return null when the file handler is being shut down
if (childDataSource != null)
{
parentDataSource.update(childDataSource);
}
}
}
catch (Exception e)
{
LOGGER.error("Unexpected in parent update propagation for parent pos: ["+DhSectionPos.toString(parentUpdatePos)+"], child pos: [" + DhSectionPos.toString(parentUpdatePos) + "], Error: [" + e.getMessage() + "].", e);
}
finally
{
childReadLock.unlock();
this.lockedPosSet.remove(childPos);
}
}
if (DhSectionPos.getDetailLevel(parentUpdatePos) < TOP_SECTION_DETAIL_LEVEL)
{
parentDataSource.applyToParent = true;
}
this.updateDataSourceAtPos(parentUpdatePos, parentDataSource, false);
for (Long childPos : updatePosByParentPos.get(parentUpdatePos))
{
this.repo.setApplyToParent(childPos, false);
}
}
}
}
}
finally
{
if (parentLocked)
{
parentWriteLock.unlock();
this.lockedPosSet.remove(parentUpdatePos);
}
this.updatingPosSet.remove(parentUpdatePos);
}
});
}
catch (RejectedExecutionException ignore)
{ /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
catch (Exception e)
{
this.updatingPosSet.remove(parentUpdatePos);
throw e;
}
}
}
}
/** stops if it finds any LOD data */
private void runChildUpdates(PriorityTaskPicker.Executor executor, DhBlockPos targetBlockPos)
{
int maxUpdateTaskCount = getMaxUpdateTaskCount();
// queue child updates
if (executor.getQueueSize() < maxUpdateTaskCount
&& this.updatingPosSet.size() < maxUpdateTaskCount)
{
// get the positions that need to be applied to their children
LongArrayList childUpdatePosList = this.repo.getChildPositionsToUpdate(targetBlockPos.getX(), targetBlockPos.getZ(), maxUpdateTaskCount);
// queue the updates
for (long parentUpdatePos : childUpdatePosList)
{
// stop if there are already a bunch of updates queued
if (this.updatingPosSet.size() > maxUpdateTaskCount
|| executor.getQueueSize() > maxUpdateTaskCount)
{
break;
}
// skip already updating positions
if (!this.updatingPosSet.add(parentUpdatePos))
{
continue;
}
try
{
executor.execute(() ->
{
ReentrantLock parentReadLock = this.updateLockProvider.getLock(parentUpdatePos);
boolean parentLocked = false;
try
{
//LOGGER.info("updating parent: "+parentUpdatePos);
// Locking the parent before the children should prevent deadlocks.
// TryLock is used instead of lock so this thread can handle a different update.
if (parentReadLock.tryLock())
{
parentLocked = true;
this.lockedPosSet.add(parentUpdatePos);
try (FullDataSourceV2 parentDataSource = this.get(parentUpdatePos))
{
// will return null if the file handler is shutting down
if (parentDataSource != null)
{
// apply parent to each child
for (int i = 0; i < 4; i++)
{
long childPos = DhSectionPos.getChildByIndex(parentUpdatePos, i);
ReentrantLock childWriteLock = this.updateLockProvider.getLock(childPos);
try
{
childWriteLock.lock();
this.lockedPosSet.add(childPos);
try (FullDataSourceV2 childDataSource = this.get(childPos))
{
// will return null if the file handler is shutting down
if (childDataSource != null)
{
childDataSource.update(parentDataSource);
// don't propagate child updates past the bottom of the tree
if (DhSectionPos.getDetailLevel(childPos) != DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL)
{
childDataSource.applyToChildren = true;
}
this.updateDataSourceAtPos(childPos, childDataSource, false);
}
}
}
catch (Exception e)
{
LOGGER.error("Unexpected in child update propagation for parent pos: ["+DhSectionPos.toString(parentUpdatePos)+"], child pos: [" + DhSectionPos.toString(parentUpdatePos) + "], Error: [" + e.getMessage() + "].", e);
}
finally
{
childWriteLock.unlock();
this.lockedPosSet.remove(childPos);
}
}
this.repo.setApplyToChild(parentUpdatePos, false);
}
}
}
}
finally
{
if (parentLocked)
{
parentReadLock.unlock();
this.lockedPosSet.remove(parentUpdatePos);
}
this.updatingPosSet.remove(parentUpdatePos);
}
});
}
catch (RejectedExecutionException ignore)
{ /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
catch (Exception e)
{
this.updatingPosSet.remove(parentUpdatePos);
throw e;
}
}
}
}
//=======================//
// data source migration //
//=======================//
private void convertLegacyDataSources()
{
try
{
String levelId = this.level.getLevelWrapper().getDhIdentifier();
LOGGER.info("Attempting to migrate data sources for: [" + levelId + "]-[" + this.saveDir + "]...");
this.migrationThreadRunning.set(true);
//============================//
// delete unused data sources //
//============================//
// this could be done all at once via SQL,
// but doing it in chunks prevents locking the database for long periods of time
long unusedCount = 0;
long totalDeleteCount = this.legacyFileHandler.repo.getUnusedDataSourceCount();
if (totalDeleteCount != 0)
{
// this should only be shown once per session but should be shown during
// either when the deletion or migration phases start
this.showMigrationStartMessage();
LOGGER.info("deleting [" + levelId + "] - [" + totalDeleteCount + "] unused data sources...");
this.legacyDeletionCount = totalDeleteCount;
ArrayList<String> unusedDataPosList = this.legacyFileHandler.repo.getUnusedDataSourcePositionStringList(50);
while (unusedDataPosList.size() != 0)
{
unusedCount += unusedDataPosList.size();
this.legacyDeletionCount -= unusedDataPosList.size();
long startTime = System.currentTimeMillis();
// delete batch and get next batch
this.legacyFileHandler.repo.deleteUnusedLegacyData(unusedDataPosList);
unusedDataPosList = this.legacyFileHandler.repo.getUnusedDataSourcePositionStringList(50);
long endStart = System.currentTimeMillis();
long deleteTime = endStart - startTime;
LOGGER.info("Deleting [" + levelId + "] - [" + unusedCount + "/" + totalDeleteCount + "] in [" + deleteTime + "]ms ...");
// a slight delay is added to prevent accidentally locking the database when deleting a lot of rows
// (that shouldn't be the case since we're using WAL journaling, but just in case)
try
{
// use the delete time so we don't make powerful computers wait super long
// and weak computers wait no time at all
Thread.sleep(deleteTime / 2);
}
catch (InterruptedException ignore)
{
}
}
LOGGER.info("Done deleting [" + levelId + "] - [" + totalDeleteCount + "] unused data sources.");
}
//===========//
// migration //
//===========//
long totalMigrationCount = this.legacyFileHandler.getDataSourceMigrationCount();
this.migrationCount = totalMigrationCount;
LOGGER.info("Found [" + totalMigrationCount + "] data sources that need migration.");
ArrayList<FullDataSourceV1> legacyDataSourceList = this.legacyFileHandler.getDataSourcesToMigrate(MIGRATION_BATCH_COUNT);
if (!legacyDataSourceList.isEmpty())
{
this.showMigrationStartMessage();
try
{
// keep going until every data source has been migrated
int progressCount = 0;
while (!legacyDataSourceList.isEmpty() && this.migrationThreadRunning.get())
{
NumberFormat numFormat = F3Screen.NUMBER_FORMAT;
LOGGER.info("Migrating [" + levelId + "] - [" + numFormat.format(progressCount) + "/" + numFormat.format(totalMigrationCount) + "]...");
ArrayList<CompletableFuture<Void>> updateFutureList = new ArrayList<>();
for (int i = 0; i < legacyDataSourceList.size() && this.migrationThreadRunning.get(); i++)
{
FullDataSourceV1 legacyDataSource = legacyDataSourceList.get(i);
try
{
// convert the legacy data source to the new format,
// this is a relatively cheap operation
FullDataSourceV2 newDataSource = FullDataSourceV2.createFromLegacyDataSourceV1(legacyDataSource);
newDataSource.applyToParent = true;
// the actual update process can be moderately expensive due to having to update
// the render data along with the full data, so running it async on the update threads gains us a good bit of speed
CompletableFuture<Void> future = this.updateDataSourceAsync(newDataSource);
updateFutureList.add(future);
future.thenRun(() ->
{
// after the update finishes the legacy data source can be safely deleted
this.legacyFileHandler.repo.deleteWithKey(legacyDataSource.getPos());
newDataSource.close();
});
}
catch (Exception e)
{
Long migrationPos = legacyDataSource.getPos();
LOGGER.warn("Unexpected issue migrating data source at pos [" + DhSectionPos.toString(migrationPos) + "]. Error: " + e.getMessage(), e);
this.legacyFileHandler.markMigrationFailed(migrationPos);
}
}
try
{
// wait for each thread to finish updating
CompletableFuture<Void> combinedFutures = CompletableFuture.allOf(updateFutureList.toArray(new CompletableFuture[0]));
combinedFutures.get(MIGRATION_MAX_UPDATE_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
}
catch (InterruptedException | TimeoutException e)
{
LOGGER.warn("Migration update timed out after [" + MIGRATION_MAX_UPDATE_TIMEOUT_IN_MS + "] milliseconds. Migration will re-try the same positions again in a moment.", e);
}
catch (ExecutionException e)
{
LOGGER.warn("Migration update failed. Migration will re-try the same positions again. Error:" + e.getMessage(), e);
}
legacyDataSourceList = this.legacyFileHandler.getDataSourcesToMigrate(MIGRATION_BATCH_COUNT);
progressCount += legacyDataSourceList.size();
this.migrationCount -= legacyDataSourceList.size();
}
}
catch (Exception e)
{
LOGGER.info("migration stopped due to error for: [" + levelId + "]-[" + this.saveDir + "], error: [" + e.getMessage() + "].", e);
this.showMigrationEndMessage(false);
this.migrationStoppedWithError = true;
}
finally
{
if (this.migrationThreadRunning.get())
{
LOGGER.info("migration complete for: [" + levelId + "]-[" + this.saveDir + "].");
this.showMigrationEndMessage(true);
this.migrationCount = 0;
}
else
{
LOGGER.info("migration stopped for: [" + levelId + "]-[" + this.saveDir + "].");
this.showMigrationEndMessage(false);
this.migrationStoppedWithError = true;
}
}
}
else
{
LOGGER.info("No migration necessary.");
}
}
finally
{
this.migrationThreadRunning.set(false);
}
}
public long getLegacyDeletionCount() { return this.legacyDeletionCount; }
public long getTotalMigrationCount() { return this.migrationCount; }
public boolean getMigrationStoppedWithError() { return this.migrationStoppedWithError; }
private void showMigrationStartMessage()
{
if (this.migrationStartMessageQueued)
{
return;
}
this.migrationStartMessageQueued = true;
String levelId = this.level.getLevelWrapper().getDhIdentifier();
ClientApi.INSTANCE.showChatMessageNextFrame(
"Old Distant Horizons data is being migrated for ["+levelId+"]. \n" +
"While migrating LODs may load slowly \n" +
"and DH world gen will be disabled. \n" +
"You can see migration progress in the F3 menu."
);
}
private void showMigrationEndMessage(boolean success)
{
String levelId = this.level.getLevelWrapper().getDhIdentifier();
if (success)
{
ClientApi.INSTANCE.showChatMessageNextFrame("Distant Horizons data migration for ["+levelId+"] completed.");
}
else
{
ClientApi.INSTANCE.showChatMessageNextFrame(
"Distant Horizons data migration for ["+levelId+"] stopped. \n" +
"Some data may not have been migrated."
);
}
}
//=======================//
// retrieval (world gen) //
//=======================//
/**
* Returns true if this provider can generate or retrieve
* {@link FullDataSourceV2}'s that aren't currently in the database.
*/
public boolean canRetrieveMissingDataSources()
{
// the base handler just handles basic reading/writing
// to the database and as such can't retrieve anything else.
return false;
}
/**
* Returns false if this provider isn't accepting new requests,
* this can be due to having a full queue or some other
* limiting factor. <br><br>
*
* Note: when overriding make sure to add: <br>
* <code>
* if (!super.canQueueRetrieval()) <br>
* { <br>
* return false; <br>
* } <br>
* </code>
* to the beginning of your override.
* Otherwise, parent retrieval limits will be ignored.
*/
public boolean canQueueRetrieval()
{
// Retrieval shouldn't happen while an unknown number of
// legacy data sources are present.
// If retrieval was allowed we might run into concurrency issues.
return !this.migrationThreadRunning.get();
}
/**
* @return null if this provider can't generate any positions and
* an empty array if all positions were generated
*/
@Nullable
public LongArrayList getPositionsToRetrieve(Long pos) { return null; }
/** @return true if the position was queued, false if not */
@Nullable
public CompletableFuture<WorldGenResult> queuePositionForRetrieval(Long genPos) { return null; }
/** does nothing if the given position isn't present in the queue */
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) { }
public void clearRetrievalQueue() { }
/** Can be used to display how many total retrieval requests might be available. */
public void setTotalRetrievalPositionCount(int newCount) { }
/** Can be used to display how many total chunk retrieval requests should be available. */
public void setEstimatedRemainingRetrievalChunkCount(int newCount) { }
public boolean fileExists(long pos) { return this.repo.getDataSizeInBytes(pos) > 0; }
//========================//
// multiplayer networking //
//========================//
@Nullable
public Long getTimestampForPos(long pos)
{ return this.repo.getTimestampForPos(pos); }
//===========//
// overrides //
//===========//
@Override
public void debugRender(DebugRenderer renderer)
{
this.lockedPosSet
.forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 74f, 0.15f, Color.PINK)); });
this.queuedUpdateCountsByPos
.forEach((pos, updateCountRef) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f + (updateCountRef.get() * 16f), 0.20f, Color.WHITE)); });
this.updatingPosSet
.forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f, 0.20f, Color.MAGENTA)); });
}
@Override
public void close()
{
super.close();
if (this.updateQueueProcessor != null)
{
this.updateQueueProcessor.shutdownNow();
}
this.legacyFileHandler.close();
this.migrationThreadRunning.set(false);
}
}
@@ -23,12 +23,15 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGeneratio
import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataUpdatePropagatorV2;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue; import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
@@ -40,7 +43,6 @@ import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -54,7 +56,7 @@ import java.util.stream.IntStream;
public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 implements IDebugRenderable public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 implements IDebugRenderable
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();;
/** /**
* Having this number too high causes the system to become overwhelmed by * Having this number too high causes the system to become overwhelmed by
@@ -79,7 +81,16 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
//=============// //=============//
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) { super(level, saveStructure); } public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) { super(level, saveStructure); }
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) { super(level, saveStructure, saveDirOverride); } public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride)
{
super(level, saveStructure, saveDirOverride);
this.addDataSourceUpdateListener((@NotNull FullDataSourceV2 updatedData) ->
{
this.onWorldGenTaskComplete(WorldGenResult.CreateSuccess(updatedData.getPos()), null);
});
}
@@ -176,7 +187,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
{ {
boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue); boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue);
LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!"); LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!");
LOGGER.info("Set world gen queue for level [" + this.level.getLevelWrapper().getDhIdentifier() + "]."); LOGGER.info("Set world gen queue for level [" + this.levelId + "].");
} }
@Override @Override
@@ -212,7 +223,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
PriorityTaskPicker.Executor renderLoadExecutor = ThreadPoolUtil.getRenderLoadingExecutor(); PriorityTaskPicker.Executor renderLoadExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
if (renderLoadExecutor == null if (renderLoadExecutor == null
|| renderLoadExecutor.getQueueSize() >= getMaxUpdateTaskCount() / 2) || renderLoadExecutor.getQueueSize() >= FullDataUpdatePropagatorV2.getMaxPropagateTaskCount() / 2)
{ {
// don't queue additional world gen requests if the render loader handler is overwhelmed, // don't queue additional world gen requests if the render loader handler is overwhelmed,
// otherwise LODs may not load in properly // otherwise LODs may not load in properly
@@ -221,7 +232,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
PriorityTaskPicker.Executor fileHandlerExecutor = ThreadPoolUtil.getFileHandlerExecutor(); PriorityTaskPicker.Executor fileHandlerExecutor = ThreadPoolUtil.getFileHandlerExecutor();
if (fileHandlerExecutor == null if (fileHandlerExecutor == null
|| fileHandlerExecutor.getQueueSize() >= getMaxUpdateTaskCount() / 2) || fileHandlerExecutor.getQueueSize() >= FullDataUpdatePropagatorV2.getMaxPropagateTaskCount() / 2)
{ {
// don't queue additional world gen requests if the file handler is overwhelmed, // don't queue additional world gen requests if the file handler is overwhelmed,
// otherwise LODs may not load in properly // otherwise LODs may not load in properly
@@ -293,17 +304,6 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
return worldGenFuture; return worldGenFuture;
} }
@Override
protected void updateDataSourceAtPos(long updatePos, @NotNull FullDataSourceV2 inputData, boolean lockOnUpdatePos)
{
super.updateDataSourceAtPos(updatePos, inputData, lockOnUpdatePos);
//if (SharedApi.getEnvironment() != EWorldEnvironment.CLIENT_ONLY)
// LOGGER.info("updated ["+DhSectionPos.toString(updatePos)+"]");
this.onWorldGenTaskComplete(WorldGenResult.CreateSuccess(updatePos), null);
}
@Override @Override
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf)
{ {
@@ -0,0 +1,7 @@
package com.seibel.distanthorizons.core.file.fullDatafile;
@FunctionalInterface
public interface IDataSourceUpdateListenerFunc<TDataSource>
{
void OnDataSourceUpdated(TDataSource updatedFullDataSource);
}
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.WorldGenModule; import com.seibel.distanthorizons.core.level.WorldGenModule;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue; import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
@@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit;
*/ */
public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvider public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvider
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@Nullable @Nullable
private final SyncOnLoadRequestQueue syncOnLoadRequestQueue; private final SyncOnLoadRequestQueue syncOnLoadRequestQueue;
@@ -1,4 +1,4 @@
package com.seibel.distanthorizons.core.file.fullDatafile; package com.seibel.distanthorizons.core.file.fullDatafile.V1;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
@@ -12,7 +12,7 @@ import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
@@ -27,7 +27,7 @@ import java.util.concurrent.locks.ReentrantLock;
public class FullDataSourceProviderV1<TDhLevel extends IDhLevel> public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
implements AutoCloseable implements AutoCloseable
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
protected final ReentrantLock closeLock = new ReentrantLock(); protected final ReentrantLock closeLock = new ReentrantLock();
protected volatile boolean isShutdown = false; protected volatile boolean isShutdown = false;
@@ -43,10 +43,10 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
// constructor // // constructor //
//=============// //=============//
public FullDataSourceProviderV1(TDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) public FullDataSourceProviderV1(TDhLevel level, File saveDir)
{ {
this.level = level; this.level = level;
this.saveDir = (saveDirOverride == null) ? saveStructure.getSaveFolder(level.getLevelWrapper()) : saveDirOverride; this.saveDir = saveDir;
if (!this.saveDir.exists() && !this.saveDir.mkdirs()) if (!this.saveDir.exists() && !this.saveDir.mkdirs())
{ {
LOGGER.warn("Unable to create full data folder, file saving may fail."); LOGGER.warn("Unable to create full data folder, file saving may fail.");
@@ -0,0 +1,346 @@
package com.seibel.distanthorizons.core.file.fullDatafile.V2;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.V1.FullDataSourceProviderV1;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import java.io.File;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class DataMigratorV1 implements IDebugRenderable, AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** how many data sources should be pulled down for migration at once */
private static final int MIGRATION_BATCH_COUNT = FullDataUpdatePropagatorV2.NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD;
/**
* 5 minutes <br>
* This should be much longer than any update should take. This is just
* to make sure the thread doesn't get stuck.
*/
private static final int MIGRATION_MAX_UPDATE_TIMEOUT_IN_MS = 5 * 60 * 1_000;
private final FullDataUpdaterV2 dataUpdater;
private boolean migrationStartMessageQueued = false;
private long legacyDeletionCount = -1;
private long migrationCount = -1;
private boolean migrationStoppedWithError = false;
/**
* Interrupting the migration thread pool doesn't work well and may corrupt the database
* vs gracefully shutting down the thread ourselves.
*/
public final AtomicBoolean migrationThreadRunning = new AtomicBoolean(true);
private final FullDataSourceProviderV1<IDhLevel> v1DataSourceProvider;
private final String levelId;
private final File saveDir;
//=============//
// constructor //
//=============//
public DataMigratorV1(
FullDataUpdaterV2 dataUpdater,
IDhLevel level, String levelId, File saveDir)
{
this.dataUpdater = dataUpdater;
this.saveDir = saveDir;
this.v1DataSourceProvider = new FullDataSourceProviderV1<>(level, saveDir);
this.levelId = levelId;
// start migrating any legacy data sources present in the background
ThreadPoolExecutor executor = ThreadPoolUtil.getFullDataMigrationExecutor();
if (executor != null)
{
executor.execute(this::convertLegacyDataSources);
}
else
{
// shouldn't happen, but just in case
LOGGER.error("Unable to start migration for level: ["+this.levelId+"] due to missing executor.");
}
}
//=======================//
// data source migration //
//=======================//
private void convertLegacyDataSources()
{
try
{
LOGGER.debug("Attempting to migrate data sources for: [" + this.levelId + "]-[" + this.saveDir + "]...");
this.migrationThreadRunning.set(true);
//============================//
// delete unused data sources //
//============================//
// this could be done all at once via SQL,
// but doing it in chunks prevents locking the database for long periods of time
long unusedCount = 0;
long totalDeleteCount = this.v1DataSourceProvider.repo.getUnusedDataSourceCount();
if (totalDeleteCount != 0)
{
// this should only be shown once per session but should be shown during
// either when the deletion or migration phases start
this.showMigrationStartMessage();
LOGGER.info("deleting [" + this.levelId + "] - [" + totalDeleteCount + "] unused data sources...");
this.legacyDeletionCount = totalDeleteCount;
ArrayList<String> unusedDataPosList = this.v1DataSourceProvider.repo.getUnusedDataSourcePositionStringList(50);
while (unusedDataPosList.size() != 0)
{
unusedCount += unusedDataPosList.size();
this.legacyDeletionCount -= unusedDataPosList.size();
long startTime = System.currentTimeMillis();
// delete batch and get next batch
this.v1DataSourceProvider.repo.deleteUnusedLegacyData(unusedDataPosList);
unusedDataPosList = this.v1DataSourceProvider.repo.getUnusedDataSourcePositionStringList(50);
long endStart = System.currentTimeMillis();
long deleteTime = endStart - startTime;
LOGGER.info("Deleting [" + this.levelId + "] - [" + unusedCount + "/" + totalDeleteCount + "] in [" + deleteTime + "]ms ...");
// a slight delay is added to prevent accidentally locking the database when deleting a lot of rows
// (that shouldn't be the case since we're using WAL journaling, but just in case)
try
{
// use the delete time so we don't make powerful computers wait super long
// and weak computers wait no time at all
Thread.sleep(deleteTime / 2);
}
catch (InterruptedException ignore)
{
}
}
LOGGER.info("Done deleting [" + this.levelId + "] - [" + totalDeleteCount + "] unused data sources.");
}
//===========//
// migration //
//===========//
long totalMigrationCount = this.v1DataSourceProvider.getDataSourceMigrationCount();
this.migrationCount = totalMigrationCount;
LOGGER.debug("Found [" + totalMigrationCount + "] data sources that need migration.");
ArrayList<FullDataSourceV1> legacyDataSourceList = this.v1DataSourceProvider.getDataSourcesToMigrate(MIGRATION_BATCH_COUNT);
if (!legacyDataSourceList.isEmpty())
{
this.showMigrationStartMessage();
try
{
// keep going until every data source has been migrated
int progressCount = 0;
while (!legacyDataSourceList.isEmpty() && this.migrationThreadRunning.get())
{
NumberFormat numFormat = F3Screen.NUMBER_FORMAT;
LOGGER.info("Migrating [" + this.levelId + "] - [" + numFormat.format(progressCount) + "/" + numFormat.format(totalMigrationCount) + "]...");
ArrayList<CompletableFuture<Void>> updateFutureList = new ArrayList<>();
for (int i = 0; i < legacyDataSourceList.size() && this.migrationThreadRunning.get(); i++)
{
FullDataSourceV1 legacyDataSource = legacyDataSourceList.get(i);
try
{
// convert the legacy data source to the new format,
// this is a relatively cheap operation
FullDataSourceV2 newDataSource = FullDataSourceV2.createFromLegacyDataSourceV1(legacyDataSource);
newDataSource.applyToParent = true;
// the actual update process can be moderately expensive due to having to update
// the render data along with the full data, so running it async on the update threads gains us a good bit of speed
CompletableFuture<Void> future = this.dataUpdater.updateDataSourceAsync(newDataSource);
updateFutureList.add(future);
future.thenRun(() ->
{
// after the update finishes the legacy data source can be safely deleted
this.v1DataSourceProvider.repo.deleteWithKey(legacyDataSource.getPos());
newDataSource.close();
});
}
catch (Exception e)
{
long migrationPos = legacyDataSource.getPos();
LOGGER.warn("Unexpected issue migrating data source at pos [" + DhSectionPos.toString(migrationPos) + "]. Error: " + e.getMessage(), e);
this.v1DataSourceProvider.markMigrationFailed(migrationPos);
}
}
try
{
// wait for each thread to finish updating
CompletableFuture<Void> combinedFutures = CompletableFuture.allOf(updateFutureList.toArray(new CompletableFuture[0]));
combinedFutures.get(MIGRATION_MAX_UPDATE_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
}
catch (InterruptedException | TimeoutException e)
{
LOGGER.warn("Migration update timed out after [" + MIGRATION_MAX_UPDATE_TIMEOUT_IN_MS + "] milliseconds. Migration will re-try the same positions again in a moment.", e);
}
catch (ExecutionException e)
{
LOGGER.warn("Migration update failed. Migration will re-try the same positions again. Error:" + e.getMessage(), e);
}
legacyDataSourceList = this.v1DataSourceProvider.getDataSourcesToMigrate(MIGRATION_BATCH_COUNT);
progressCount += legacyDataSourceList.size();
this.migrationCount -= legacyDataSourceList.size();
}
}
catch (Exception e)
{
LOGGER.info("migration stopped due to error for: [" + this.levelId + "]-[" + this.saveDir + "], error: [" + e.getMessage() + "].", e);
this.showMigrationEndMessage(false);
this.migrationStoppedWithError = true;
}
finally
{
if (this.migrationThreadRunning.get())
{
LOGGER.info("migration complete for: [" + this.levelId + "]-[" + this.saveDir + "].");
this.showMigrationEndMessage(true);
this.migrationCount = 0;
}
else
{
LOGGER.info("migration stopped for: [" + this.levelId + "]-[" + this.saveDir + "].");
this.showMigrationEndMessage(false);
this.migrationStoppedWithError = true;
}
}
}
else
{
LOGGER.info("No migration necessary.");
}
}
finally
{
this.migrationThreadRunning.set(false);
}
}
private void showMigrationStartMessage()
{
if (this.migrationStartMessageQueued)
{
return;
}
this.migrationStartMessageQueued = true;
ClientApi.INSTANCE.showChatMessageNextFrame(
"Old Distant Horizons data is being migrated for ["+this.levelId+"]. \n" +
"While migrating LODs may load slowly \n" +
"and DH world gen will be disabled. \n" +
"You can see migration progress in the F3 menu."
);
}
private void showMigrationEndMessage(boolean success)
{
if (success)
{
ClientApi.INSTANCE.showChatMessageNextFrame("Distant Horizons data migration for ["+this.levelId+"] completed.");
}
else
{
ClientApi.INSTANCE.showChatMessageNextFrame(
"Distant Horizons data migration for ["+this.levelId+"] stopped. \n" +
"Some data may not have been migrated."
);
}
}
//===========//
// debugging //
//===========//
public void addDebugMenuStringsToList(List<String> messageList)
{
// migration
boolean migrationErrored = this.migrationStoppedWithError;
if (!migrationErrored)
{
long legacyDeletionCount = this.legacyDeletionCount;
if (legacyDeletionCount > 0)
{
messageList.add(" Migrating - Deleting #: " + F3Screen.NUMBER_FORMAT.format(legacyDeletionCount));
}
long migrationCount = this.migrationCount;
if (migrationCount > 0)
{
messageList.add(" Migrating - Conversion #: " + F3Screen.NUMBER_FORMAT.format(migrationCount));
}
}
else
{
messageList.add(" Migration Failed");
}
}
//===========//
// overrides //
//===========//
@Override
public void debugRender(DebugRenderer renderer)
{
// nothing currently needed
}
@Override
public void close()
{
//LOGGER.info("Closing [" + this.getClass().getSimpleName() + "] for level: [" + this.levelId + "].");
}
}
@@ -0,0 +1,452 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.distanthorizons.core.file.fullDatafile.V2;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
/**
* Handles reading/writing {@link FullDataSourceV2}
* to and from the database.
*/
public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final Set<String> CORRUPT_DATA_ERRORS_LOGGED = Collections.newSetFromMap(new ConcurrentHashMap<>());
/**
* The highest numerical detail level possible.
* Used when determining which positions to update.
*
* @see FullDataSourceProviderV2#LEAF_SECTION_DETAIL_LEVEL
*/
public static final byte ROOT_SECTION_DETAIL_LEVEL
= DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL
+ LodUtil.REGION_DETAIL_LEVEL;
/**
* The lowest numerical detail level possible.
*
* @see FullDataSourceProviderV2#ROOT_SECTION_DETAIL_LEVEL
*/
public static final byte LEAF_SECTION_DETAIL_LEVEL = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
public final FullDataSourceV2Repo repo;
protected final AtomicBoolean isShutdownRef = new AtomicBoolean(false);
protected final File saveDir;
protected final IDhLevel level;
protected final String levelId;
private final FullDataUpdaterV2 dataUpdater;
private final FullDataUpdatePropagatorV2 updatePropagator;
private final DataMigratorV1 dataMigratorV1;
//=============//
// constructor //
//=============//
public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure) { this(level, saveStructure, null); }
public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride)
{
this.saveDir = (saveDirOverride == null) ? saveStructure.getSaveFolder(level.getLevelWrapper()) : saveDirOverride;
this.repo = this.createRepo();
this.level = level;
this.levelId = this.level.getLevelWrapper().getDhIdentifier();
this.dataUpdater = new FullDataUpdaterV2(this, this.levelId);
this.updatePropagator = new FullDataUpdatePropagatorV2(this, this.dataUpdater, this.levelId);
this.dataMigratorV1 = new DataMigratorV1(this.dataUpdater, this.level, this.levelId, this.saveDir);
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
}
private FullDataSourceV2Repo createRepo()
{
try
{
return new FullDataSourceV2Repo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, new File(this.saveDir.getPath() + File.separator + ISaveStructure.DATABASE_NAME));
}
catch (SQLException e)
{
// should only happen if there is an issue with the database (it's locked or the folder path is missing)
// or the database update failed
throw new RuntimeException(e);
}
}
//=================//
// event listeners //
//=================//
public void addDataSourceUpdateListener(IDataSourceUpdateListenerFunc<FullDataSourceV2> listener)
{
synchronized (this.dataUpdater)
{
this.dataUpdater.dateSourceUpdateListeners.add(listener);
}
}
public void removeDataSourceUpdateListener(IDataSourceUpdateListenerFunc<FullDataSourceV2> listener)
{
synchronized (this.dataUpdater)
{
this.dataUpdater.dateSourceUpdateListeners.add(listener);
}
}
//================//
// DTO converters //
//================//
protected FullDataSourceV2 createDataSourceFromDto(FullDataSourceV2DTO dto) throws InterruptedException, IOException, DataCorruptedException
{ return dto.createDataSource(this.level.getLevelWrapper(), null); }
protected FullDataSourceV2 createAdjDataSourceFromDto(FullDataSourceV2DTO dto, EDhDirection direction) throws InterruptedException, IOException, DataCorruptedException
{ return dto.createDataSource(this.level.getLevelWrapper(), direction); }
//=========================//
// basic DataSource getter //
//=========================//
/**
* Returns the {@link FullDataSourceV2} for the given section position. <Br>
* The returned data source may be null if repo is in the process of shutting down. <Br> <Br>
*
* This call is concurrent. I.e. it supports being called by multiple threads at the same time.
*/
public CompletableFuture<FullDataSourceV2> getAsync(long pos)
{
if (this.isShutdownRef.get())
{
return CompletableFuture.completedFuture(null);
}
AbstractExecutorService executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor == null || executor.isTerminated())
{
return CompletableFuture.completedFuture(null);
}
try
{
return CompletableFuture.supplyAsync(() -> this.get(pos), executor);
}
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
return CompletableFuture.completedFuture(null);
}
}
/**
* Should only be used in internal file handler methods where we are already running on a file handler thread.
* Can return null if the repo is in the process of being shut down
* @see FullDataSourceProviderV2#getAsync(long)
*/
@Nullable
public FullDataSourceV2 get(long pos)
{
if (this.isShutdownRef.get())
{
return null;
}
try(FullDataSourceV2DTO dto = this.repo.getByKey(pos))
{
if (dto == null)
{
return FullDataSourceV2.createEmpty(pos);
}
try
{
FullDataSourceV2 dataSource = this.createDataSourceFromDto(dto);
// automatically create and save adjacent data if missing
if (dto.dataFormatVersion == FullDataSourceV2DTO.DATA_FORMAT.V1_NO_ADJACENT_DATA)
{
EDhApiDataCompressionMode compressionMode = Config.Common.LodBuilding.dataCompression.get();
try(FullDataSourceV2DTO updatedDto = FullDataSourceV2DTO.CreateFromDataSource(dataSource, compressionMode))
{
this.repo.save(updatedDto);
}
}
return dataSource;
}
catch (DataCorruptedException e)
{
this.tryLogCorruptedDataError(DhSectionPos.toString(pos), e);
this.repo.deleteWithKey(pos);
}
}
catch (InterruptedException ignore) { }
catch (IOException e)
{
LOGGER.warn("File read Error for pos ["+DhSectionPos.toString(pos)+"], error: "+e.getMessage(), e);
}
// an error occurred
return null;
}
protected void tryLogCorruptedDataError(String whereClause, Exception e)
{
// there's a rare issue where the exception doesn't
// have a message, which can cause problems
String message = (e.getMessage() == null) ? e.getMessage() : "No Error message for exception ["+e.getClass().getSimpleName()+"]";
// Only log each message type once.
// This is done to prevent logging "No compression mode with the value [2]" 10,000 times
// if the user is migrating from a nightly build and used ZStd.
if (CORRUPT_DATA_ERRORS_LOGGED.add(message))
{
LOGGER.warn("Corrupted data found at [" + whereClause + "]. Data at will be deleted so it can be re-generated to prevent issues. Future errors with this same message won't be logged. Error: [" + message + "].", e);
}
}
//=================//
// partial getters //
//=================//
/**
* Only returns the data row/column for the given compass-cardinal
* direction. <br>
* This is generally used for generating LOD render data
* where we only need the adjacent data, not the full thing.
*/
public FullDataSourceV2 getAdjForDirection(long pos, EDhDirection direction)
{
if (this.isShutdownRef.get())
{
return null;
}
try(FullDataSourceV2DTO dto = this.repo.getAdjByPosAndDirection(pos, direction))
{
if (dto == null)
{
return FullDataSourceV2.createEmpty(pos);
}
// migrate to the V2 format first if needed
if (dto.dataFormatVersion == FullDataSourceV2DTO.DATA_FORMAT.V1_NO_ADJACENT_DATA)
{
// get automatically converts from V1 to V2
FullDataSourceV2 migratedDataSource = this.get(pos);
if (migratedDataSource != null)
{
migratedDataSource.clearAllNonAdjData(direction);
}
return migratedDataSource;
}
try
{
// load from database
return this.createAdjDataSourceFromDto(dto, direction);
}
catch (DataCorruptedException e)
{
this.tryLogCorruptedDataError(DhSectionPos.toString(pos), e);
this.repo.deleteWithKey(pos);
}
}
catch (InterruptedException ignore) { }
catch (IOException e)
{
LOGGER.warn("File read Error for pos ["+DhSectionPos.toString(pos)+"], error: "+e.getMessage(), e);
}
// an error occurred
return null;
}
//=======================//
// retrieval (world gen) //
//=======================//
/**
* Returns true if this provider can generate or retrieve
* {@link FullDataSourceV2}'s that aren't currently in the database.
*/
public boolean canRetrieveMissingDataSources()
{
// the base handler just handles basic reading/writing
// to the database and as such can't retrieve anything else.
return false;
}
/**
* Returns false if this provider isn't accepting new requests,
* this can be due to having a full queue or some other
* limiting factor. <br><br>
*
* Note: when overriding make sure to add: <br>
* <code>
* if (!super.canQueueRetrieval()) <br>
* { <br>
* return false; <br>
* } <br>
* </code>
* to the beginning of your override.
* Otherwise, parent retrieval limits will be ignored.
*/
public boolean canQueueRetrieval()
{
// Retrieval shouldn't happen while an unknown number of
// legacy data sources are present.
// If retrieval was allowed we might run into concurrency issues.
return !this.dataMigratorV1.migrationThreadRunning.get();
}
/**
* @return null if this provider can't generate any positions and
* an empty array if all positions were generated
*/
@Nullable
public LongArrayList getPositionsToRetrieve(Long pos) { return null; }
/** @return true if the position was queued, false if not */
@Nullable
public CompletableFuture<WorldGenResult> queuePositionForRetrieval(Long genPos) { return null; }
/** does nothing if the given position isn't present in the queue */
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) { }
public void clearRetrievalQueue() { }
/** Can be used to display how many total retrieval requests might be available. */
public void setTotalRetrievalPositionCount(int newCount) { }
/** Can be used to display how many total chunk retrieval requests should be available. */
public void setEstimatedRemainingRetrievalChunkCount(int newCount) { }
//=============//
// data update //
//=============//
public CompletableFuture<Void> updateDataSourceAsync(@NotNull FullDataSourceV2 inputData)
{ return this.dataUpdater.updateDataSourceAsync(inputData); }
//========================//
// multiplayer networking //
//========================//
@Nullable
public Long getTimestampForPos(long pos)
{
if (this.isShutdownRef.get())
{
return null;
}
return this.repo.getTimestampForPos(pos);
}
//===========//
// debugging //
//===========//
public void addDebugMenuStringsToList(List<String> messageList)
{
this.dataMigratorV1.addDebugMenuStringsToList(messageList);
}
//===========//
// overrides //
//===========//
@Override
public void debugRender(DebugRenderer renderer)
{
this.dataUpdater.debugRender(renderer);
this.updatePropagator.debugRender(renderer);
this.dataMigratorV1.debugRender(renderer);
}
@Override
public void close()
{
LOGGER.debug("Closing [" + this.getClass().getSimpleName() + "] for level: [" + this.levelId + "].");
this.isShutdownRef.set(true);
this.dataUpdater.close();
this.updatePropagator.close();
this.dataMigratorV1.close();
this.repo.close();
}
}
@@ -0,0 +1,399 @@
package com.seibel.distanthorizons.core.file.fullDatafile.V2;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
/** indicates how long the update queue thread should wait between queuing ticks */
protected static final int PROPAGATE_QUEUE_THREAD_DELAY_IN_MS = 250;
public static final int NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD = 5;
/** how many parent update tasks can be in the queue at once */
public static int getMaxPropagateTaskCount()
{ return NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get(); }
/**
* Tracks which positions are currently being updated
* to prevent duplicate concurrent updates.
*/
private final Set<Long> updatingPosSet = ConcurrentHashMap.newKeySet();
// TODO only run thread if modifications happened recently
/**
* Will be null on the dedicated server since updates don't need to be propagated,
* only the highest detail level is needed.
*/
@Nullable
public final ThreadPoolExecutor updateQueueProcessor;
private final AtomicBoolean isShutdownRef = new AtomicBoolean(false);
private final String levelId;
private final FullDataSourceProviderV2 provider;
private final FullDataUpdaterV2 dataUpdater;
//=============//
// constructor //
//=============//
public FullDataUpdatePropagatorV2(FullDataSourceProviderV2 provider, FullDataUpdaterV2 dataUpdater, String levelId)
{
this.provider = provider;
this.dataUpdater = dataUpdater;
this.levelId = levelId;
// update propagation doesn't need to be run on the server since only the highest detail level is needed
this.updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Update Propagate Queue [" + this.levelId + "]");
this.updateQueueProcessor.execute(this::runUpdateQueue);
}
//================//
// parent updates //
//================//
private void runUpdateQueue()
{
while (!Thread.interrupted())
{
try
{
Thread.sleep(PROPAGATE_QUEUE_THREAD_DELAY_IN_MS);
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getUpdatePropagatorExecutor();
if (executor == null || executor.isTerminated())
{
continue;
}
// TODO it might be worth skipping this logic if no parent updates happened
// update positions closest to the player (if not on a server)
// to make world gen appear faster
DhBlockPos targetBlockPos = DhBlockPos.ZERO;
if (MC_CLIENT != null
&& MC_CLIENT.playerExists())
{
targetBlockPos = MC_CLIENT.getPlayerBlockPos();
}
this.runParentUpdates(executor, targetBlockPos);
if (Config.Common.LodBuilding.Experimental.upsampleLowerDetailLodsToFillHoles.get())
{
this.runChildUpdates(executor, targetBlockPos);
}
}
catch (InterruptedException ignored)
{
Thread.currentThread().interrupt();
}
catch (Exception e)
{
LOGGER.error("Unexpected error in the parent update queue thread. Error: " + e.getMessage(), e);
}
}
}
/** will always apply updates */
private void runParentUpdates(PriorityTaskPicker.Executor executor, DhBlockPos targetBlockPos)
{
int maxUpdateTaskCount = getMaxPropagateTaskCount();
// queue parent updates
if (executor.getQueueSize() < maxUpdateTaskCount
&& this.updatingPosSet.size() < maxUpdateTaskCount)
{
// get the positions that need to be applied to their parents
LongArrayList parentUpdatePosList = this.provider.repo.getPositionsToUpdate(targetBlockPos.getX(), targetBlockPos.getZ(), maxUpdateTaskCount);
// combine updates together based on their parent
HashMap<Long, HashSet<Long>> updatePosByParentPos = new HashMap<>();
for (Long pos : parentUpdatePosList)
{
updatePosByParentPos.compute(DhSectionPos.getParentPos(pos), (parentPos, updatePosSet) ->
{
if (updatePosSet == null)
{
updatePosSet = new HashSet<>();
}
updatePosSet.add(pos);
return updatePosSet;
});
}
// queue the updates
for (Long parentUpdatePos : updatePosByParentPos.keySet())
{
// stop if there are already a bunch of updates queued
if (this.updatingPosSet.size() > maxUpdateTaskCount
|| executor.getQueueSize() > maxUpdateTaskCount
|| !this.updatingPosSet.add(parentUpdatePos))
{
break;
}
try
{
executor.execute(() ->
{
ReentrantLock parentWriteLock = this.dataUpdater.updateLockProvider.getLock(parentUpdatePos);
boolean parentLocked = false;
try
{
//LOGGER.info("updating parent: "+parentUpdatePos);
// Locking the parent before the children should prevent deadlocks.
// TryLock is used instead of lock so this thread can handle a different update.
if (parentWriteLock.tryLock())
{
parentLocked = true;
this.dataUpdater.lockedPosSet.add(parentUpdatePos);
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos))
{
// will return null if the file handler is shutting down
if (parentDataSource != null)
{
// apply each child pos to the parent
for (Long childPos : updatePosByParentPos.get(parentUpdatePos))
{
ReentrantLock childReadLock = this.dataUpdater.updateLockProvider.getLock(childPos);
try
{
childReadLock.lock();
this.dataUpdater.lockedPosSet.add(childPos);
try (FullDataSourceV2 childDataSource = this.provider.get(childPos))
{
// can return null when the file handler is being shut down
if (childDataSource != null)
{
parentDataSource.updateFromChunk(childDataSource);
}
}
}
catch (Exception e)
{
LOGGER.error("Unexpected in parent update propagation for parent pos: ["+DhSectionPos.toString(parentUpdatePos)+"], child pos: [" + DhSectionPos.toString(parentUpdatePos) + "], Error: [" + e.getMessage() + "].", e);
}
finally
{
childReadLock.unlock();
this.dataUpdater.lockedPosSet.remove(childPos);
}
}
if (DhSectionPos.getDetailLevel(parentUpdatePos) < FullDataSourceProviderV2.ROOT_SECTION_DETAIL_LEVEL)
{
parentDataSource.applyToParent = true;
}
this.dataUpdater.updateDataSource(parentDataSource, false);
for (Long childPos : updatePosByParentPos.get(parentUpdatePos))
{
this.provider.repo.setApplyToParent(childPos, false);
}
}
}
}
}
finally
{
if (parentLocked)
{
parentWriteLock.unlock();
this.dataUpdater.lockedPosSet.remove(parentUpdatePos);
}
this.updatingPosSet.remove(parentUpdatePos);
}
});
}
catch (RejectedExecutionException ignore)
{ /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
catch (Exception e)
{
this.updatingPosSet.remove(parentUpdatePos);
throw e;
}
}
}
}
/** stops if it finds any LOD data */
private void runChildUpdates(PriorityTaskPicker.Executor executor, DhBlockPos targetBlockPos)
{
int maxUpdateTaskCount = getMaxPropagateTaskCount();
// queue child updates
if (executor.getQueueSize() < maxUpdateTaskCount
&& this.updatingPosSet.size() < maxUpdateTaskCount)
{
// get the positions that need to be applied to their children
LongArrayList childUpdatePosList = this.provider.repo.getChildPositionsToUpdate(targetBlockPos.getX(), targetBlockPos.getZ(), maxUpdateTaskCount);
// queue the updates
for (long parentUpdatePos : childUpdatePosList)
{
// stop if there are already a bunch of updates queued
if (this.updatingPosSet.size() > maxUpdateTaskCount
|| executor.getQueueSize() > maxUpdateTaskCount)
{
break;
}
// skip already updating positions
if (!this.updatingPosSet.add(parentUpdatePos))
{
continue;
}
try
{
executor.execute(() ->
{
ReentrantLock parentReadLock = this.dataUpdater.updateLockProvider.getLock(parentUpdatePos);
boolean parentLocked = false;
try
{
//LOGGER.info("updating parent: "+parentUpdatePos);
// Locking the parent before the children should prevent deadlocks.
// TryLock is used instead of lock so this thread can handle a different update.
if (parentReadLock.tryLock())
{
parentLocked = true;
this.dataUpdater.lockedPosSet.add(parentUpdatePos);
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos))
{
// will return null if the file handler is shutting down
if (parentDataSource != null)
{
// apply parent to each child
for (int i = 0; i < 4; i++)
{
long childPos = DhSectionPos.getChildByIndex(parentUpdatePos, i);
ReentrantLock childWriteLock = this.dataUpdater.updateLockProvider.getLock(childPos);
try
{
childWriteLock.lock();
this.dataUpdater.lockedPosSet.add(childPos);
try (FullDataSourceV2 childDataSource = this.provider.get(childPos))
{
// will return null if the file handler is shutting down
if (childDataSource != null)
{
childDataSource.updateFromChunk(parentDataSource);
// don't propagate child updates past the bottom of the tree
if (DhSectionPos.getDetailLevel(childPos) != DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL)
{
childDataSource.applyToChildren = true;
}
this.dataUpdater.updateDataSource(childDataSource, false);
}
}
}
catch (Exception e)
{
LOGGER.error("Unexpected in child update propagation for parent pos: ["+DhSectionPos.toString(parentUpdatePos)+"], child pos: [" + DhSectionPos.toString(parentUpdatePos) + "], Error: [" + e.getMessage() + "].", e);
}
finally
{
childWriteLock.unlock();
this.dataUpdater.lockedPosSet.remove(childPos);
}
}
this.provider.repo.setApplyToChild(parentUpdatePos, false);
}
}
}
}
finally
{
if (parentLocked)
{
parentReadLock.unlock();
this.dataUpdater.lockedPosSet.remove(parentUpdatePos);
}
this.updatingPosSet.remove(parentUpdatePos);
}
});
}
catch (RejectedExecutionException ignore)
{ /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
catch (Exception e)
{
this.updatingPosSet.remove(parentUpdatePos);
throw e;
}
}
}
}
//===========//
// overrides //
//===========//
@Override
public void debugRender(DebugRenderer renderer)
{
this.updatingPosSet
.forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f, 0.20f, Color.MAGENTA)); });
}
@Override
public void close()
{
if (this.updateQueueProcessor != null)
{
this.updateQueueProcessor.shutdownNow();
}
}
}
@@ -0,0 +1,249 @@
package com.seibel.distanthorizons.core.file.fullDatafile.V2;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.threading.PositionalLockProvider;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
protected final PositionalLockProvider updateLockProvider = new PositionalLockProvider();
/**
* generally just used for debugging,
* keeps track of which positions are currently locked.
*/
public final Set<Long> lockedPosSet = ConcurrentHashMap.newKeySet();
private final ConcurrentHashMap<Long, AtomicInteger> queuedUpdateCountsByPos = new ConcurrentHashMap<>();
public final ArrayList<IDataSourceUpdateListenerFunc<FullDataSourceV2>> dateSourceUpdateListeners = new ArrayList<>();
private final String levelId;
private final AtomicBoolean isShutdownRef = new AtomicBoolean(false);
private final FullDataSourceProviderV2 provider;
//=============//
// constructor //
//=============//
public FullDataUpdaterV2(FullDataSourceProviderV2 provider, String levelId)
{
this.provider = provider;
this.levelId = levelId;
}
//===============//
// data updating //
//===============//
/**
* Can be used if you don't want to lock the current thread
* Otherwise the sync version {@link FullDataUpdaterV2#updateDataSource(FullDataSourceV2, boolean)} may be a better choice.
*/
public CompletableFuture<Void> updateDataSourceAsync(@NotNull FullDataSourceV2 inputDataSource)
{
if (this.isShutdownRef.get())
{
return CompletableFuture.completedFuture(null);
}
AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor == null || executor.isTerminated())
{
return CompletableFuture.completedFuture(null);
}
try
{
this.markUpdateStart(inputDataSource.getPos());
return CompletableFuture.runAsync(() ->
{
try
{
this.updateDataSource(inputDataSource, true);
}
catch (Exception e)
{
LOGGER.error("Unexpected error in async data source update at pos: ["+ DhSectionPos.toString(inputDataSource.getPos())+"], error: ["+e.getMessage()+"].", e);
}
finally
{
this.markUpdateEnd(inputDataSource.getPos());
}
}, executor);
}
catch (RejectedExecutionException ignore)
{
// can happen if the executor was shutdown while this task was queued
this.markUpdateEnd(inputDataSource.getPos());
return CompletableFuture.completedFuture(null);
}
}
/** After this method returns the inputData will be written to file. */
public void updateDataSource(@NotNull FullDataSourceV2 inputData, boolean lockOnUpdatePos)
{
if (this.isShutdownRef.get())
{
return;
}
long updatePos = inputData.getPos();
boolean methodLocked = false;
// a lock is necessary to prevent two threads from writing to the same position at once,
// if that happens only the second update will apply and the LOD will end up with hole(s)
ReentrantLock updateLock = this.updateLockProvider.getLock(updatePos);
try
{
if (lockOnUpdatePos)
{
methodLocked = true;
updateLock.lock();
this.lockedPosSet.add(updatePos);
}
// get or create the data source
try (FullDataSourceV2 recipientDataSource = this.provider.get(updatePos))
{
if (recipientDataSource != null)
{
boolean dataModified = recipientDataSource.updateFromChunk(inputData);
if (dataModified)
{
// save the updated data to the database
try (FullDataSourceV2DTO dto = this.createDtoFromDataSource(recipientDataSource))
{
if (dto != null)
{
this.provider.repo.save(dto);
}
}
for (IDataSourceUpdateListenerFunc<FullDataSourceV2> listener : this.dateSourceUpdateListeners)
{
if (listener != null)
{
listener.OnDataSourceUpdated(recipientDataSource);
}
}
}
}
}
}
catch (Exception e)
{
LOGGER.error("Error updating pos ["+DhSectionPos.toString(updatePos)+"], error: "+e.getMessage(), e);
}
finally
{
if (methodLocked)
{
updateLock.unlock();
this.lockedPosSet.remove(updatePos);
}
}
}
private FullDataSourceV2DTO createDtoFromDataSource(FullDataSourceV2 dataSource)
{
try
{
// when creating new data use the compressor currently selected in the config
EDhApiDataCompressionMode compressionModeEnum = Config.Common.LodBuilding.dataCompression.get();
return FullDataSourceV2DTO.CreateFromDataSource(dataSource, compressionModeEnum);
}
catch (IOException e)
{
LOGGER.warn("Unable to create DTO, error: ["+e.getMessage() + "].", e);
return null;
}
}
//==================//
// debugger methods //
//==================//
/** used for debugging to track which positions are queued for updating */
private void markUpdateStart(long dataSourcePos)
{
this.queuedUpdateCountsByPos.compute(dataSourcePos, (pos, atomicCount) ->
{
if (atomicCount == null)
{
atomicCount = new AtomicInteger(0);
}
atomicCount.incrementAndGet();
return atomicCount;
});
}
/** used for debugging to track which positions are queued for updating */
private void markUpdateEnd(long dataSourcePos)
{
this.queuedUpdateCountsByPos.compute(dataSourcePos, (pos, atomicCount) ->
{
if (atomicCount != null && atomicCount.decrementAndGet() <= 0)
{
atomicCount = null;
}
return atomicCount;
});
}
//===========//
// overrides //
//===========//
@Override
public void debugRender(DebugRenderer renderer)
{
this.lockedPosSet
.forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 74f, 0.15f, Color.PINK)); });
this.queuedUpdateCountsByPos
.forEach((pos, updateCountRef) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f + (updateCountRef.get() * 16f), 0.20f, Color.WHITE)); });
}
@Override
public void close()
{
this.isShutdownRef.set(true);
}
}
@@ -32,7 +32,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSha
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
import com.seibel.distanthorizons.coreapi.util.StringUtil; import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
@@ -47,7 +47,7 @@ public class ClientOnlySaveStructure implements ISaveStructure
public static final String REPLAY_SERVER_FOLDER_NAME = "REPLAY"; public static final String REPLAY_SERVER_FOLDER_NAME = "REPLAY";
public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]"; public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]";
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class); private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
@@ -81,13 +81,20 @@ public class ClientOnlySaveStructure implements ISaveStructure
{ {
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper; IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
LOGGER.info("Loading level [" + newLevelWrapper.getDhIdentifier() + "] with key: [" + keyedClientLevel.getServerLevelKey() + "]."); LOGGER.info("Loading level [" + newLevelWrapper.getDhIdentifier() + "] with key: [" + keyedClientLevel.getServerLevelKey() + "].");
String serverKey = keyedClientLevel.getServerKey();
if (serverKey.isEmpty())
{
serverKey = getServerFolderName();
}
// This world was identified by the server directly, so we can know for sure which folder to use. // This world was identified by the server directly, so we can know for sure which folder to use.
saveFolder = getSaveFolderByLevelId(keyedClientLevel.getServerLevelKey()); saveFolder = getSaveFolderByLevelId(serverKey, keyedClientLevel.getServerLevelKey());
} }
else else
{ {
// get the default folder // get the default folder
saveFolder = getSaveFolderByLevelId(levelWrapper.getDhIdentifier()); saveFolder = getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDhIdentifier());
} }
// Allow API users to override the save folder // Allow API users to override the save folder
@@ -116,7 +123,7 @@ public class ClientOnlySaveStructure implements ISaveStructure
return this.getSaveFolder(levelWrapper); return this.getSaveFolder(levelWrapper);
} }
return getSaveFolderByLevelId(levelWrapper.getDimensionType().getName()); return getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDimensionType().getName());
} }
@@ -173,11 +180,11 @@ public class ClientOnlySaveStructure implements ISaveStructure
} }
private static File getSaveFolderByLevelId(String dimensionName) private static File getSaveFolderByLevelId(String folderName, String dimensionName)
{ {
String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar
+ SERVER_DATA_FOLDER_NAME + File.separatorChar + SERVER_DATA_FOLDER_NAME + File.separatorChar
+ getServerFolderName() + File.separatorChar + folderName + File.separatorChar
+ dimensionName.replaceAll(":", "@@"); + dimensionName.replaceAll(":", "@@");
return new File(path); return new File(path);
@@ -26,7 +26,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
import com.seibel.distanthorizons.coreapi.util.StringUtil; import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.io.File; import java.io.File;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -36,7 +36,7 @@ import java.util.concurrent.ConcurrentHashMap;
*/ */
public class LocalSaveStructure implements ISaveStructure public class LocalSaveStructure implements ISaveStructure
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final ConcurrentHashMap<ILevelWrapper, File> levelWrapperToFileMap = new ConcurrentHashMap<>(); private final ConcurrentHashMap<ILevelWrapper, File> levelWrapperToFileMap = new ConcurrentHashMap<>();
@@ -30,7 +30,7 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGenerat
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvironmentWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvironmentWrapper;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -43,7 +43,7 @@ import java.util.function.Consumer;
public class BatchGenerator implements IDhApiWorldGenerator public class BatchGenerator implements IDhApiWorldGenerator
{ {
private static final IWrapperFactory FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private static final IWrapperFactory FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public AbstractBatchGenerationEnvironmentWrapper generationEnvironment; public AbstractBatchGenerationEnvironmentWrapper generationEnvironment;
public IDhLevel targetDhLevel; public IDhLevel targetDhLevel;
@@ -33,7 +33,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.awt.*; import java.awt.*;
import java.util.*; import java.util.*;
@@ -49,7 +49,7 @@ import org.jetbrains.annotations.NotNull;
*/ */
public class DhLightingEngine public class DhLightingEngine
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static final DhLightingEngine INSTANCE = new DhLightingEngine(); public static final DhLightingEngine INSTANCE = new DhLightingEngine();
/** /**
@@ -314,7 +314,7 @@ public class DhLightingEngine
// propagate the lighting in each cardinal direction, IE: -x, +x, -y, +y, -z, +z // propagate the lighting in each cardinal direction, IE: -x, +x, -y, +y, -z, +z
for (EDhDirection direction : EDhDirection.CARDINAL_DIRECTIONS) // since this is an array instead of an ArrayList this advanced for-loop shouldn't cause any GC issues for (EDhDirection direction : EDhDirection.ALL) // since this is an array instead of an ArrayList this advanced for-loop shouldn't cause any GC issues
{ {
lightPos.mutateOffset(direction, neighbourBlockPos); lightPos.mutateOffset(direction, neighbourBlockPos);
neighbourBlockPos.mutateToChunkRelativePos(relNeighbourBlockPos); neighbourBlockPos.mutateToChunkRelativePos(relNeighbourBlockPos);
@@ -413,7 +413,7 @@ public class DhLightingEngine
{ {
for (int x = 0; x < FullDataSourceV2.WIDTH; x++) for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
{ {
LongArrayList dataPoints = dataSource.get(x, z); LongArrayList dataPoints = dataSource.getColumnAtRelPos(x, z);
if (dataPoints != null && !dataPoints.isEmpty()) if (dataPoints != null && !dataPoints.isEmpty())
{ {
// iterate through the data points in this column top-down // iterate through the data points in this column top-down
@@ -564,7 +564,7 @@ public class DhLightingEngine
// check if the adjacent position is within the bounds of this data source... // check if the adjacent position is within the bounds of this data source...
if (adjacentX >= 0 && adjacentX < FullDataSourceV2.WIDTH && adjacentZ >= 0 && adjacentZ < FullDataSourceV2.WIDTH) if (adjacentX >= 0 && adjacentX < FullDataSourceV2.WIDTH && adjacentZ >= 0 && adjacentZ < FullDataSourceV2.WIDTH)
{ {
LongArrayList adjacentDataPoints = chunk.get(adjacentX, adjacentZ); LongArrayList adjacentDataPoints = chunk.getColumnAtRelPos(adjacentX, adjacentZ);
// ...and also check to make sure we have some data points // ...and also check to make sure we have some data points
// (potentially transparent ones) to propagate through in the adjacent column. // (potentially transparent ones) to propagate through in the adjacent column.
if (adjacentDataPoints != null) if (adjacentDataPoints != null)
@@ -13,7 +13,7 @@ import com.seibel.distanthorizons.core.util.FormatUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.text.MessageFormat; import java.text.MessageFormat;
@@ -26,7 +26,7 @@ import java.util.concurrent.atomic.AtomicReference;
public class PregenManager public class PregenManager
{ {
protected static final Logger LOGGER = DhLoggerBuilder.getLogger(); protected static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final AtomicReference<PregenState> pregenFuture = new AtomicReference<>(); private final AtomicReference<PregenState> pregenFuture = new AtomicReference<>();
@@ -38,7 +38,7 @@ public class PregenManager
) )
{ {
PregenState pregenState = new PregenState( PregenState pregenState = new PregenState(
(GeneratedFullDataSourceProvider) SharedApi.getIDhServerWorld().getLevel(levelWrapper).getFullDataProvider(), (GeneratedFullDataSourceProvider) SharedApi.tryGetDhServerWorld().getLevel(levelWrapper).getFullDataProvider(),
DhSectionPos.convertToDetailLevel( DhSectionPos.convertToDetailLevel(
DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, origin.x, origin.z), DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, origin.x, origin.z),
DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL
@@ -140,7 +140,9 @@ public class PregenManager
} }
this.pendingGenerations.put(nextSectionPos, System.currentTimeMillis()); this.pendingGenerations.put(nextSectionPos, System.currentTimeMillis());
this.fullDataSourceProvider.getAsync(nextSectionPos).thenAccept(fullDataSource -> { this.fullDataSourceProvider.getAsync(nextSectionPos)
.thenAccept(fullDataSource ->
{
if (this.fullDataSourceProvider.isFullyGenerated(fullDataSource.columnGenerationSteps)) if (this.fullDataSourceProvider.isFullyGenerated(fullDataSource.columnGenerationSteps))
{ {
this.pendingGenerations.invalidate(fullDataSource.getPos()); this.pendingGenerations.invalidate(fullDataSource.getPos());
@@ -12,7 +12,7 @@ import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -21,7 +21,7 @@ import java.util.concurrent.*;
public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private int estimatedRemainingTaskCount; private int estimatedRemainingTaskCount;
private int estimatedTotalChunkCount; private int estimatedTotalChunkCount;
@@ -90,7 +90,7 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
return WorldGenResult.CreateSplit(childFutures); return WorldGenResult.CreateSplit(childFutures);
} }
LodUtil.assertNotReach(); LodUtil.assertNotReach("Unexpected and unhandled request response result: ["+requestResult+"]");
return WorldGenResult.CreateFail(); return WorldGenResult.CreateFail();
}); });
} }
@@ -52,7 +52,7 @@ import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.awt.*; import java.awt.*;
import java.util.*; import java.util.*;
@@ -62,7 +62,7 @@ import java.util.function.Consumer;
public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
/** /**
@@ -416,7 +416,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// but this should work for now // but this should work for now
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<IChunkWrapper>(); ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<IChunkWrapper>();
nearbyChunkList.add(chunkWrapper); nearbyChunkList.add(chunkWrapper);
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, this.level.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
try (FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(this.level.getLevelWrapper(), chunkWrapper)) try (FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(this.level.getLevelWrapper(), chunkWrapper))
{ {
@@ -621,19 +621,13 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
LodUtil.assertTrue(this.generatorClosingFuture != null); LodUtil.assertTrue(this.generatorClosingFuture != null);
LOGGER.info("Awaiting world generator thread pool termination..."); LOGGER.info("Shutting down world generator thread pool...");
try
AbstractExecutorService executor = ThreadPoolUtil.getWorldGenExecutor();
if (executor != null)
{ {
int waitTimeInSeconds = 3; List<Runnable> tasks = executor.shutdownNow();
AbstractExecutorService executor = ThreadPoolUtil.getWorldGenExecutor(); LOGGER.info("World generator thread pool shutdown with [" + tasks.size() + "] incomplete tasks.");
if (executor != null && !executor.awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS))
{
LOGGER.warn("World generator thread pool shutdown didn't complete after [" + waitTimeInSeconds + "] seconds. Some world generator requests may still be running.");
}
}
catch (InterruptedException e)
{
LOGGER.warn("World generator thread pool shutdown interrupted! Ignoring child threads...", e);
} }
@@ -663,24 +657,27 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
@Override @Override
public void debugRender(DebugRenderer renderer) public void debugRender(DebugRenderer renderer)
{ {
int levelMinY = this.level.getLevelWrapper().getMinHeight();
int levelMaxY = this.level.getLevelWrapper().getMaxHeight();
// show the wireframe a bit lower than world max height, // show the wireframe a bit lower than world max height,
// since most worlds don't render all the way up to the max height // since most worlds don't render all the way up to the max height
int levelHeightRange = (this.level.getMaxY() - this.level.getMinY()); int levelHeightRange = (levelMaxY - levelMinY);
int maxY = this.level.getMaxY() - (levelHeightRange / 2); int maxY = levelMaxY - (levelHeightRange / 2);
// blue - queued // blue - queued
this.waitingTasks.keySet().forEach((pos) -> this.waitingTasks.keySet().forEach((pos) ->
{ {
renderer.renderBox( renderer.renderBox(
new DebugRenderer.Box(pos, this.level.getMinY(), maxY, 0.05f, Color.blue)); new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.blue));
}); });
// red - in progress // red - in progress
this.inProgressGenTasksByLodPos.forEach((pos, t) -> this.inProgressGenTasksByLodPos.forEach((pos, t) ->
{ {
renderer.renderBox( renderer.renderBox(
new DebugRenderer.Box(pos, this.level.getMinY(), maxY, 0.05f, Color.red)); new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.red));
}); });
} }
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.jar; package com.seibel.distanthorizons.core.jar;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo; import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
@@ -28,7 +29,7 @@ import com.seibel.distanthorizons.core.jar.gui.cusomJObject.JBox;
import com.seibel.distanthorizons.core.jar.installer.ModrinthGetter; import com.seibel.distanthorizons.core.jar.installer.ModrinthGetter;
import com.seibel.distanthorizons.core.jar.installer.WebDownloader; import com.seibel.distanthorizons.core.jar.installer.WebDownloader;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.LoggerContext;
import org.lwjgl.util.tinyfd.TinyFileDialogs; import org.lwjgl.util.tinyfd.TinyFileDialogs;
@@ -49,7 +50,7 @@ import java.util.concurrent.atomic.AtomicReference;
// Once built it would be in core/build/libs/DistantHorizons-<Version>-dev-all.jar // Once built it would be in core/build/libs/DistantHorizons-<Version>-dev-all.jar
public class JarMain public class JarMain
{ {
public static final Logger logger = LogManager.getLogger(JarMain.class.getSimpleName()); public static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static List<String> argList; public static List<String> argList;
public static final boolean isDarkTheme = DarkModeDetector.isDarkMode(); public static final boolean isDarkTheme = DarkModeDetector.isDarkMode();
public static boolean isOffline = WebDownloader.netIsAvailable(); public static boolean isOffline = WebDownloader.netIsAvailable();
@@ -71,15 +72,15 @@ public class JarMain
} }
catch (Exception e) catch (Exception e)
{ {
logger.error("Failed to set log4j config. Try running with the \"--no-custom-logger\" argument"); LOGGER.error("Failed to set log4j config. Try running with the \"--no-custom-logger\" argument");
e.printStackTrace(); e.printStackTrace();
} }
} }
logger.debug("Running " + ModInfo.READABLE_NAME + " standalone jar"); LOGGER.debug("Running " + ModInfo.READABLE_NAME + " standalone jar");
logger.warn("The standalone jar is still a massive WIP, expect bugs"); LOGGER.warn("The standalone jar is still a massive WIP, expect bugs");
logger.debug("Java version " + System.getProperty("java.version")); LOGGER.debug("Java version " + System.getProperty("java.version"));
//logger.debug(argList); //LOGGER.debug(argList);
if (args.length == 0 || Arrays.asList(args).contains("--gui")) if (args.length == 0 || Arrays.asList(args).contains("--gui"))
@@ -87,7 +88,7 @@ public class JarMain
// Sets up the local // Sets up the local
if (JarUtils.accessFile("assets/lod/lang/" + Locale.getDefault().toString().toLowerCase() + ".json") == null) if (JarUtils.accessFile("assets/lod/lang/" + Locale.getDefault().toString().toLowerCase() + ".json") == null)
{ {
logger.warn("The language setting [" + Locale.getDefault().toString().toLowerCase() + "] isn't allowed yet. Defaulting to [" + Locale.US.toString().toLowerCase() + "]."); LOGGER.warn("The language setting [" + Locale.getDefault().toString().toLowerCase() + "] isn't allowed yet. Defaulting to [" + Locale.US.toString().toLowerCase() + "].");
Locale.setDefault(Locale.US); Locale.setDefault(Locale.US);
} }
JarDependencySetup.createInitialBindings(); JarDependencySetup.createInitialBindings();
@@ -124,7 +125,7 @@ public class JarMain
} }
catch (NumberFormatException e) catch (NumberFormatException e)
{ {
logger.error("Unable to parse detail level ["+detailLevelString+"], error: ["+e.getMessage()+"]."); LOGGER.error("Unable to parse detail level ["+detailLevelString+"], error: ["+e.getMessage()+"].");
} }
} }
else if (argList.size() == 4) else if (argList.size() == 4)
@@ -145,14 +146,14 @@ public class JarMain
} }
catch (NumberFormatException e) catch (NumberFormatException e)
{ {
logger.error("Unable to parse position ["+detailLevelString+"], ["+posXString+"], ["+posZString+"], error: ["+e.getMessage()+"]."); LOGGER.error("Unable to parse position ["+detailLevelString+"], ["+posXString+"], ["+posZString+"], error: ["+e.getMessage()+"].");
} }
} }
} }
if (showHelp) if (showHelp)
{ {
logger.info("--export parses the 'DistantHorizons.sqlite' file next to this jar and exports the given data into a CSV file. \n" + LOGGER.info("--export parses the 'DistantHorizons.sqlite' file next to this jar and exports the given data into a CSV file. \n" +
"Usage: \n" + "Usage: \n" +
"--export [LOD position Detail Level] [LOD position X] [LOD position Z] \n" + "--export [LOD position Detail Level] [LOD position X] [LOD position Z] \n" +
"\tExport the given position's data if present. \n" + "\tExport the given position's data if present. \n" +
@@ -170,7 +171,7 @@ public class JarMain
File dbFile = new File("./DistantHorizons.sqlite"); File dbFile = new File("./DistantHorizons.sqlite");
if (!dbFile.exists()) if (!dbFile.exists())
{ {
logger.error("Unable to find a database to parse at: ["+dbFile.getAbsolutePath()+"]."); LOGGER.error("Unable to find a database to parse at: ["+dbFile.getAbsolutePath()+"].");
return; return;
} }
@@ -180,7 +181,7 @@ public class JarMain
File exportFile = new File("DistantHorizons-export.csv"); // TODO allow setting an export folder File exportFile = new File("DistantHorizons-export.csv"); // TODO allow setting an export folder
if (exportFile.isDirectory()) if (exportFile.isDirectory())
{ {
logger.error("Export file can't be a folder. Given path: ["+exportFile+"]."); LOGGER.error("Export file can't be a folder. Given path: ["+exportFile+"].");
return; return;
} }
@@ -192,22 +193,22 @@ public class JarMain
if (exportFile.exists()) if (exportFile.exists())
{ {
logger.error("Export file already exists: ["+exportFile.getAbsolutePath()+"]."); LOGGER.error("Export file already exists: ["+exportFile.getAbsolutePath()+"].");
return; return;
} }
else if (exportFile.createNewFile()) else if (exportFile.createNewFile())
{ {
logger.error("Failed to create file: ["+exportFile.getAbsolutePath()+"]."); LOGGER.error("Failed to create file: ["+exportFile.getAbsolutePath()+"].");
return; return;
} }
} }
catch (Exception e) catch (Exception e)
{ {
logger.error("Unable to create export file: ["+exportFile.getAbsolutePath()+"]."); LOGGER.error("Unable to create export file: ["+exportFile.getAbsolutePath()+"].");
return; return;
} }
logger.info("LOD data will be exported to ["+exportFile.getAbsolutePath()+"]."); LOGGER.info("LOD data will be exported to ["+exportFile.getAbsolutePath()+"].");
FullDataSourceV2Repo repo; FullDataSourceV2Repo repo;
@@ -217,7 +218,7 @@ public class JarMain
} }
catch (SQLException e) catch (SQLException e)
{ {
logger.error("Failed to initialize connection with database: ["+exportFile.getAbsolutePath()+"], error: ["+e.getMessage()+"].", e); LOGGER.error("Failed to initialize connection with database: ["+exportFile.getAbsolutePath()+"], error: ["+e.getMessage()+"].", e);
return; return;
} }
@@ -241,7 +242,7 @@ public class JarMain
FullDataSourceV2DTO dto = repo.getByKey(pos); FullDataSourceV2DTO dto = repo.getByKey(pos);
if (dto == null) if (dto == null)
{ {
logger.error("Unable to find any data at the position ["+DhSectionPos.toString(pos)+"]."); LOGGER.error("Unable to find any data at the position ["+DhSectionPos.toString(pos)+"].");
return; return;
} }
// TODO need a way to create datasources (specifically data mappings) without a MC level object to deserialize with // TODO need a way to create datasources (specifically data mappings) without a MC level object to deserialize with
@@ -20,10 +20,11 @@
package com.seibel.distanthorizons.core.jar; package com.seibel.distanthorizons.core.jar;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.*; import java.io.*;
@@ -42,7 +43,7 @@ import java.util.Objects;
*/ */
public class JarUtils public class JarUtils
{ {
private static final Logger LOGGER = LogManager.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@Nullable @Nullable
public static File jarFile = null; public static File jarFile = null;
@@ -22,8 +22,9 @@ package com.seibel.distanthorizons.core.jar;
import com.electronwill.nightconfig.core.Config; import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.io.ParsingMode; import com.electronwill.nightconfig.core.io.ParsingMode;
import com.electronwill.nightconfig.json.JsonFormat; import com.electronwill.nightconfig.json.JsonFormat;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
/** /**
* Get info on the git for the mod <br> * Get info on the git for the mod <br>
@@ -33,7 +34,7 @@ import org.apache.logging.log4j.Logger;
*/ */
public final class ModJarInfo public final class ModJarInfo
{ {
private static final Logger LOGGER = LogManager.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final String FILE_NAME = "build_info.json"; private static final String FILE_NAME = "build_info.json";
static static
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.jar.installer;
import com.electronwill.nightconfig.core.Config; import com.electronwill.nightconfig.core.Config;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.net.URL; import java.net.URL;
@@ -34,7 +34,7 @@ import java.util.*;
*/ */
public class GitlabGetter public class GitlabGetter
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** DH's instance of the Gitlab getter */ /** DH's instance of the Gitlab getter */
public static GitlabGetter INSTANCE = new GitlabGetter(); public static GitlabGetter INSTANCE = new GitlabGetter();
@@ -23,7 +23,7 @@ import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.json.JsonFormat; import com.electronwill.nightconfig.json.JsonFormat;
import com.seibel.distanthorizons.core.jar.updater.SelfUpdater; import com.seibel.distanthorizons.core.jar.updater.SelfUpdater;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.*;
@@ -35,7 +35,7 @@ import java.util.*;
*/ */
public class ModrinthGetter public class ModrinthGetter
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static final String ModrinthAPI = "https://api.modrinth.com/v2/project/"; public static final String ModrinthAPI = "https://api.modrinth.com/v2/project/";
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.jar.installer;
import com.electronwill.nightconfig.core.Config; import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.json.JsonFormat; import com.electronwill.nightconfig.json.JsonFormat;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import java.io.*; import java.io.*;
@@ -40,7 +40,7 @@ import java.util.ArrayList;
*/ */
public class WebDownloader public class WebDownloader
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static boolean netIsAvailable() public static boolean netIsAvailable()
@@ -33,7 +33,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.StringUtil; import com.seibel.distanthorizons.coreapi.util.StringUtil;
import com.seibel.distanthorizons.coreapi.util.jar.DeleteOnUnlock; import com.seibel.distanthorizons.coreapi.util.jar.DeleteOnUnlock;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.lwjgl.util.tinyfd.TinyFileDialogs; import org.lwjgl.util.tinyfd.TinyFileDialogs;
import javax.swing.*; import javax.swing.*;
@@ -57,7 +57,7 @@ import java.util.zip.ZipFile;
*/ */
public class SelfUpdater public class SelfUpdater
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** As we cannot delete(or replace) the jar while the mod is running, we just have this to delete it once the game closes */ /** As we cannot delete(or replace) the jar while the mod is running, we just have this to delete it once the game closes */
public static boolean deleteOldJarOnJvmShutdown = false; public static boolean deleteOldJarOnJvmShutdown = false;
@@ -96,7 +96,7 @@ public class SelfUpdater
} }
catch (Exception e) // Shouldn't be needed, but just in case catch (Exception e) // Shouldn't be needed, but just in case
{ {
LOGGER.warn(e); LOGGER.warn("Unexpected updater startup error: ["+e.getMessage()+"].", e);
} }
return returnValue; return returnValue;
} }
@@ -258,13 +258,14 @@ public class SelfUpdater
deleteOldJarOnJvmShutdown = true; deleteOldJarOnJvmShutdown = true;
String message = "Distant Horizons successfully updated. It will apply on game's relaunch"; // TODO one of these messages contains something TinyFd doesn't like, find it and fix it
LOGGER.info(message); String successMessage = "Distant Horizons successfully updated. It will apply on game's relaunch";
LOGGER.info(successMessage);
new Thread(() -> new Thread(() ->
{ {
try try
{ {
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, message, "ok", "info", false); TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, successMessage, "ok", "info", false);
} }
catch (Exception ignore) { } catch (Exception ignore) { }
}).start(); }).start();
@@ -283,12 +284,11 @@ public class SelfUpdater
} }
String message = "Failed to update Distant Horizons to version [" + ModrinthGetter.getLatestNameForVersion(minecraftVersion) + "], error: ["+e.getMessage()+"]."; String failMessage = "Failed to update Distant Horizons to version [" + ModrinthGetter.getLatestNameForVersion(minecraftVersion) + "], error: ["+e.getMessage()+"].";
LOGGER.error(failMessage, e);
LOGGER.error(message, e);
try try
{ {
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, message, "ok", "error", false); TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, failMessage, "ok", "error", false);
} }
catch (Exception ignore) { } catch (Exception ignore) { }
@@ -380,13 +380,13 @@ public class SelfUpdater
deleteOldJarOnJvmShutdown = true; deleteOldJarOnJvmShutdown = true;
String message = "Distant Horizons updated, this will be applied on game restart."; String successMessage = "Distant Horizons updated, this will be applied on game restart.";
LOGGER.info(message); LOGGER.info(successMessage);
new Thread(() -> new Thread(() ->
{ {
try try
{ {
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, message, "ok", "info", false); TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, successMessage, "ok", "info", false);
} }
catch (Exception ignore) { } catch (Exception ignore) { }
}).start(); }).start();
@@ -419,13 +419,12 @@ public class SelfUpdater
} }
String versionHash = GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha");
String message = "Failed to update [" + ModInfo.READABLE_NAME + "] to version [" + GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha") + "], error: ["+e.getMessage()+"]."; String failMessage = "Failed to update [" + ModInfo.READABLE_NAME + "] to version [" + versionHash + "], error: ["+e.getMessage()+"].";
LOGGER.error(failMessage, e);
LOGGER.error(message, e);
try try
{ {
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, message, "ok", "error", false); TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, failMessage, "ok", "error", false);
} }
catch (Exception ignore) { } catch (Exception ignore) { }
@@ -23,48 +23,53 @@ import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.io.ParsingMode; import com.electronwill.nightconfig.core.io.ParsingMode;
import com.electronwill.nightconfig.json.JsonFormat; import com.electronwill.nightconfig.json.JsonFormat;
import com.seibel.distanthorizons.core.jar.JarUtils; import com.seibel.distanthorizons.core.jar.JarUtils;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper;
import org.apache.logging.log4j.LogManager; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.Logger;
import java.util.Locale; import java.util.Locale;
public class LangWrapper implements ILangWrapper public class LangWrapper implements ILangWrapper
{ {
public static final LangWrapper INSTANCE = new LangWrapper(); public static final LangWrapper INSTANCE = new LangWrapper();
private static final Config jsonObject = Config.inMemory();
private static final Logger logger = LogManager.getLogger(LangWrapper.class.getSimpleName()); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final Config JSON_OBJECT = Config.inMemory();
public static void init() public static void init()
{ {
try try
{ {
// System.out.println(JarUtils.convertInputStreamToString(JarUtils.accessFile("assets/lod/lang/"+ Locale.getDefault().toString().toLowerCase()+".json")).replaceAll(":\\n.+?(?=\")",":"));
// FIXME: Is there something in the config that the parser cant read? // FIXME: Is there something in the config that the parser cant read?
JsonFormat.fancyInstance().createParser().parse( JsonFormat.fancyInstance().createParser().parse(
JarUtils.convertInputStreamToString(JarUtils.accessFile("assets/lod/lang/" + Locale.getDefault().toString().toLowerCase() + ".json")), JarUtils.convertInputStreamToString(JarUtils.accessFile("assets/lod/lang/" + Locale.getDefault().toString().toLowerCase() + ".json")),
jsonObject, ParsingMode.REPLACE JSON_OBJECT, ParsingMode.REPLACE
); );
} }
catch (Exception e) catch (Exception e)
{ {
logger.error("Failed to read lang file, error: ["+e.getMessage()+"]", e); LOGGER.error("Failed to read lang file, error: ["+e.getMessage()+"]", e);
} }
} }
@Override @Override
public boolean langExists(String str) public boolean langExists(String str) { return JSON_OBJECT.get(str) != null; }
{
return jsonObject.get(str) != null;
}
@Override @Override
public String getLang(String str) public String getLang(String str)
{ {
if (jsonObject.get(str) != null) if (JSON_OBJECT.get(str) != null)
return (String) jsonObject.get(str); {
return (String) JSON_OBJECT.get(str);
}
else else
{
return str; return str;
}
} }
} }
@@ -50,7 +50,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.awt.*; import java.awt.*;
@@ -66,7 +66,7 @@ import java.util.concurrent.locks.ReentrantLock;
public abstract class AbstractDhLevel implements IDhLevel public abstract class AbstractDhLevel implements IDhLevel
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** if this is null then the other handler is probably null too, but just in case */ /** if this is null then the other handler is probably null too, but just in case */
@Nullable @Nullable
@@ -86,8 +86,6 @@ public abstract class AbstractDhLevel implements IDhLevel
@Nullable @Nullable
protected CloudRenderHandler cloudRenderHandler; protected CloudRenderHandler cloudRenderHandler;
private IDhApiRenderableBoxGroup unexploredFogRenderableBoxGroup;
//=============// //=============//
@@ -187,7 +185,7 @@ public abstract class AbstractDhLevel implements IDhLevel
// block lights should have been populated at the chunkWrapper stage // block lights should have been populated at the chunkWrapper stage
// waiting to populate the data source's skylight at this stage prevents re-lighting and // waiting to populate the data source's skylight at this stage prevents re-lighting and
// allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once // allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, this.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, this.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
return this.updateDataSourcesAsync(fullDataSource) return this.updateDataSourcesAsync(fullDataSource)
@@ -393,15 +391,10 @@ public abstract class AbstractDhLevel implements IDhLevel
} }
GenericObjectRenderer genericRenderer = this.getGenericRenderer();
if (genericRenderer != null
&& this.unexploredFogRenderableBoxGroup != null)
{
genericRenderer.remove(this.unexploredFogRenderableBoxGroup.getId());
}
this.delayedFullDataSourceSaveCache.close(); this.delayedFullDataSourceSaveCache.close();
} }
} }
@@ -2,10 +2,9 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.multiplayer.server.FullDataSourceRequestHandler; import com.seibel.distanthorizons.core.multiplayer.server.FullDataSourceRequestHandler;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
@@ -26,7 +25,8 @@ import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.List; import java.util.List;
@@ -34,7 +34,7 @@ import java.util.concurrent.*;
public abstract class AbstractDhServerLevel extends AbstractDhLevel implements IDhServerLevel public abstract class AbstractDhServerLevel extends AbstractDhLevel implements IDhServerLevel
{ {
protected static final Logger LOGGER = DhLoggerBuilder.getLogger(); protected static final DhLogger LOGGER = new DhLoggerBuilder().build();
public final ServerLevelModule serverside; public final ServerLevelModule serverside;
protected final IServerLevelWrapper serverLevelWrapper; protected final IServerLevelWrapper serverLevelWrapper;
@@ -296,27 +296,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
@Override @Override
public void addDebugMenuStringsToList(List<String> messageList) public void addDebugMenuStringsToList(List<String> messageList)
{ {
// migration this.serverside.fullDataFileHandler.addDebugMenuStringsToList(messageList);
boolean migrationErrored = this.serverside.fullDataFileHandler.getMigrationStoppedWithError();
if (!migrationErrored)
{
long legacyDeletionCount = this.serverside.fullDataFileHandler.getLegacyDeletionCount();
if (legacyDeletionCount > 0)
{
messageList.add(" Migrating - Deleting #: " + F3Screen.NUMBER_FORMAT.format(legacyDeletionCount));
}
long migrationCount = this.serverside.fullDataFileHandler.getTotalMigrationCount();
if (migrationCount > 0)
{
messageList.add(" Migrating - Conversion #: " + F3Screen.NUMBER_FORMAT.format(migrationCount));
}
}
else
{
messageList.add(" Migration Failed");
}
// world gen
this.serverside.worldGenModule.addDebugMenuStringsToList(messageList); this.serverside.worldGenModule.addDebugMenuStringsToList(messageList);
} }
@@ -326,15 +306,11 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
// getters // // getters //
//=========// //=========//
@Override
public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
@Override
public int getMaxY() { return this.getLevelWrapper().getMaxHeight(); }
@Override @Override
public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; } public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
@Override @Override
@NotNull
public ILevelWrapper getLevelWrapper() { return this.getServerLevelWrapper(); } public ILevelWrapper getLevelWrapper() { return this.getServerLevelWrapper(); }
@Override @Override
@@ -343,9 +319,6 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
@Override @Override
public ISaveStructure getSaveStructure() { return this.serverside.saveStructure; } public ISaveStructure getSaveStructure() { return this.serverside.saveStructure; }
@Override
public boolean hasSkyLight() { return this.serverLevelWrapper.hasSkyLight(); }
//==========// //==========//
@@ -19,33 +19,29 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler; import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.LodQuadTree; import com.seibel.distanthorizons.core.render.LodQuadTree;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import javax.annotation.WillNotClose; import javax.annotation.WillNotClose;
import java.io.Closeable; import java.io.Closeable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.IDataSourceUpdateFunc<FullDataSourceV2> public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFunc<FullDataSourceV2>
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private final IDhClientLevel clientLevel; private final IDhClientLevel clientLevel;
@@ -73,7 +69,7 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
this.clientLevel = clientLevel; this.clientLevel = clientLevel;
this.fullDataSourceProvider = this.clientLevel.getFullDataProvider(); this.fullDataSourceProvider = this.clientLevel.getFullDataProvider();
this.fullDataSourceProvider.dateSourceUpdateListeners.add(this); this.fullDataSourceProvider.addDataSourceUpdateListener(this);
} }
@@ -82,8 +78,6 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
// tick methods // // tick methods //
//==============// //==============//
private EDhApiDebugRendering lastDebugRendering = EDhApiDebugRendering.OFF;
public void clientTick() public void clientTick()
{ {
// can be false if the level is unloading // can be false if the level is unloading
@@ -97,6 +91,7 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
{ {
return; return;
} }
// TODO this should probably be handled via a config change listener // TODO this should probably be handled via a config change listener
// recreate the RenderState if the render distance changes // recreate the RenderState if the render distance changes
if (clientRenderState.quadtree.blockRenderDistanceDiameter != Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() * LodUtil.CHUNK_WIDTH * 2) if (clientRenderState.quadtree.blockRenderDistanceDiameter != Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() * LodUtil.CHUNK_WIDTH * 2)
@@ -106,14 +101,8 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
return; return;
} }
IClientLevelWrapper clientLevelWrapper = this.clientLevel.getClientLevelWrapper();
if (clientLevelWrapper == null)
{
return;
}
clientRenderState.close(); clientRenderState.close();
clientRenderState = new ClientRenderState(this.clientLevel, clientLevelWrapper, this.clientLevel.getFullDataProvider(), this.genericRenderer); clientRenderState = new ClientRenderState(this.clientLevel, this.clientLevel.getFullDataProvider());
if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState)) if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState))
{ {
//FIXME: How to handle this? //FIXME: How to handle this?
@@ -122,69 +111,28 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
return; return;
} }
} }
clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
boolean isBuffersDirty = false; clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
EDhApiDebugRendering newDebugRendering = Config.Client.Advanced.Debugging.debugRendering.get();
if (newDebugRendering != lastDebugRendering)
{
lastDebugRendering = newDebugRendering;
isBuffersDirty = true;
}
if (isBuffersDirty)
{
clientRenderState.lodRenderer.bufferHandler.MarkAllBuffersDirty();
}
} }
//========// //========//
// render // // render //
//========// //========//
/** @return if the {@link ClientRenderState} was successfully swapped */ // TODO start rendering during frame request
public boolean startRenderer(IClientLevelWrapper clientLevelWrapper) public void startRenderer()
{ {
// TODO why are we passing in a level wrapper? Our client level is already defined. ClientRenderState ClientRenderState = new ClientRenderState(this.clientLevel, this.clientLevel.getFullDataProvider());
ClientRenderState ClientRenderState = new ClientRenderState(this.clientLevel, clientLevelWrapper, this.clientLevel.getFullDataProvider(), this.genericRenderer);
if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState)) if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState))
{ {
LOGGER.warn("Failed to start renderer due to concurrency"); LOGGER.warn("Failed to start renderer due to concurrency");
ClientRenderState.close(); ClientRenderState.close();
return false;
}
else
{
return true;
} }
} }
public boolean isRendering() public boolean isRendering() { return this.ClientRenderStateRef.get() != null; }
{
return this.ClientRenderStateRef.get() != null;
}
public void render(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
{
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
// either the renderer hasn't been started yet, or is being reloaded
return;
}
ClientRenderState.lodRenderer.drawLods(ClientRenderState.clientLevelWrapper, renderEventParam, profiler);
}
public void renderDeferred(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
{
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
// either the renderer hasn't been started yet, or is being reloaded
return;
}
ClientRenderState.lodRenderer.drawDeferredLods(ClientRenderState.clientLevelWrapper, renderEventParam, profiler);
}
public void stopRenderer() public void stopRenderer()
{ {
@@ -213,7 +161,8 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
// data handling // // data handling //
//===============// //===============//
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.clientLevel.getFullDataProvider().updateDataSourceAsync(data); } public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data)
{ return this.clientLevel.getFullDataProvider().updateDataSourceAsync(data); }
@Override @Override
public void OnDataSourceUpdated(FullDataSourceV2 updatedFullDataSource) public void OnDataSourceUpdated(FullDataSourceV2 updatedFullDataSource)
{ {
@@ -247,7 +196,7 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
} }
} }
this.fullDataSourceProvider.dateSourceUpdateListeners.remove(this); this.fullDataSourceProvider.removeDataSourceUpdateListener(this);
} }
@@ -288,12 +237,10 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
public static class ClientRenderState implements Closeable public static class ClientRenderState implements Closeable
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public final IClientLevelWrapper clientLevelWrapper;
public final LodQuadTree quadtree; public final LodQuadTree quadtree;
public final RenderBufferHandler renderBufferHandler; public final RenderBufferHandler renderBufferHandler;
public final LodRenderer lodRenderer;
@@ -302,19 +249,17 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
//=============// //=============//
public ClientRenderState( public ClientRenderState(
IDhClientLevel dhClientLevel, IClientLevelWrapper clientLevelWrapper, IDhClientLevel dhClientLevel,
FullDataSourceProviderV2 fullDataSourceProvider, FullDataSourceProviderV2 fullDataSourceProvider)
GenericObjectRenderer genericRenderer)
{ {
this.clientLevelWrapper = clientLevelWrapper; this.quadtree = new LodQuadTree(
dhClientLevel,
this.quadtree = new LodQuadTree(dhClientLevel, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() * LodUtil.CHUNK_WIDTH * 2, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() * LodUtil.CHUNK_WIDTH * 2,
// initial position is (0,0) just in case the player hasn't loaded in yet, the tree will be moved once the level starts ticking // initial position is (0,0) just in case the player hasn't loaded in yet, the tree will be moved once the level starts ticking
0, 0, 0, 0,
fullDataSourceProvider); fullDataSourceProvider);
this.renderBufferHandler = new RenderBufferHandler(this.quadtree); this.renderBufferHandler = new RenderBufferHandler(this.quadtree);
this.lodRenderer = new LodRenderer(this.renderBufferHandler, genericRenderer);
} }
@@ -327,8 +272,6 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
public void close() public void close()
{ {
LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName()); LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName());
this.lodRenderer.close();
this.quadtree.close(); this.quadtree.close();
} }
@@ -20,15 +20,14 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue; import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState; import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue; import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
@@ -42,11 +41,9 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.LogManager; import org.jetbrains.annotations.NotNull;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
@@ -61,22 +58,24 @@ import java.util.concurrent.TimeUnit;
/** The level used when connected to a server */ /** The level used when connected to a server */
public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
protected static final ConfigBasedLogger NETWORK_LOGGER = new ConfigBasedLogger(LogManager.getLogger(), private static final DhLogger NETWORK_LOGGER = new DhLoggerBuilder()
() -> Config.Common.Logging.logNetworkEvent.get()); .fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public final ClientLevelModule clientside; public final ClientLevelModule clientside;
public final IClientLevelWrapper levelWrapper; public final IClientLevelWrapper levelWrapper;
public final ISaveStructure saveStructure; public final ISaveStructure saveStructure;
public final RemoteFullDataSourceProvider dataFileHandler; public final RemoteFullDataSourceProvider remoteDataSourceProvider;
@CheckForNull @CheckForNull
private final ClientNetworkState networkState; private final ClientNetworkState networkState;
@Nullable @Nullable
private final ScopedNetworkEventSource networkEventSource; private final ScopedNetworkEventSource networkEventSource;
/** used when connected to a DH supported server so we don't process the same chunks multiple times */
private final Set<DhChunkPos> loadedOnceChunks = Collections.newSetFromMap( private final Set<DhChunkPos> loadedOnceChunks = Collections.newSetFromMap(
CacheBuilder.newBuilder() CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) .expireAfterWrite(10, TimeUnit.MINUTES)
@@ -96,8 +95,8 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
//=============// //=============//
public DhClientLevel(ISaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable ClientNetworkState networkState) public DhClientLevel(ISaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable ClientNetworkState networkState)
{ this(saveStructure, clientLevelWrapper, null, true, networkState); } { this(saveStructure, clientLevelWrapper, null, networkState); }
public DhClientLevel(ISaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable File fullDataSaveDirOverride, boolean enableRendering, @Nullable ClientNetworkState networkState) public DhClientLevel(ISaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable File fullDataSaveDirOverride, @Nullable ClientNetworkState networkState)
{ {
File saveFolder = saveStructure.getSaveFolder(clientLevelWrapper); File saveFolder = saveStructure.getSaveFolder(clientLevelWrapper);
File pre23Folder = saveStructure.getPre23SaveFolder(clientLevelWrapper); File pre23Folder = saveStructure.getPre23SaveFolder(clientLevelWrapper);
@@ -115,7 +114,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
} }
this.levelWrapper = clientLevelWrapper; this.levelWrapper = clientLevelWrapper;
this.levelWrapper.setParentLevel(this); this.levelWrapper.setDhLevel(this);
this.saveStructure = saveStructure; this.saveStructure = saveStructure;
this.networkState = networkState; this.networkState = networkState;
@@ -131,19 +130,16 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
this.syncOnLoadRequestQueue = null; this.syncOnLoadRequestQueue = null;
} }
this.dataFileHandler = new RemoteFullDataSourceProvider(this, saveStructure, fullDataSaveDirOverride, this.syncOnLoadRequestQueue); this.remoteDataSourceProvider = new RemoteFullDataSourceProvider(this, saveStructure, fullDataSaveDirOverride, this.syncOnLoadRequestQueue);
this.worldGenModule = new WorldGenModule(this, this.dataFileHandler, () -> new WorldGenState(this, networkState)); this.worldGenModule = new WorldGenModule(this, this.remoteDataSourceProvider, () -> new WorldGenState(this, networkState));
this.clientside = new ClientLevelModule(this); this.clientside = new ClientLevelModule(this);
this.createAndSetSupportingRepos(this.dataFileHandler.repo.databaseFile); this.createAndSetSupportingRepos(this.remoteDataSourceProvider.repo.databaseFile);
this.runRepoReliantSetup(); this.runRepoReliantSetup();
if (enableRendering) this.clientside.startRenderer();
{ LOGGER.info("Started DHLevel for " + this.levelWrapper + " with saves at " + this.saveStructure);
this.clientside.startRenderer(clientLevelWrapper);
LOGGER.info("Started DHLevel for " + this.levelWrapper + " with saves at " + this.saveStructure);
}
} }
private void registerNetworkHandlers() private void registerNetworkHandlers()
{ {
@@ -161,7 +157,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload)) try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload))
{ {
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper); boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
NETWORK_LOGGER.debug("Buffer {} isSameLevel: {}", message.payload.dtoBufferId, isSameLevel); NETWORK_LOGGER.debug("Buffer ["+message.payload.dtoBufferId+"] isSameLevel: ["+isSameLevel+"]");
if (!isSameLevel) if (!isSameLevel)
{ {
return; return;
@@ -186,7 +182,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
} }
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.levelWrapper); FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.levelWrapper, null);
this.updateDataSourcesAsync(fullDataSource) this.updateDataSourcesAsync(fullDataSource)
.whenComplete((result, e) -> fullDataSource.close()); .whenComplete((result, e) -> fullDataSource.close());
} }
@@ -241,30 +237,12 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Override @Override
@Nullable @Nullable
public DhBlockPos2D getTargetPosForGeneration() public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); }
{
return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos());
}
@Override @Override
public void worldGenTick() public void worldGenTick() { this.worldGenModule.worldGenTick(); }
{
this.worldGenModule.worldGenTick();
}
public void startRenderer() { this.clientside.startRenderer(); }
//===========//
// rendering //
//===========//
@Override
public void render(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
{ this.clientside.render(renderEventParam, profiler); }
@Override
public void renderDeferred(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
{ this.clientside.renderDeferred(renderEventParam, profiler); }
@@ -298,25 +276,18 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
public void clearRenderCache() { this.clientside.clearRenderCache(); } public void clearRenderCache() { this.clientside.clearRenderCache(); }
@Override @Override
@NotNull
public ILevelWrapper getLevelWrapper() { return this.levelWrapper; } public ILevelWrapper getLevelWrapper() { return this.levelWrapper; }
@Override @Override
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.clientside.updateDataSourcesAsync(data); } public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.clientside.updateDataSourcesAsync(data); }
@Override @Override
public int getMinY() { return this.levelWrapper.getMinHeight(); } public FullDataSourceProviderV2 getFullDataProvider() { return this.remoteDataSourceProvider; }
@Override
public int getMaxY() { return this.levelWrapper.getMaxHeight(); }
@Override
public FullDataSourceProviderV2 getFullDataProvider() { return this.dataFileHandler; }
@Override @Override
public ISaveStructure getSaveStructure() { return this.saveStructure; } public ISaveStructure getSaveStructure() { return this.saveStructure; }
@Override
public boolean hasSkyLight() { return this.levelWrapper.hasSkyLight(); }
@Override @Override
public GenericObjectRenderer getGenericRenderer() { return this.clientside.genericRenderer; } public GenericObjectRenderer getGenericRenderer() { return this.clientside.genericRenderer; }
@Override @Override
@@ -350,24 +321,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
messageList.add("["+dimName+"] rendering: "+(rendering ? "yes" : "no")); messageList.add("["+dimName+"] rendering: "+(rendering ? "yes" : "no"));
boolean migrationErrored = this.dataFileHandler.getMigrationStoppedWithError(); this.remoteDataSourceProvider.addDebugMenuStringsToList(messageList);
if (!migrationErrored)
{
long legacyDeletionCount = this.dataFileHandler.getLegacyDeletionCount();
if (legacyDeletionCount > 0)
{
messageList.add(" Migrating - Deleting #: " + legacyDeletionCount);
}
long migrationCount = this.dataFileHandler.getTotalMigrationCount();
if (migrationCount > 0)
{
messageList.add(" Migrating - Conversion #: " + migrationCount);
}
}
else
{
messageList.add(" Migration Failed");
}
// world gen // world gen
@@ -404,10 +358,10 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
this.networkEventSource.close(); this.networkEventSource.close();
} }
this.levelWrapper.setParentLevel(null); this.levelWrapper.setDhLevel(null);
this.clientside.close(); this.clientside.close();
super.close(); super.close();
this.dataFileHandler.close(); this.remoteDataSourceProvider.close();
LOGGER.info("Closed [" + DhClientLevel.class.getSimpleName() + "] for [" + this.levelWrapper + "]"); LOGGER.info("Closed [" + DhClientLevel.class.getSimpleName() + "] for [" + this.levelWrapper + "]");
} }
@@ -20,28 +20,22 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.awt.*; import java.awt.*;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
/** The level used on a singleplayer world */ /** The level used for a singleplayer world */
public class DhClientServerLevel extends AbstractDhServerLevel implements IDhClientLevel public class DhClientServerLevel extends AbstractDhServerLevel implements IDhClientLevel
{ {
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@@ -58,7 +52,7 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
{ {
super(saveStructure, serverLevelWrapper, serverPlayerStateManager, false); super(saveStructure, serverLevelWrapper, serverPlayerStateManager, false);
this.serverLevelWrapper.setParentLevel(this); this.serverLevelWrapper.setDhLevel(this);
this.clientside = new ClientLevelModule(this); this.clientside = new ClientLevelModule(this);
this.runRepoReliantSetup(); this.runRepoReliantSetup();
} }
@@ -72,19 +66,13 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
@Override @Override
public void clientTick() { this.clientside.clientTick(); } public void clientTick() { this.clientside.clientTick(); }
@Override
public void render(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
{ this.clientside.render(renderEventParam, profiler); }
@Override
public void renderDeferred(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
{ this.clientside.renderDeferred(renderEventParam, profiler); }
//========// //========//
// render // // render //
//========// //========//
public void startRenderer(IClientLevelWrapper clientLevel) { this.clientside.startRenderer(clientLevel); } public void startRenderer() { this.clientside.startRenderer(); }
public void stopRenderer() { this.clientside.stopRenderer(); } public void stopRenderer() { this.clientside.stopRenderer(); }
@@ -107,7 +107,6 @@ public class DhServerLevel extends AbstractDhServerLevel
{ {
super.close(); super.close();
this.serverside.close(); this.serverside.close();
LOGGER.info("Closed DHLevel for ["+this.getLevelWrapper()+"].");
} }
} }
@@ -19,21 +19,17 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
/**
* Used when running in singleplayer
* or when connected to a server.
*/
public interface IDhClientLevel extends IDhLevel public interface IDhClientLevel extends IDhLevel
{ {
void clientTick(); void clientTick();
void render(DhApiRenderParam renderEventParam, IProfilerWrapper profiler);
void renderDeferred(DhApiRenderParam renderEventParam, IProfilerWrapper profiler);
@Nullable @Nullable
IClientLevelWrapper getClientLevelWrapper(); IClientLevelWrapper getClientLevelWrapper();
@@ -19,10 +19,10 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
@@ -31,25 +31,39 @@ import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRend
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/**
* A DH Level handles all DH-centric logic related to a given MC level.
* A Level in this context is defined as a Minecraft dimension the player can play in
* (IE the overworld, nether, end, etc.). <br><br>
*
* This is different from a {@link ILevelWrapper}
* in the following ways: <br>
* - a DH level is created after a MC level is wrapped and passed into the {@link ClientApi} or {@link ServerApi} respectively <br>
* - a DH level doesn't handle any MC level logic (IE getting the min/max world height) <br>
* - a DH level keeps track of DH's database file paths and rendering <br>
*
* @see ILevelWrapper
* @see IDhClientLevel
* @see IDhServerLevel
*/
public interface IDhLevel extends AutoCloseable, GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener public interface IDhLevel extends AutoCloseable, GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener
{ {
@Deprecated
void worldGenTick(); void worldGenTick();
int getMinY();
int getMaxY();
/** /**
* May return either a client or server level wrapper. <br> * May return either a client or server level wrapper. <br>
* Should not return null * Should not return null
*/ */
@NotNull
ILevelWrapper getLevelWrapper(); ILevelWrapper getLevelWrapper();
/** @return 0 if no hash is known */ /** @return 0 if no hash is known */
@@ -72,8 +86,6 @@ public interface IDhLevel extends AutoCloseable, GeneratedFullDataSourceProvider
ISaveStructure getSaveStructure(); ISaveStructure getSaveStructure();
boolean hasSkyLight();
CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data); CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data);
void addDebugMenuStringsToList(List<String> messageList); void addDebugMenuStringsToList(List<String> messageList);
@@ -30,7 +30,7 @@ public interface IKeyedClientLevelManager extends IBindable
{ {
IServerKeyedClientLevel getServerKeyedLevel(); IServerKeyedClientLevel getServerKeyedLevel();
/** Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. */ /** Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. */
IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String levelKey); IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String serverKey, String levelKey);
void clearKeyedLevel(); void clearKeyedLevel();
boolean isEnabled(); boolean isEnabled();
@@ -24,6 +24,9 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
/** Enhances a {@link IClientLevelWrapper} with server provided level information. */ /** Enhances a {@link IClientLevelWrapper} with server provided level information. */
public interface IServerKeyedClientLevel extends IClientLevelWrapper public interface IServerKeyedClientLevel extends IClientLevelWrapper
{ {
/** Returns the folder name the server wants the player to use. */
String getServerKey();
/** Returns the level key, which is used to select the correct folder on the client. */ /** Returns the level key, which is used to select the correct folder on the client. */
String getServerLevelKey(); String getServerLevelKey();
@@ -26,11 +26,11 @@ import com.seibel.distanthorizons.core.generation.BatchGenerator;
import com.seibel.distanthorizons.core.generation.WorldGenerationQueue; import com.seibel.distanthorizons.core.generation.WorldGenerationQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.DependencyInjection.WorldGeneratorInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.WorldGeneratorInjector;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
public class ServerLevelModule implements AutoCloseable public class ServerLevelModule implements AutoCloseable
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final IDhServerLevel parentServerLevel; private final IDhServerLevel parentServerLevel;
public final ISaveStructure saveStructure; public final ISaveStructure saveStructure;
@@ -33,7 +33,7 @@ import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.io.Closeable; import java.io.Closeable;
import java.time.Duration; import java.time.Duration;
@@ -49,7 +49,7 @@ import java.util.function.Supplier;
*/ */
public class WorldGenModule implements Closeable public class WorldGenModule implements Closeable
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener; private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener;
@@ -1,128 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.distanthorizons.core.logging;
import com.seibel.distanthorizons.api.enums.config.EDhApiLoggerMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Supplier;
public class ConfigBasedLogger
{
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final List<WeakReference<ConfigBasedLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<ConfigBasedLogger>>());
public static synchronized void updateAll()
{
loggers.removeIf((logger) -> logger.get() == null);
loggers.forEach((logger) -> {
ConfigBasedLogger l = logger.get();
if (l != null)
l.update();
});
}
private EDhApiLoggerMode mode;
private final Supplier<EDhApiLoggerMode> getter;
private final Logger logger;
public ConfigBasedLogger(Logger logger, Supplier<EDhApiLoggerMode> configQuery)
{
getter = configQuery;
mode = getter.get();
this.logger = logger;
loggers.add(new WeakReference<>(this));
}
private static boolean isLessSpecificThan(Level _this, Level other)
{
return _this.intLevel() >= other.intLevel();
}
private String _throwableToDetailString(Throwable t)
{
StringBuilder sb = new StringBuilder();
sb.append(t.toString()).append('\n');
StackTraceElement[] stacks = t.getStackTrace();
if (stacks == null || stacks.length == 0)
return sb.append(" at {Stack trace unavailable}").toString();
for (StackTraceElement stack : stacks)
{
sb.append(" at ").append(stack.toString()).append("\n");
}
return sb.toString();
}
public void update()
{
mode = getter.get();
}
public boolean canMaybeLog()
{
return mode != EDhApiLoggerMode.DISABLED;
}
public void log(Level level, String str, Object... param)
{
Message msg = param.length > 0
? this.logger.getMessageFactory().newMessage(str, param)
: this.logger.getMessageFactory().newMessage("{}", str);
String msgStr = msg.getFormattedMessage();
if (isLessSpecificThan(mode.levelForFile, level))
{
Level logLevel = isLessSpecificThan(level, Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable) param[param.length - 1]);
else
logger.log(logLevel, msgStr);
}
if (MC != null && isLessSpecificThan(mode.levelForChat, level))
{
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
MC.logToChat(level, msgStr + "\n" +
_throwableToDetailString(((Throwable) param[param.length - 1])));
else
MC.logToChat(level, msgStr);
}
}
public void error(String str, Object... param) { this.log(Level.ERROR, str, param); }
public void warn(String str, Object... param) { this.log(Level.WARN, str, param); }
public void info(String str, Object... param) { this.log(Level.INFO, str, param); }
public void debug(String str, Object... param) { this.log(Level.DEBUG, str, param); }
public void trace(String str, Object... param) { this.log(Level.TRACE, str, param); }
}
@@ -1,211 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.distanthorizons.core.logging;
import com.seibel.distanthorizons.api.enums.config.EDhApiLoggerMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class ConfigBasedSpamLogger
{
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final List<WeakReference<ConfigBasedSpamLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<ConfigBasedSpamLogger>>());
public static synchronized void updateAll(boolean flush)
{
loggers.removeIf((logger) -> logger.get() == null);
loggers.forEach((logger) -> {
ConfigBasedSpamLogger l = logger.get();
if (l != null)
l.update();
if (l != null && flush)
l.reset();
});
}
private EDhApiLoggerMode mode;
private final Supplier<EDhApiLoggerMode> getter;
private final int maxLogCount;
private final AtomicInteger logTries = new AtomicInteger(0);
private final Logger logger;
public ConfigBasedSpamLogger(Logger logger, Supplier<EDhApiLoggerMode> configQuery, int maxLogPerSec)
{
getter = configQuery;
mode = getter.get();
maxLogCount = maxLogPerSec;
this.logger = logger;
loggers.add(new WeakReference<>(this));
}
private static boolean isLessSpecificThan(Level _this, Level other)
{
return _this.intLevel() >= other.intLevel();
}
public void reset()
{
logTries.set(0);
}
public boolean canMaybeLog()
{
return mode != EDhApiLoggerMode.DISABLED && logTries.get() < maxLogCount;
}
public void update()
{
mode = getter.get();
}
private String _throwableToDetailString(Throwable t)
{
StringBuilder sb = new StringBuilder();
sb.append(t.toString()).append('\n');
StackTraceElement[] stacks = t.getStackTrace();
if (stacks == null || stacks.length == 0)
return sb.append(" at {Stack trace unavailable}").toString();
for (StackTraceElement stack : stacks)
{
sb.append(" at ").append(stack.toString()).append("\n");
}
return sb.toString();
}
public void log(Level level, String str, Object... param)
{
if (logTries.get() >= maxLogCount)
return;
Message msg = logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage();
if (isLessSpecificThan(mode.levelForFile, level))
{
Level logLevel = isLessSpecificThan(level, Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable) param[param.length - 1]);
else
logger.log(logLevel, msgStr);
}
if (isLessSpecificThan(mode.levelForChat, level))
{
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
MC.logToChat(level, msgStr + "\n" +
_throwableToDetailString(((Throwable) param[param.length - 1])));
else
MC.logToChat(level, msgStr);
}
}
public void error(String str, Object... param)
{
log(Level.ERROR, str, param);
}
public void warn(String str, Object... param)
{
log(Level.WARN, str, param);
}
public void info(String str, Object... param)
{
log(Level.INFO, str, param);
}
public void debug(String str, Object... param)
{
log(Level.DEBUG, str, param);
}
public void trace(String str, Object... param)
{
log(Level.TRACE, str, param);
}
public void incLogTries()
{
logTries.getAndIncrement();
}
public void logInc(Level level, String str, Object... param)
{
if (logTries.getAndIncrement() >= maxLogCount)
return;
Message msg = logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage();
if (isLessSpecificThan(mode.levelForFile, level))
{
Level logLevel = isLessSpecificThan(level, Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable) param[param.length - 1]);
else
logger.log(logLevel, msgStr);
}
if (isLessSpecificThan(mode.levelForChat, level))
{
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
MC.logToChat(level, msgStr + "\n" +
_throwableToDetailString(((Throwable) param[param.length - 1])));
else
MC.logToChat(level, msgStr);
}
}
public void errorInc(String str, Object... param)
{
logInc(Level.ERROR, str, param);
}
public void warnInc(String str, Object... param)
{
logInc(Level.WARN, str, param);
}
public void infoInc(String str, Object... param)
{
logInc(Level.INFO, str, param);
}
public void debugInc(String str, Object... param)
{
logInc(Level.DEBUG, str, param);
}
public void traceInc(String str, Object... param)
{
logInc(Level.TRACE, str, param);
}
}
@@ -0,0 +1,343 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.distanthorizons.core.logging;
import com.seibel.distanthorizons.api.enums.config.EDhApiLoggerLevel;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
/**
* should only be created in a static context,
* otherwise leaks will occur.
*
* @see DhLoggerBuilder
*/
public class DhLogger implements IConfigListener
{
private static final List<WeakReference<DhLogger>> LOGGER_REF_LIST = Collections.synchronizedList(new LinkedList<>());
private static final ThreadPoolExecutor TICKER_THREAD = ThreadUtil.makeSingleDaemonThreadPool("Log Ticker");
/**
* This logger uses the base 4J logger so we don't have to worry about
* setup order and can track errors when setting up DH loggers.
* <br>
* Yo dog, I heard you liked loggers, so I put a logger in your logger.
*/
private static final Logger LOGGER = LogManager.getLogger();
private static IMinecraftClientWrapper mc_client = null;
// both global configs start at "all" so we don't accidentally loose any logging messages
private static EDhApiLoggerLevel globalMaxFileLevel = EDhApiLoggerLevel.ALL;
private static EDhApiLoggerLevel globalMaxChatLevel = EDhApiLoggerLevel.ALL;
private EDhApiLoggerLevel fileLevel;
private EDhApiLoggerLevel chatLevel;
private boolean delayedSetupComplete = false;
@Nullable
private final ConfigEntry<EDhApiLoggerLevel> fileLevelConfig;
@Nullable
private final ConfigEntry<EDhApiLoggerLevel> chatLevelConfig;
/** if less than 0 then this logger won't be rate limited */
private final int maxLogCountPerSecond;
private final AtomicInteger logCountsThisSecondRef = new AtomicInteger(0);
private final Logger logger;
//==============//
// static setup //
//==============//
static { TICKER_THREAD.execute(() -> runTickerLoop()); }
/**
* needs to be run at a later date since loggers
* are created before the config is set up.
*/
public static void runDelayedConfigSetup()
{
LOGGER.info("Applying config to loggers");
LOGGER_REF_LIST.forEach((loggerRef) ->
{
DhLogger logger = loggerRef.get();
if (logger != null
&& !logger.delayedSetupComplete)
{
logger.delayedSetupComplete = true;
Config.Common.Logging.globalChatMaxLevel.addListener(logger);
Config.Common.Logging.globalFileMaxLevel.addListener(logger);
logger.onConfigValueSet();
}
});
}
//=============//
// constructor //
//=============//
public DhLogger(String loggerName,
@Nullable ConfigEntry<EDhApiLoggerLevel> chatLevelConfig, @Nullable ConfigEntry<EDhApiLoggerLevel> fileLevelConfig,
int maxLogPerSec)
{
this.logger = LogManager.getLogger(ModInfo.NAME + "-" + loggerName);
this.maxLogCountPerSecond = maxLogPerSec;
// chat config //
this.chatLevelConfig = chatLevelConfig;
if (this.chatLevelConfig != null)
{
this.chatLevel = this.chatLevelConfig.get();
this.chatLevelConfig.addListener(this);
}
else
{
// chat messages should only be sent when explicitly desired
this.chatLevel = EDhApiLoggerLevel.DISABLED;
}
// file config //
this.fileLevelConfig = fileLevelConfig;
if (this.fileLevelConfig != null)
{
this.fileLevel = this.fileLevelConfig.get();
this.fileLevelConfig.addListener(this);
}
else
{
this.fileLevel = EDhApiLoggerLevel.ALL;
}
LOGGER_REF_LIST.add(new WeakReference<>(this));
}
//========//
// events //
//========//
@Override
public void onConfigValueSet()
{
if (this.fileLevelConfig != null)
{
this.fileLevel = this.fileLevelConfig.get();
}
if (this.chatLevelConfig != null)
{
this.chatLevel = this.chatLevelConfig.get();
}
globalMaxFileLevel = Config.Common.Logging.globalFileMaxLevel.get();
globalMaxChatLevel = Config.Common.Logging.globalChatMaxLevel.get();
}
//===============//
// log filtering //
//===============//
public boolean canLog()
{
// is this logger enabled?
if (this.fileLevel == EDhApiLoggerLevel.DISABLED
&& this.chatLevel == EDhApiLoggerLevel.DISABLED)
{
return false;
}
// is logging globally enabled?
if (globalMaxFileLevel == EDhApiLoggerLevel.DISABLED
&& globalMaxChatLevel == EDhApiLoggerLevel.DISABLED)
{
return false;
}
// has this logger run to many times this second already?
if (this.maxLogCountPerSecond > 0
&& this.logCountsThisSecondRef.get() >= this.maxLogCountPerSecond)
{
return false;
}
return true;
}
//=========//
// logging //
//=========//
public void fatal(String str, Object... param) { this.log(Level.FATAL, str, param); }
public void error(String str, Object... param) { this.log(Level.ERROR, str, param); }
public void warn(String str, Object... param){ this.log(Level.WARN, str, param); }
public void info(String str, Object... param) { this.log(Level.INFO, str, param); }
public void debug(String str, Object... param) { this.log(Level.DEBUG, str, param); }
public void trace(String str, Object... param) { this.log(Level.TRACE, str, param); }
public void log(Level level, String str, Object... param)
{
if (!this.canLog())
{
return;
}
Message msg = this.logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage();
boolean messageLogged = false;
// file
if (canLogThisLevel(this.fileLevel.level, globalMaxFileLevel.level, level))
{
// by default MC's console/file logging config doesn't include debug or trace
// logs, so to make sure our messages are included we need to set any lower levels as "info"
Level logLevel = loggingLevelIsLessSpecificThan(level, Level.INFO) ? Level.INFO : level;
if (param.length > 0
&& param[param.length - 1] instanceof Throwable)
{
this.logger.log(logLevel, msgStr, (Throwable) param[param.length - 1]);
}
else
{
this.logger.log(logLevel, msgStr);
}
messageLogged = true;
}
// chat
if (canLogThisLevel(this.chatLevel.level, globalMaxChatLevel.level, level))
{
// lazy initialization since loggers may be created before the wrapper has been bound
if (mc_client == null)
{
mc_client = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
}
if (mc_client != null)
{
mc_client.logToChat(level, msgStr);
messageLogged = true;
}
}
if (messageLogged)
{
this.logCountsThisSecondRef.incrementAndGet();
}
}
private static boolean canLogThisLevel(Level thisLoggingLevel, Level thisGlobalLoggingLevel, Level requestedLogLevel)
{
// we can only continue if this logger's level and the global level
// are at the same or a higher level than the requested level
return thisLoggingLevel.intLevel() >= requestedLogLevel.intLevel()
&& thisGlobalLoggingLevel.intLevel() >= requestedLogLevel.intLevel();
}
private static boolean loggingLevelIsLessSpecificThan(Level thisLoggingLevel, Level requestedLogLevel)
{ return thisLoggingLevel.intLevel() >= requestedLogLevel.intLevel(); }
//===============//
// static ticker //
//===============//
private static void runTickerLoop()
{
while (true)
{
try
{
// tick once every second
Thread.sleep(1_000);
LOGGER_REF_LIST.removeIf((logger) ->
{
boolean loggerGarbageCollected = logger.get() == null;
if (loggerGarbageCollected)
{
LOGGER.warn("Logger garbage collected. Loggers should only be created in static contexts otherwise memory leaks may occur.");
}
return loggerGarbageCollected;
});
LOGGER_REF_LIST.forEach((loggerRef) ->
{
DhLogger logger = loggerRef.get();
if (logger != null)
{
// reset the number of logs for this second
logger.logCountsThisSecondRef.set(0);
}
});
}
catch (Exception e)
{
LOGGER.error("Unexpected error in ticker thread: [" + e.getMessage() + "].", e);
}
}
}
}
@@ -19,52 +19,37 @@
package com.seibel.distanthorizons.core.logging; package com.seibel.distanthorizons.core.logging;
import com.seibel.distanthorizons.api.enums.config.EDhApiLoggerLevel;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
/** /**
* Used to create loggers with specific names. * @see DhLogger
*
* @author James Seibel
* @version 2022-4-24
*/ */
public class DhLoggerBuilder public class DhLoggerBuilder
{ {
/** private String name;
* Creates a logger in the format <br> private @Nullable ConfigEntry<EDhApiLoggerLevel> chatLevelConfig;
* "ModInfo.Name-className" <br> private @Nullable ConfigEntry<EDhApiLoggerLevel> fileLevelConfig;
* For example: <br> private int maxLogPerSec = -1;
* "DistantHorizons-ReflectionHandler" <br><br>
*
* The suggested way to use this method is like this: <br><br>
* <code>
* private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
* </code> <br><br>
* By using MethodHandles you don't have to manually enter the class name,
* Java figures that out for you. Even in a static context.
*
* @param className name of the class this logger will be named after.
*/
public static Logger getLogger(String className)
{
return LogManager.getLogger(ModInfo.NAME + "-" + className);
}
/** Returns a logger for the given class. */
public static Logger getLogger(Class<?> clazz)
{
return LogManager.getLogger(ModInfo.NAME + "-" + clazz.getSimpleName());
}
/** Attempts to return the logger for this containing class. */
public static Logger getLogger()
{
return LogManager.getLogger(ModInfo.NAME + "-" + getCallingClassName());
}
//=============//
// constructor //
//=============//
public DhLoggerBuilder() { this.name = ModInfo.NAME + "-" + getCallingClassName(); }
/** @return "??" if no name could be found */ /** @return "??" if no name could be found */
public static String getCallingClassName() private static String getCallingClassName()
{ {
StackTraceElement[] stElements = Thread.currentThread().getStackTrace(); StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
String callerClassName = "??"; String callerClassName = "??";
@@ -83,4 +68,57 @@ public class DhLoggerBuilder
} }
//===========//
// variables //
//===========//
public DhLoggerBuilder name(String name)
{
this.name = name;
return this;
}
public DhLoggerBuilder chatLevelConfig(ConfigEntry<EDhApiLoggerLevel> chatLevelConfig)
{
this.chatLevelConfig = chatLevelConfig;
return this;
}
public DhLoggerBuilder fileLevelConfig(ConfigEntry<EDhApiLoggerLevel> fileLevelConfig)
{
this.fileLevelConfig = fileLevelConfig;
return this;
}
public DhLoggerBuilder maxCountPerSecond(int maxLogPerSec)
{
this.maxLogPerSec = maxLogPerSec;
return this;
}
//=======//
// build //
//=======//
public DhLogger build()
{
try
{
return new DhLogger(
this.name,
this.chatLevelConfig, this.fileLevelConfig,
this.maxLogPerSec
);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
} }
@@ -1,143 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.distanthorizons.core.logging;
import java.lang.invoke.MethodHandles;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class SpamReducedLogger
{
private static final Logger LOGGER = LogManager.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
public static final List<WeakReference<SpamReducedLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<SpamReducedLogger>>());
public static synchronized void flushAll()
{
loggers.removeIf((logger) -> logger.get() == null);
loggers.forEach((logger) -> {
SpamReducedLogger l = logger.get();
if (l != null)
l.reset();
});
}
private final int maxLogCount;
private final AtomicInteger logTries = new AtomicInteger(0);
public SpamReducedLogger(int maxLogPerSec)
{
maxLogCount = maxLogPerSec;
loggers.add(new WeakReference<SpamReducedLogger>(this));
}
private static boolean isLessSpecificThan(Level _this, Level other)
{
return _this.intLevel() >= other.intLevel();
}
public void reset()
{
logTries.set(0);
}
public boolean canMaybeLog()
{
return logTries.get() < maxLogCount;
}
public void log(Level level, String str, Object... param)
{
if (logTries.get() >= maxLogCount)
return;
LOGGER.log(isLessSpecificThan(level, Level.INFO) ? Level.INFO : level, str, param);
}
public void error(String str, Object... param)
{
log(Level.ERROR, str, param);
}
public void warn(String str, Object... param)
{
log(Level.WARN, str, param);
}
public void info(String str, Object... param)
{
log(Level.INFO, str, param);
}
public void debug(String str, Object... param)
{
log(Level.DEBUG, str, param);
}
public void trace(String str, Object... param)
{
log(Level.TRACE, str, param);
}
public void incLogTries()
{
logTries.getAndIncrement();
}
public void logInc(Level level, String str, Object... param)
{
if (logTries.getAndIncrement() >= maxLogCount)
return;
LOGGER.log(isLessSpecificThan(level, Level.INFO) ? Level.INFO : level, str, param);
}
public void errorInc(String str, Object... param)
{
logInc(Level.ERROR, str, param);
}
public void warnInc(String str, Object... param)
{
logInc(Level.WARN, str, param);
}
public void infoInc(String str, Object... param)
{
logInc(Level.INFO, str, param);
}
public void debugInc(String str, Object... param)
{
logInc(Level.DEBUG, str, param);
}
public void traceInc(String str, Object... param)
{
logInc(Level.TRACE, str, param);
}
}
@@ -19,11 +19,13 @@
package com.seibel.distanthorizons.core.logging.f3; package com.seibel.distanthorizons.core.logging.f3;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.jar.ModJarInfo; import com.seibel.distanthorizons.core.jar.ModJarInfo;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
@@ -35,7 +37,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.StringUtil; import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.*; import java.util.*;
@@ -43,7 +45,7 @@ import java.util.concurrent.ThreadPoolExecutor;
public class F3Screen public class F3Screen
{ {
private static final Logger LOGGER = LogManager.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final NumberFormat NUMBER_FORMAT = NumberFormat.getIntegerInstance(); public static final NumberFormat NUMBER_FORMAT = NumberFormat.getIntegerInstance();
@@ -109,6 +111,13 @@ public class F3Screen
messageList.add("Build: " + StringUtil.shortenString(ModJarInfo.Git_Commit, 8) + " (" + ModJarInfo.Git_Branch + ")"); messageList.add("Build: " + StringUtil.shortenString(ModJarInfo.Git_Commit, 8) + " (" + ModJarInfo.Git_Branch + ")");
} }
// render validation error
if (ClientApi.INSTANCE.lastRenderParamValidationMessage != null)
{
messageList.add("Render Validation Err: " + ClientApi.INSTANCE.lastRenderParamValidationMessage);
}
// player pos // player pos
if (Config.Client.Advanced.Debugging.F3Screen.showPlayerPos.get()) if (Config.Client.Advanced.Debugging.F3Screen.showPlayerPos.get())
{ {
@@ -7,7 +7,8 @@ import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException; import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException; import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
@@ -25,7 +26,6 @@ import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimite
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.LogManager;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -39,8 +39,10 @@ import java.util.function.Consumer;
public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRenderable, AutoCloseable public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRenderable, AutoCloseable
{ {
private static final ConfigBasedSpamLogger LOGGER = new ConfigBasedSpamLogger(LogManager.getLogger(), private static final DhLogger LOGGER = new DhLoggerBuilder()
() -> Config.Common.Logging.logNetworkEvent.get(), 3); .fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.maxCountPerSecond(3)
.build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@@ -277,7 +279,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
{ {
this.level.updateBeaconBeamsForSectionPos(dataSourceDto.pos, response.payload.beaconBeams); this.level.updateBeaconBeamsForSectionPos(dataSourceDto.pos, response.payload.beaconBeams);
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.level.getLevelWrapper()); FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.level.getLevelWrapper(), null);
entry.dataSourceConsumer.accept(fullDataSource); entry.dataSourceConsumer.accept(fullDataSource);
} }
catch (Exception e) catch (Exception e)
@@ -364,7 +366,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
if (removeIf.accept(pos)) if (removeIf.accept(pos))
{ {
LOGGER.debug("Removing request " + mapEntry.getKey() + "..."); LOGGER.debug("Removing request [" + mapEntry.getKey() + "]...");
entry.future.cancel(false); entry.future.cancel(false);
if (entry.networkDataSourceFuture != null) if (entry.networkDataSourceFuture != null)
@@ -3,7 +3,8 @@ package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig; import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadReceiver; import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadReceiver;
import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource; import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource;
@@ -17,7 +18,6 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartial
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.Closeable; import java.io.Closeable;
@@ -25,8 +25,9 @@ import java.util.List;
public class ClientNetworkState implements Closeable public class ClientNetworkState implements Closeable
{ {
protected static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), protected static final DhLogger LOGGER = new DhLoggerBuilder()
() -> Config.Common.Logging.logNetworkEvent.get()); .fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@@ -1,27 +1,24 @@
package com.seibel.distanthorizons.core.multiplayer.fullData; package com.seibel.distanthorizons.core.multiplayer.fullData;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalNotification;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.INetworkObject; import com.seibel.distanthorizons.core.network.INetworkObject;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.buffer.UnpooledByteBufAllocator;
import org.apache.logging.log4j.LogManager;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class FullDataPayloadReceiver implements AutoCloseable public class FullDataPayloadReceiver implements AutoCloseable
{ {
private static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), private static final DhLogger LOGGER = new DhLoggerBuilder()
() -> Config.Common.Logging.logNetworkEvent.get()); .fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build();
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder() private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS) .expireAfterAccess(10, TimeUnit.SECONDS)
@@ -5,7 +5,8 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel; import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayload; import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayload;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException; import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.exceptions.SectionRequiresSplittingException; import com.seibel.distanthorizons.core.network.exceptions.SectionRequiresSplittingException;
@@ -14,7 +15,6 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceR
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import org.apache.logging.log4j.LogManager;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -23,8 +23,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class FullDataSourceRequestHandler public class FullDataSourceRequestHandler
{ {
private static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), private static final DhLogger LOGGER = new DhLoggerBuilder()
() -> Config.Common.Logging.logNetworkEvent.get()); .fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build();
private final AbstractDhServerLevel serverLevel; private final AbstractDhServerLevel serverLevel;
@@ -28,6 +28,10 @@ public class ServerPlayerState implements Closeable
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage); private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
private final String serverKeyWithoutId = Config.Server.serverKey.get();
private final String serverKey = (this.serverKeyWithoutId.isEmpty() ? "" : Config.Server.serverId.get() + "_" + this.serverKeyWithoutId.trim())
.replaceAll("[^" + LevelInitMessage.ALLOWED_CHARS_REGEX + " ]", "")
.replaceAll(" ", "_");
private String lastLevelKey = ""; private String lastLevelKey = "";
@@ -90,7 +94,7 @@ public class ServerPlayerState implements Closeable
if (!levelKey.equals(this.lastLevelKey)) if (!levelKey.equals(this.lastLevelKey))
{ {
this.lastLevelKey = levelKey; this.lastLevelKey = levelKey;
this.networkSession.sendMessage(new LevelInitMessage(levelKey)); this.networkSession.sendMessage(new LevelInitMessage(this.serverKey, levelKey));
} }
} }
} }
@@ -21,7 +21,8 @@ package com.seibel.distanthorizons.core.network.event;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.event.internal.AbstractInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.AbstractInternalEvent;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage; import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
@@ -30,7 +31,6 @@ import com.seibel.distanthorizons.core.network.session.SessionClosedException;
import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage; import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage; import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
import java.io.InvalidClassException; import java.io.InvalidClassException;
import java.util.Collections; import java.util.Collections;
@@ -40,8 +40,9 @@ import java.util.function.Consumer;
public abstract class AbstractNetworkEventSource public abstract class AbstractNetworkEventSource
{ {
private static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), private static final DhLogger LOGGER = new DhLoggerBuilder()
() -> Config.Common.Logging.logNetworkEvent.get()); .fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build();
/** /**
@@ -8,14 +8,20 @@ public class LevelInitMessage extends AbstractNetworkMessage
{ {
public static final int MAX_LENGTH = 150; public static final int MAX_LENGTH = 150;
public static final String PART_ALLOWED_CHARS_REGEX = "a-zA-Z0-9-_"; public static final String ALLOWED_CHARS_REGEX = "a-zA-Z0-9-_";
// A plain string of characters
// 1-150 characters in total
public static final String SERVER_KEY_REGEX = String.format("^(?=.{1,%s}$)[%s]+$",
MAX_LENGTH, ALLOWED_CHARS_REGEX);
// prefix@namespace:path // prefix@namespace:path
// 1-150 characters in total, all parts except namespace can be omitted // 1-150 characters in total, all parts except namespace can be omitted
public static final String VALIDATION_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$", public static final String LEVEL_KEY_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$",
MAX_LENGTH, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX); MAX_LENGTH, ALLOWED_CHARS_REGEX, ALLOWED_CHARS_REGEX, ALLOWED_CHARS_REGEX);
public String serverKey;
public String levelKey; public String levelKey;
public long serverTime; public long serverTime;
@@ -26,8 +32,9 @@ public class LevelInitMessage extends AbstractNetworkMessage
//==============// //==============//
public LevelInitMessage() { } public LevelInitMessage() { }
public LevelInitMessage(String levelKey) public LevelInitMessage(String serverKey, String levelKey)
{ {
this.serverKey = serverKey;
this.levelKey = levelKey; this.levelKey = levelKey;
this.serverTime = System.currentTimeMillis(); this.serverTime = System.currentTimeMillis();
} }
@@ -41,6 +48,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
@Override @Override
public void encode(ByteBuf out) public void encode(ByteBuf out)
{ {
this.writeString(this.serverKey, out);
this.writeString(this.levelKey, out); this.writeString(this.levelKey, out);
out.writeLong(this.serverTime); out.writeLong(this.serverTime);
} }
@@ -48,6 +56,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
@Override @Override
public void decode(ByteBuf in) public void decode(ByteBuf in)
{ {
this.serverKey = this.readString(in);
this.levelKey = this.readString(in); this.levelKey = this.readString(in);
this.serverTime = in.readLong(); this.serverTime = in.readLong();
} }
@@ -62,6 +71,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
public MoreObjects.ToStringHelper toStringHelper() public MoreObjects.ToStringHelper toStringHelper()
{ {
return super.toStringHelper() return super.toStringHelper()
.add("serverKey", this.serverKey)
.add("levelKey", this.levelKey) .add("levelKey", this.levelKey)
.add("serverTime", this.serverTime); .add("serverTime", this.serverTime);
} }
@@ -2,7 +2,8 @@ package com.seibel.distanthorizons.core.network.session;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.event.AbstractNetworkEventSource; import com.seibel.distanthorizons.core.network.event.AbstractNetworkEventSource;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.event.internal.ProtocolErrorInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.ProtocolErrorInternalEvent;
@@ -11,7 +12,6 @@ import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage; import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import org.apache.logging.log4j.LogManager;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -21,8 +21,9 @@ import java.util.function.Consumer;
public class NetworkSession extends AbstractNetworkEventSource public class NetworkSession extends AbstractNetworkEventSource
{ {
private static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), private static final DhLogger LOGGER = new DhLoggerBuilder()
() -> Config.Common.Logging.logNetworkEvent.get()); .fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build();
private static final IPluginPacketSender PACKET_SENDER = SingletonInjector.INSTANCE.get(IPluginPacketSender.class); private static final IPluginPacketSender PACKET_SENDER = SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
@@ -1,7 +1,8 @@
package com.seibel.distanthorizons.core.pooling; package com.seibel.distanthorizons.core.pooling;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.lang.ref.PhantomReference; import java.lang.ref.PhantomReference;
@@ -18,7 +19,7 @@ import java.lang.ref.PhantomReference;
*/ */
public abstract class AbstractPhantomArrayList implements AutoCloseable public abstract class AbstractPhantomArrayList implements AutoCloseable
{ {
private static final Logger LOGGER = LogManager.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final PhantomArrayListPool phantomArrayListPool; private final PhantomArrayListPool phantomArrayListPool;
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.pooling;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.ThreadUtil;
@@ -11,7 +12,7 @@ import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList; import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import org.apache.logging.log4j.Logger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -54,7 +55,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
public class PhantomArrayListPool public class PhantomArrayListPool
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** /**
* the recycler thread needs to be triggered relatively frequently to prevent * the recycler thread needs to be triggered relatively frequently to prevent
@@ -344,8 +344,8 @@ public class DhSectionPos
} }
return DhSectionPos.encode(getDetailLevel(pos), return DhSectionPos.encode(getDetailLevel(pos),
getX(pos) + dir.getNormal().x, getX(pos) + dir.normal.x,
getZ(pos) + dir.getNormal().z); getZ(pos) + dir.normal.z);
} }
@Deprecated @Deprecated
@@ -75,9 +75,9 @@ public class DhBlockPos implements INetworkObject
//========// //========//
/** creates a new {@link DhBlockPos} with the given offset from the current pos. */ /** creates a new {@link DhBlockPos} with the given offset from the current pos. */
public DhBlockPos createOffset(EDhDirection direction) { return this.mutateOrCreateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, null); } public DhBlockPos createOffset(EDhDirection direction) { return this.mutateOrCreateOffset(direction.normal.x, direction.normal.y, direction.normal.z, null); }
/** if not null, mutates "mutablePos" so it matches the current pos after being offset. Otherwise creates a new {@link DhBlockPos}. */ /** if not null, mutates "mutablePos" so it matches the current pos after being offset. Otherwise creates a new {@link DhBlockPos}. */
public void mutateOffset(EDhDirection direction, @NotNull DhBlockPosMutable mutablePos) { this.mutateOrCreateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, mutablePos); } public void mutateOffset(EDhDirection direction, @NotNull DhBlockPosMutable mutablePos) { this.mutateOrCreateOffset(direction.normal.x, direction.normal.y, direction.normal.z, mutablePos); }
public DhBlockPos createOffset(int x, int y, int z) { return this.mutateOrCreateOffset(x,y,z, null); } public DhBlockPos createOffset(int x, int y, int z) { return this.mutateOrCreateOffset(x,y,z, null); }
public void mutateOffset(int x, int y, int z, @NotNull DhBlockPosMutable mutablePos) { this.mutateOrCreateOffset(x, y, z, mutablePos); } public void mutateOffset(int x, int y, int z, @NotNull DhBlockPosMutable mutablePos) { this.mutateOrCreateOffset(x, y, z, mutablePos); }

Some files were not shown because too many files have changed in this diff Show More