Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 034ec7d656 | |||
| fb5e15a2f1 | |||
| 674fc30e77 | |||
| a05bd307f9 | |||
| d78a50ce49 | |||
| 013eab9268 | |||
| 435cbde238 | |||
| d7040bad13 | |||
| a588070ce1 | |||
| d156772438 | |||
| de7ae41769 | |||
| 618ad1938b | |||
| 5b10263f82 | |||
| 34f914c52f | |||
| 67b766c674 | |||
| a3e7469203 | |||
| 4ecaa6a9a1 | |||
| 24f9dadc58 | |||
| b3ebaffa85 | |||
| b7ac1909d6 | |||
| 32c3118afa | |||
| 3a525f53f0 | |||
| f3947312c1 | |||
| 839ea1e778 | |||
| d2becd2c03 | |||
| 7d87347199 | |||
| f4117751c9 | |||
| a8a085f296 | |||
| 317319593e | |||
| 4633f90a03 | |||
| 5802bbb3f3 | |||
| e93d5b90f1 | |||
| 9be56607a5 | |||
| 91743bf742 | |||
| d40d293f54 | |||
| a075e60e3e | |||
| d72c7c3695 | |||
| 309fa07664 | |||
| 0a017567c4 | |||
| e01261da5c | |||
| a0879d07c5 | |||
| bbb15263f2 | |||
| 5ca3563c66 | |||
| 30256a2779 | |||
| 4b4f10f5e6 | |||
| ad995544f7 | |||
| d521e931f4 | |||
| dd30a8274a | |||
| 3ca5efadc9 | |||
| 09174c2d2a | |||
| e079b28e77 | |||
| 136124a703 | |||
| 3ed50e5134 | |||
| b5e3e6867c | |||
| 3e04342148 | |||
| 6699b568df | |||
| 53bee4ad42 | |||
| 5d5e462221 | |||
| d9b924cfed | |||
| 8bd70d593c | |||
| 5597044604 | |||
| 5d7c043d06 | |||
| 4aac61b37f | |||
| 22460fa1f5 | |||
| 2d127c7d98 | |||
| 91e17c420a | |||
| 93f5a85cb5 | |||
| b275971486 | |||
| 1234ff4d28 | |||
| f9bd7e2daf | |||
| 8ec4e235eb | |||
| b8a59d0ef6 | |||
| e500143781 | |||
| 406468b54c | |||
| 6857300ae2 | |||
| 6775ee23c3 | |||
| 44645943e2 | |||
| f385c4a56b | |||
| 0cf5e6d594 | |||
| 7b5b8da0d2 | |||
| 851f2ccd06 | |||
| 6c40389c07 | |||
| fada9e4cf6 | |||
| 06198fdbb8 | |||
| 3158eed5a3 | |||
| d2ff4a5806 |
+1
-2
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = crlf
|
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
indent_style = space
|
indent_style = space
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
@@ -537,7 +536,7 @@ ij_groovy_wrap_chain_calls_after_dot = false
|
|||||||
ij_groovy_wrap_long_lines = false
|
ij_groovy_wrap_long_lines = false
|
||||||
|
|
||||||
[{*.har,*.json,*.png.mcmeta,mcmod.info,pack.mcmeta}]
|
[{*.har,*.json,*.png.mcmeta,mcmod.info,pack.mcmeta}]
|
||||||
indent_size = 2
|
indent_size = 4
|
||||||
ij_json_array_wrapping = split_into_lines
|
ij_json_array_wrapping = split_into_lines
|
||||||
ij_json_keep_blank_lines_in_code = 0
|
ij_json_keep_blank_lines_in_code = 0
|
||||||
ij_json_keep_indents_on_empty_lines = false
|
ij_json_keep_indents_on_empty_lines = false
|
||||||
|
|||||||
+17
-16
@@ -39,8 +39,8 @@ public enum EDhApiDataCompressionMode
|
|||||||
/**
|
/**
|
||||||
* Should only be used internally and for unit testing. <br><br>
|
* Should only be used internally and for unit testing. <br><br>
|
||||||
*
|
*
|
||||||
* Read Speed: 1.64 MS / DTO <br>
|
* Read Speed: 6.09 MS / DTO <br>
|
||||||
* Write Speed: 12.44 MS / DTO <br>
|
* Write Speed: 6.01 MS / DTO <br>
|
||||||
* Compression ratio: 1.0 <br>
|
* Compression ratio: 1.0 <br>
|
||||||
*/
|
*/
|
||||||
@DisallowSelectingViaConfigGui
|
@DisallowSelectingViaConfigGui
|
||||||
@@ -49,28 +49,29 @@ public enum EDhApiDataCompressionMode
|
|||||||
/**
|
/**
|
||||||
* Extremely fast (often faster than uncompressed), but generally poor compression. <br><br>
|
* Extremely fast (often faster than uncompressed), but generally poor compression. <br><br>
|
||||||
*
|
*
|
||||||
* Read Speed: 1.85 MS / DTO <br>
|
* Read Speed: 3.25 MS / DTO <br>
|
||||||
* Write Speed: 9.46 MS / DTO <br>
|
* Write Speed: 5.99 MS / DTO <br>
|
||||||
* Compression ratio: 0.3638 <br>
|
* Compression ratio: 0.4513 <br>
|
||||||
*/
|
*/
|
||||||
LZ4(1),
|
LZ4(1),
|
||||||
|
|
||||||
/*
|
///**
|
||||||
* Decent speed and good compression. <br><br>
|
// * Decent speed and good compression. <br><br>
|
||||||
*
|
// *
|
||||||
* Read Speed: 11.78 MS / DTO <br>
|
// * Read Speed: 9.31 MS / DTO <br>
|
||||||
* Write Speed: 16.76 MS / DTO <br>
|
// * Write Speed: 15.13 MS / DTO <br>
|
||||||
* Compression ratio: 0.2199 <br>
|
// * Compression ratio: 0.2606 <br>
|
||||||
*/
|
// */
|
||||||
//@Deprecated
|
////@DisallowSelectingViaConfigGui
|
||||||
//Z_STD(2),
|
//Z_STD(2),
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extremely slow, but very good compression. <br><br>
|
* Extremely slow, but very good compression. <br><br>
|
||||||
*
|
*
|
||||||
* Read Speed: 12.25 MS / DTO <br>
|
* Read Speed: 13.29 MS / DTO <br>
|
||||||
* Write Speed: 490.07 MS / DTO <br>
|
* Write Speed: 70.95 MS / DTO <br>
|
||||||
* Compression ratio: 0.1242 <br>
|
* Compression ratio: 0.2068 <br>
|
||||||
*/
|
*/
|
||||||
LZMA2(3);
|
LZMA2(3);
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public enum EDhApiLoggerMode
|
|||||||
LOG_DEBUG_TO_CHAT_AND_FILE(Level.DEBUG, Level.DEBUG),
|
LOG_DEBUG_TO_CHAT_AND_FILE(Level.DEBUG, Level.DEBUG),
|
||||||
LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.WARN),
|
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_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 levelForFile;
|
||||||
|
|||||||
+4
-1
@@ -9,12 +9,15 @@ package com.seibel.distanthorizons.api.interfaces.data;
|
|||||||
* @version 2024-7-14
|
* @version 2024-7-14
|
||||||
* @since API 3.0.0
|
* @since API 3.0.0
|
||||||
*/
|
*/
|
||||||
public interface IDhApiTerrainDataCache
|
public interface IDhApiTerrainDataCache // TODO should this be AutoClosable?
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Removes any data that's currently stored in this cache.
|
* Removes any data that's currently stored in this cache.
|
||||||
* This cane be done to free up memory or invalidate
|
* This cane be done to free up memory or invalidate
|
||||||
* the cache so fresh data can be pulled in.
|
* the cache so fresh data can be pulled in.
|
||||||
|
* <br><br>
|
||||||
|
* This should be called before de-referencing this object
|
||||||
|
* so DH can handle any necessary cleanup for internal objects.
|
||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
|||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.methods.events.abstractEvents;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after Distant Horizons (re)creates
|
||||||
|
* the color and depth textures it renders to. <br>
|
||||||
|
*
|
||||||
|
* @author James Seibel
|
||||||
|
* @version 2025-6-9
|
||||||
|
* @since API 4.1.0
|
||||||
|
*/
|
||||||
|
public abstract class DhApiAfterColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiTextureCreatedParam>
|
||||||
|
{
|
||||||
|
/** Fired before Distant Horizons creates. */
|
||||||
|
public abstract void onResize(DhApiEventParam<DhApiTextureCreatedParam> event);
|
||||||
|
|
||||||
|
|
||||||
|
//=========================//
|
||||||
|
// internal DH API methods //
|
||||||
|
//=========================//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void fireEvent(DhApiEventParam<DhApiTextureCreatedParam> event) { this.onResize(event); }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.methods.events.abstractEvents;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before Distant Horizons (re)creates
|
||||||
|
* the color and depth textures it renders to. <br>
|
||||||
|
*
|
||||||
|
* @author James Seibel
|
||||||
|
* @version 2025-6-9
|
||||||
|
* @since API 4.1.0
|
||||||
|
*/
|
||||||
|
public abstract class DhApiBeforeColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiTextureCreatedParam>
|
||||||
|
{
|
||||||
|
/** Fired before Distant Horizons creates. */
|
||||||
|
public abstract void onResize(DhApiEventParam<DhApiTextureCreatedParam> event);
|
||||||
|
|
||||||
|
|
||||||
|
//=========================//
|
||||||
|
// internal DH API methods //
|
||||||
|
//=========================//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void fireEvent(DhApiEventParam<DhApiTextureCreatedParam> event) { this.onResize(event); }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+13
-1
@@ -22,15 +22,18 @@ package com.seibel.distanthorizons.api.methods.events.abstractEvents;
|
|||||||
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
|
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
|
||||||
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
|
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
|
||||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called whenever Distant Horizons (re)creates
|
* Called before Distant Horizons (re)creates
|
||||||
* the color and depth textures it renders to. <br>
|
* the color and depth textures it renders to. <br>
|
||||||
*
|
*
|
||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 2024-3-2
|
* @version 2024-3-2
|
||||||
* @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
|
||||||
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. */
|
||||||
@@ -73,6 +76,15 @@ public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<
|
|||||||
this.newHeight = newHeight;
|
this.newHeight = newHeight;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
public EventParam(DhApiTextureCreatedParam textureCreatedParam)
|
||||||
|
{
|
||||||
|
this.previousWidth = textureCreatedParam.previousWidth;
|
||||||
|
this.previousHeight = textureCreatedParam.previousHeight;
|
||||||
|
|
||||||
|
this.newWidth = textureCreatedParam.newWidth;
|
||||||
|
this.newHeight = textureCreatedParam.newHeight;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.methods.events.sharedParameterObjects;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
|
||||||
|
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains information relevant to when Distant Horizons (re)creates
|
||||||
|
* depth/color textures for rendering.
|
||||||
|
*
|
||||||
|
* @author James Seibel
|
||||||
|
* @version 2025-6-9
|
||||||
|
* @since API 4.1.0
|
||||||
|
*/
|
||||||
|
public class DhApiTextureCreatedParam implements IDhApiEventParam
|
||||||
|
{
|
||||||
|
/** Measured in pixels */
|
||||||
|
public final int previousWidth;
|
||||||
|
/** Measured in pixels */
|
||||||
|
public final int previousHeight;
|
||||||
|
|
||||||
|
/** Measured in pixels */
|
||||||
|
public final int newWidth;
|
||||||
|
/** Measured in pixels */
|
||||||
|
public final int newHeight;
|
||||||
|
|
||||||
|
|
||||||
|
public DhApiTextureCreatedParam(
|
||||||
|
int previousWidth, int previousHeight,
|
||||||
|
int newWidth, int newHeight)
|
||||||
|
{
|
||||||
|
this.previousWidth = previousWidth;
|
||||||
|
this.previousHeight = previousHeight;
|
||||||
|
|
||||||
|
this.newWidth = newWidth;
|
||||||
|
this.newHeight = newHeight;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DhApiTextureCreatedParam copy()
|
||||||
|
{
|
||||||
|
return new DhApiTextureCreatedParam(
|
||||||
|
this.previousWidth, this.previousHeight,
|
||||||
|
this.newWidth, this.newHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 = 10;
|
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.0-b";
|
public static final String VERSION = "2.3.5-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 = 4;
|
||||||
/** 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 = 0;
|
public static final int API_MINOR_VERSION = 1;
|
||||||
/** 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;
|
||||||
|
|
||||||
|
|||||||
+6
-8
@@ -31,14 +31,12 @@ import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
|
|||||||
public class RenderModeEnabledConverter implements IConverter<EDhApiRendererMode, Boolean>
|
public class RenderModeEnabledConverter implements IConverter<EDhApiRendererMode, Boolean>
|
||||||
{
|
{
|
||||||
|
|
||||||
@Override public EDhApiRendererMode convertToCoreType(Boolean renderingEnabled)
|
@Override
|
||||||
{
|
public EDhApiRendererMode convertToCoreType(Boolean renderingEnabled)
|
||||||
return renderingEnabled ? EDhApiRendererMode.DEFAULT : EDhApiRendererMode.DISABLED;
|
{ return renderingEnabled ? EDhApiRendererMode.DEFAULT : EDhApiRendererMode.DISABLED; }
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Boolean convertToApiType(EDhApiRendererMode renderingMode)
|
@Override
|
||||||
{
|
public Boolean convertToApiType(EDhApiRendererMode renderingMode)
|
||||||
return renderingMode == EDhApiRendererMode.DEFAULT;
|
{ return renderingMode == EDhApiRendererMode.DEFAULT; }
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,3 +61,9 @@ shadowJar {
|
|||||||
// relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil"
|
// relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil"
|
||||||
mergeServiceFiles()
|
mergeServiceFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
// this is necessary specifically for the Compression tests since those
|
||||||
|
// need more than the default 512 MB of RAM
|
||||||
|
jvmArgs '-Xmx4096m'
|
||||||
|
}
|
||||||
|
|||||||
+2
-1
@@ -26,6 +26,7 @@ import com.seibel.distanthorizons.api.interfaces.config.client.*;
|
|||||||
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
|
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.converters.RenderModeEnabledConverter;
|
||||||
|
|
||||||
public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
|
public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
|
||||||
{
|
{
|
||||||
@@ -60,7 +61,7 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IDhApiConfigValue<Boolean> renderingEnabled()
|
public IDhApiConfigValue<Boolean> renderingEnabled()
|
||||||
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.quickEnableRendering); }
|
{ return new DhApiConfigValue<EDhApiRendererMode, Boolean>(Config.Client.Advanced.Debugging.rendererMode, new RenderModeEnabledConverter()); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IDhApiConfigValue<EDhApiRendererMode> renderingMode()
|
public IDhApiConfigValue<EDhApiRendererMode> renderingMode()
|
||||||
|
|||||||
+18
-6
@@ -197,10 +197,10 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
|
|||||||
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
|
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
|
||||||
|
|
||||||
|
|
||||||
if (!(apiDataCache instanceof DhApiTerrainDataCache))
|
// the data cache can be null, but must be our own implementation
|
||||||
|
if (apiDataCache != null
|
||||||
|
&& !(apiDataCache instanceof DhApiTerrainDataCache))
|
||||||
{
|
{
|
||||||
// custom level wrappers aren't supported,
|
|
||||||
// the API user must get a level wrapper from our code somewhere
|
|
||||||
return DhApiResult.createFail("Unsupported [" + IDhApiTerrainDataCache.class.getSimpleName() + "] implementation, only the core class [" + DhApiTerrainDataCache.class.getSimpleName() + "] is a valid parameter.");
|
return DhApiResult.createFail("Unsupported [" + IDhApiTerrainDataCache.class.getSimpleName() + "] implementation, only the core class [" + DhApiTerrainDataCache.class.getSimpleName() + "] is a valid parameter.");
|
||||||
}
|
}
|
||||||
DhApiTerrainDataCache dataCache = (DhApiTerrainDataCache) apiDataCache;
|
DhApiTerrainDataCache dataCache = (DhApiTerrainDataCache) apiDataCache;
|
||||||
@@ -226,10 +226,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
|
|||||||
// get the data source //
|
// get the data source //
|
||||||
//=====================//
|
//=====================//
|
||||||
|
|
||||||
|
FullDataSourceV2 dataSource = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
FullDataSourceV2 dataSource = null;
|
|
||||||
|
|
||||||
// try using the cached data if possible
|
// try using the cached data if possible
|
||||||
if (dataCache != null)
|
if (dataCache != null)
|
||||||
{
|
{
|
||||||
@@ -244,7 +243,12 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
|
|||||||
{
|
{
|
||||||
return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + DhSectionPos.toString(sectionPos) + "].");
|
return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + DhSectionPos.toString(sectionPos) + "].");
|
||||||
}
|
}
|
||||||
dataCache.add(sectionPos, dataSource);
|
|
||||||
|
// save to the cache if present
|
||||||
|
if (dataCache != null)
|
||||||
|
{
|
||||||
|
dataCache.add(sectionPos, dataSource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -316,6 +320,14 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
|
|||||||
LOGGER.error("Unexpected exception in getTerrainDataColumnArray. Error: [" + e.getMessage() + "]", e);
|
LOGGER.error("Unexpected exception in getTerrainDataColumnArray. Error: [" + e.getMessage() + "]", e);
|
||||||
return DhApiResult.createFail("Unexpected exception: [" + e.getMessage() + "].");
|
return DhApiResult.createFail("Unexpected exception: [" + e.getMessage() + "].");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (dataCache == null
|
||||||
|
&& dataSource != null)
|
||||||
|
{
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,15 @@ public class ClientApi
|
|||||||
/** 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This isn't the cleanest way of storing variables before passing them to the LOD renderer,
|
||||||
|
* but due to how mixins work and the inconsistency between MC versions,
|
||||||
|
* having a static object that stores a single frame's data
|
||||||
|
* is often the easiest solution. <br><br>
|
||||||
|
*
|
||||||
|
* Only downside is making sure each variable is populated before rendering.
|
||||||
|
*/
|
||||||
|
public static final RenderState RENDER_STATE = new RenderState();
|
||||||
|
|
||||||
|
|
||||||
private boolean isDevBuildMessagePrinted = false;
|
private boolean isDevBuildMessagePrinted = false;
|
||||||
@@ -389,7 +398,7 @@ public class ClientApi
|
|||||||
// rendering //
|
// rendering //
|
||||||
//===========//
|
//===========//
|
||||||
|
|
||||||
/** Should be called before {@link ClientApi#renderDeferredLods} */
|
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
|
||||||
public void renderLods(IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
|
public void renderLods(IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
|
||||||
{ this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, false); }
|
{ this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, false); }
|
||||||
|
|
||||||
@@ -397,9 +406,10 @@ public class ClientApi
|
|||||||
* Only necessary when Shaders are in use.
|
* Only necessary when Shaders are in use.
|
||||||
* Should be called after {@link ClientApi#renderLods}
|
* Should be called after {@link ClientApi#renderLods}
|
||||||
*/
|
*/
|
||||||
public void renderDeferredLods(IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
|
public void renderDeferredLodsForShaders(IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
|
||||||
{ this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, true); }
|
{ this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, true); }
|
||||||
|
|
||||||
|
|
||||||
private void renderLodLayer(
|
private void renderLodLayer(
|
||||||
IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks,
|
IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks,
|
||||||
boolean renderingDeferredLayer)
|
boolean renderingDeferredLayer)
|
||||||
@@ -445,6 +455,25 @@ public class ClientApi
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Mat4f mcCombined = mcModelViewMatrix.copy();
|
||||||
|
//mcCombined.multiply(mcProjectionMatrix);
|
||||||
|
//
|
||||||
|
//com.seibel.distanthorizons.api.objects.math.DhApiMat4f dhCombined = renderEventParam.dhModelViewMatrix.copy();
|
||||||
|
//dhCombined.multiply(renderEventParam.dhProjectionMatrix);
|
||||||
|
//
|
||||||
|
//LOGGER.info("\n\n" +
|
||||||
|
// "API\n" +
|
||||||
|
// "Mc MVM: \n" + mcModelViewMatrix.toString() + "\n" +
|
||||||
|
// "Mc Proj: \n" + mcProjectionMatrix + "\n" +
|
||||||
|
// "Mc Combined:\n" + mcCombined.toString() + "\n" +
|
||||||
|
// "\n" +
|
||||||
|
// "DH MVM: \n" + renderEventParam.dhModelViewMatrix.toString() + "\n" +
|
||||||
|
// "DH Proj: \n" + renderEventParam.dhProjectionMatrix + "\n" +
|
||||||
|
// "DH Combined:\n" + mcCombined.toString()
|
||||||
|
//);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// render validation //
|
// render validation //
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -555,25 +584,26 @@ public class ClientApi
|
|||||||
public void renderFadeOpaque(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level)
|
public void renderFadeOpaque(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level)
|
||||||
{
|
{
|
||||||
// only fade when DH is rendering
|
// only fade when DH is rendering
|
||||||
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
|
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT
|
||||||
|
// only fade when requested
|
||||||
|
&& Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() == EDhApiMcRenderingFadeMode.DOUBLE_PASS
|
||||||
|
// don't fade when Iris shaders are active, otherwise the rendering can get weird
|
||||||
|
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
|
||||||
{
|
{
|
||||||
if (Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() == EDhApiMcRenderingFadeMode.DOUBLE_PASS)
|
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level);
|
||||||
{
|
|
||||||
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/** should be called after DH and MC finish rendering so we can smooth the transition between the two */
|
/** should be called after DH and MC finish rendering so we can smooth the transition between the two */
|
||||||
public void renderFade(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level)
|
public void renderFade(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level)
|
||||||
{
|
{
|
||||||
// only fade when DH is rendering
|
// only fade when DH is rendering
|
||||||
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
|
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT
|
||||||
|
// only fade when requested
|
||||||
|
&& Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() != EDhApiMcRenderingFadeMode.NONE
|
||||||
|
// don't fade when Iris shaders are active, otherwise the rendering can get weird
|
||||||
|
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
|
||||||
{
|
{
|
||||||
// fade if any level fading is active
|
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level);
|
||||||
if (Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() != EDhApiMcRenderingFadeMode.NONE)
|
|
||||||
{
|
|
||||||
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,7 +682,8 @@ public class ClientApi
|
|||||||
private void detectAndSendBootTimeWarnings()
|
private void detectAndSendBootTimeWarnings()
|
||||||
{
|
{
|
||||||
// dev build
|
// dev build
|
||||||
if (ModInfo.IS_DEV_BUILD && !this.isDevBuildMessagePrinted && MC_CLIENT.playerExists())
|
if (ModInfo.IS_DEV_BUILD
|
||||||
|
&& !this.isDevBuildMessagePrinted && MC_CLIENT.playerExists())
|
||||||
{
|
{
|
||||||
this.isDevBuildMessagePrinted = true;
|
this.isDevBuildMessagePrinted = true;
|
||||||
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
|
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
|
||||||
@@ -669,7 +700,8 @@ public class ClientApi
|
|||||||
|
|
||||||
// memory
|
// memory
|
||||||
if (this.staticStartupMessageSentRecently()) return;
|
if (this.staticStartupMessageSentRecently()) return;
|
||||||
if (!this.lowMemoryWarningPrinted && Config.Common.Logging.Warning.showLowMemoryWarningOnStartup.get())
|
if (!this.lowMemoryWarningPrinted
|
||||||
|
&& Config.Common.Logging.Warning.showLowMemoryWarningOnStartup.get())
|
||||||
{
|
{
|
||||||
this.lowMemoryWarningPrinted = true;
|
this.lowMemoryWarningPrinted = true;
|
||||||
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
|
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
|
||||||
@@ -694,7 +726,8 @@ public class ClientApi
|
|||||||
|
|
||||||
// high vanilla render distance
|
// high vanilla render distance
|
||||||
if (this.staticStartupMessageSentRecently()) return;
|
if (this.staticStartupMessageSentRecently()) return;
|
||||||
if (!this.highVanillaRenderDistanceWarningPrinted && Config.Common.Logging.Warning.showHighVanillaRenderDistanceWarning.get())
|
if (!this.highVanillaRenderDistanceWarningPrinted
|
||||||
|
&& Config.Common.Logging.Warning.showHighVanillaRenderDistanceWarning.get())
|
||||||
{
|
{
|
||||||
// DH generally doesn't need a vanilla render distance above 12
|
// DH generally doesn't need a vanilla render distance above 12
|
||||||
if (MC_RENDER.getRenderDistance() > 12)
|
if (MC_RENDER.getRenderDistance() > 12)
|
||||||
@@ -721,7 +754,8 @@ public class ClientApi
|
|||||||
{
|
{
|
||||||
if (this.lastStaticWarningMessageSentMsTime == 0)
|
if (this.lastStaticWarningMessageSentMsTime == 0)
|
||||||
{
|
{
|
||||||
return true;
|
// no static message has ever been sent
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
long timeSinceLastMessage = System.currentTimeMillis() - this.lastStaticWarningMessageSentMsTime;
|
long timeSinceLastMessage = System.currentTimeMillis() - this.lastStaticWarningMessageSentMsTime;
|
||||||
@@ -740,4 +774,47 @@ public class ClientApi
|
|||||||
*/
|
*/
|
||||||
public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); }
|
public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// helper classes //
|
||||||
|
//================//
|
||||||
|
|
||||||
|
public static class RenderState
|
||||||
|
{
|
||||||
|
public Mat4f mcModelViewMatrix = null;
|
||||||
|
public Mat4f mcProjectionMatrix = null;
|
||||||
|
public float frameTime = -1;
|
||||||
|
|
||||||
|
|
||||||
|
public void canRenderOrThrow() throws IllegalStateException
|
||||||
|
{
|
||||||
|
String errorReasons = "";
|
||||||
|
|
||||||
|
if (this.mcModelViewMatrix == null)
|
||||||
|
{
|
||||||
|
errorReasons += "no MVM Matrix, ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mcProjectionMatrix == null)
|
||||||
|
{
|
||||||
|
errorReasons += "no Projection Matrix, ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.frameTime == -1)
|
||||||
|
{
|
||||||
|
errorReasons += "no Frame Time, ";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!errorReasons.isEmpty())
|
||||||
|
{
|
||||||
|
throw new IllegalStateException(errorReasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-3
@@ -75,7 +75,12 @@ public class ClientPluginChannelApi
|
|||||||
|
|
||||||
private void onLevelInitMessage(LevelInitMessage msg)
|
private void onLevelInitMessage(LevelInitMessage msg)
|
||||||
{
|
{
|
||||||
if (!msg.levelKey.matches(LevelInitMessage.VALIDATION_REGEX))
|
if (!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 +110,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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ public class SharedApi
|
|||||||
|
|
||||||
public static void setDhWorld(AbstractDhWorld newWorld)
|
public static void setDhWorld(AbstractDhWorld newWorld)
|
||||||
{
|
{
|
||||||
|
AbstractDhWorld oldWorld = currentWorld;
|
||||||
|
if (oldWorld != null)
|
||||||
|
{
|
||||||
|
oldWorld.close();
|
||||||
|
}
|
||||||
currentWorld = newWorld;
|
currentWorld = newWorld;
|
||||||
|
|
||||||
// starting and stopping the DataRenderTransformer is necessary to prevent attempting to
|
// starting and stopping the DataRenderTransformer is necessary to prevent attempting to
|
||||||
|
|||||||
@@ -1015,9 +1015,23 @@ public class Config
|
|||||||
public static class OpenGl
|
public static class OpenGl
|
||||||
{
|
{
|
||||||
public static ConfigEntry<Boolean> overrideVanillaGLLogger = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> overrideVanillaGLLogger = new ConfigEntry.Builder<Boolean>()
|
||||||
.set(ModInfo.IS_DEV_BUILD)
|
.set(true)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "Requires a reboot to change. \n"
|
+ "Defines how OpenGL errors are handled. \n "
|
||||||
|
+ "Requires rebooting Minecraft to change. \n"
|
||||||
|
+ "Will catch OpenGL errors thrown by other mods. \n"
|
||||||
|
+ "")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> onlyLogGlErrorsOnce = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(true)
|
||||||
|
.comment(""
|
||||||
|
+ "If true each Open GL error will only be logged once. \n"
|
||||||
|
+ "Enabling this may cause some error logs to be missed. \n"
|
||||||
|
+ "Does nothing if overrideVanillaGLLogger is set to false. \n"
|
||||||
|
+ " \n"
|
||||||
|
+ "Generally this can be kept as 'true' to prevent log spam. \n"
|
||||||
|
+ "However, Please set this to 'false' if a developer needs your log to debug a GL issue. \n"
|
||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -1281,7 +1295,7 @@ public class Config
|
|||||||
|
|
||||||
public static ConfigEntry<Integer> generationProgressDisplayIntervalInSeconds = new ConfigEntry.Builder<Integer>()
|
public static ConfigEntry<Integer> generationProgressDisplayIntervalInSeconds = new ConfigEntry.Builder<Integer>()
|
||||||
.setChatCommandName("generation.logInterval")
|
.setChatCommandName("generation.logInterval")
|
||||||
.setMinDefaultMax(1, 5, 60 * 60 * 4) // max = 4 hours
|
.setMinDefaultMax(1, 2, 60 * 60 * 4) // max = 4 hours
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "How often should the distant generator progress be displayed? \n"
|
+ "How often should the distant generator progress be displayed? \n"
|
||||||
+ "")
|
+ "")
|
||||||
@@ -1328,20 +1342,20 @@ public class Config
|
|||||||
+ EDhApiDataCompressionMode.UNCOMPRESSED + " \n"
|
+ EDhApiDataCompressionMode.UNCOMPRESSED + " \n"
|
||||||
+ "Should only be used for testing, is worse in every way vs ["+EDhApiDataCompressionMode.LZ4+"].\n"
|
+ "Should only be used for testing, is worse in every way vs ["+EDhApiDataCompressionMode.LZ4+"].\n"
|
||||||
+ "Expected Compression Ratio: 1.0\n"
|
+ "Expected Compression Ratio: 1.0\n"
|
||||||
+ "Estimated average DTO read speed: 1.64 milliseconds\n"
|
+ "Estimated average DTO read speed: 3.25 milliseconds\n"
|
||||||
+ "Estimated average DTO write speed: 12.44 milliseconds\n"
|
+ "Estimated average DTO write speed: 5.99 milliseconds\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ EDhApiDataCompressionMode.LZ4 + " \n"
|
+ EDhApiDataCompressionMode.LZ4 + " \n"
|
||||||
+ "A good option if you're CPU limited and have plenty of hard drive space.\n"
|
+ "A good option if you're CPU limited and have plenty of hard drive space.\n"
|
||||||
+ "Expected Compression Ratio: 0.36\n"
|
+ "Expected Compression Ratio: 0.26\n"
|
||||||
+ "Estimated average DTO read speed: 1.85 ms\n"
|
+ "Estimated average DTO read speed: 1.85 ms\n"
|
||||||
+ "Estimated average DTO write speed: 9.46 ms\n"
|
+ "Estimated average DTO write speed: 9.46 ms\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ EDhApiDataCompressionMode.LZMA2 + " \n"
|
+ EDhApiDataCompressionMode.LZMA2 + " \n"
|
||||||
+ "Slow but very good compression.\n"
|
+ "Slow but very good compression.\n"
|
||||||
+ "Expected Compression Ratio: 0.14\n"
|
+ "Expected Compression Ratio: 0.2\n"
|
||||||
+ "Estimated average DTO read speed: 11.89 ms\n"
|
+ "Estimated average DTO read speed: 13.29 ms\n"
|
||||||
+ "Estimated average DTO write speed: 192.01 ms\n"
|
+ "Estimated average DTO write speed: 70.95 ms\n"
|
||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -1506,7 +1520,7 @@ public class Config
|
|||||||
|
|
||||||
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
||||||
.setChatCommandName("logging.logNetworkEvent")
|
.setChatCommandName("logging.logNetworkEvent")
|
||||||
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
|
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE)
|
||||||
.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.")
|
||||||
@@ -1585,6 +1599,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("")
|
||||||
@@ -1699,6 +1735,15 @@ public class Config
|
|||||||
+ "Value of 0 disables the limit."
|
+ "Value of 0 disables the limit."
|
||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
public static ConfigEntry<Boolean> enableAdaptiveTransferSpeed = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(false)
|
||||||
|
.comment(""
|
||||||
|
+ "Enables adaptive transfer speed based on client performance.\n"
|
||||||
|
+ "If true, DH will automatically adjust transfer rate to minimize connection lag.\n"
|
||||||
|
+ "If false, transfer speed will remain fixed.\n"
|
||||||
|
+ "")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build();
|
public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build();
|
||||||
|
|
||||||
|
|||||||
+10
@@ -105,6 +105,15 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
|
|||||||
this.put(EDhApiQualityPreset.HIGH, true);
|
this.put(EDhApiQualityPreset.HIGH, true);
|
||||||
this.put(EDhApiQualityPreset.EXTREME, true);
|
this.put(EDhApiQualityPreset.EXTREME, true);
|
||||||
}});
|
}});
|
||||||
|
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, Boolean> caveCulling = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Culling.enableCaveCulling,
|
||||||
|
new HashMap<EDhApiQualityPreset, Boolean>()
|
||||||
|
{{
|
||||||
|
this.put(EDhApiQualityPreset.MINIMUM, true);
|
||||||
|
this.put(EDhApiQualityPreset.LOW, true);
|
||||||
|
this.put(EDhApiQualityPreset.MEDIUM, true);
|
||||||
|
this.put(EDhApiQualityPreset.HIGH, false);
|
||||||
|
this.put(EDhApiQualityPreset.EXTREME, false);
|
||||||
|
}});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -123,6 +132,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
|
|||||||
this.configList.add(this.ssaoEnabled);
|
this.configList.add(this.ssaoEnabled);
|
||||||
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);
|
||||||
|
|
||||||
|
|
||||||
for (ConfigEntryWithPresetOptions<EDhApiQualityPreset, ?> config : this.configList)
|
for (ConfigEntryWithPresetOptions<EDhApiQualityPreset, ?> config : this.configList)
|
||||||
|
|||||||
+8
-6
@@ -70,7 +70,12 @@ public class ConfigFileHandling
|
|||||||
this.configBase = configBase;
|
this.configBase = configBase;
|
||||||
this.configPath = configPath;
|
this.configPath = configPath;
|
||||||
|
|
||||||
this.nightConfig = CommentedFileConfig.builder(this.configPath.toFile()).build();
|
this.nightConfig = CommentedFileConfig
|
||||||
|
.builder(this.configPath.toFile())
|
||||||
|
// sync is needed so file reading/writing only happens during locked sections,
|
||||||
|
// otherwise some GUI changes may be lost when changing screens
|
||||||
|
.sync()
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -321,10 +326,7 @@ public class ConfigFileHandling
|
|||||||
*
|
*
|
||||||
* @apiNote This overwrites any value currently stored in the config
|
* @apiNote This overwrites any value currently stored in the config
|
||||||
*/
|
*/
|
||||||
public void loadNightConfig()
|
public void loadNightConfig() { this.loadNightConfig(this.nightConfig); }
|
||||||
{
|
|
||||||
loadNightConfig(this.nightConfig);
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Does {@link CommentedFileConfig#load()} but with error checking
|
* Does {@link CommentedFileConfig#load()} but with error checking
|
||||||
*
|
*
|
||||||
@@ -353,7 +355,7 @@ public class ConfigFileHandling
|
|||||||
{
|
{
|
||||||
System.out.println("Creating file failed");
|
System.out.println("Creating file failed");
|
||||||
this.logger.error(ex);
|
this.logger.error(ex);
|
||||||
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Loading file and resetting config file failed at path [" + configPath + "]. Please check the file is ok and you have the permissions", 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-23
@@ -29,14 +29,12 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
|
|||||||
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.IWrapperFactory;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WARNING: This is not THREAD-SAFE! <br><br>
|
* WARNING: This is not THREAD-SAFE! <br><br>
|
||||||
@@ -352,14 +350,14 @@ public class FullDataPointIdMap
|
|||||||
{
|
{
|
||||||
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 ConcurrentHashMap<Integer, Entry> ENTRY_BY_HASH = new ConcurrentHashMap<>();
|
/** two levels are present so we don't need to use a key object */
|
||||||
/** lock is necessary since {@link Int2ReferenceOpenHashMap} isn't concurrent and concurrent threads can cause infinite loops */
|
private static final ConcurrentHashMap<IBiomeWrapper, ConcurrentHashMap<IBlockStateWrapper, Entry>> ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER = new ConcurrentHashMap<>();
|
||||||
private static final ReentrantReadWriteLock ENTRY_POOL_LOCK = new ReentrantReadWriteLock();
|
|
||||||
|
|
||||||
public final IBiomeWrapper biome;
|
public final IBiomeWrapper biome;
|
||||||
public final IBlockStateWrapper blockState;
|
public final IBlockStateWrapper blockState;
|
||||||
|
|
||||||
private Integer hashCode = null;
|
private int hashCode = 0;
|
||||||
|
private boolean hashGenerated = false;
|
||||||
private String serialString = null;
|
private String serialString = null;
|
||||||
|
|
||||||
|
|
||||||
@@ -370,25 +368,21 @@ public class FullDataPointIdMap
|
|||||||
|
|
||||||
public static Entry getEntry(IBiomeWrapper biome, IBlockStateWrapper blockState)
|
public static Entry getEntry(IBiomeWrapper biome, IBlockStateWrapper blockState)
|
||||||
{
|
{
|
||||||
int entryHash = generateHashCode(biome, blockState);
|
// check for existing entry
|
||||||
|
ConcurrentHashMap<IBlockStateWrapper, Entry> entryByBlockState = ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER.get(biome);
|
||||||
// try getting the existing Entry
|
if (entryByBlockState != null)
|
||||||
Entry entry = ENTRY_BY_HASH.get(entryHash);
|
|
||||||
if (entry != null)
|
|
||||||
{
|
{
|
||||||
return entry;
|
Entry entry = entryByBlockState.get(blockState);
|
||||||
|
if (entry != null)
|
||||||
|
{
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the missing entry
|
// Lazily create the inner map and new Entry
|
||||||
return ENTRY_BY_HASH.compute(entryHash, (Integer newHash, Entry currentEntry) ->
|
return ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER
|
||||||
{
|
.computeIfAbsent(biome, newBiome -> new ConcurrentHashMap<>())
|
||||||
if (currentEntry != null)
|
.computeIfAbsent(blockState, newBlockState -> new Entry(biome, blockState));
|
||||||
{
|
|
||||||
return currentEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Entry(biome, blockState);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
private Entry(IBiomeWrapper biome, IBlockStateWrapper blockState)
|
private Entry(IBiomeWrapper biome, IBlockStateWrapper blockState)
|
||||||
{
|
{
|
||||||
@@ -402,13 +396,18 @@ public class FullDataPointIdMap
|
|||||||
// overrides //
|
// 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
|
@Override
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
{
|
{
|
||||||
// cache the hash code to improve speed
|
// cache the hash code to improve speed
|
||||||
if (this.hashCode == null)
|
if (!this.hashGenerated)
|
||||||
{
|
{
|
||||||
this.hashCode = generateHashCode(this);
|
this.hashCode = generateHashCode(this);
|
||||||
|
this.hashGenerated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.hashCode;
|
return this.hashCode;
|
||||||
@@ -430,10 +429,14 @@ public class FullDataPointIdMap
|
|||||||
public boolean equals(Object otherObj)
|
public boolean equals(Object otherObj)
|
||||||
{
|
{
|
||||||
if (otherObj == this)
|
if (otherObj == this)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(otherObj instanceof Entry))
|
if (!(otherObj instanceof Entry))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Entry other = (Entry) otherObj;
|
Entry other = (Entry) otherObj;
|
||||||
return other.biome.getSerialString().equals(this.biome.getSerialString())
|
return other.biome.getSerialString().equals(this.biome.getSerialString())
|
||||||
|
|||||||
+89
-107
@@ -65,8 +65,6 @@ public class LodDataBuilder
|
|||||||
// only block lighting is needed here, sky lighting is populated at the data source stage
|
// only block lighting is needed here, sky lighting is populated at the data source stage
|
||||||
LodUtil.assertTrue(chunkWrapper.isDhBlockLightingCorrect());
|
LodUtil.assertTrue(chunkWrapper.isDhBlockLightingCorrect());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX());
|
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX());
|
||||||
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ());
|
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ());
|
||||||
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
|
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
|
||||||
@@ -80,47 +78,31 @@ public class LodDataBuilder
|
|||||||
|
|
||||||
// compute the chunk dataSource offset
|
// compute the chunk dataSource offset
|
||||||
// this offset is used to determine where in the dataSource this chunk's data should go
|
// this offset is used to determine where in the dataSource this chunk's data should go
|
||||||
int chunkOffsetX = chunkWrapper.getChunkPos().getX();
|
|
||||||
if (chunkWrapper.getChunkPos().getX() < 0)
|
|
||||||
{
|
|
||||||
// expected offset positions:
|
|
||||||
// chunkPos -> offset
|
|
||||||
// 5 -> 1
|
|
||||||
// 4 -> 0 ---
|
|
||||||
// 3 -> 3
|
|
||||||
// 2 -> 2
|
|
||||||
// 1 -> 1
|
|
||||||
// 0 -> 0 ===
|
|
||||||
// -1 -> 3
|
|
||||||
// -2 -> 2
|
|
||||||
// -3 -> 1
|
|
||||||
// -4 -> 0 ---
|
|
||||||
// -5 -> 3
|
|
||||||
chunkOffsetX = ((chunkOffsetX) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
|
|
||||||
if (chunkOffsetX != 0)
|
|
||||||
{
|
|
||||||
chunkOffsetX += FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
chunkOffsetX %= FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
|
|
||||||
}
|
|
||||||
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
|
|
||||||
|
|
||||||
int chunkOffsetZ = chunkWrapper.getChunkPos().getZ();
|
// expected offset positions:
|
||||||
if (chunkWrapper.getChunkPos().getZ() < 0)
|
// chunkPos -> offset
|
||||||
{
|
// 5 -> 1
|
||||||
chunkOffsetZ = ((chunkOffsetZ) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
|
// 4 -> 0 ---
|
||||||
if (chunkOffsetZ != 0)
|
// 3 -> 3
|
||||||
{
|
// 2 -> 2
|
||||||
chunkOffsetZ += FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
|
// 1 -> 1
|
||||||
}
|
// 0 -> 0 ===
|
||||||
}
|
// -1 -> 3
|
||||||
else
|
// -2 -> 2
|
||||||
{
|
// -3 -> 1
|
||||||
chunkOffsetZ %= FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
|
// -4 -> 0 ---
|
||||||
}
|
// -5 -> 3
|
||||||
|
|
||||||
|
// Fast modulo calculation using bitwise AND since NUMB_OF_CHUNKS_WIDE is a power of 2 (4)
|
||||||
|
// For any number n: n & (2^k - 1) is equivalent to Math.floorMod(n, 2^k)
|
||||||
|
// Original: Math.floorMod(x, 4) - Handles negative numbers, gives non-negative result in range [0,3]
|
||||||
|
// Bitwise: x & (4-1) - Also gives non-negative result in range [0,3]
|
||||||
|
// Example: -5 & 3 = 3, which equals Math.floorMod(-5, 4) = 3
|
||||||
|
int chunkOffsetX = chunkWrapper.getChunkPos().getX() & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1);
|
||||||
|
int chunkOffsetZ = chunkWrapper.getChunkPos().getZ() & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1);
|
||||||
|
|
||||||
|
// Convert from chunk coordinates to block coordinates
|
||||||
|
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
|
||||||
chunkOffsetZ *= LodUtil.CHUNK_WIDTH;
|
chunkOffsetZ *= LodUtil.CHUNK_WIDTH;
|
||||||
|
|
||||||
|
|
||||||
@@ -138,54 +120,49 @@ public class LodDataBuilder
|
|||||||
IBlockStateWrapper previousBlockState = null;
|
IBlockStateWrapper previousBlockState = null;
|
||||||
|
|
||||||
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
|
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
|
||||||
|
int exclusiveMaxBuildHeight = chunkWrapper.getExclusiveMaxBuildHeight();
|
||||||
|
int inclusiveMinBuildHeight = chunkWrapper.getInclusiveMinBuildHeight();
|
||||||
|
int dataCapacity = chunkWrapper.getHeight() / 4;
|
||||||
|
|
||||||
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
|
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
|
||||||
{
|
{
|
||||||
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
|
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
|
||||||
{
|
{
|
||||||
LongArrayList longs = dataSource.get(
|
// Calculate column position
|
||||||
relBlockX + chunkOffsetX,
|
int columnX = relBlockX + chunkOffsetX;
|
||||||
relBlockZ + chunkOffsetZ);
|
int columnZ = relBlockZ + chunkOffsetZ;
|
||||||
|
|
||||||
|
// Get column data
|
||||||
|
LongArrayList longs = dataSource.get(columnX, columnZ);
|
||||||
if (longs == null)
|
if (longs == null)
|
||||||
{
|
{
|
||||||
longs = new LongArrayList(chunkWrapper.getHeight() / 4);
|
longs = new LongArrayList(dataCapacity);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
longs.clear();
|
longs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
int lastY = chunkWrapper.getExclusiveMaxBuildHeight();
|
int lastY = exclusiveMaxBuildHeight;
|
||||||
IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
|
IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
|
||||||
IBlockStateWrapper blockState = AIR;
|
IBlockStateWrapper blockState = AIR;
|
||||||
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
|
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
|
||||||
|
|
||||||
|
// Determine lighting (we are at the height limit. There are no torches here, and sky is not obscured.) // TODO: Per face lighting someday?
|
||||||
|
byte blockLight = LodUtil.MIN_MC_LIGHT;
|
||||||
|
byte skyLight = LodUtil.MAX_MC_LIGHT;
|
||||||
|
|
||||||
byte blockLight;
|
// Get the maximum height from both heightmaps
|
||||||
byte skyLight;
|
|
||||||
if (lastY < chunkWrapper.getExclusiveMaxBuildHeight())
|
|
||||||
{
|
|
||||||
// FIXME: The lastY +1 offset is to reproduce the old behavior. Remove this when we get per-face lighting
|
|
||||||
blockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, lastY + 1, relBlockZ);
|
|
||||||
skyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, lastY + 1, relBlockZ);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//we are at the height limit. There are no torches here, and sky is not obscured.
|
|
||||||
blockLight = LodUtil.MIN_MC_LIGHT;
|
|
||||||
skyLight = LodUtil.MAX_MC_LIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// determine the starting Y Pos
|
|
||||||
int y = Math.max(
|
int y = Math.max(
|
||||||
// max between both heightmaps to account for solid invisible blocks (glass)
|
// max between both heightmaps to account for solid invisible blocks (glass)
|
||||||
// and non-solid opaque blocks (at one point this was stairs, not sure what would fit this now)
|
// and non-solid opaque blocks (at one point this was stairs, not sure what would fit this now)
|
||||||
chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ),
|
chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ),
|
||||||
chunkWrapper.getSolidHeightMapValue(relBlockX, relBlockZ)
|
chunkWrapper.getSolidHeightMapValue(relBlockX, relBlockZ)
|
||||||
);
|
);
|
||||||
// go up until we reach open air or the world limit
|
|
||||||
|
// Go up until we reach open air or the world limit
|
||||||
IBlockStateWrapper topBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState);
|
IBlockStateWrapper topBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState);
|
||||||
while (!topBlockState.isAir() && y < chunkWrapper.getExclusiveMaxBuildHeight())
|
while (!topBlockState.isAir() && y < exclusiveMaxBuildHeight)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -198,7 +175,7 @@ public class LodDataBuilder
|
|||||||
{
|
{
|
||||||
if (!getTopErrorLogged)
|
if (!getTopErrorLogged)
|
||||||
{
|
{
|
||||||
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getExclusiveMaxBuildHeight() + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
|
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + exclusiveMaxBuildHeight + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
|
||||||
getTopErrorLogged = true;
|
getTopErrorLogged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +184,7 @@ public class LodDataBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process blocks from top to bottom
|
||||||
for (; y >= minBuildHeight; y--)
|
for (; y >= minBuildHeight; y--)
|
||||||
{
|
{
|
||||||
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
|
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
|
||||||
@@ -215,10 +192,10 @@ public class LodDataBuilder
|
|||||||
byte newBlockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, y + 1, relBlockZ);
|
byte newBlockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, y + 1, relBlockZ);
|
||||||
byte newSkyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, y + 1, relBlockZ);
|
byte newSkyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, y + 1, relBlockZ);
|
||||||
|
|
||||||
// save the biome/block change
|
// Save the biome/block change if different from previous
|
||||||
if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
|
if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
|
||||||
{
|
{
|
||||||
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getInclusiveMinBuildHeight(), blockLight, skyLight));
|
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - inclusiveMinBuildHeight, blockLight, skyLight));
|
||||||
biome = newBiome;
|
biome = newBiome;
|
||||||
blockState = newBlockState;
|
blockState = newBlockState;
|
||||||
|
|
||||||
@@ -228,13 +205,12 @@ public class LodDataBuilder
|
|||||||
lastY = y;
|
lastY = y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getInclusiveMinBuildHeight(), blockLight, skyLight));
|
|
||||||
|
|
||||||
dataSource.setSingleColumn(longs,
|
// Add the final data point
|
||||||
relBlockX + chunkOffsetX,
|
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - inclusiveMinBuildHeight, blockLight, skyLight));
|
||||||
relBlockZ + chunkOffsetZ,
|
|
||||||
EDhApiWorldGenerationStep.LIGHT,
|
// Set the column in the data source
|
||||||
worldCompressionMode);
|
dataSource.setSingleColumn(longs, columnX, columnZ, EDhApiWorldGenerationStep.LIGHT, worldCompressionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +251,7 @@ public class LodDataBuilder
|
|||||||
{
|
{
|
||||||
long currentPoint = centerColumn.getLong(centerIndex);
|
long currentPoint = centerColumn.getLong(centerIndex);
|
||||||
|
|
||||||
// translucent data points are not eligible to be culled.
|
// Translucent data points are not eligible to be culled.
|
||||||
if (isTranslucent(dataSource, currentPoint))
|
if (isTranslucent(dataSource, currentPoint))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -283,8 +259,8 @@ public class LodDataBuilder
|
|||||||
|
|
||||||
// the top segment should never be culled.
|
// the top segment should never be culled.
|
||||||
if (centerIndex == 0
|
if (centerIndex == 0
|
||||||
|| isTranslucent(dataSource, centerColumn.getLong(centerIndex - 1))
|
|| isTranslucent(dataSource, centerColumn.getLong(centerIndex - 1))
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -293,8 +269,8 @@ public class LodDataBuilder
|
|||||||
// assume it will not be seen from below,
|
// assume it will not be seen from below,
|
||||||
// because this would imply the player is in the void.
|
// because this would imply the player is in the void.
|
||||||
if (centerIndex + 1 < centerColumn.size()
|
if (centerIndex + 1 < centerColumn.size()
|
||||||
&& isTranslucent(dataSource, centerColumn.getLong(centerIndex + 1))
|
&& isTranslucent(dataSource, centerColumn.getLong(centerIndex + 1))
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -327,9 +303,11 @@ public class LodDataBuilder
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// current point is fully surrounded. remove it.
|
// Current point is fully surrounded. remove it.
|
||||||
centerColumn.removeLong(centerIndex);
|
centerColumn.removeLong(centerIndex);
|
||||||
// make the above data point cover the area that the current point used to occupy.
|
|
||||||
|
// 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);
|
long above = centerColumn.getLong(centerIndex - 1);
|
||||||
above = FullDataPointUtil.setBottomY(above, FullDataPointUtil.getBottomY(currentPoint));
|
above = FullDataPointUtil.setBottomY(above, FullDataPointUtil.getBottomY(currentPoint));
|
||||||
above = FullDataPointUtil.setHeight(above, FullDataPointUtil.getHeight(currentPoint) + FullDataPointUtil.getHeight(above));
|
above = FullDataPointUtil.setHeight(above, FullDataPointUtil.getHeight(currentPoint) + FullDataPointUtil.getHeight(above));
|
||||||
@@ -340,29 +318,29 @@ public class LodDataBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
checks if centerPoint is "covered" by opaque data points in adjacentColumn.
|
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,
|
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.
|
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 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 centerPoint the point being checked to see if it's fully covered.
|
||||||
@param adjacentColumn the data points which might cover centerPoint.
|
@param adjacentColumn the data points which might cover centerPoint.
|
||||||
@param adjacentIndex the starting index in adjacentColumn to start scanning at.
|
@param adjacentIndex the starting index in adjacentColumn to start scanning at.
|
||||||
indices greater than adjacentIndex have already been checked and confirmed to
|
indices greater than adjacentIndex have already been checked and confirmed to
|
||||||
not overlap or only overlap partially with centerPoint's Y range.
|
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.
|
@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
|
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.
|
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
|
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.
|
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.
|
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
|
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
|
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.
|
centerPoint is covered; both are packed into the same int, and returned.
|
||||||
*/
|
*/
|
||||||
private static int checkOcclusion(FullDataSourceV2 source, long centerPoint, LongArrayList adjacentColumn, int adjacentIndex)
|
private static int checkOcclusion(FullDataSourceV2 source, long centerPoint, LongArrayList adjacentColumn, int adjacentIndex)
|
||||||
{
|
{
|
||||||
int bottomOfCenter = FullDataPointUtil.getBottomY(centerPoint);
|
int bottomOfCenter = FullDataPointUtil.getBottomY(centerPoint);
|
||||||
@@ -402,9 +380,13 @@ public class LodDataBuilder
|
|||||||
int sectionPosZ = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosZ);
|
int sectionPosZ = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosZ);
|
||||||
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
|
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
|
||||||
|
|
||||||
// chunk relative block position in the data source
|
// Fast modulo calculation using bitwise AND since NUMB_OF_CHUNKS_WIDE is a power of 2 (4)
|
||||||
int relSourceBlockX = Math.floorMod(apiChunk.chunkPosX, 4) * LodUtil.CHUNK_WIDTH;
|
// For any number n: n & (2^k - 1) is equivalent to Math.floorMod(n, 2^k)
|
||||||
int relSourceBlockZ = Math.floorMod(apiChunk.chunkPosZ, 4) * LodUtil.CHUNK_WIDTH;
|
// Original: Math.floorMod(x, 4) - Handles negative numbers, gives non-negative result in range [0,3]
|
||||||
|
// Bitwise: x & (4-1) - Also gives non-negative result in range [0,3]
|
||||||
|
// Example: -5 & 3 = 3, which equals Math.floorMod(-5, 4) = 3
|
||||||
|
int relSourceBlockX = (apiChunk.chunkPosX & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1)) * LodUtil.CHUNK_WIDTH;
|
||||||
|
int relSourceBlockZ = (apiChunk.chunkPosZ & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1)) * LodUtil.CHUNK_WIDTH;
|
||||||
|
|
||||||
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
|
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
|
||||||
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
|
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
|
||||||
@@ -533,7 +515,7 @@ public class LodDataBuilder
|
|||||||
}
|
}
|
||||||
// is there a gap between the last datapoint?
|
// is there a gap between the last datapoint?
|
||||||
if (topYPos != lastBottomYPos
|
if (topYPos != lastBottomYPos
|
||||||
&& lastBottomYPos != Integer.MIN_VALUE)
|
&& lastBottomYPos != Integer.MIN_VALUE)
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] has a gap between it and index ["+(i-1)+"]. Empty spaces should be filled by air, otherwise DH's downsampling won't calculate lighting correctly.");
|
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] has a gap between it and index ["+(i-1)+"]. Empty spaces should be filled by air, otherwise DH's downsampling won't calculate lighting correctly.");
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-5
@@ -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);
|
||||||
|
|||||||
@@ -328,7 +328,8 @@ public class DhLightingEngine
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.getY() > neighbourChunk.getExclusiveMaxBuildHeight())
|
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight()
|
||||||
|
|| relNeighbourBlockPos.getY() >= neighbourChunk.getExclusiveMaxBuildHeight())
|
||||||
{
|
{
|
||||||
// the light pos is outside the chunk's min/max height,
|
// the light pos is outside the chunk's min/max height,
|
||||||
// this can happen if given a chunk that hasn't finished generating
|
// this can happen if given a chunk that hasn't finished generating
|
||||||
|
|||||||
+2
-2
@@ -113,8 +113,8 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
|
|||||||
if (this.networkState.sessionConfig.getGenerationBoundsRadius() > 0)
|
if (this.networkState.sessionConfig.getGenerationBoundsRadius() > 0)
|
||||||
{
|
{
|
||||||
if (DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, new DhBlockPos2D(
|
if (DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, new DhBlockPos2D(
|
||||||
(int) (this.networkState.sessionConfig.getGenerationBoundsX() / this.level.levelWrapper.getDimensionType().getCoordinateScale()),
|
this.networkState.sessionConfig.getGenerationBoundsX(),
|
||||||
(int) (this.networkState.sessionConfig.getGenerationBoundsZ() / this.level.levelWrapper.getDimensionType().getCoordinateScale())
|
this.networkState.sessionConfig.getGenerationBoundsZ()
|
||||||
)) > this.networkState.sessionConfig.getGenerationBoundsRadius())
|
)) > this.networkState.sessionConfig.getGenerationBoundsRadius())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public class SelfUpdater
|
|||||||
}
|
}
|
||||||
if (!ModrinthGetter.mcVersions.contains(mcVersion))
|
if (!ModrinthGetter.mcVersions.contains(mcVersion))
|
||||||
{
|
{
|
||||||
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Modrinth, only findable versions are ["+ StringUtil.join(",", ModrinthGetter.mcVersions) +"]");
|
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Modrinth, only findable versions are ["+ StringUtil.join(", ", ModrinthGetter.mcVersions) +"]");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ public abstract class AbstractDhLevel implements IDhLevel
|
|||||||
|
|
||||||
|
|
||||||
// locked to prevent two threads from updating the same section at the same time
|
// locked to prevent two threads from updating the same section at the same time
|
||||||
ReentrantLock lock = this.beaconUpdateLockContainer.getLockForPos(sectionPosForLock);
|
ReentrantLock lock = this.beaconUpdateLockContainer.getLockForPos(sectionPosForLock); // TODO this can cause a lot of slow-downs
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
lock.lock();
|
lock.lock();
|
||||||
|
|||||||
+18
-21
@@ -151,10 +151,9 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
|
|||||||
|
|
||||||
if (Config.Server.generationBoundsRadius.get() > 0)
|
if (Config.Server.generationBoundsRadius.get() > 0)
|
||||||
{
|
{
|
||||||
double coordinateScale = this.serverLevelWrapper.getDimensionType().getCoordinateScale();
|
|
||||||
if (DhSectionPos.getChebyshevSignedBlockDistance(message.sectionPos, new DhBlockPos2D(
|
if (DhSectionPos.getChebyshevSignedBlockDistance(message.sectionPos, new DhBlockPos2D(
|
||||||
(int) (Config.Server.generationBoundsX.get() / coordinateScale),
|
serverPlayerState.sessionConfig.getGenerationBoundsX(),
|
||||||
(int) (Config.Server.generationBoundsZ.get() / coordinateScale)
|
serverPlayerState.sessionConfig.getGenerationBoundsZ()
|
||||||
)) > Config.Server.generationBoundsRadius.get())
|
)) > Config.Server.generationBoundsRadius.get())
|
||||||
{
|
{
|
||||||
message.sendResponse(new RequestOutOfRangeException("Section out of allowed bounds"));
|
message.sendResponse(new RequestOutOfRangeException("Section out of allowed bounds"));
|
||||||
@@ -262,29 +261,27 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
|
|||||||
}
|
}
|
||||||
|
|
||||||
LodUtil.assertTrue(this.beaconBeamRepo != null, "beaconBeamRepo should not be null");
|
LodUtil.assertTrue(this.beaconBeamRepo != null, "beaconBeamRepo should not be null");
|
||||||
try (FullDataPayload payload = new FullDataPayload(data, this.beaconBeamRepo.getAllBeamsForPos(data.getPos())))
|
FullDataPayload payload = new FullDataPayload(data, this.beaconBeamRepo.getAllBeamsForPos(data.getPos()));
|
||||||
|
for (ServerPlayerState serverPlayerState : this.serverPlayerStateManager.getReadyPlayers())
|
||||||
{
|
{
|
||||||
for (ServerPlayerState serverPlayerState : this.serverPlayerStateManager.getReadyPlayers())
|
if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
|
||||||
{
|
{
|
||||||
if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
|
continue;
|
||||||
{
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
|
if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
|
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
|
||||||
int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
|
int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
|
||||||
if (distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
|
if (distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
|
||||||
|
{
|
||||||
|
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
|
||||||
{
|
{
|
||||||
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
|
serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
|
||||||
{
|
});
|
||||||
serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -258,7 +258,11 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
|
|||||||
|
|
||||||
public void clearRenderCache()
|
public void clearRenderCache()
|
||||||
{
|
{
|
||||||
this.clientLevel.getClientLevelWrapper().clearBlockColorCache();
|
IClientLevelWrapper clientLevelWrapper = this.clientLevel.getClientLevelWrapper();
|
||||||
|
if (clientLevelWrapper != null)
|
||||||
|
{
|
||||||
|
clientLevelWrapper.clearBlockColorCache();
|
||||||
|
}
|
||||||
|
|
||||||
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
|
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
|
||||||
if (ClientRenderState != null && ClientRenderState.quadtree != null)
|
if (ClientRenderState != null && ClientRenderState.quadtree != null)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV
|
|||||||
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.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;
|
||||||
@@ -41,12 +42,14 @@ import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRend
|
|||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
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.wrapperInterfaces.block.IBlockStateWrapper;
|
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.minecraft.IProfilerWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
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.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -56,13 +59,16 @@ import java.io.File;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.TimeUnit;
|
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 Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
|
protected static final ConfigBasedLogger NETWORK_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
|
||||||
|
() -> Config.Common.Logging.logNetworkEvent.get());
|
||||||
|
|
||||||
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;
|
||||||
@@ -157,17 +163,38 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSourceAndReleaseBuffer(message.payload))
|
|
||||||
|
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload))
|
||||||
{
|
{
|
||||||
if (!message.isSameLevelAs(this.levelWrapper))
|
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
|
||||||
|
NETWORK_LOGGER.debug("Buffer {} isSameLevel: {}", message.payload.dtoBufferId, isSameLevel);
|
||||||
|
if (!isSameLevel)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateBeaconBeamsForSectionPos(dataSourceDto.pos, message.payload.beaconBeams);
|
|
||||||
|
Executor executor = ThreadPoolUtil.getFileHandlerExecutor();
|
||||||
|
if (executor != null)
|
||||||
|
{
|
||||||
|
executor.execute(() ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// TODO this has a lock which can cause stuttering/lag issues
|
||||||
|
this.updateBeaconBeamsForSectionPos(dataSourceDto.pos, message.payload.beaconBeams);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected erorr while updating full data source, error: ["+e.getMessage()+"].", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.levelWrapper);
|
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.levelWrapper);
|
||||||
this.updateDataSourcesAsync(fullDataSource).whenComplete((result, e) -> fullDataSource.close());
|
this.updateDataSourcesAsync(fullDataSource)
|
||||||
|
.whenComplete((result, e) -> fullDataSource.close());
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
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 java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -107,6 +108,7 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); }
|
public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); }
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
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;
|
||||||
|
|
||||||
public interface IDhClientLevel extends IDhLevel
|
public interface IDhClientLevel extends IDhLevel
|
||||||
{
|
{
|
||||||
@@ -35,6 +36,7 @@ public interface IDhClientLevel extends IDhLevel
|
|||||||
|
|
||||||
int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block);
|
int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
IClientLevelWrapper getClientLevelWrapper();
|
IClientLevelWrapper getClientLevelWrapper();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ public class WorldGenModule implements Closeable
|
|||||||
remainingChunkCount += this.worldGenerationQueue.getQueuedChunkCount();
|
remainingChunkCount += this.worldGenerationQueue.getQueuedChunkCount();
|
||||||
String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount);
|
String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount);
|
||||||
|
|
||||||
String message = "DH Gen/Import: " + remainingChunkCountStr + " chunks left.";
|
String message = "DH is loading chunks. " + remainingChunkCountStr + " left.";
|
||||||
|
|
||||||
// show a message about how to disable progress logging if requested
|
// show a message about how to disable progress logging if requested
|
||||||
int msToShowDisableInstructions = Config.Common.WorldGenerator.generationProgressDisableMessageDisplayTimeInSeconds.get() * 1_000;
|
int msToShowDisableInstructions = Config.Common.WorldGenerator.generationProgressDisableMessageDisplayTimeInSeconds.get() * 1_000;
|
||||||
|
|||||||
+1
-1
@@ -256,7 +256,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
|
|
||||||
if (response.payload != null)
|
if (response.payload != null)
|
||||||
{
|
{
|
||||||
FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSourceAndReleaseBuffer(response.payload);
|
FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(response.payload);
|
||||||
|
|
||||||
// set application flags based on the received detail level,
|
// set application flags based on the received detail level,
|
||||||
// this is needed so the data sources propagate correctly
|
// this is needed so the data sources propagate correctly
|
||||||
|
|||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
package com.seibel.distanthorizons.core.multiplayer.client;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
|
||||||
|
public class ClientCongestionControl
|
||||||
|
{
|
||||||
|
private static final double ADDITIVE_INCREASE = 50000;
|
||||||
|
private static final long INTERVAL_MS = 1000;
|
||||||
|
|
||||||
|
private final Runnable rateUpdateHandler;
|
||||||
|
|
||||||
|
private final AtomicLong bytesReceived = new AtomicLong(0);
|
||||||
|
|
||||||
|
private double desiredRate;
|
||||||
|
private long lastAdjustTime;
|
||||||
|
|
||||||
|
|
||||||
|
public ClientCongestionControl(
|
||||||
|
Runnable rateUpdateHandler
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.rateUpdateHandler = rateUpdateHandler;
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
this.desiredRate = ADDITIVE_INCREASE;
|
||||||
|
this.lastAdjustTime = System.currentTimeMillis();
|
||||||
|
this.bytesReceived.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void onPayloadReceived(FullDataSplitMessage message)
|
||||||
|
{
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now - this.lastAdjustTime >= INTERVAL_MS)
|
||||||
|
{
|
||||||
|
this.adjustRate(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bytesReceived.addAndGet(message.buffer.readableBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void adjustRate(long now)
|
||||||
|
{
|
||||||
|
double throughput = this.bytesReceived.getAndSet(0);
|
||||||
|
if (throughput >= this.desiredRate)
|
||||||
|
{
|
||||||
|
this.desiredRate += ADDITIVE_INCREASE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.desiredRate = Math.max(throughput - ADDITIVE_INCREASE / 2, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastAdjustTime = now;
|
||||||
|
this.rateUpdateHandler.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDesiredRate()
|
||||||
|
{
|
||||||
|
return (int) (this.desiredRate / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+35
-3
@@ -1,6 +1,7 @@
|
|||||||
package com.seibel.distanthorizons.core.multiplayer.client;
|
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.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
|
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
|
||||||
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
|
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
|
||||||
@@ -56,6 +57,23 @@ public class ClientNetworkState implements Closeable
|
|||||||
private long serverTimeOffset = 0;
|
private long serverTimeOffset = 0;
|
||||||
public long getServerTimeOffset() { return this.serverTimeOffset; }
|
public long getServerTimeOffset() { return this.serverTimeOffset; }
|
||||||
|
|
||||||
|
private final ClientCongestionControl congestionControl = new ClientCongestionControl(
|
||||||
|
() -> {
|
||||||
|
if (Config.Server.enableAdaptiveTransferSpeed.get())
|
||||||
|
{
|
||||||
|
this.sendConfigMessage(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
private final ConfigChangeListener<Boolean> adaptiveTransferSpeedListener = new ConfigChangeListener<>(Config.Server.enableAdaptiveTransferSpeed, isEnabled -> {
|
||||||
|
if (isEnabled)
|
||||||
|
{
|
||||||
|
this.congestionControl.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendConfigMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
@@ -116,6 +134,7 @@ public class ClientNetworkState implements Closeable
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.networkSession.registerHandler(FullDataSplitMessage.class, this.fullDataPayloadReceiver::receiveChunk);
|
this.networkSession.registerHandler(FullDataSplitMessage.class, this.fullDataPayloadReceiver::receiveChunk);
|
||||||
|
this.networkSession.registerHandler(FullDataSplitMessage.class, this.congestionControl::onPayloadReceived);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,10 +146,22 @@ public class ClientNetworkState implements Closeable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void sendConfigMessage()
|
public void sendConfigMessage() { this.sendConfigMessage(true); }
|
||||||
|
public void sendConfigMessage(boolean blocking)
|
||||||
{
|
{
|
||||||
this.configReceived = false;
|
SessionConfig sessionConfig = new SessionConfig();
|
||||||
this.getSession().sendMessage(new SessionConfigMessage(new SessionConfig()));
|
|
||||||
|
if (Config.Server.enableAdaptiveTransferSpeed.get())
|
||||||
|
{
|
||||||
|
sessionConfig.constrainValue(Config.Server.maxDataTransferSpeed, this.congestionControl.getDesiredRate());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocking)
|
||||||
|
{
|
||||||
|
this.configReceived = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getSession().sendMessage(new SessionConfigMessage(sessionConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -166,6 +197,7 @@ public class ClientNetworkState implements Closeable
|
|||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
this.fullDataPayloadReceiver.close();
|
this.fullDataPayloadReceiver.close();
|
||||||
|
this.adaptiveTransferSpeedListener.close();
|
||||||
this.configAnyChangeListener.close();
|
this.configAnyChangeListener.close();
|
||||||
this.networkSession.close();
|
this.networkSession.close();
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-5
@@ -18,7 +18,7 @@ public class SessionConfig implements INetworkObject
|
|||||||
private static final LinkedHashMap<String, Entry> CONFIG_ENTRIES = new LinkedHashMap<>();
|
private static final LinkedHashMap<String, Entry> CONFIG_ENTRIES = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
private final LinkedHashMap<String, Object> values = new LinkedHashMap<>();
|
private final HashMap<String, Object> values = new HashMap<>();
|
||||||
public SessionConfig constrainingConfig;
|
public SessionConfig constrainingConfig;
|
||||||
|
|
||||||
|
|
||||||
@@ -33,9 +33,9 @@ public class SessionConfig implements INetworkObject
|
|||||||
|
|
||||||
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, Boolean::logicalAnd);
|
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, Boolean::logicalAnd);
|
||||||
registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min);
|
registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min);
|
||||||
registerConfigEntry(Config.Server.generationBoundsX, (x, y) -> x);
|
registerConfigEntry(Config.Server.generationBoundsX, (x, y) -> y);
|
||||||
registerConfigEntry(Config.Server.generationBoundsZ, (x, y) -> x);
|
registerConfigEntry(Config.Server.generationBoundsZ, (x, y) -> y);
|
||||||
registerConfigEntry(Config.Server.generationBoundsRadius, (x, y) -> x);
|
registerConfigEntry(Config.Server.generationBoundsRadius, (x, y) -> y);
|
||||||
registerConfigEntry(Config.Server.generationRequestRateLimit, Math::min);
|
registerConfigEntry(Config.Server.generationRequestRateLimit, Math::min);
|
||||||
|
|
||||||
registerConfigEntry(Config.Server.enableRealTimeUpdates, Boolean::logicalAnd);
|
registerConfigEntry(Config.Server.enableRealTimeUpdates, Boolean::logicalAnd);
|
||||||
@@ -119,10 +119,17 @@ public class SessionConfig implements INetworkObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (this.constrainingConfig != null
|
return (this.constrainingConfig != null
|
||||||
? (T) entry.valueConstrainer.apply(value, this.constrainingConfig.getValue(name))
|
? (T) entry.valueConstrainer.apply(this.constrainingConfig.getValue(name), value)
|
||||||
: value);
|
: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> void constrainValue(ConfigEntry<T> configEntry, T value) { this.constrainValue(configEntry.getChatCommandName(), value); }
|
||||||
|
private void constrainValue(String name, Object value)
|
||||||
|
{
|
||||||
|
Entry entry = CONFIG_ENTRIES.get(name);
|
||||||
|
this.values.put(name, entry.valueConstrainer.apply(this.getValue(name), value));
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, ?> getValues()
|
private Map<String, ?> getValues()
|
||||||
{
|
{
|
||||||
return CONFIG_ENTRIES.keySet().stream().collect(Collectors.toMap(
|
return CONFIG_ENTRIES.keySet().stream().collect(Collectors.toMap(
|
||||||
|
|||||||
+3
-10
@@ -9,18 +9,17 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMe
|
|||||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
import io.netty.buffer.Unpooled;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see FullDataSplitMessage
|
* @see FullDataSplitMessage
|
||||||
*/
|
*/
|
||||||
public class FullDataPayload implements INetworkObject, AutoCloseable
|
public class FullDataPayload implements INetworkObject
|
||||||
{
|
{
|
||||||
private static final AtomicInteger lastBufferId = new AtomicInteger();
|
private static final AtomicInteger lastBufferId = new AtomicInteger();
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ public class FullDataPayload implements INetworkObject, AutoCloseable
|
|||||||
EDhApiDataCompressionMode compressionMode = Config.Common.LodBuilding.dataCompression.get();
|
EDhApiDataCompressionMode compressionMode = Config.Common.LodBuilding.dataCompression.get();
|
||||||
try (FullDataSourceV2DTO dataSourceDto = FullDataSourceV2DTO.CreateFromDataSource(fullDataSource, compressionMode))
|
try (FullDataSourceV2DTO dataSourceDto = FullDataSourceV2DTO.CreateFromDataSource(fullDataSource, compressionMode))
|
||||||
{
|
{
|
||||||
this.dtoBuffer = ByteBufAllocator.DEFAULT.buffer();
|
this.dtoBuffer = Unpooled.buffer();
|
||||||
dataSourceDto.encode(this.dtoBuffer);
|
dataSourceDto.encode(this.dtoBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,12 +84,6 @@ public class FullDataPayload implements INetworkObject, AutoCloseable
|
|||||||
// base overrides //
|
// base overrides //
|
||||||
//================//
|
//================//
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close()
|
|
||||||
{
|
|
||||||
this.dtoBuffer.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|||||||
+9
-23
@@ -9,8 +9,8 @@ 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.ByteBufAllocator;
|
|
||||||
import io.netty.buffer.CompositeByteBuf;
|
import io.netty.buffer.CompositeByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -24,17 +24,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
|
|
||||||
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
|
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
|
||||||
.expireAfterAccess(10, TimeUnit.SECONDS)
|
.expireAfterAccess(10, TimeUnit.SECONDS)
|
||||||
.removalListener((RemovalNotification<Integer, CompositeByteBuf> notification) ->
|
.<Integer, CompositeByteBuf>build().asMap();
|
||||||
{
|
|
||||||
// If an entry was replaced without removing, the buffer has to be released manually
|
|
||||||
if (notification.getCause() != RemovalCause.REPLACED)
|
|
||||||
{
|
|
||||||
Objects.requireNonNull(notification.getValue()).release();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build().asMap();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
@@ -48,13 +38,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
{
|
{
|
||||||
if (message.isFirst)
|
if (message.isFirst)
|
||||||
{
|
{
|
||||||
if (composite != null)
|
composite = Unpooled.compositeBuffer();
|
||||||
{
|
|
||||||
composite.release();
|
|
||||||
LOGGER.debug("Released existing full data buffer [" + message.bufferId + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
composite = ByteBufAllocator.DEFAULT.compositeBuffer();
|
|
||||||
LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]");
|
LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]");
|
||||||
}
|
}
|
||||||
else if (composite == null)
|
else if (composite == null)
|
||||||
@@ -69,19 +53,21 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public FullDataSourceV2DTO decodeDataSourceAndReleaseBuffer(FullDataPayload msg)
|
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
||||||
{
|
{
|
||||||
CompositeByteBuf compositeByteBuffer = this.buffersById.get(msg.dtoBufferId);
|
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
|
||||||
LodUtil.assertTrue(compositeByteBuffer != null);
|
LodUtil.assertTrue(compositeByteBuffer != null);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return INetworkObject.decodeToInstance(FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(), compositeByteBuffer);
|
FullDataSourceV2DTO dataSourceDto = INetworkObject.decodeToInstance(FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(), compositeByteBuffer);
|
||||||
|
LOGGER.debug("Buffer {} DTO: {}", payload.dtoBufferId, dataSourceDto);
|
||||||
|
return dataSourceDto;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Releasing the buffer is handled by cache
|
// Releasing the buffer is handled by cache
|
||||||
this.buffersById.remove(msg.dtoBufferId);
|
this.buffersById.remove(payload.dtoBufferId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-20
@@ -13,7 +13,7 @@ import java.util.function.*;
|
|||||||
|
|
||||||
public class FullDataPayloadSender implements AutoCloseable
|
public class FullDataPayloadSender implements AutoCloseable
|
||||||
{
|
{
|
||||||
private static final int TICK_RATE = 4;
|
private static final int TICK_RATE = 20;
|
||||||
|
|
||||||
/** 1 Mebibyte minus 576 bytes for other info */
|
/** 1 Mebibyte minus 576 bytes for other info */
|
||||||
public static final int FULL_DATA_SPLIT_SIZE_IN_BYTES = 1_048_000;
|
public static final int FULL_DATA_SPLIT_SIZE_IN_BYTES = 1_048_000;
|
||||||
@@ -38,12 +38,6 @@ public class FullDataPayloadSender implements AutoCloseable
|
|||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
this.tickTimerTask.cancel();
|
this.tickTimerTask.cancel();
|
||||||
|
|
||||||
PendingTransfer pendingTransfer;
|
|
||||||
while ((pendingTransfer = this.transferQueue.poll()) != null)
|
|
||||||
{
|
|
||||||
pendingTransfer.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -78,36 +72,25 @@ public class FullDataPayloadSender implements AutoCloseable
|
|||||||
if (pendingTransfer.buffer.readableBytes() == 0)
|
if (pendingTransfer.buffer.readableBytes() == 0)
|
||||||
{
|
{
|
||||||
pendingTransfer.sendFinalMessage.run();
|
pendingTransfer.sendFinalMessage.run();
|
||||||
pendingTransfer.close();
|
|
||||||
this.transferQueue.poll();
|
this.transferQueue.poll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class PendingTransfer implements AutoCloseable
|
private static class PendingTransfer
|
||||||
{
|
{
|
||||||
public final int bufferId;
|
public final int bufferId;
|
||||||
public final ByteBuf buffer;
|
public final ByteBuf buffer;
|
||||||
public final Runnable sendFinalMessage;
|
public final Runnable sendFinalMessage;
|
||||||
private final AtomicBoolean isClosed = new AtomicBoolean();
|
|
||||||
|
|
||||||
private PendingTransfer(FullDataPayload payload, Runnable sendFinalMessage)
|
private PendingTransfer(FullDataPayload payload, Runnable sendFinalMessage)
|
||||||
{
|
{
|
||||||
this.bufferId = payload.dtoBufferId;
|
this.bufferId = payload.dtoBufferId;
|
||||||
this.buffer = payload.dtoBuffer.retainedDuplicate().readerIndex(0);
|
this.buffer = payload.dtoBuffer.duplicate().readerIndex(0);
|
||||||
this.sendFinalMessage = sendFinalMessage;
|
this.sendFinalMessage = sendFinalMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close()
|
|
||||||
{
|
|
||||||
if (this.isClosed.compareAndSet(false, true))
|
|
||||||
{
|
|
||||||
this.buffer.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-38
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.server;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
|
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
|
||||||
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.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.ConfigBasedLogger;
|
||||||
@@ -59,45 +60,80 @@ public class FullDataSourceRequestHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// the client timestamp will be null if we want to retrieve the LOD regardless of when it was last updated
|
|
||||||
long clientTimestamp = (message.clientTimestamp != null) ? message.clientTimestamp : -1;
|
AbstractExecutorService fileHandlerExecutor = ThreadPoolUtil.getFileHandlerExecutor();
|
||||||
// the server timestamp will be null if no LOD data exists for this position
|
if (fileHandlerExecutor == null)
|
||||||
Long serverTimestamp = this.fullDataSourceProvider().getTimestampForPos(message.sectionPos);
|
|
||||||
if (serverTimestamp == null
|
|
||||||
|| serverTimestamp <= clientTimestamp)
|
|
||||||
{
|
{
|
||||||
// either no data exists to sync, or the client is already up to date
|
// shouldn't normally happen, but just in case
|
||||||
rateLimiterSet.syncOnLoginRateLimiter.release();
|
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getFileHandlerExecutor() is null");
|
||||||
message.sendResponse(new FullDataSourceResponseMessage(null));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AbstractExecutorService networkCompressionExecutor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
||||||
AbstractExecutorService executor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
if (networkCompressionExecutor == null)
|
||||||
if (executor == null)
|
|
||||||
{
|
{
|
||||||
// shouldn't normally happen, but just in case
|
// shouldn't normally happen, but just in case
|
||||||
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
|
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fullDataSourceProvider().getAsync(message.sectionPos).thenAcceptAsync(fullDataSource ->
|
|
||||||
{
|
|
||||||
try (FullDataPayload payload = new FullDataPayload(fullDataSource, this.getAllBeamsForPos(message.sectionPos)))
|
|
||||||
{
|
|
||||||
fullDataSource.close();
|
|
||||||
|
|
||||||
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
|
// get the data requested by the client
|
||||||
{
|
CompletableFuture<FullDataSourceV2> getServerDatasourceFuture = CompletableFuture.supplyAsync(() ->
|
||||||
message.sendResponse(new FullDataSourceResponseMessage(payload));
|
|
||||||
rateLimiterSet.syncOnLoginRateLimiter.release();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
{
|
||||||
LOGGER.error("Unexpected issue getting request for pos ["+DhSectionPos.toString(message.sectionPos)+"], error: ["+e.getMessage()+"].", e);
|
try
|
||||||
}
|
{
|
||||||
}, executor);
|
// the client timestamp will be null if we want to retrieve the LOD regardless of when it was last updated
|
||||||
|
long clientTimestamp = (message.clientTimestamp != null) ? message.clientTimestamp : -1;
|
||||||
|
|
||||||
|
// the server timestamp will be null if no LOD data exists for this position
|
||||||
|
Long serverTimestamp = this.fullDataSourceProvider().getTimestampForPos(message.sectionPos);
|
||||||
|
if (serverTimestamp == null
|
||||||
|
|| serverTimestamp <= clientTimestamp)
|
||||||
|
{
|
||||||
|
// either no data exists to sync, or the client is already up to date
|
||||||
|
rateLimiterSet.syncOnLoginRateLimiter.release();
|
||||||
|
message.sendResponse(new FullDataSourceResponseMessage(null));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the server's datasource
|
||||||
|
return this.fullDataSourceProvider().get(message.sectionPos);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue getting server-side LOD for request at pos [" + DhSectionPos.toString(message.sectionPos) + "], error: [" + e.getMessage() + "].", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, fileHandlerExecutor);
|
||||||
|
|
||||||
|
// send the found data
|
||||||
|
getServerDatasourceFuture.thenAcceptAsync(fullDataSource ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// no server data source found
|
||||||
|
if (fullDataSource == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the found data source to client
|
||||||
|
FullDataPayload payload = new FullDataPayload(fullDataSource, this.getAllBeamsForPos(message.sectionPos));
|
||||||
|
fullDataSource.close();
|
||||||
|
|
||||||
|
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
|
||||||
|
{
|
||||||
|
message.sendResponse(new FullDataSourceResponseMessage(payload));
|
||||||
|
rateLimiterSet.syncOnLoginRateLimiter.release();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue sending request for pos [" + DhSectionPos.toString(message.sectionPos) + "], error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
}, networkCompressionExecutor);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void queueWorldGenForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
|
public void queueWorldGenForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
|
||||||
@@ -207,19 +243,17 @@ public class FullDataSourceRequestHandler
|
|||||||
}
|
}
|
||||||
CompletableFuture.runAsync(() ->
|
CompletableFuture.runAsync(() ->
|
||||||
{
|
{
|
||||||
try (FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource, this.getAllBeamsForPos(entry.getKey())))
|
FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource, this.getAllBeamsForPos(entry.getKey()));
|
||||||
|
requestGroup.fullDataSource.close();
|
||||||
|
|
||||||
|
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
|
||||||
{
|
{
|
||||||
requestGroup.fullDataSource.close();
|
this.requestGroupsByFutureId.remove(requestData.futureId());
|
||||||
|
|
||||||
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
|
requestData.serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () -> {
|
||||||
{
|
requestData.message.sendResponse(new FullDataSourceResponseMessage(payload));
|
||||||
this.requestGroupsByFutureId.remove(requestData.futureId());
|
requestData.rateLimiterSet.generationRequestRateLimiter.release();
|
||||||
|
});
|
||||||
requestData.serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () -> {
|
|
||||||
requestData.message.sendResponse(new FullDataSourceResponseMessage(payload));
|
|
||||||
requestData.rateLimiterSet.generationRequestRateLimiter.release();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, executor);
|
}, executor);
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-4
@@ -24,9 +24,13 @@ public class ServerPlayerState implements Closeable
|
|||||||
{
|
{
|
||||||
private final ConfigChangeListener<String> levelKeyPrefixChangeListener
|
private final ConfigChangeListener<String> levelKeyPrefixChangeListener
|
||||||
= new ConfigChangeListener<>(Config.Server.levelKeyPrefix, this::onLevelKeyPrefixConfigChanged);
|
= new ConfigChangeListener<>(Config.Server.levelKeyPrefix, this::onLevelKeyPrefixConfigChanged);
|
||||||
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::onSessionConfigChanged);
|
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.PART_ALLOWED_CHARS_REGEX + " ]", "")
|
||||||
|
.replaceAll(" ", "_");
|
||||||
private String lastLevelKey = "";
|
private String lastLevelKey = "";
|
||||||
|
|
||||||
|
|
||||||
@@ -56,8 +60,9 @@ public class ServerPlayerState implements Closeable
|
|||||||
this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) ->
|
this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) ->
|
||||||
{
|
{
|
||||||
this.sessionConfig.constrainingConfig = sessionConfigMessage.config;
|
this.sessionConfig.constrainingConfig = sessionConfigMessage.config;
|
||||||
|
|
||||||
this.sendLevelKey();
|
this.sendLevelKey();
|
||||||
this.networkSession.sendMessage(new SessionConfigMessage(this.sessionConfig));
|
this.sendConfigMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.networkSession.registerHandler(CloseInternalEvent.class, event -> {
|
this.networkSession.registerHandler(CloseInternalEvent.class, event -> {
|
||||||
@@ -88,12 +93,19 @@ 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSessionConfigChanged() { this.networkSession.sendMessage(new SessionConfigMessage(this.sessionConfig)); }
|
private void sendConfigMessage()
|
||||||
|
{
|
||||||
|
double coordinateScale = this.getServerPlayer().getLevel().getDimensionType().getCoordinateScale();
|
||||||
|
this.sessionConfig.constrainValue(Config.Server.generationBoundsX, (int) (Config.Server.generationBoundsX.get() / coordinateScale));
|
||||||
|
this.sessionConfig.constrainValue(Config.Server.generationBoundsZ, (int) (Config.Server.generationBoundsZ.get() / coordinateScale));
|
||||||
|
|
||||||
|
this.networkSession.sendMessage(new SessionConfigMessage(this.sessionConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+7
@@ -46,6 +46,13 @@ public class ServerPlayerStateManager
|
|||||||
|
|
||||||
public void handlePluginMessage(IServerPlayerWrapper player, AbstractNetworkMessage message)
|
public void handlePluginMessage(IServerPlayerWrapper player, AbstractNetworkMessage message)
|
||||||
{
|
{
|
||||||
|
// done to prevent a rare null-pointer on Neo/Forge
|
||||||
|
if (player == null || message == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
MessageQueueState messageQueue = this.messageQueueByPlayerWrapper.computeIfAbsent(player, k -> new MessageQueueState());
|
MessageQueueState messageQueue = this.messageQueueByPlayerWrapper.computeIfAbsent(player, k -> new MessageQueueState());
|
||||||
messageQueue.messageQueue.add(message);
|
messageQueue.messageQueue.add(message);
|
||||||
|
|
||||||
|
|||||||
+12
-2
@@ -10,12 +10,18 @@ public class LevelInitMessage extends AbstractNetworkMessage
|
|||||||
|
|
||||||
public static final String PART_ALLOWED_CHARS_REGEX = "a-zA-Z0-9-_";
|
public static final String PART_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, PART_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, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX, PART_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
-14
@@ -22,8 +22,8 @@ package com.seibel.distanthorizons.core.network.messages.fullData;
|
|||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayload;
|
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayload;
|
||||||
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
|
|
||||||
@@ -34,16 +34,10 @@ import java.util.Timer;
|
|||||||
*/
|
*/
|
||||||
public class FullDataSplitMessage extends AbstractNetworkMessage
|
public class FullDataSplitMessage extends AbstractNetworkMessage
|
||||||
{
|
{
|
||||||
private static final long BUFFER_RELEASE_DELAY_MS = 5000L;
|
|
||||||
|
|
||||||
public int bufferId;
|
public int bufferId;
|
||||||
public ByteBuf buffer;
|
public ByteBuf buffer;
|
||||||
public boolean isFirst;
|
public boolean isFirst;
|
||||||
|
|
||||||
// Reference counting is unreliable here for some reason so this is a "fix"
|
|
||||||
private static final Timer bufferReleaseTimer = TimerUtil.CreateTimer("FullDataBufferCleanupTimer");
|
|
||||||
private boolean releaseScheduled = false;
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
// constructors //
|
// constructors //
|
||||||
@@ -72,12 +66,6 @@ public class FullDataSplitMessage extends AbstractNetworkMessage
|
|||||||
out.writeBytes(this.buffer.readerIndex(0));
|
out.writeBytes(this.buffer.readerIndex(0));
|
||||||
|
|
||||||
out.writeBoolean(this.isFirst);
|
out.writeBoolean(this.isFirst);
|
||||||
|
|
||||||
if (!this.releaseScheduled)
|
|
||||||
{
|
|
||||||
bufferReleaseTimer.schedule(TimerUtil.createTimerTask(this.buffer::release), BUFFER_RELEASE_DELAY_MS);
|
|
||||||
this.releaseScheduled = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -86,7 +74,7 @@ public class FullDataSplitMessage extends AbstractNetworkMessage
|
|||||||
this.bufferId = in.readInt();
|
this.bufferId = in.readInt();
|
||||||
|
|
||||||
int bufferSize = in.readInt();
|
int bufferSize = in.readInt();
|
||||||
this.buffer = in.readBytes(bufferSize);
|
this.buffer = Unpooled.copiedBuffer(in.readSlice(bufferSize));
|
||||||
|
|
||||||
this.isFirst = in.readBoolean();
|
this.isFirst = in.readBoolean();
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -306,9 +306,9 @@ public class PhantomArrayListPool
|
|||||||
{
|
{
|
||||||
// we only want to log when something has been returned
|
// we only want to log when something has been returned
|
||||||
if (checkoutCount != 0
|
if (checkoutCount != 0
|
||||||
|| returnedByteArrayCount != 0
|
|| returnedByteArrayCount != 0
|
||||||
|| returnedShortArrayCount != 0
|
|| returnedShortArrayCount != 0
|
||||||
|| returnedLongArrayCount != 0)
|
|| returnedLongArrayCount != 0)
|
||||||
{
|
{
|
||||||
LOGGER.warn("Pool: ["+ pool.name+"] phantom recovery. Returned checkouts:["+F3Screen.NUMBER_FORMAT.format(checkoutCount)+"], byte:["+F3Screen.NUMBER_FORMAT.format(returnedByteArrayCount)+"], short:["+F3Screen.NUMBER_FORMAT.format(returnedShortArrayCount)+"], long:["+F3Screen.NUMBER_FORMAT.format(returnedLongArrayCount)+"].");
|
LOGGER.warn("Pool: ["+ pool.name+"] phantom recovery. Returned checkouts:["+F3Screen.NUMBER_FORMAT.format(checkoutCount)+"], byte:["+F3Screen.NUMBER_FORMAT.format(returnedByteArrayCount)+"], short:["+F3Screen.NUMBER_FORMAT.format(returnedShortArrayCount)+"], long:["+F3Screen.NUMBER_FORMAT.format(returnedLongArrayCount)+"].");
|
||||||
|
|
||||||
|
|||||||
@@ -399,9 +399,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
// prepare this section for rendering
|
// prepare this section for rendering
|
||||||
if (!renderSection.gpuUploadInProgress()
|
if (!renderSection.gpuUploadInProgress()
|
||||||
&& renderSection.renderBuffer == null
|
&& renderSection.renderBuffer == null
|
||||||
|
// TODO this is commented out since some users reported LODs refusing to
|
||||||
|
// load at their expected higher-detail levels
|
||||||
// this check is specifically for N-sized world generators where the higher quality
|
// this check is specifically for N-sized world generators where the higher quality
|
||||||
// data source may not exist yet, this is done to prevent holes while waiting for said generator
|
// data source may not exist yet, this is done to prevent holes while waiting for said generator
|
||||||
&& renderSection.getFullDataSourceExists()
|
//&& renderSection.getFullDataSourceExists()
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
nodesNeedingLoading.add(renderSection);
|
nodesNeedingLoading.add(renderSection);
|
||||||
|
|||||||
@@ -510,10 +510,13 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
{
|
{
|
||||||
// TODO memoization is needed for multiplayer, otherwise
|
// TODO memoization is needed for multiplayer, otherwise
|
||||||
// new retrieval requests won't be submitted.
|
// new retrieval requests won't be submitted.
|
||||||
// TODO why is that the case? Shouldn't the missing positions be un-changing?
|
// TODO why is that the case? Shouldn't the missing positions be un-changing?
|
||||||
|
// TODO setting this value to low can cause world gen to slow down significantly
|
||||||
|
// due to a race condition where the world gen thinks it is finished, but the results
|
||||||
|
// haven't been saved to file yet, causing the gen to fire again
|
||||||
this.missingGenerationPosFunc = Suppliers.memoizeWithExpiration(
|
this.missingGenerationPosFunc = Suppliers.memoizeWithExpiration(
|
||||||
() -> this.fullDataSourceProvider.getPositionsToRetrieve(this.pos),
|
() -> this.fullDataSourceProvider.getPositionsToRetrieve(this.pos),
|
||||||
15, TimeUnit.SECONDS);
|
10, TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
|
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
|
||||||
|
|||||||
@@ -359,6 +359,7 @@ public class RenderBufferHandler implements AutoCloseable
|
|||||||
// debug wireframe setup //
|
// debug wireframe setup //
|
||||||
//=======================//
|
//=======================//
|
||||||
|
|
||||||
|
// TODO move this logic into LodRenderer so all the GL states can be handled there
|
||||||
boolean renderWireframe = Config.Client.Advanced.Debugging.renderWireframe.get();
|
boolean renderWireframe = Config.Client.Advanced.Debugging.renderWireframe.get();
|
||||||
if (renderWireframe)
|
if (renderWireframe)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ 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.ConfigBasedLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.util.objects.GLMessage;
|
import com.seibel.distanthorizons.core.util.objects.GLMessages.*;
|
||||||
import com.seibel.distanthorizons.core.util.objects.GLMessageOutputStream;
|
|
||||||
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 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 org.apache.logging.log4j.Logger;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
@@ -38,31 +38,28 @@ import org.lwjgl.opengl.GLCapabilities;
|
|||||||
import org.lwjgl.opengl.GLUtil;
|
import org.lwjgl.opengl.GLUtil;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A singleton that holds references to different openGL contexts
|
* A singleton that holds references to different openGL contexts
|
||||||
* and GPU capabilities.
|
* and GPU capabilities.
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Helpful OpenGL resources:
|
|
||||||
* <p>
|
|
||||||
* https://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf <br>
|
|
||||||
* https://learnopengl.com/Advanced-OpenGL/Advanced-Data <br>
|
|
||||||
* https://www.slideshare.net/CassEveritt/approaching-zero-driver-overhead <br><br>
|
|
||||||
*
|
|
||||||
* https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one <br>
|
|
||||||
* https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer <br><br>
|
|
||||||
*/
|
*/
|
||||||
public class GLProxy
|
public class GLProxy
|
||||||
{
|
{
|
||||||
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
|
|
||||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
|
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
public static final ConfigBasedLogger GL_LOGGER = new ConfigBasedLogger(LogManager.getLogger(GLProxy.class),
|
public static final ConfigBasedLogger GL_LOGGER = new ConfigBasedLogger(LogManager.getLogger(GLProxy.class),
|
||||||
() -> Config.Common.Logging.logRendererGLEvent.get());
|
() -> Config.Common.Logging.logRendererGLEvent.get());
|
||||||
|
|
||||||
|
public static final Set<String> LOGGED_GL_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static GLProxy instance = null;
|
private static GLProxy instance = null;
|
||||||
|
|
||||||
|
|
||||||
@@ -79,7 +76,29 @@ public class GLProxy
|
|||||||
|
|
||||||
private final EDhApiGpuUploadMethod preferredUploadMethod;
|
private final EDhApiGpuUploadMethod preferredUploadMethod;
|
||||||
|
|
||||||
public final GLMessage.Builder vanillaDebugMessageBuilder = GLMessage.Builder.DEFAULT_MESSAGE_BUILDER;
|
public final GLMessageBuilder vanillaDebugMessageBuilder =
|
||||||
|
new GLMessageBuilder(
|
||||||
|
(type) ->
|
||||||
|
{
|
||||||
|
if (type == EGLMessageType.POP_GROUP)
|
||||||
|
return false;
|
||||||
|
else if (type == EGLMessageType.PUSH_GROUP)
|
||||||
|
return false;
|
||||||
|
else if (type == EGLMessageType.MARKER)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
(severity) ->
|
||||||
|
{
|
||||||
|
// notifications can generally be ignored (if they are logged at all)
|
||||||
|
if (severity == EGLMessageSeverity.NOTIFICATION)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -258,6 +277,7 @@ public class GLProxy
|
|||||||
// logging //
|
// logging //
|
||||||
//=========//
|
//=========//
|
||||||
|
|
||||||
|
/** this method is called on the render thread at the point of the GL Error */
|
||||||
private static void logMessage(GLMessage msg)
|
private static void logMessage(GLMessage msg)
|
||||||
{
|
{
|
||||||
EDhApiGLErrorHandlingMode errorHandlingMode = Config.Client.Advanced.Debugging.OpenGl.glErrorHandlingMode.get();
|
EDhApiGLErrorHandlingMode errorHandlingMode = Config.Client.Advanced.Debugging.OpenGl.glErrorHandlingMode.get();
|
||||||
@@ -268,44 +288,56 @@ public class GLProxy
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (msg.type == GLMessage.EType.ERROR || msg.type == GLMessage.EType.UNDEFINED_BEHAVIOR)
|
boolean onlyLogOnce = Config.Client.Advanced.Debugging.OpenGl.onlyLogGlErrorsOnce.get();
|
||||||
|
String errorMessage = "GL ERROR [" + msg.id + "] from [" + msg.source + "]: [" + msg.message + "]"+(onlyLogOnce ? " this message will only be logged once" : "")+".";
|
||||||
|
if (onlyLogOnce
|
||||||
|
&& !LOGGED_GL_MESSAGES.add(errorMessage))
|
||||||
|
{
|
||||||
|
// this message has already been logged
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create an exception so we get a stacktrace of where the message was triggered from
|
||||||
|
RuntimeException exception = new RuntimeException(errorMessage);
|
||||||
|
|
||||||
|
if (msg.type == EGLMessageType.ERROR || msg.type == EGLMessageType.UNDEFINED_BEHAVIOR)
|
||||||
{
|
{
|
||||||
// critical error
|
// critical error
|
||||||
|
|
||||||
GL_LOGGER.error("GL ERROR " + msg.id + " from " + msg.source + ": " + msg.message);
|
GL_LOGGER.error(exception.getMessage(), exception);
|
||||||
|
|
||||||
if (errorHandlingMode == EDhApiGLErrorHandlingMode.LOG_THROW)
|
if (errorHandlingMode == EDhApiGLErrorHandlingMode.LOG_THROW)
|
||||||
{
|
{
|
||||||
throw new RuntimeException("GL ERROR: " + msg);
|
// will probably crash the game,
|
||||||
|
// good for quickly checking if there's a problem while preventing log spam
|
||||||
|
throw exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// non-critical log
|
// non-critical log
|
||||||
|
|
||||||
GLMessage.ESeverity severity = msg.severity;
|
EGLMessageSeverity severity = msg.severity;
|
||||||
RuntimeException ex = new RuntimeException("GL MESSAGE: " + msg);
|
|
||||||
|
|
||||||
if (severity == null)
|
if (severity == null)
|
||||||
{
|
{
|
||||||
// just in case the message was malformed
|
// just in case the message was malformed
|
||||||
severity = GLMessage.ESeverity.LOW;
|
severity = EGLMessageSeverity.LOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (severity)
|
switch (severity)
|
||||||
{
|
{
|
||||||
case HIGH:
|
case HIGH:
|
||||||
GL_LOGGER.error("{}", ex);
|
GL_LOGGER.error(exception.getMessage(), exception);
|
||||||
break;
|
break;
|
||||||
case MEDIUM:
|
case MEDIUM:
|
||||||
GL_LOGGER.warn("{}", ex);
|
GL_LOGGER.warn(exception.getMessage(), exception);
|
||||||
break;
|
break;
|
||||||
case LOW:
|
case LOW:
|
||||||
GL_LOGGER.info("{}", ex);
|
GL_LOGGER.info(exception.getMessage(), exception);
|
||||||
break;
|
break;
|
||||||
case NOTIFICATION:
|
case NOTIFICATION:
|
||||||
GL_LOGGER.debug("{}", ex);
|
GL_LOGGER.debug(exception.getMessage(), exception);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class GLState
|
|||||||
public int vao;
|
public int vao;
|
||||||
public int vbo;
|
public int vbo;
|
||||||
public int ebo;
|
public int ebo;
|
||||||
public int[] fbo;
|
public int fbo;
|
||||||
public int texture2D;
|
public int texture2D;
|
||||||
/** IE: GL_TEXTURE0, GL_TEXTURE1, etc. */
|
/** IE: GL_TEXTURE0, GL_TEXTURE1, etc. */
|
||||||
public int activeTextureNumber;
|
public int activeTextureNumber;
|
||||||
@@ -57,10 +57,10 @@ public class GLState
|
|||||||
public boolean depth;
|
public boolean depth;
|
||||||
public boolean writeToDepthBuffer;
|
public boolean writeToDepthBuffer;
|
||||||
public int depthFunc;
|
public int depthFunc;
|
||||||
//public boolean stencil;
|
public boolean stencil;
|
||||||
//public int stencilFunc;
|
public int stencilFunc;
|
||||||
//public int stencilRef;
|
public int stencilRef;
|
||||||
//public int stencilMask;
|
public int stencilMask;
|
||||||
public int[] view;
|
public int[] view;
|
||||||
public boolean cull;
|
public boolean cull;
|
||||||
public int cullMode;
|
public int cullMode;
|
||||||
@@ -68,12 +68,7 @@ public class GLState
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public GLState()
|
public GLState() { this.saveState(); }
|
||||||
{
|
|
||||||
this.fbo = new int[FBO_MAX];
|
|
||||||
|
|
||||||
this.saveState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveState()
|
public void saveState()
|
||||||
{
|
{
|
||||||
@@ -82,7 +77,7 @@ public class GLState
|
|||||||
this.vbo = GL32.glGetInteger(GL32.GL_ARRAY_BUFFER_BINDING);
|
this.vbo = GL32.glGetInteger(GL32.GL_ARRAY_BUFFER_BINDING);
|
||||||
this.ebo = GL32.glGetInteger(GL32.GL_ELEMENT_ARRAY_BUFFER_BINDING);
|
this.ebo = GL32.glGetInteger(GL32.GL_ELEMENT_ARRAY_BUFFER_BINDING);
|
||||||
|
|
||||||
GL32.glGetIntegerv(GL32.GL_FRAMEBUFFER_BINDING, this.fbo);
|
this.fbo = GL32.glGetInteger(GL32.GL_FRAMEBUFFER_BINDING);
|
||||||
|
|
||||||
this.texture2D = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D);
|
this.texture2D = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D);
|
||||||
this.activeTextureNumber = GL32.glGetInteger(GL32.GL_ACTIVE_TEXTURE);
|
this.activeTextureNumber = GL32.glGetInteger(GL32.GL_ACTIVE_TEXTURE);
|
||||||
@@ -101,9 +96,19 @@ public class GLState
|
|||||||
|
|
||||||
GLMC.glActiveTexture(this.activeTextureNumber);
|
GLMC.glActiveTexture(this.activeTextureNumber);
|
||||||
|
|
||||||
this.frameBufferTexture0 = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
|
if (this.fbo != 0)
|
||||||
this.frameBufferTexture1 = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
|
{
|
||||||
this.frameBufferDepthTexture = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
|
this.frameBufferTexture0 = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
|
||||||
|
this.frameBufferTexture1 = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
|
||||||
|
this.frameBufferDepthTexture = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// attempting to get values from the default framebuffer can throw errors on Linux
|
||||||
|
this.frameBufferTexture0 = 0;
|
||||||
|
this.frameBufferTexture1 = 0;
|
||||||
|
this.frameBufferDepthTexture = 0;
|
||||||
|
}
|
||||||
|
|
||||||
this.blend = GL32.glIsEnabled(GL32.GL_BLEND);
|
this.blend = GL32.glIsEnabled(GL32.GL_BLEND);
|
||||||
this.scissor = GL32.glIsEnabled(GL32.GL_SCISSOR_TEST);
|
this.scissor = GL32.glIsEnabled(GL32.GL_SCISSOR_TEST);
|
||||||
@@ -116,10 +121,10 @@ public class GLState
|
|||||||
this.depth = GL32.glIsEnabled(GL32.GL_DEPTH_TEST);
|
this.depth = GL32.glIsEnabled(GL32.GL_DEPTH_TEST);
|
||||||
this.writeToDepthBuffer = GL32.glGetInteger(GL32.GL_DEPTH_WRITEMASK) == GL32.GL_TRUE;
|
this.writeToDepthBuffer = GL32.glGetInteger(GL32.GL_DEPTH_WRITEMASK) == GL32.GL_TRUE;
|
||||||
this.depthFunc = GL32.glGetInteger(GL32.GL_DEPTH_FUNC);
|
this.depthFunc = GL32.glGetInteger(GL32.GL_DEPTH_FUNC);
|
||||||
//this.stencil = GL32.glIsEnabled(GL32.GL_STENCIL_TEST);
|
this.stencil = GL32.glIsEnabled(GL32.GL_STENCIL_TEST);
|
||||||
//this.stencilFunc = GL32.glGetInteger(GL32.GL_STENCIL_FUNC);
|
this.stencilFunc = GL32.glGetInteger(GL32.GL_STENCIL_FUNC);
|
||||||
//this.stencilRef = GL32.glGetInteger(GL32.GL_STENCIL_REF);
|
this.stencilRef = GL32.glGetInteger(GL32.GL_STENCIL_REF);
|
||||||
//this.stencilMask = GL32.glGetInteger(GL32.GL_STENCIL_VALUE_MASK);
|
this.stencilMask = GL32.glGetInteger(GL32.GL_STENCIL_VALUE_MASK);
|
||||||
this.view = new int[4];
|
this.view = new int[4];
|
||||||
GL32.glGetIntegerv(GL32.GL_VIEWPORT, this.view);
|
GL32.glGetIntegerv(GL32.GL_VIEWPORT, this.view);
|
||||||
this.cull = GL32.glIsEnabled(GL32.GL_CULL_FACE);
|
this.cull = GL32.glIsEnabled(GL32.GL_CULL_FACE);
|
||||||
@@ -131,38 +136,33 @@ public class GLState
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "GLState{" +
|
return "GLState{" +
|
||||||
"program=" + this.program + ", vao=" + this.vao + ", vbo=" + this.vbo + ", ebo=" + this.ebo + ", fbo=" + this.fbo[0] +
|
"program=" + this.program + ", vao=" + this.vao + ", vbo=" + this.vbo + ", ebo=" + this.ebo + ", fbo=" + this.fbo +
|
||||||
", text=" + GLEnums.getString(this.texture2D) + "@" + this.activeTextureNumber + ", text0=" + GLEnums.getString(this.texture0) +
|
", text=" + GLEnums.getString(this.texture2D) + "@" + this.activeTextureNumber + ", text0=" + GLEnums.getString(this.texture0) +
|
||||||
", FB text0=" + this.frameBufferTexture0 +
|
", FB text0=" + this.frameBufferTexture0 +
|
||||||
", FB text1=" + this.frameBufferTexture1 +
|
", FB text1=" + this.frameBufferTexture1 +
|
||||||
", FB depth=" + this.frameBufferDepthTexture +
|
", FB depth=" + this.frameBufferDepthTexture +
|
||||||
", blend=" + this.blend + ", scissor=" + this.scissor + ", blendMode=" + GLEnums.getString(this.blendSrcColor) + "," + GLEnums.getString(this.blendDstColor) +
|
", blend=" + this.blend + ", scissor=" + this.scissor + ", blendMode=" + GLEnums.getString(this.blendSrcColor) + "," + GLEnums.getString(this.blendDstColor) +
|
||||||
", depth=" + this.depth +
|
", depth=" + this.depth +
|
||||||
//", depthFunc=" + GLEnums.getString(this.depthFunc) + ", stencil=" + this.stencil + ", stencilFunc=" +
|
", depthFunc=" + GLEnums.getString(this.depthFunc) + ", stencil=" + this.stencil +
|
||||||
//GLEnums.getString(this.stencilFunc) + ", stencilRef=" + this.stencilRef + ", stencilMask=" + this.stencilMask +
|
", stencilFunc=" + GLEnums.getString(this.stencilFunc) + ", stencilRef=" + this.stencilRef + ", stencilMask=" + this.stencilMask +
|
||||||
", view={x:" + this.view[0] + ", y:" + this.view[1] +
|
", view={x:" + this.view[0] + ", y:" + this.view[1] +
|
||||||
", w:" + this.view[2] + ", h:" + this.view[3] + "}" + ", cull=" + this.cull + ", cullMode="
|
", w:" + this.view[2] + ", h:" + this.view[3] + "}" + ", cull=" + this.cull +
|
||||||
+ GLEnums.getString(this.cullMode) + ", polyMode=" + GLEnums.getString(this.polyMode) +
|
", cullMode=" + GLEnums.getString(this.cullMode) + ", polyMode=" + GLEnums.getString(this.polyMode) +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RestoreFrameBuffer()
|
|
||||||
{
|
|
||||||
// explicitly unbinding the frame buffer is necessary to prevent GL_CLEAR calls from hitting the wrong buffer
|
|
||||||
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, 0);
|
|
||||||
|
|
||||||
for (int i = 0; i < FBO_MAX; i++)
|
|
||||||
{
|
|
||||||
int buffer = this.fbo[i];
|
|
||||||
if (i > 0 && buffer == 0) break;
|
|
||||||
|
|
||||||
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, GL32.glIsFramebuffer(buffer) ? buffer : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restore()
|
public void restore()
|
||||||
{
|
{
|
||||||
this.RestoreFrameBuffer();
|
// explicitly unbinding the frame buffer is necessary to prevent GL_CLEAR calls from hitting the wrong buffer
|
||||||
|
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, 0);
|
||||||
|
boolean frameBufferSet = false;
|
||||||
|
|
||||||
|
if (this.fbo != 0 && GL32.glIsFramebuffer(this.fbo))
|
||||||
|
{
|
||||||
|
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fbo);
|
||||||
|
frameBufferSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (this.blend)
|
if (this.blend)
|
||||||
{
|
{
|
||||||
@@ -197,9 +197,13 @@ public class GLState
|
|||||||
GLMC.glActiveTexture(this.activeTextureNumber);
|
GLMC.glActiveTexture(this.activeTextureNumber);
|
||||||
GLMC.glBindTexture(GL32.glIsTexture(this.texture2D) ? this.texture2D : 0);
|
GLMC.glBindTexture(GL32.glIsTexture(this.texture2D) ? this.texture2D : 0);
|
||||||
|
|
||||||
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.frameBufferTexture0, 0);
|
// attempting to set textures on the default frame buffer (ID 0) will throw errors
|
||||||
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, GL32.GL_TEXTURE_2D, this.frameBufferTexture1, 0);
|
if (frameBufferSet)
|
||||||
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, GL32.GL_TEXTURE_2D, this.frameBufferDepthTexture, 0);
|
{
|
||||||
|
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.frameBufferTexture0, 0);
|
||||||
|
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, GL32.GL_TEXTURE_2D, this.frameBufferTexture1, 0);
|
||||||
|
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, GL32.GL_TEXTURE_2D, this.frameBufferDepthTexture, 0);
|
||||||
|
}
|
||||||
|
|
||||||
GL32.glBindVertexArray(GL32.glIsVertexArray(this.vao) ? this.vao : 0);
|
GL32.glBindVertexArray(GL32.glIsVertexArray(this.vao) ? this.vao : 0);
|
||||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, GL32.glIsBuffer(this.vbo) ? this.vbo : 0);
|
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, GL32.glIsBuffer(this.vbo) ? this.vbo : 0);
|
||||||
@@ -229,15 +233,15 @@ public class GLState
|
|||||||
}
|
}
|
||||||
GLMC.glDepthFunc(this.depthFunc);
|
GLMC.glDepthFunc(this.depthFunc);
|
||||||
|
|
||||||
//if (this.stencil)
|
if (this.stencil)
|
||||||
//{
|
{
|
||||||
// GL32.glEnable(GL32.GL_STENCIL_TEST);
|
GL32.glEnable(GL32.GL_STENCIL_TEST);
|
||||||
//}
|
}
|
||||||
//else
|
else
|
||||||
//{
|
{
|
||||||
// GL32.glDisable(GL32.GL_STENCIL_TEST);
|
GL32.glDisable(GL32.GL_STENCIL_TEST);
|
||||||
//}
|
}
|
||||||
//GL32.glStencilFunc(this.stencilFunc, this.stencilRef, this.stencilMask);
|
GL32.glStencilFunc(this.stencilFunc, this.stencilRef, this.stencilMask);
|
||||||
|
|
||||||
GL32.glViewport(this.view[0], this.view[1], this.view[2], this.view[3]);
|
GL32.glViewport(this.view[0], this.view[1], this.view[2], this.view[3]);
|
||||||
if (this.cull)
|
if (this.cull)
|
||||||
|
|||||||
+1
-1
@@ -223,7 +223,7 @@ public class GLBuffer implements AutoCloseable
|
|||||||
this.destroyAsync();
|
this.destroyAsync();
|
||||||
this.create(true);
|
this.create(true);
|
||||||
this.bind();
|
this.bind();
|
||||||
GL44.glBufferStorage(this.getBufferBindingTarget(), bb, bufferStorageHint);
|
GL44.glBufferStorage(this.getBufferBindingTarget(), bb, 0);
|
||||||
this.size = bbSize;
|
this.size = bbSize;
|
||||||
}
|
}
|
||||||
/** Requires the buffer to be bound */
|
/** Requires the buffer to be bound */
|
||||||
|
|||||||
+83
-25
@@ -25,22 +25,31 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
||||||
|
import org.lwjgl.PointerBuffer;
|
||||||
import org.lwjgl.opengl.GL32;
|
import org.lwjgl.opengl.GL32;
|
||||||
|
import org.lwjgl.opengl.GL32C;
|
||||||
|
import org.lwjgl.system.MemoryStack;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
import org.lwjgl.system.NativeType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This object holds a OpenGL reference to a shader
|
* This object holds a OpenGL reference to a shader
|
||||||
* and allows for reading in and compiling a shader file.
|
* and allows for reading in and compiling a shader file.
|
||||||
*
|
|
||||||
* @author James Seibel
|
|
||||||
* @version 11-8-2021
|
|
||||||
*/
|
*/
|
||||||
public class Shader
|
public class Shader
|
||||||
{
|
{
|
||||||
/** OpenGL shader ID */
|
/** OpenGL shader ID */
|
||||||
public final int id;
|
public final int id;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============//
|
||||||
|
// constructors //
|
||||||
|
//==============//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a shader with specified type.
|
* Creates a shader with specified type.
|
||||||
*
|
*
|
||||||
@@ -51,52 +60,99 @@ public class Shader
|
|||||||
*/
|
*/
|
||||||
public Shader(int type, String path, boolean absoluteFilePath)
|
public Shader(int type, String path, boolean absoluteFilePath)
|
||||||
{
|
{
|
||||||
GLProxy.GL_LOGGER.info("Loading shader at " + path);
|
GLProxy.GL_LOGGER.info("Loading shader at [" + path + "]");
|
||||||
// Create an empty shader object
|
// Create an empty shader object
|
||||||
id = GL32.glCreateShader(type);
|
this.id = GL32.glCreateShader(type);
|
||||||
StringBuilder source = loadFile(path, absoluteFilePath, new StringBuilder());
|
if (this.id == 0)
|
||||||
GL32.glShaderSource(id, source);
|
{
|
||||||
|
throw new IllegalArgumentException("Failed to create shader with type ["+type+"].");
|
||||||
|
}
|
||||||
|
|
||||||
GL32.glCompileShader(id);
|
StringBuilder source = loadFile(path, absoluteFilePath, new StringBuilder());
|
||||||
|
safeShaderSource(this.id, source);
|
||||||
|
|
||||||
|
GL32.glCompileShader(this.id);
|
||||||
// check if the shader compiled
|
// check if the shader compiled
|
||||||
int status = GL32.glGetShaderi(id, GL32.GL_COMPILE_STATUS);
|
int status = GL32.glGetShaderi(this.id, GL32.GL_COMPILE_STATUS);
|
||||||
if (status != GL32.GL_TRUE)
|
if (status != GL32.GL_TRUE)
|
||||||
{
|
{
|
||||||
String message = "Shader compiler error. Details: " + GL32.glGetShaderInfoLog(id);
|
String message = "Shader compiler error. Details: ["+GL32.glGetShaderInfoLog(this.id)+"].";
|
||||||
free(); // important!
|
this.free(); // important!
|
||||||
throw new RuntimeException(message);
|
throw new RuntimeException(message);
|
||||||
}
|
}
|
||||||
GLProxy.GL_LOGGER.info("Shader at " + path + " loaded sucessfully.");
|
GLProxy.GL_LOGGER.info("Shader at " + path + " loaded successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shader(int type, String sourceString)
|
public Shader(int type, String sourceString)
|
||||||
{
|
{
|
||||||
GLProxy.GL_LOGGER.info("Loading shader with type: {}", type);
|
GLProxy.GL_LOGGER.info("Loading shader with type: ["+type+"]");
|
||||||
GLProxy.GL_LOGGER.debug("Source:\n{}", sourceString);
|
GLProxy.GL_LOGGER.debug("Source: \n["+sourceString+"]");
|
||||||
// Create an empty shader object
|
if (sourceString == null || sourceString.isEmpty())
|
||||||
id = GL32.glCreateShader(type);
|
{
|
||||||
GL32.glShaderSource(id, sourceString);
|
throw new IllegalArgumentException("No shader source given.");
|
||||||
|
}
|
||||||
|
|
||||||
GL32.glCompileShader(id);
|
// Create an empty shader object
|
||||||
|
this.id = GL32.glCreateShader(type);
|
||||||
|
if (this.id == 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Failed to create shader with type ["+type+"] and Source: \n["+sourceString+"].");
|
||||||
|
}
|
||||||
|
|
||||||
|
safeShaderSource(this.id, sourceString);
|
||||||
|
GL32.glCompileShader(this.id);
|
||||||
// check if the shader compiled
|
// check if the shader compiled
|
||||||
int status = GL32.glGetShaderi(id, GL32.GL_COMPILE_STATUS);
|
int status = GL32.glGetShaderi(this.id, GL32.GL_COMPILE_STATUS);
|
||||||
if (status != GL32.GL_TRUE)
|
if (status != GL32.GL_TRUE)
|
||||||
{
|
{
|
||||||
|
|
||||||
String message = "Shader compiler error. Details: " + GL32.glGetShaderInfoLog(id);
|
String message = "Shader compiler error. Details: [" + GL32.glGetShaderInfoLog(this.id) + "]\n";
|
||||||
message += "\nSource:\n" + sourceString;
|
message += "Source: \n[" + sourceString + "]";
|
||||||
free(); // important!
|
this.free(); // important!
|
||||||
throw new RuntimeException(message);
|
throw new RuntimeException(message);
|
||||||
}
|
}
|
||||||
GLProxy.GL_LOGGER.info("Shader loaded sucessfully.");
|
GLProxy.GL_LOGGER.info("Shader loaded sucessfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// REMEMBER to always free the resource!
|
|
||||||
public void free()
|
|
||||||
|
//=========//
|
||||||
|
// helpers //
|
||||||
|
//=========//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identical in function to {@link GL32C#glShaderSource(int, CharSequence)} but
|
||||||
|
* passes a null pointer for string length to force the driver to rely on the null
|
||||||
|
* terminator for string length. This is a workaround for an apparent flaw with some
|
||||||
|
* AMD drivers that don't receive or interpret the length correctly, resulting in
|
||||||
|
* an access violation when the driver tries to read past the string memory.
|
||||||
|
*
|
||||||
|
* <p>Hat tip to fewizz for the find and the fix.
|
||||||
|
*
|
||||||
|
* <p>Source: https://github.com/vram-guild/canvas/commit/820bf754092ccaf8d0c169620c2ff575722d7d96
|
||||||
|
*/
|
||||||
|
private static void safeShaderSource(@NativeType("GLuint") int glId, @NativeType("GLchar const **") CharSequence source)
|
||||||
{
|
{
|
||||||
GL32.glDeleteShader(id);
|
final MemoryStack stack = MemoryStack.stackGet();
|
||||||
|
final int stackPointer = stack.getPointer();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
final ByteBuffer sourceBuffer = MemoryUtil.memUTF8(source, true);
|
||||||
|
final PointerBuffer pointers = stack.mallocPointer(1);
|
||||||
|
pointers.put(sourceBuffer);
|
||||||
|
|
||||||
|
GL32.nglShaderSource(glId, 1, pointers.address0(), 0);
|
||||||
|
org.lwjgl.system.APIUtil.apiArrayFree(pointers.address0(), 1);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
stack.setPointer(stackPointer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void free() { GL32.glDeleteShader(this.id); }
|
||||||
|
|
||||||
public static StringBuilder loadFile(String path, boolean absoluteFilePath, StringBuilder stringBuilder)
|
public static StringBuilder loadFile(String path, boolean absoluteFilePath, StringBuilder stringBuilder)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -132,4 +188,6 @@ public class Shader
|
|||||||
return stringBuilder;
|
return stringBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-8
@@ -1,5 +1,7 @@
|
|||||||
package com.seibel.distanthorizons.core.render.glObject.texture;
|
package com.seibel.distanthorizons.core.render.glObject.texture;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
|
||||||
import org.lwjgl.opengl.GL11C;
|
import org.lwjgl.opengl.GL11C;
|
||||||
import org.lwjgl.opengl.GL13C;
|
import org.lwjgl.opengl.GL13C;
|
||||||
import org.lwjgl.opengl.GL43C;
|
import org.lwjgl.opengl.GL43C;
|
||||||
@@ -8,12 +10,15 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
public class DHDepthTexture
|
public class DHDepthTexture
|
||||||
{
|
{
|
||||||
|
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
|
||||||
|
|
||||||
|
|
||||||
private int id;
|
private int id;
|
||||||
public DHDepthTexture(int width, int height, EDhDepthBufferFormat format)
|
public DHDepthTexture(int width, int height, EDhDepthBufferFormat format)
|
||||||
{
|
{
|
||||||
this.id = GL43C.glGenTextures();
|
this.id = GL43C.glGenTextures();
|
||||||
|
|
||||||
resize(width, height, format);
|
this.resize(width, height, format);
|
||||||
|
|
||||||
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
|
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
|
||||||
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
|
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
|
||||||
@@ -24,27 +29,31 @@ public class DHDepthTexture
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For internal use by Iris for copying data. Do not use this in DH.
|
// For internal use by Iris for copying data. Do not use this in DH.
|
||||||
public DHDepthTexture(int id) {
|
public DHDepthTexture(int id) { this.id = id; }
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resize(int width, int height, EDhDepthBufferFormat format)
|
public void resize(int width, int height, EDhDepthBufferFormat format)
|
||||||
{
|
{
|
||||||
GL43C.glBindTexture(GL43C.GL_TEXTURE_2D, getTextureId());
|
GL43C.glBindTexture(GL43C.GL_TEXTURE_2D, this.getTextureId());
|
||||||
GL43C.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, format.getGlInternalFormat(), width, height, 0,
|
GL43C.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, format.getGlInternalFormat(), width, height, 0,
|
||||||
format.getGlType(), format.getGlFormat(), (ByteBuffer) null);
|
format.getGlType(), format.getGlFormat(), (ByteBuffer) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTextureId()
|
public int getTextureId()
|
||||||
{
|
{
|
||||||
if (id == -1) throw new IllegalStateException("Depth texture does not exist!");
|
if (this.id == -1)
|
||||||
return id;
|
{
|
||||||
|
throw new IllegalStateException("Depth texture does not exist!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void destroy()
|
public void destroy()
|
||||||
{
|
{
|
||||||
GL43C.glDeleteTextures(getTextureId());
|
GLMC.glDeleteTextures(this.getTextureId());
|
||||||
this.id = -1;
|
this.id = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -1,5 +1,7 @@
|
|||||||
package com.seibel.distanthorizons.core.render.glObject.texture;
|
package com.seibel.distanthorizons.core.render.glObject.texture;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
|
||||||
import org.joml.Vector2i;
|
import org.joml.Vector2i;
|
||||||
import org.lwjgl.opengl.GL11C;
|
import org.lwjgl.opengl.GL11C;
|
||||||
import org.lwjgl.opengl.GL13C;
|
import org.lwjgl.opengl.GL13C;
|
||||||
@@ -9,6 +11,9 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
public class DhColorTexture
|
public class DhColorTexture
|
||||||
{
|
{
|
||||||
|
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
|
||||||
|
|
||||||
|
|
||||||
private final EDhInternalTextureFormat internalFormat;
|
private final EDhInternalTextureFormat internalFormat;
|
||||||
private final EDhPixelFormat format;
|
private final EDhPixelFormat format;
|
||||||
private final EDhPixelType type;
|
private final EDhPixelType type;
|
||||||
@@ -100,7 +105,7 @@ public class DhColorTexture
|
|||||||
this.throwIfInvalid();
|
this.throwIfInvalid();
|
||||||
this.isValid = false;
|
this.isValid = false;
|
||||||
|
|
||||||
GL43C.glDeleteTextures(this.id);
|
GLMC.glDeleteTextures(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @throws IllegalStateException if the texture isn't valid */
|
/** @throws IllegalStateException if the texture isn't valid */
|
||||||
|
|||||||
+28
-14
@@ -86,22 +86,31 @@ public class FadeRenderer
|
|||||||
this.fadeFramebuffer = -1;
|
this.fadeFramebuffer = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.fadeTexture != -1)
|
|
||||||
{
|
|
||||||
GLMC.glDeleteTextures(this.fadeTexture);
|
|
||||||
this.fadeTexture = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fadeFramebuffer = GL32.glGenFramebuffers();
|
this.fadeFramebuffer = GL32.glGenFramebuffers();
|
||||||
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fadeFramebuffer);
|
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fadeFramebuffer);
|
||||||
|
|
||||||
this.fadeTexture = GL32.glGenTextures();
|
|
||||||
GLMC.glBindTexture(this.fadeTexture);
|
|
||||||
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
|
|
||||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
|
|
||||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
|
|
||||||
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0);
|
|
||||||
|
|
||||||
|
// Applying the fade texture is only needed if MC is drawing to their own frame buffer,
|
||||||
|
// otherwise we can directly render to their texture
|
||||||
|
if (MC_RENDER.mcRendersToFrameBuffer())
|
||||||
|
{
|
||||||
|
if (this.fadeTexture != -1)
|
||||||
|
{
|
||||||
|
GLMC.glDeleteTextures(this.fadeTexture);
|
||||||
|
this.fadeTexture = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fadeTexture = GL32.glGenTextures();
|
||||||
|
GLMC.glBindTexture(this.fadeTexture);
|
||||||
|
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
|
||||||
|
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
|
||||||
|
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
|
||||||
|
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, MC_RENDER.getColorTextureId(), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -146,8 +155,13 @@ public class FadeRenderer
|
|||||||
|
|
||||||
profiler.popPush("Fade Apply");
|
profiler.popPush("Fade Apply");
|
||||||
|
|
||||||
FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture;
|
// Applying the fade texture is only needed if MC is drawing to their own frame buffer,
|
||||||
FadeApplyShader.INSTANCE.render(partialTicks);
|
// otherwise we can directly render to their texture
|
||||||
|
if (MC_RENDER.mcRendersToFrameBuffer())
|
||||||
|
{
|
||||||
|
FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture;
|
||||||
|
FadeApplyShader.INSTANCE.render(partialTicks);
|
||||||
|
}
|
||||||
|
|
||||||
profiler.pop();
|
profiler.pop();
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-45
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiFrameb
|
|||||||
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShaderProgram;
|
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShaderProgram;
|
||||||
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.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
|
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
||||||
@@ -557,34 +558,24 @@ public class LodRenderer
|
|||||||
activeFrameBuffer.bind();
|
activeFrameBuffer.bind();
|
||||||
|
|
||||||
|
|
||||||
boolean clearTextures = !ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeTextureClearEvent.class, renderEventParam);
|
|
||||||
if (clearTextures)
|
|
||||||
{
|
|
||||||
if (this.usingMcFrameBuffer && framebufferOverride == null)
|
|
||||||
{
|
|
||||||
// Due to using MC/Optifine's framebuffer we need to re-bind the depth texture,
|
|
||||||
// otherwise we'll be writing to MC/Optifine's depth texture which causes rendering issues
|
|
||||||
activeFrameBuffer.addDepthAttachment(this.depthTexture.getTextureId(), EDhDepthBufferFormat.DEPTH32F.isCombinedStencil());
|
|
||||||
|
|
||||||
|
|
||||||
// don't clear the color texture, that removes the sky
|
|
||||||
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
else if (firstPass)
|
|
||||||
{
|
|
||||||
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// by default draw everything as triangles
|
// by default draw everything as triangles
|
||||||
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
|
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
|
||||||
GLMC.enableFaceCulling();
|
GLMC.enableFaceCulling();
|
||||||
|
|
||||||
|
GLMC.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
GLMC.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ZERO);
|
||||||
|
|
||||||
|
GL32.glDisable(GL32.GL_SCISSOR_TEST);
|
||||||
|
|
||||||
// Enable depth test and depth mask
|
// Enable depth test and depth mask
|
||||||
GLMC.enableDepthTest();
|
GLMC.enableDepthTest();
|
||||||
GLMC.glDepthFunc(GL32.GL_LESS);
|
GLMC.glDepthFunc(GL32.GL_LESS);
|
||||||
GLMC.enableDepthMask();
|
GLMC.enableDepthMask();
|
||||||
|
|
||||||
|
// This is required for MC versions 1.21.5+
|
||||||
|
// due to MC updating the lightmap by changing the viewport size
|
||||||
|
GL32.glViewport(0, 0, this.cachedWidth, this.cachedHeight);
|
||||||
|
|
||||||
/*---------Bind required objects--------*/
|
/*---------Bind required objects--------*/
|
||||||
// Setup LodRenderProgram and the LightmapTexture if it has not yet been done
|
// Setup LodRenderProgram and the LightmapTexture if it has not yet been done
|
||||||
// also binds LightmapTexture, VAO, and ShaderProgram
|
// also binds LightmapTexture, VAO, and ShaderProgram
|
||||||
@@ -605,6 +596,33 @@ public class LodRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.lodRenderProgram.fillUniformData(renderEventParam);
|
this.lodRenderProgram.fillUniformData(renderEventParam);
|
||||||
|
|
||||||
|
|
||||||
|
// needs to be fired after all the textures have been created/bound
|
||||||
|
boolean clearTextures = !ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeTextureClearEvent.class, renderEventParam);
|
||||||
|
if (clearTextures)
|
||||||
|
{
|
||||||
|
GL32.glClearDepth(1.0);
|
||||||
|
|
||||||
|
float[] clearColorValues = new float[4];
|
||||||
|
GL32.glGetFloatv(GL32.GL_COLOR_CLEAR_VALUE, clearColorValues);
|
||||||
|
GL32.glClearColor(clearColorValues[0], clearColorValues[1], clearColorValues[2], 1.0f);
|
||||||
|
|
||||||
|
if (this.usingMcFrameBuffer && framebufferOverride == null)
|
||||||
|
{
|
||||||
|
// Due to using MC/Optifine's framebuffer we need to re-bind the depth texture,
|
||||||
|
// otherwise we'll be writing to MC/Optifine's depth texture which causes rendering issues
|
||||||
|
activeFrameBuffer.addDepthAttachment(this.depthTexture.getTextureId(), EDhDepthBufferFormat.DEPTH32F.isCombinedStencil());
|
||||||
|
|
||||||
|
|
||||||
|
// don't clear the color texture, that removes the sky
|
||||||
|
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
else if (firstPass)
|
||||||
|
{
|
||||||
|
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Setup all render objects - MUST be called on the render thread */
|
/** Setup all render objects - MUST be called on the render thread */
|
||||||
@@ -656,7 +674,7 @@ public class LodRenderer
|
|||||||
if(this.framebuffer.getStatus() != GL32.GL_FRAMEBUFFER_COMPLETE)
|
if(this.framebuffer.getStatus() != GL32.GL_FRAMEBUFFER_COMPLETE)
|
||||||
{
|
{
|
||||||
// This generally means something wasn't bound, IE missing either the color or depth texture
|
// This generally means something wasn't bound, IE missing either the color or depth texture
|
||||||
SPAM_LOGGER.warn("FrameBuffer ["+this.framebuffer.getId()+"] isn't complete.");
|
EVENT_LOGGER.warn("FrameBuffer ["+this.framebuffer.getId()+"] isn't complete.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -675,12 +693,15 @@ public class LodRenderer
|
|||||||
this.cachedWidth = MC_RENDER.getTargetFrameBufferViewportWidth();
|
this.cachedWidth = MC_RENDER.getTargetFrameBufferViewportWidth();
|
||||||
this.cachedHeight = MC_RENDER.getTargetFrameBufferViewportHeight();
|
this.cachedHeight = MC_RENDER.getTargetFrameBufferViewportHeight();
|
||||||
|
|
||||||
|
DhApiTextureCreatedParam textureCreatedParam = new DhApiTextureCreatedParam(
|
||||||
|
oldWidth, oldHeight,
|
||||||
|
this.cachedWidth, this.cachedHeight
|
||||||
|
);
|
||||||
|
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiColorDepthTextureCreatedEvent.class,
|
|
||||||
new DhApiColorDepthTextureCreatedEvent.EventParam(
|
|
||||||
oldWidth, oldHeight,
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiColorDepthTextureCreatedEvent.class, new DhApiColorDepthTextureCreatedEvent.EventParam(textureCreatedParam));
|
||||||
this.cachedWidth, this.cachedHeight
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeColorDepthTextureCreatedEvent.class, textureCreatedParam);
|
||||||
));
|
|
||||||
|
|
||||||
|
|
||||||
// also update the override if present
|
// also update the override if present
|
||||||
@@ -712,29 +733,13 @@ public class LodRenderer
|
|||||||
{
|
{
|
||||||
this.nullableColorTexture = null;
|
this.nullableColorTexture = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterColorDepthTextureCreatedEvent.class, textureCreatedParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private Color getFogColor(float partialTicks)
|
|
||||||
{
|
|
||||||
Color fogColor;
|
|
||||||
|
|
||||||
if (Config.Client.Advanced.Graphics.Fog.colorMode.get() == EDhApiFogColorMode.USE_SKY_COLOR)
|
|
||||||
{
|
|
||||||
fogColor = MC_RENDER.getSkyColor();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fogColor = MC_RENDER.getFogColor(partialTicks);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fogColor;
|
|
||||||
}
|
|
||||||
private Color getSpecialFogColor(float partialTicks) { return MC_RENDER.getSpecialFogColor(partialTicks); }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===============//
|
//===============//
|
||||||
// API functions //
|
// API functions //
|
||||||
//===============//
|
//===============//
|
||||||
@@ -748,7 +753,10 @@ public class LodRenderer
|
|||||||
public static int getActiveColorTextureId() { return activeColorTextureId; }
|
public static int getActiveColorTextureId() { return activeColorTextureId; }
|
||||||
|
|
||||||
private void setActiveDepthTextureId(int depthTextureId) { activeDepthTextureId = depthTextureId; }
|
private void setActiveDepthTextureId(int depthTextureId) { activeDepthTextureId = depthTextureId; }
|
||||||
/** Returns -1 if no texture has been bound yet */
|
/**
|
||||||
|
* FIXME it's possible for this to return an invalid texture ID if the renderer is being re-built at the same time
|
||||||
|
* Returns -1 if no texture has been bound yet
|
||||||
|
*/
|
||||||
public static int getActiveDepthTextureId() { return activeDepthTextureId; }
|
public static int getActiveDepthTextureId() { return activeDepthTextureId; }
|
||||||
|
|
||||||
|
|
||||||
@@ -796,6 +804,9 @@ public class LodRenderer
|
|||||||
if (this.depthTexture != null)
|
if (this.depthTexture != null)
|
||||||
this.depthTexture.destroy();
|
this.depthTexture.destroy();
|
||||||
|
|
||||||
|
this.setActiveDepthTextureId(-1);
|
||||||
|
this.setActiveColorTextureId(-1);
|
||||||
|
|
||||||
EVENT_LOGGER.info("Renderer Cleanup Complete");
|
EVENT_LOGGER.info("Renderer Cleanup Complete");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+82
-3
@@ -75,6 +75,18 @@ public class DhApplyShader extends AbstractShaderRenderer
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRender()
|
protected void onRender()
|
||||||
|
{
|
||||||
|
if (MC_RENDER.mcRendersToFrameBuffer())
|
||||||
|
{
|
||||||
|
this.renderToFrameBuffer();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.renderToMcTexture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO merge duplicate code between these to render methods
|
||||||
|
private void renderToFrameBuffer()
|
||||||
{
|
{
|
||||||
int targetFrameBuffer = MC_RENDER.getTargetFrameBuffer();
|
int targetFrameBuffer = MC_RENDER.getTargetFrameBuffer();
|
||||||
if (targetFrameBuffer == -1)
|
if (targetFrameBuffer == -1)
|
||||||
@@ -87,9 +99,15 @@ public class DhApplyShader extends AbstractShaderRenderer
|
|||||||
|
|
||||||
GLMC.disableDepthTest();
|
GLMC.disableDepthTest();
|
||||||
|
|
||||||
GLMC.enableBlend();
|
// blending isn't needed, we're manually merging the MC and DH textures
|
||||||
GL32.glBlendEquation(GL32.GL_FUNC_ADD);
|
// Note: this prevents the sun/moon and stars from rendering through transparent LODs,
|
||||||
GLMC.glBlendFunc(GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
|
// however this also fixes transparent LODs from glowing when rendered against the sky during the day
|
||||||
|
GLMC.disableBlend();
|
||||||
|
|
||||||
|
// old blending logic in case it's ever needed:
|
||||||
|
//GLMC.enableBlend();
|
||||||
|
//GL32.glBlendEquation(GL32.GL_FUNC_ADD);
|
||||||
|
//GLMC.glBlendFunc(GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
|
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
|
||||||
GLMC.glBindTexture(LodRenderer.getActiveColorTextureId());
|
GLMC.glBindTexture(LodRenderer.getActiveColorTextureId());
|
||||||
@@ -110,5 +128,66 @@ public class DhApplyShader extends AbstractShaderRenderer
|
|||||||
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, targetFrameBuffer);
|
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, targetFrameBuffer);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
private void renderToMcTexture()
|
||||||
|
{
|
||||||
|
int targetColorTextureId = MC_RENDER.getColorTextureId();
|
||||||
|
if (targetColorTextureId == -1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dhFrameBufferId = LodRenderer.getActiveFramebufferId();
|
||||||
|
if (dhFrameBufferId == -1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mcFrameBufferId = MC_RENDER.getTargetFrameBuffer();
|
||||||
|
if (mcFrameBufferId == -1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
GLState state = new GLState();
|
||||||
|
|
||||||
|
GLMC.disableDepthTest();
|
||||||
|
|
||||||
|
// blending isn't needed, we're just directly merging the MC and DH textures
|
||||||
|
// Note: this prevents the sun/moon and stars from rendering through transparent LODs,
|
||||||
|
// however this also fixes
|
||||||
|
GLMC.disableBlend();
|
||||||
|
|
||||||
|
// old blending logic in case it's ever needed:
|
||||||
|
//GLMC.enableBlend();
|
||||||
|
//GL32.glBlendEquation(GL32.GL_FUNC_ADD);
|
||||||
|
//GLMC.glBlendFunc(GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
|
||||||
|
GLMC.glBindTexture(LodRenderer.getActiveColorTextureId());
|
||||||
|
GL32.glUniform1i(this.gDhColorTextureUniform, 0);
|
||||||
|
|
||||||
|
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
|
||||||
|
GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
|
||||||
|
GL32.glUniform1i(this.gDepthMapUniform, 1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
GL32.glFramebufferTexture(GL32.GL_DRAW_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, targetColorTextureId, 0);
|
||||||
|
|
||||||
|
// Copy to MC's texture via MC's framebuffer
|
||||||
|
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, dhFrameBufferId);
|
||||||
|
|
||||||
|
ScreenQuad.INSTANCE.render();
|
||||||
|
|
||||||
|
|
||||||
|
// restore everything, except at this point the MC framebuffer should now be used instead
|
||||||
|
state.restore();
|
||||||
|
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, mcFrameBufferId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -20,6 +20,7 @@
|
|||||||
package com.seibel.distanthorizons.core.render.renderer.shaders;
|
package com.seibel.distanthorizons.core.render.renderer.shaders;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.render.glObject.GLState;
|
||||||
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
|
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.FadeRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.FadeRenderer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
||||||
@@ -104,6 +105,12 @@ public class FadeApplyShader extends AbstractShaderRenderer
|
|||||||
@Override
|
@Override
|
||||||
protected void onRender()
|
protected void onRender()
|
||||||
{
|
{
|
||||||
|
if (!MC_RENDER.mcRendersToFrameBuffer())
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("If Minecraft is directly rendering to a texture the apply shader isn't needed, just draw the fade directly to the MC color texture.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
GLMC.disableBlend();
|
GLMC.disableBlend();
|
||||||
|
|
||||||
// Depth testing must be disabled otherwise this application shader won't apply anything.
|
// Depth testing must be disabled otherwise this application shader won't apply anything.
|
||||||
@@ -119,6 +126,9 @@ public class FadeApplyShader extends AbstractShaderRenderer
|
|||||||
ScreenQuad.INSTANCE.render();
|
ScreenQuad.INSTANCE.render();
|
||||||
|
|
||||||
GLMC.enableDepthTest();
|
GLMC.enableDepthTest();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-2
@@ -155,6 +155,19 @@ public class FadeShader extends AbstractShaderRenderer
|
|||||||
@Override
|
@Override
|
||||||
protected void onRender()
|
protected void onRender()
|
||||||
{
|
{
|
||||||
|
int depthTextureId = LodRenderer.getActiveDepthTextureId();
|
||||||
|
int colorTextureId = LodRenderer.getActiveColorTextureId();
|
||||||
|
|
||||||
|
if (depthTextureId == -1
|
||||||
|
|| colorTextureId == -1)
|
||||||
|
{
|
||||||
|
// the renderer is currently being re-built and/or inactive,
|
||||||
|
// we don't need to/can't render fading
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.frameBuffer);
|
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.frameBuffer);
|
||||||
GLMC.disableScissorTest();
|
GLMC.disableScissorTest();
|
||||||
GLMC.disableDepthTest();
|
GLMC.disableDepthTest();
|
||||||
@@ -165,7 +178,7 @@ public class FadeShader extends AbstractShaderRenderer
|
|||||||
GL32.glUniform1i(this.uMcDepthTexture, 0);
|
GL32.glUniform1i(this.uMcDepthTexture, 0);
|
||||||
|
|
||||||
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
|
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
|
||||||
GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
|
GLMC.glBindTexture(depthTextureId);
|
||||||
GL32.glUniform1i(this.uDhDepthTexture, 1);
|
GL32.glUniform1i(this.uDhDepthTexture, 1);
|
||||||
|
|
||||||
GLMC.glActiveTexture(GL32.GL_TEXTURE2);
|
GLMC.glActiveTexture(GL32.GL_TEXTURE2);
|
||||||
@@ -173,7 +186,7 @@ public class FadeShader extends AbstractShaderRenderer
|
|||||||
GL32.glUniform1i(this.uCombinedMcDhColorTexture, 2);
|
GL32.glUniform1i(this.uCombinedMcDhColorTexture, 2);
|
||||||
|
|
||||||
GLMC.glActiveTexture(GL32.GL_TEXTURE3);
|
GLMC.glActiveTexture(GL32.GL_TEXTURE3);
|
||||||
GLMC.glBindTexture(LodRenderer.getActiveColorTextureId());
|
GLMC.glBindTexture(colorTextureId);
|
||||||
GL32.glUniform1i(this.uDhColorTexture, 3);
|
GL32.glUniform1i(this.uDhColorTexture, 3);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
@@ -84,6 +84,7 @@ public class FogApplyShader extends AbstractShaderRenderer
|
|||||||
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
|
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
|
||||||
GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
|
GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
|
||||||
GL32.glUniform1i(this.depthTextureUniform, 1);
|
GL32.glUniform1i(this.depthTextureUniform, 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -28,9 +28,11 @@ import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
|
|||||||
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
|
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
import org.lwjgl.opengl.GL32;
|
import org.lwjgl.opengl.GL32;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -41,6 +43,8 @@ public class FogShader extends AbstractShaderRenderer
|
|||||||
|
|
||||||
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
|
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
|
||||||
|
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public int frameBuffer;
|
public int frameBuffer;
|
||||||
@@ -48,6 +52,7 @@ public class FogShader extends AbstractShaderRenderer
|
|||||||
private Mat4f inverseMvmProjMatrix;
|
private Mat4f inverseMvmProjMatrix;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==========//
|
//==========//
|
||||||
// Uniforms //
|
// Uniforms //
|
||||||
//==========//
|
//==========//
|
||||||
@@ -253,7 +258,15 @@ public class FogShader extends AbstractShaderRenderer
|
|||||||
|
|
||||||
// this is necessary for MC 1.16 (IE Legacy OpenGL)
|
// this is necessary for MC 1.16 (IE Legacy OpenGL)
|
||||||
// otherwise the framebuffer isn't cleared correctly and the fog smears across the screen
|
// otherwise the framebuffer isn't cleared correctly and the fog smears across the screen
|
||||||
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
|
if (MC_RENDER.runningLegacyOpenGL())
|
||||||
|
{
|
||||||
|
// in another part of the DH code we set the fog color to opaque, here it needs to be transparent
|
||||||
|
float[] clearColorValues = new float[4];
|
||||||
|
GL32.glGetFloatv(GL32.GL_COLOR_CLEAR_VALUE, clearColorValues);
|
||||||
|
GL32.glClearColor(clearColorValues[0], clearColorValues[1], clearColorValues[2], 0.0f);
|
||||||
|
|
||||||
|
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ScreenQuad.INSTANCE.render();
|
ScreenQuad.INSTANCE.render();
|
||||||
|
|||||||
-3
@@ -134,9 +134,6 @@ public class SSAOApplyShader extends AbstractShaderRenderer
|
|||||||
// it should be automatically restored after rendering is complete.
|
// it should be automatically restored after rendering is complete.
|
||||||
GLMC.disableDepthTest();
|
GLMC.disableDepthTest();
|
||||||
|
|
||||||
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
|
|
||||||
GLMC.glBindTexture(0);
|
|
||||||
|
|
||||||
// apply the rendered SSAO to the LODs
|
// apply the rendered SSAO to the LODs
|
||||||
GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, SSAOShader.INSTANCE.frameBuffer);
|
GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, SSAOShader.INSTANCE.frameBuffer);
|
||||||
GLMC.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, LodRenderer.getActiveFramebufferId());
|
GLMC.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, LodRenderer.getActiveFramebufferId());
|
||||||
|
|||||||
+11
-2
@@ -142,7 +142,16 @@ public class FullDataSourceV2DTO
|
|||||||
public FullDataSourceV2 createDataSource(@NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
|
public FullDataSourceV2 createDataSource(@NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
|
||||||
{
|
{
|
||||||
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(this.pos);
|
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(this.pos);
|
||||||
this.internalPopulateDataSource(dataSource, levelWrapper, false);
|
try
|
||||||
|
{
|
||||||
|
this.internalPopulateDataSource(dataSource, levelWrapper, false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
dataSource.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
return dataSource;
|
return dataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,7 +463,7 @@ public class FullDataSourceV2DTO
|
|||||||
{
|
{
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
.add("levelMinY", this.levelMinY)
|
.add("levelMinY", this.levelMinY)
|
||||||
.add("pos", this.pos)
|
.add("pos", DhSectionPos.toString(this.pos))
|
||||||
.add("dataChecksum", this.dataChecksum)
|
.add("dataChecksum", this.dataChecksum)
|
||||||
.add("compressedDataByteArray length", this.compressedDataByteArray.size())
|
.add("compressedDataByteArray length", this.compressedDataByteArray.size())
|
||||||
.add("compressedColumnGenStepByteArray length", this.compressedColumnGenStepByteArray.size())
|
.add("compressedColumnGenStepByteArray length", this.compressedColumnGenStepByteArray.size())
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.sql.DbConnectionClosedException;
|
|||||||
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
|
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
|
||||||
import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper;
|
import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper;
|
||||||
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
|
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
|
||||||
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -501,7 +502,12 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOGGER.warn("Attempting to close already closed database connection: [" + connectionString + "]");
|
// these warnings can be ignored in release builds, as long as the connection is closed it doesn't really matter
|
||||||
|
// TODO fix duplicate closes
|
||||||
|
if (ModInfo.IS_DEV_BUILD)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Attempting to close already closed database connection: [" + connectionString + "]");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -562,7 +568,12 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOGGER.warn("Attempting to close already closed database connection: [" + this.connectionString + "]");
|
// these warnings can be ignored in release builds, as long as the connection is closed it doesn't really matter
|
||||||
|
// TODO fix duplicate closes
|
||||||
|
if (ModInfo.IS_DEV_BUILD)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Attempting to close already closed database connection: [" + this.connectionString + "]");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ACTIVE_CONNECTION_STRINGS_BY_REPO.remove(this);
|
ACTIVE_CONNECTION_STRINGS_BY_REPO.remove(this);
|
||||||
|
|||||||
@@ -73,7 +73,10 @@ public class RenderDataPointUtil
|
|||||||
|
|
||||||
|
|
||||||
public final static int EMPTY_DATA = 0;
|
public final static int EMPTY_DATA = 0;
|
||||||
public final static int MAX_WORLD_Y_SIZE = 4096;
|
|
||||||
|
// the maximum valid Y value is the maximum min y + world height.
|
||||||
|
// min y is [-2032, 2031], height is < 4064.
|
||||||
|
public final static int MAX_WORLD_Y_SIZE = 2031 + 4064;
|
||||||
|
|
||||||
public final static int ALPHA_DOWNSIZE_SHIFT = 4;
|
public final static int ALPHA_DOWNSIZE_SHIFT = 4;
|
||||||
|
|
||||||
|
|||||||
@@ -1,430 +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.util.objects;
|
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles parsing and creating string messages from OpenGL messages.
|
|
||||||
*
|
|
||||||
* @author Leetom
|
|
||||||
* @version 2022-10-1
|
|
||||||
*/
|
|
||||||
public final class GLMessage
|
|
||||||
{
|
|
||||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
|
|
||||||
|
|
||||||
static final String HEADER = "[LWJGL] OpenGL debug message";
|
|
||||||
public final EType type;
|
|
||||||
public final ESeverity severity;
|
|
||||||
public final ESource source;
|
|
||||||
public final String id;
|
|
||||||
public final String message;
|
|
||||||
|
|
||||||
/** This is needed since gl callback will not have the correct class loader set, which causes issues. */
|
|
||||||
static void initLoadClass()
|
|
||||||
{
|
|
||||||
Builder dummy = new Builder();
|
|
||||||
dummy.add(GLMessage.HEADER);
|
|
||||||
dummy.add("ID");
|
|
||||||
dummy.add(":");
|
|
||||||
dummy.add("dummyId");
|
|
||||||
dummy.add("Source");
|
|
||||||
dummy.add(":");
|
|
||||||
dummy.add(ESource.API.name);
|
|
||||||
dummy.add("Type");
|
|
||||||
dummy.add(":");
|
|
||||||
dummy.add(EType.OTHER.name);
|
|
||||||
dummy.add("Severity");
|
|
||||||
dummy.add(":");
|
|
||||||
dummy.add(ESeverity.LOW.name);
|
|
||||||
dummy.add("Message");
|
|
||||||
dummy.add(":");
|
|
||||||
dummy.add("dummyMessage");
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
initLoadClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
GLMessage(EType type, ESeverity severity, ESource source, String id, String message)
|
|
||||||
{
|
|
||||||
this.type = type;
|
|
||||||
this.source = source;
|
|
||||||
this.severity = severity;
|
|
||||||
this.id = id;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() { return "[level:" + severity + ", type:" + type + ", source:" + source + ", id:" + id + ", msg:{" + message + "}]"; }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
|
||||||
// helper enums //
|
|
||||||
//==============//
|
|
||||||
|
|
||||||
public enum EType
|
|
||||||
{
|
|
||||||
ERROR,
|
|
||||||
DEPRECATED_BEHAVIOR,
|
|
||||||
UNDEFINED_BEHAVIOR,
|
|
||||||
PORTABILITY,
|
|
||||||
PERFORMANCE,
|
|
||||||
MARKER,
|
|
||||||
PUSH_GROUP,
|
|
||||||
POP_GROUP,
|
|
||||||
OTHER;
|
|
||||||
|
|
||||||
|
|
||||||
private static final HashMap<String, EType> ENUM_BY_NAME = new HashMap<>();
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
for (EType type : EType.values())
|
|
||||||
{
|
|
||||||
ENUM_BY_NAME.put(type.name, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EType() { name = super.toString().toUpperCase(); }
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() { return name; }
|
|
||||||
|
|
||||||
public static EType get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ESource
|
|
||||||
{
|
|
||||||
API,
|
|
||||||
WINDOW_SYSTEM,
|
|
||||||
SHADER_COMPILER,
|
|
||||||
THIRD_PARTY,
|
|
||||||
APPLICATION,
|
|
||||||
OTHER;
|
|
||||||
|
|
||||||
|
|
||||||
private static final HashMap<String, ESource> ENUM_BY_NAME = new HashMap<>();
|
|
||||||
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
for (ESource source : ESource.values())
|
|
||||||
{
|
|
||||||
ENUM_BY_NAME.put(source.name, source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ESource() { name = super.toString().toUpperCase(); }
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() { return name; }
|
|
||||||
|
|
||||||
public static ESource get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ESeverity
|
|
||||||
{
|
|
||||||
HIGH,
|
|
||||||
MEDIUM,
|
|
||||||
LOW,
|
|
||||||
NOTIFICATION;
|
|
||||||
|
|
||||||
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
static final HashMap<String, ESeverity> ENUM_BY_NAME = new HashMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
for (ESeverity severity : ESeverity.values())
|
|
||||||
{
|
|
||||||
ENUM_BY_NAME.put(severity.name, severity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ESeverity() { name = super.toString().toUpperCase(); }
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final String toString() { return name; }
|
|
||||||
|
|
||||||
public static ESeverity get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
|
||||||
// helper classes //
|
|
||||||
//================//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expected message format: <br>
|
|
||||||
* <code>
|
|
||||||
* [LWJGL] OpenGL debug message <br>
|
|
||||||
* ID: 0x20071 <br>
|
|
||||||
* Source: API <br>
|
|
||||||
* Type: OTHER <br>
|
|
||||||
* Severity: NOTIFICATION <br>
|
|
||||||
* Message: Buffer detailed info: Buffer object 1014084 (bound to ...
|
|
||||||
* </code>
|
|
||||||
*/
|
|
||||||
public static class Builder
|
|
||||||
{
|
|
||||||
/** how many stages are present in the message parser */
|
|
||||||
private static final int FINAL_PARSER_STAGE_INDEX = 15;
|
|
||||||
|
|
||||||
public static final Builder DEFAULT_MESSAGE_BUILDER =
|
|
||||||
new Builder(
|
|
||||||
(type) ->
|
|
||||||
{ // type filter
|
|
||||||
if (type == GLMessage.EType.POP_GROUP)
|
|
||||||
return false;
|
|
||||||
if (type == GLMessage.EType.PUSH_GROUP)
|
|
||||||
return false;
|
|
||||||
if (type == GLMessage.EType.MARKER)
|
|
||||||
return false;
|
|
||||||
// if (type == GLMessage.Type.PERFORMANCE) return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
(severity) ->
|
|
||||||
{ // severity filter
|
|
||||||
if (severity == GLMessage.ESeverity.NOTIFICATION)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
private final StringBuilder inProgressMessageBuilder = new StringBuilder();
|
|
||||||
|
|
||||||
private EType type;
|
|
||||||
private ESeverity severity;
|
|
||||||
private ESource source;
|
|
||||||
|
|
||||||
/** if the function returns false the message will be allowed */
|
|
||||||
private final Function<EType, Boolean> typeFilter;
|
|
||||||
/** if the function returns false the message will be allowed */
|
|
||||||
private final Function<ESeverity, Boolean> severityFilter;
|
|
||||||
/** if the function returns false the message will be allowed */
|
|
||||||
private final Function<ESource, Boolean> sourceFilter;
|
|
||||||
|
|
||||||
private String id;
|
|
||||||
private String message;
|
|
||||||
/** how far into the message parser this builder is */
|
|
||||||
private int parserStage = 0;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
initLoadClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder() { this(null, null, null); }
|
|
||||||
|
|
||||||
public Builder(
|
|
||||||
Function<EType, Boolean> typeFilter,
|
|
||||||
Function<ESeverity, Boolean> severityFilter,
|
|
||||||
Function<ESource, Boolean> sourceFilter)
|
|
||||||
{
|
|
||||||
this.typeFilter = typeFilter;
|
|
||||||
this.severityFilter = severityFilter;
|
|
||||||
this.sourceFilter = sourceFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the given string to the message builder. <br> <br>
|
|
||||||
*
|
|
||||||
* Will log a warning if the string given wasn't expected
|
|
||||||
* for the next stage of the OpenGL message format.<br> <br>
|
|
||||||
*
|
|
||||||
* @return null if the message isn't complete
|
|
||||||
*/
|
|
||||||
public GLMessage add(String str)
|
|
||||||
{
|
|
||||||
// TODO fix implementation for MC 1.20.2 and newer
|
|
||||||
// please see the incomplete GLMessageTest for an example as to how the message formats differ
|
|
||||||
if (true)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
str = str.trim();
|
|
||||||
if (str.isEmpty())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
boolean parseSuccess = runNextParserStage(str);
|
|
||||||
if (parseSuccess && parserStage > FINAL_PARSER_STAGE_INDEX)
|
|
||||||
{
|
|
||||||
this.parserStage = 0;
|
|
||||||
GLMessage msg = new GLMessage(this.type, this.severity, this.source, this.id, this.message);
|
|
||||||
if (doesMessagePassFilters(msg))
|
|
||||||
{
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!parseSuccess)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Failed to parse GLMessage line '{}' at stage {}", str, parserStage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// the message isn't finished yet
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// TODO implement a method that works for both MC 1.20.2+ and 1.20.1-
|
|
||||||
//if (str.equals(HEADER) && inProgressMessageBuilder.length() != 0)
|
|
||||||
//{
|
|
||||||
// boolean parseSuccess = runNextParserStage(str);
|
|
||||||
// if (parseSuccess && parserStage > FINAL_PARSER_STAGE_INDEX)
|
|
||||||
// {
|
|
||||||
// this.parserStage = 0;
|
|
||||||
// GLMessage msg = new GLMessage(this.type, this.severity, this.source, this.id, this.message);
|
|
||||||
// if (doesMessagePassFilters(msg))
|
|
||||||
// {
|
|
||||||
// return msg;
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// inProgressMessageBuilder.setLength(0);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// if (!parseSuccess)
|
|
||||||
// {
|
|
||||||
// LOGGER.warn("Failed to parse GLMessage line '{}' at stage {}", str, parserStage);
|
|
||||||
// inProgressMessageBuilder.setLength(0);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// inProgressMessageBuilder.append(str);
|
|
||||||
// return null;
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean doesMessagePassFilters(GLMessage msg)
|
|
||||||
{
|
|
||||||
if (this.sourceFilter != null && !this.sourceFilter.apply(msg.source))
|
|
||||||
return false;
|
|
||||||
else if (this.typeFilter != null && !this.typeFilter.apply(msg.type))
|
|
||||||
return false;
|
|
||||||
else if (this.severityFilter != null && !this.severityFilter.apply(msg.severity))
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return true if the given string was expected next for the OpenGL message format */
|
|
||||||
private boolean runNextParserStage(String str)
|
|
||||||
{
|
|
||||||
switch (this.parserStage)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
return checkAndIncStage(str, GLMessage.HEADER);
|
|
||||||
case 1:
|
|
||||||
return checkAndIncStage(str, "ID");
|
|
||||||
case 2:
|
|
||||||
return checkAndIncStage(str, ":");
|
|
||||||
case 3:
|
|
||||||
this.id = str;
|
|
||||||
this.parserStage++;
|
|
||||||
return true;
|
|
||||||
case 4:
|
|
||||||
return checkAndIncStage(str, "Source");
|
|
||||||
case 5:
|
|
||||||
return checkAndIncStage(str, ":");
|
|
||||||
case 6:
|
|
||||||
this.source = ESource.get(str);
|
|
||||||
this.parserStage++;
|
|
||||||
return true;
|
|
||||||
case 7:
|
|
||||||
return checkAndIncStage(str, "Type");
|
|
||||||
case 8:
|
|
||||||
return checkAndIncStage(str, ":");
|
|
||||||
case 9:
|
|
||||||
this.type = EType.get(str);
|
|
||||||
this.parserStage++;
|
|
||||||
return true;
|
|
||||||
case 10:
|
|
||||||
return checkAndIncStage(str, "Severity");
|
|
||||||
case 11:
|
|
||||||
return checkAndIncStage(str, ":");
|
|
||||||
case 12:
|
|
||||||
this.severity = ESeverity.get(str);
|
|
||||||
this.parserStage++;
|
|
||||||
return true;
|
|
||||||
case 13:
|
|
||||||
return checkAndIncStage(str, "Message");
|
|
||||||
case 14:
|
|
||||||
return checkAndIncStage(str, ":");
|
|
||||||
case 15:
|
|
||||||
this.message = str;
|
|
||||||
this.parserStage++;
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true and increments the parserStage
|
|
||||||
* if the given and expected strings are the same.
|
|
||||||
*/
|
|
||||||
private boolean checkAndIncStage(String givenString, String expectedString)
|
|
||||||
{
|
|
||||||
boolean equal = givenString.equals(expectedString);
|
|
||||||
//boolean equal = givenString.contains(expectedString);
|
|
||||||
if (equal)
|
|
||||||
this.parserStage++;
|
|
||||||
return equal;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // builder class
|
|
||||||
|
|
||||||
}
|
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.util.objects.GLMessages;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public enum EGLMessageSeverity
|
||||||
|
{
|
||||||
|
HIGH,
|
||||||
|
MEDIUM,
|
||||||
|
LOW,
|
||||||
|
NOTIFICATION;
|
||||||
|
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
static final HashMap<String, EGLMessageSeverity> ENUM_BY_NAME = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
for (EGLMessageSeverity severity : EGLMessageSeverity.values())
|
||||||
|
{
|
||||||
|
ENUM_BY_NAME.put(severity.name, severity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLMessageSeverity() { this.name = super.toString().toUpperCase(); }
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String toString() { return this.name; }
|
||||||
|
|
||||||
|
public static EGLMessageSeverity get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.util.objects.GLMessages;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public enum EGLMessageSource
|
||||||
|
{
|
||||||
|
API,
|
||||||
|
WINDOW_SYSTEM,
|
||||||
|
SHADER_COMPILER,
|
||||||
|
THIRD_PARTY,
|
||||||
|
APPLICATION,
|
||||||
|
OTHER;
|
||||||
|
|
||||||
|
|
||||||
|
private static final HashMap<String, EGLMessageSource> ENUM_BY_NAME = new HashMap<>();
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
for (EGLMessageSource source : EGLMessageSource.values())
|
||||||
|
{
|
||||||
|
ENUM_BY_NAME.put(source.name, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLMessageSource() { this.name = super.toString().toUpperCase(); }
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String toString() { return this.name; }
|
||||||
|
|
||||||
|
public static EGLMessageSource get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
|
||||||
|
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.util.objects.GLMessages;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public enum EGLMessageType
|
||||||
|
{
|
||||||
|
ERROR,
|
||||||
|
DEPRECATED_BEHAVIOR,
|
||||||
|
UNDEFINED_BEHAVIOR,
|
||||||
|
PORTABILITY,
|
||||||
|
PERFORMANCE,
|
||||||
|
MARKER,
|
||||||
|
PUSH_GROUP,
|
||||||
|
POP_GROUP,
|
||||||
|
OTHER;
|
||||||
|
|
||||||
|
|
||||||
|
private static final HashMap<String, EGLMessageType> ENUM_BY_NAME = new HashMap<>();
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
for (EGLMessageType type : EGLMessageType.values())
|
||||||
|
{
|
||||||
|
ENUM_BY_NAME.put(type.name, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLMessageType() { this.name = super.toString().toUpperCase(); }
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String toString() { return this.name; }
|
||||||
|
|
||||||
|
public static EGLMessageType get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
|
||||||
|
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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.util.objects.GLMessages;
|
||||||
|
|
||||||
|
public final class GLMessage
|
||||||
|
{
|
||||||
|
static final String HEADER = "[LWJGL] OpenGL debug message";
|
||||||
|
public final EGLMessageType type;
|
||||||
|
public final EGLMessageSeverity severity;
|
||||||
|
public final EGLMessageSource source;
|
||||||
|
public final String id;
|
||||||
|
public final String message;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
GLMessage(EGLMessageType type, EGLMessageSeverity severity, EGLMessageSource source, String id, String message)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
this.source = source;
|
||||||
|
this.severity = severity;
|
||||||
|
this.id = id;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "level: [" + this.severity + "], " +
|
||||||
|
"type: [" + this.type + "], " +
|
||||||
|
"source: [" + this.source + "], " +
|
||||||
|
"id: [" + this.id + "], " +
|
||||||
|
"msg: [" + this.message + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+319
@@ -0,0 +1,319 @@
|
|||||||
|
/*
|
||||||
|
* 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.util.objects.GLMessages;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/** Expected message formats can be found in GLMessageTest. */
|
||||||
|
public class GLMessageBuilder
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
|
|
||||||
|
/** how many stages are present in the message parser */
|
||||||
|
private static final int FINAL_LEGACY_PARSER_STAGE_INDEX = 15;
|
||||||
|
private static final int FINAL_NEW_PARSER_STAGE_INDEX = 5;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private EGLMessageType type;
|
||||||
|
private EGLMessageSeverity severity;
|
||||||
|
private EGLMessageSource source;
|
||||||
|
|
||||||
|
/** if the function returns false the message will be allowed */
|
||||||
|
private final Function<EGLMessageType, Boolean> typeFilter;
|
||||||
|
/** if the function returns false the message will be allowed */
|
||||||
|
private final Function<EGLMessageSeverity, Boolean> severityFilter;
|
||||||
|
/** if the function returns false the message will be allowed */
|
||||||
|
private final Function<EGLMessageSource, Boolean> sourceFilter;
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String message;
|
||||||
|
/** how far into the message parser this builder is */
|
||||||
|
private int parserStage = 0;
|
||||||
|
|
||||||
|
private boolean legacyMessage = true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============//
|
||||||
|
// constructors //
|
||||||
|
//==============//
|
||||||
|
|
||||||
|
public GLMessageBuilder() { this(null, null, null); }
|
||||||
|
|
||||||
|
public GLMessageBuilder(
|
||||||
|
Function<EGLMessageType, Boolean> typeFilter,
|
||||||
|
Function<EGLMessageSeverity, Boolean> severityFilter,
|
||||||
|
Function<EGLMessageSource, Boolean> sourceFilter)
|
||||||
|
{
|
||||||
|
this.typeFilter = typeFilter;
|
||||||
|
this.severityFilter = severityFilter;
|
||||||
|
this.sourceFilter = sourceFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=================//
|
||||||
|
// message parsing //
|
||||||
|
//=================//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given string to the message builder. <br> <br>
|
||||||
|
*
|
||||||
|
* Will log a warning if the string given wasn't expected
|
||||||
|
* for the next stage of the OpenGL message format.<br> <br>
|
||||||
|
*
|
||||||
|
* @return null if the message isn't complete
|
||||||
|
*/
|
||||||
|
public GLMessage add(String str)
|
||||||
|
{
|
||||||
|
str = str.trim();
|
||||||
|
if (str.isEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean messageFinished = false;
|
||||||
|
boolean parseSuccess = this.runNextParserStage(str);
|
||||||
|
|
||||||
|
|
||||||
|
if (this.legacyMessage
|
||||||
|
&& this.parserStage > FINAL_LEGACY_PARSER_STAGE_INDEX)
|
||||||
|
{
|
||||||
|
messageFinished = true;
|
||||||
|
}
|
||||||
|
else if (!this.legacyMessage
|
||||||
|
&& this.parserStage > FINAL_NEW_PARSER_STAGE_INDEX)
|
||||||
|
{
|
||||||
|
messageFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (parseSuccess && messageFinished)
|
||||||
|
{
|
||||||
|
this.parserStage = 0;
|
||||||
|
GLMessage msg = new GLMessage(this.type, this.severity, this.source, this.id, this.message);
|
||||||
|
if (this.doesMessagePassFilters(msg))
|
||||||
|
{
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!parseSuccess && messageFinished)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Failed to parse GLMessage line [" + str + "] at stage [" + this.parserStage + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// the message isn't finished yet
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doesMessagePassFilters(GLMessage msg)
|
||||||
|
{
|
||||||
|
if (this.sourceFilter != null && !this.sourceFilter.apply(msg.source))
|
||||||
|
return false;
|
||||||
|
else if (this.typeFilter != null && !this.typeFilter.apply(msg.type))
|
||||||
|
return false;
|
||||||
|
else if (this.severityFilter != null && !this.severityFilter.apply(msg.severity))
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return true if the given string was expected next for the OpenGL message format */
|
||||||
|
private boolean runNextParserStage(String str)
|
||||||
|
{
|
||||||
|
if (this.parserStage == 0)
|
||||||
|
{
|
||||||
|
return this.checkExactAndIncStage(str, GLMessage.HEADER);
|
||||||
|
}
|
||||||
|
else if (this.parserStage == 1)
|
||||||
|
{
|
||||||
|
// legacy message only contains "ID" (not the colon)
|
||||||
|
this.legacyMessage = !str.contains("ID: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (this.legacyMessage)
|
||||||
|
{
|
||||||
|
return this.runNextLegacyParserStage(str);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.runNextNewParserStage(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** MC 1.20.2 and older */
|
||||||
|
private boolean runNextLegacyParserStage(String str)
|
||||||
|
{
|
||||||
|
switch (this.parserStage)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
throw new IllegalStateException("Parser should be past stage ["+this.parserStage+"], next stage is [1].");
|
||||||
|
case 1:
|
||||||
|
return this.checkExactAndIncStage(str, "ID");
|
||||||
|
case 2:
|
||||||
|
return this.checkExactAndIncStage(str, ":");
|
||||||
|
case 3:
|
||||||
|
this.id = str;
|
||||||
|
this.parserStage++;
|
||||||
|
return true;
|
||||||
|
case 4:
|
||||||
|
return this.checkExactAndIncStage(str, "Source");
|
||||||
|
case 5:
|
||||||
|
return this.checkExactAndIncStage(str, ":");
|
||||||
|
case 6:
|
||||||
|
this.source = EGLMessageSource.get(str);
|
||||||
|
this.parserStage++;
|
||||||
|
return true;
|
||||||
|
case 7:
|
||||||
|
return this.checkExactAndIncStage(str, "Type");
|
||||||
|
case 8:
|
||||||
|
return this.checkExactAndIncStage(str, ":");
|
||||||
|
case 9:
|
||||||
|
this.type = EGLMessageType.get(str);
|
||||||
|
this.parserStage++;
|
||||||
|
return true;
|
||||||
|
case 10:
|
||||||
|
return this.checkExactAndIncStage(str, "Severity");
|
||||||
|
case 11:
|
||||||
|
return this.checkExactAndIncStage(str, ":");
|
||||||
|
case 12:
|
||||||
|
this.severity = EGLMessageSeverity.get(str);
|
||||||
|
this.parserStage++;
|
||||||
|
return true;
|
||||||
|
case 13:
|
||||||
|
return this.checkExactAndIncStage(str, "Message");
|
||||||
|
case 14:
|
||||||
|
return this.checkExactAndIncStage(str, ":");
|
||||||
|
case 15:
|
||||||
|
this.message = str;
|
||||||
|
this.parserStage++;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** after MC 1.20.2 */
|
||||||
|
private boolean runNextNewParserStage(String str)
|
||||||
|
{
|
||||||
|
switch (this.parserStage)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
throw new IllegalStateException("Parser should be past stage [" + this.parserStage + "], next stage is [1].");
|
||||||
|
case 1:
|
||||||
|
String idPrefix = "ID: ";
|
||||||
|
return this.checkPrefixAndRun(str, idPrefix,
|
||||||
|
(line) ->
|
||||||
|
{
|
||||||
|
this.id = trySubstring(str, idPrefix.length());
|
||||||
|
this.parserStage++;
|
||||||
|
});
|
||||||
|
case 2:
|
||||||
|
String sourcePrefix = "Source: ";
|
||||||
|
return this.checkPrefixAndRun(str, sourcePrefix,
|
||||||
|
(line) ->
|
||||||
|
{
|
||||||
|
String sourceString = trySubstring(str, sourcePrefix.length());
|
||||||
|
this.source = EGLMessageSource.get(sourceString);
|
||||||
|
this.parserStage++;
|
||||||
|
});
|
||||||
|
case 3:
|
||||||
|
String typePrefix = "Type: ";
|
||||||
|
return this.checkPrefixAndRun(str, typePrefix,
|
||||||
|
(line) ->
|
||||||
|
{
|
||||||
|
String sourceString = trySubstring(str, typePrefix.length());
|
||||||
|
this.type = EGLMessageType.get(sourceString);
|
||||||
|
this.parserStage++;
|
||||||
|
});
|
||||||
|
case 4:
|
||||||
|
String severityPrefix = "Severity: ";
|
||||||
|
return this.checkPrefixAndRun(str, severityPrefix,
|
||||||
|
(line) ->
|
||||||
|
{
|
||||||
|
String sourceString = trySubstring(str, severityPrefix.length());
|
||||||
|
this.severity = EGLMessageSeverity.get(sourceString);
|
||||||
|
this.parserStage++;
|
||||||
|
});
|
||||||
|
case 5:
|
||||||
|
String messagePrefix = "Message: ";
|
||||||
|
return this.checkPrefixAndRun(str, messagePrefix,
|
||||||
|
(line) ->
|
||||||
|
{
|
||||||
|
this.message = trySubstring(str, messagePrefix.length());
|
||||||
|
this.parserStage++;
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// helper methods //
|
||||||
|
//================//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true and increments the parserStage
|
||||||
|
* if the message and expected strings are the same.
|
||||||
|
*/
|
||||||
|
private boolean checkExactAndIncStage(String message, String expectedString)
|
||||||
|
{
|
||||||
|
boolean equal = message.equals(expectedString);
|
||||||
|
if (equal)
|
||||||
|
{
|
||||||
|
this.parserStage++;
|
||||||
|
}
|
||||||
|
return equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true and increments the parserStage
|
||||||
|
* if the message starts with the given prefix.
|
||||||
|
*/
|
||||||
|
private boolean checkPrefixAndRun(String message, String expectedPrefix, Consumer<String> successConsumer)
|
||||||
|
{
|
||||||
|
boolean equal = message.startsWith(expectedPrefix);
|
||||||
|
if (equal)
|
||||||
|
{
|
||||||
|
successConsumer.accept(message);
|
||||||
|
}
|
||||||
|
return equal;
|
||||||
|
}
|
||||||
|
/** returns "" if the string isn't long enough */
|
||||||
|
private static String trySubstring(String string, int beginIndex)
|
||||||
|
{
|
||||||
|
if (beginIndex > string.length())
|
||||||
|
{
|
||||||
|
// prevent index-out-of-bounds errors
|
||||||
|
// if the message isn't what we expected
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.substring(beginIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+17
-11
@@ -17,7 +17,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.seibel.distanthorizons.core.util.objects;
|
package com.seibel.distanthorizons.core.util.objects.GLMessages;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -27,12 +27,12 @@ import java.util.function.Consumer;
|
|||||||
public final class GLMessageOutputStream extends OutputStream
|
public final class GLMessageOutputStream extends OutputStream
|
||||||
{
|
{
|
||||||
final Consumer<GLMessage> func;
|
final Consumer<GLMessage> func;
|
||||||
final GLMessage.Builder builder;
|
final GLMessageBuilder builder;
|
||||||
|
|
||||||
|
|
||||||
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
public GLMessageOutputStream(Consumer<GLMessage> func, GLMessage.Builder builder)
|
public GLMessageOutputStream(Consumer<GLMessage> func, GLMessageBuilder builder)
|
||||||
{
|
{
|
||||||
this.func = func;
|
this.func = func;
|
||||||
this.builder = builder;
|
this.builder = builder;
|
||||||
@@ -41,24 +41,30 @@ public final class GLMessageOutputStream extends OutputStream
|
|||||||
@Override
|
@Override
|
||||||
public void write(int b)
|
public void write(int b)
|
||||||
{
|
{
|
||||||
buffer.write(b);
|
this.buffer.write(b);
|
||||||
if (b == '\n') flush();
|
if (b == '\n')
|
||||||
|
{
|
||||||
|
this.flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush()
|
public void flush()
|
||||||
{
|
{
|
||||||
String str = buffer.toString();
|
String str = this.buffer.toString();
|
||||||
GLMessage msg = builder.add(str);
|
GLMessage msg = this.builder.add(str);
|
||||||
if (msg != null) func.accept(msg);
|
if (msg != null)
|
||||||
buffer.reset();
|
{
|
||||||
|
this.func.accept(msg);
|
||||||
|
}
|
||||||
|
this.buffer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException
|
public void close() throws IOException
|
||||||
{
|
{
|
||||||
flush();
|
this.flush();
|
||||||
buffer.close();
|
this.buffer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+4
-3
@@ -20,10 +20,8 @@
|
|||||||
package com.seibel.distanthorizons.core.util.objects.dataStreams;
|
package com.seibel.distanthorizons.core.util.objects.dataStreams;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
|
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
|
||||||
import com.seibel.distanthorizons.core.Initializer;
|
|
||||||
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
|
||||||
import net.jpountz.lz4.LZ4FrameInputStream;
|
import net.jpountz.lz4.LZ4FrameInputStream;
|
||||||
|
//import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.tukaani.xz.ResettableArrayCache;
|
import org.tukaani.xz.ResettableArrayCache;
|
||||||
@@ -44,6 +42,7 @@ import java.io.*;
|
|||||||
public class DhDataInputStream extends DataInputStream
|
public class DhDataInputStream extends DataInputStream
|
||||||
{
|
{
|
||||||
private static final ThreadLocal<ResettableArrayCache> LZMA_RESETTABLE_ARRAY_CACHE_GETTER = ThreadLocal.withInitial(() -> new ResettableArrayCache(new LzmaArrayCache()));
|
private static final ThreadLocal<ResettableArrayCache> LZMA_RESETTABLE_ARRAY_CACHE_GETTER = ThreadLocal.withInitial(() -> new ResettableArrayCache(new LzmaArrayCache()));
|
||||||
|
//private static final ThreadLocal<ZstdArrayCache> ZSTD_RESETTABLE_ARRAY_CACHE_GETTER = ThreadLocal.withInitial(() -> new ZstdArrayCache());
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger();
|
private static final Logger LOGGER = LogManager.getLogger();
|
||||||
|
|
||||||
@@ -62,6 +61,8 @@ public class DhDataInputStream extends DataInputStream
|
|||||||
return stream;
|
return stream;
|
||||||
case LZ4:
|
case LZ4:
|
||||||
return new LZ4FrameInputStream(stream);
|
return new LZ4FrameInputStream(stream);
|
||||||
|
//case Z_STD:
|
||||||
|
// return new ZstdCompressorInputStream(stream, ZSTD_RESETTABLE_ARRAY_CACHE_GETTER.get());
|
||||||
case LZMA2:
|
case LZMA2:
|
||||||
// using an array cache significantly reduces GC pressure
|
// using an array cache significantly reduces GC pressure
|
||||||
ResettableArrayCache arrayCache = LZMA_RESETTABLE_ARRAY_CACHE_GETTER.get();
|
ResettableArrayCache arrayCache = LZMA_RESETTABLE_ARRAY_CACHE_GETTER.get();
|
||||||
|
|||||||
+4
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
|
|||||||
import net.jpountz.lz4.LZ4Factory;
|
import net.jpountz.lz4.LZ4Factory;
|
||||||
import net.jpountz.lz4.LZ4FrameOutputStream;
|
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||||
import net.jpountz.xxhash.XXHashFactory;
|
import net.jpountz.xxhash.XXHashFactory;
|
||||||
|
//import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.tukaani.xz.*;
|
import org.tukaani.xz.*;
|
||||||
@@ -53,6 +54,9 @@ public class DhDataOutputStream extends DataOutputStream
|
|||||||
{
|
{
|
||||||
case UNCOMPRESSED:
|
case UNCOMPRESSED:
|
||||||
return stream;
|
return stream;
|
||||||
|
|
||||||
|
//case Z_STD:
|
||||||
|
// return new ZstdCompressorOutputStream(stream, 3, true, true);
|
||||||
case LZ4:
|
case LZ4:
|
||||||
return new LZ4FrameOutputStream(stream,
|
return new LZ4FrameOutputStream(stream,
|
||||||
LZ4FrameOutputStream.BLOCKSIZE.SIZE_64KB, -1L,
|
LZ4FrameOutputStream.BLOCKSIZE.SIZE_64KB, -1L,
|
||||||
|
|||||||
+20
-4
@@ -63,8 +63,16 @@ public class LzmaArrayCache extends ArrayCache
|
|||||||
{
|
{
|
||||||
return new byte[size];
|
return new byte[size];
|
||||||
}
|
}
|
||||||
// the array needs to be cleared to prevent accidentally sending dirty data
|
|
||||||
Arrays.fill(array, (byte)0);
|
// the array only sometimes needs to be cleared,
|
||||||
|
// clearing all the time results in unnecessary slowdowns
|
||||||
|
if (fillWithZeros)
|
||||||
|
{
|
||||||
|
// TODO it appears that this can prevent the CPU from working on
|
||||||
|
// other tasks, thus causing render thread lag even when run on a separate thread
|
||||||
|
Arrays.fill(array, (byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,8 +115,16 @@ public class LzmaArrayCache extends ArrayCache
|
|||||||
{
|
{
|
||||||
return new int[size];
|
return new int[size];
|
||||||
}
|
}
|
||||||
// the array needs to be cleared to prevent accidentally sending dirty data
|
|
||||||
Arrays.fill(array, (byte)0);
|
// the array only sometimes needs to be cleared,
|
||||||
|
// clearing all the time results in unnecessary slowdowns
|
||||||
|
if (fillWithZeros)
|
||||||
|
{
|
||||||
|
// TODO it appears that this can prevent the CPU from working on
|
||||||
|
// other tasks, thus causing render thread lag even when run on a separate thread
|
||||||
|
Arrays.fill(array, (byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
package com.seibel.distanthorizons.core.util.objects.dataStreams;
|
||||||
|
|
||||||
|
//import com.github.luben.zstd.BufferPool;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.IntUnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LZMA requires a custom object to cache it's backend arrays.
|
||||||
|
*/
|
||||||
|
public class ZstdArrayCache //implements BufferPool
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In James' testing the byte and int caches only ever had to store 2 and 4 arrays respectively.
|
||||||
|
* With the in mind we could take a few shortcuts, but if that changes then we need to be
|
||||||
|
* notified as it might cause issues with the current logic.
|
||||||
|
*/
|
||||||
|
public static final int WARN_CACHE_LENGTH_EXCEEDED = 10;
|
||||||
|
|
||||||
|
public static final AtomicInteger MAX_BYTE_CACHE_LENGTH_REF = new AtomicInteger(WARN_CACHE_LENGTH_EXCEEDED);
|
||||||
|
|
||||||
|
public final IntUnaryOperator maxByteCacheSizeUnaryOperator = (x) -> Math.max(this.bufferCache.size(), x);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generally only 2 items long <br>
|
||||||
|
* {@link Int2ReferenceArrayMap} can be used since the cache should only be a few items long.
|
||||||
|
* If the array ends up being longer then this design will need to be changed.
|
||||||
|
*/
|
||||||
|
public final Int2ReferenceArrayMap<ArrayList<ByteBuffer>> bufferCache = new Int2ReferenceArrayMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// byte arrays //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
//@Override
|
||||||
|
public ByteBuffer get(int size)
|
||||||
|
{
|
||||||
|
ArrayList<ByteBuffer> cacheList = this.bufferCache.computeIfAbsent(size, (newSize) -> new ArrayList<>(4));
|
||||||
|
if (cacheList.isEmpty())
|
||||||
|
{
|
||||||
|
return ByteBuffer.allocate(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer array = cacheList.remove(cacheList.size()-1);
|
||||||
|
if (array == null)
|
||||||
|
{
|
||||||
|
return ByteBuffer.allocate(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
//@Override
|
||||||
|
public void release(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
int size = buffer.array().length;
|
||||||
|
this.bufferCache.computeIfAbsent(size, (newSize) -> new ArrayList<>());
|
||||||
|
this.bufferCache.get(size).add(buffer);
|
||||||
|
|
||||||
|
|
||||||
|
if (this.bufferCache.size() > WARN_CACHE_LENGTH_EXCEEDED)
|
||||||
|
{
|
||||||
|
int previousMax = MAX_BYTE_CACHE_LENGTH_REF.getAndUpdate(this.maxByteCacheSizeUnaryOperator);
|
||||||
|
int newMax = MAX_BYTE_CACHE_LENGTH_REF.get();
|
||||||
|
if (newMax > previousMax)
|
||||||
|
{
|
||||||
|
LOGGER.warn("LZMA byte array cache expected size exceeded. Expected max length ["+WARN_CACHE_LENGTH_EXCEEDED+"], actual length ["+this.bufferCache.size()+"].");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+4
-2
@@ -78,9 +78,11 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
|
|||||||
long runTime = System.nanoTime() - this.runStartTime.get();
|
long runTime = System.nanoTime() - this.runStartTime.get();
|
||||||
Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / this.runTimeRatioConfig.get() - runTime)));
|
Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / this.runTimeRatioConfig.get() - runTime)));
|
||||||
}
|
}
|
||||||
catch (InterruptedException e)
|
catch (InterruptedException ignore)
|
||||||
{
|
{
|
||||||
throw new RuntimeException(e);
|
// if this thread is interrupted that means the
|
||||||
|
// thread pool is being shut down,
|
||||||
|
// we don't need to log that.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-2
@@ -89,7 +89,14 @@ public class ThreadPoolUtil
|
|||||||
|
|
||||||
public static void setupThreadPools()
|
public static void setupThreadPools()
|
||||||
{
|
{
|
||||||
// thread pools
|
//==================//
|
||||||
|
// main thread pool //
|
||||||
|
//==================//
|
||||||
|
|
||||||
|
if (taskPicker != null)
|
||||||
|
{
|
||||||
|
taskPicker.shutdown();
|
||||||
|
}
|
||||||
taskPicker = new PriorityTaskPicker();
|
taskPicker = new PriorityTaskPicker();
|
||||||
|
|
||||||
networkCompressionThreadPool = taskPicker.createExecutor();
|
networkCompressionThreadPool = taskPicker.createExecutor();
|
||||||
@@ -98,8 +105,22 @@ public class ThreadPoolUtil
|
|||||||
updatePropagatorThreadPool = taskPicker.createExecutor();
|
updatePropagatorThreadPool = taskPicker.createExecutor();
|
||||||
worldGenThreadPool = taskPicker.createExecutor();
|
worldGenThreadPool = taskPicker.createExecutor();
|
||||||
|
|
||||||
// single thread pools
|
|
||||||
|
|
||||||
|
//=========================//
|
||||||
|
// standalone thread pools //
|
||||||
|
//=========================//
|
||||||
|
|
||||||
|
if (beaconCullingThreadPool != null)
|
||||||
|
{
|
||||||
|
beaconCullingThreadPool.shutdown();
|
||||||
|
}
|
||||||
beaconCullingThreadPool = ThreadUtil.makeSingleThreadPool(BEACON_CULLING_THREAD_NAME);
|
beaconCullingThreadPool = ThreadUtil.makeSingleThreadPool(BEACON_CULLING_THREAD_NAME);
|
||||||
|
|
||||||
|
if (fullDataMigrationThreadPool != null)
|
||||||
|
{
|
||||||
|
fullDataMigrationThreadPool.shutdown();
|
||||||
|
}
|
||||||
fullDataMigrationThreadPool = ThreadUtil.makeSingleThreadPool(FULL_DATA_MIGRATION_THREAD_NAME);
|
fullDataMigrationThreadPool = ThreadUtil.makeSingleThreadPool(FULL_DATA_MIGRATION_THREAD_NAME);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
|
|||||||
public void addPlayer(IServerPlayerWrapper serverPlayer)
|
public void addPlayer(IServerPlayerWrapper serverPlayer)
|
||||||
{
|
{
|
||||||
ServerPlayerState playerState = this.serverPlayerStateManager.registerJoinedPlayer(serverPlayer);
|
ServerPlayerState playerState = this.serverPlayerStateManager.registerJoinedPlayer(serverPlayer);
|
||||||
this.getLevel(serverPlayer.getLevel()).addPlayer(serverPlayer);
|
((TDhServerLevel) this.getOrLoadServerLevel(serverPlayer.getLevel())).addPlayer(serverPlayer);
|
||||||
|
|
||||||
Iterator<TDhServerLevel> it = this.dhLevelByLevelWrapper.values().stream().distinct().iterator();
|
Iterator<TDhServerLevel> it = this.dhLevelByLevelWrapper.values().stream().distinct().iterator();
|
||||||
while (it.hasNext())
|
while (it.hasNext())
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
|||||||
public final ClientOnlySaveStructure saveStructure;
|
public final ClientOnlySaveStructure saveStructure;
|
||||||
public final ClientNetworkState networkState = new ClientNetworkState();
|
public final ClientNetworkState networkState = new ClientNetworkState();
|
||||||
|
|
||||||
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client World Ticker Thread");
|
public final ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client World Ticker Thread");
|
||||||
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick);
|
public final EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -128,6 +128,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
|||||||
{
|
{
|
||||||
this.networkState.close();
|
this.networkState.close();
|
||||||
|
|
||||||
|
this.dhTickerThread.shutdownNow();
|
||||||
|
|
||||||
|
|
||||||
for (DhClientLevel dhClientLevel : this.levels.values())
|
for (DhClientLevel dhClientLevel : this.levels.values())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
// level handling //
|
// level handling //
|
||||||
//================//
|
//================//
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
|
|||||||
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.NotNull;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public interface IDhWorld
|
// TODO why is this exist alongside AbstractDhWorld?
|
||||||
|
public interface IDhWorld extends Closeable
|
||||||
{
|
{
|
||||||
|
|
||||||
IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper);
|
IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper);
|
||||||
|
|||||||
+5
@@ -49,6 +49,11 @@ public interface IBlockStateWrapper extends IDhApiBlockStateWrapper
|
|||||||
boolean isBeaconBlock();
|
boolean isBeaconBlock();
|
||||||
/** IE a glass block that can affect the beacon beam color */
|
/** IE a glass block that can affect the beacon beam color */
|
||||||
boolean isBeaconTintBlock();
|
boolean isBeaconTintBlock();
|
||||||
|
/**
|
||||||
|
* Returns true for any blocks that allow beacon beams to go through.
|
||||||
|
* IE: glass, stairs, bedrock, chests, end portal frames, carpet, cake
|
||||||
|
*/
|
||||||
|
boolean allowsBeaconBeamPassage();
|
||||||
/**
|
/**
|
||||||
* The blocks used by a beacon's base
|
* The blocks used by a beacon's base
|
||||||
* IE Iron, diamond, gold, etc.
|
* IE Iron, diamond, gold, etc.
|
||||||
|
|||||||
+2
-1
@@ -381,8 +381,9 @@ public interface IChunkWrapper extends IBindable
|
|||||||
for (int y = beaconRelPos.getY() +1; y <= maxY; y++)
|
for (int y = beaconRelPos.getY() +1; y <= maxY; y++)
|
||||||
{
|
{
|
||||||
IBlockStateWrapper block = centerChunk.getBlockState(beaconRelPos.getX(), y, beaconRelPos.getZ());
|
IBlockStateWrapper block = centerChunk.getBlockState(beaconRelPos.getX(), y, beaconRelPos.getZ());
|
||||||
if (!block.isAir() && block.getOpacity() == LodUtil.BLOCK_FULLY_OPAQUE)
|
if (!block.allowsBeaconBeamPassage())
|
||||||
{
|
{
|
||||||
|
// beam is blocked by this block
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -60,6 +60,9 @@ public interface IMinecraftRenderWrapper extends IBindable
|
|||||||
int getScreenWidth();
|
int getScreenWidth();
|
||||||
int getScreenHeight();
|
int getScreenHeight();
|
||||||
|
|
||||||
|
boolean mcRendersToFrameBuffer();
|
||||||
|
boolean runningLegacyOpenGL();
|
||||||
|
|
||||||
/** @return -1 if no valid framebuffer is available yet */
|
/** @return -1 if no valid framebuffer is available yet */
|
||||||
int getTargetFrameBuffer(); // Note: Iris is now hooking onto this for DH + Iris compat, try not to change (unless we wanna deal with some annoyances)
|
int getTargetFrameBuffer(); // Note: Iris is now hooking onto this for DH + Iris compat, try not to change (unless we wanna deal with some annoyances)
|
||||||
// Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16
|
// Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16
|
||||||
|
|||||||
@@ -477,7 +477,11 @@
|
|||||||
"distanthorizons.config.client.advanced.debugging.openGl.overrideVanillaGLLogger":
|
"distanthorizons.config.client.advanced.debugging.openGl.overrideVanillaGLLogger":
|
||||||
"Override Vanilla GL Logger",
|
"Override Vanilla GL Logger",
|
||||||
"distanthorizons.config.client.advanced.debugging.openGl.overrideVanillaGLLogger.@tooltip":
|
"distanthorizons.config.client.advanced.debugging.openGl.overrideVanillaGLLogger.@tooltip":
|
||||||
"Defines how OpenGL errors are handled. \n Requires rebooting Minecraft to apply. \nMay incorrectly catch OpenGL errors thrown by other mods.",
|
"Defines how OpenGL errors are handled. \nRequires rebooting Minecraft to change. \nWill catch OpenGL errors thrown by other mods.",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.openGl.onlyLogGlErrorsOnce":
|
||||||
|
"Only Log GL Errors Once",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.openGl.onlyLogGlErrorsOnce.@tooltip":
|
||||||
|
"If true each Open GL error will only be logged once. \nTEnabling this may cause some error logs to be missed. \nDoes nothing if overrideVanillaGLLogger is set to false. \n\nGenerally this can be kept as 'true' to prevent log spam. \nHowever, Please set this to 'false' if a developer needs your log to debug a GL issue. \n",
|
||||||
"distanthorizons.config.client.advanced.debugging.openGl.glErrorHandlingMode":
|
"distanthorizons.config.client.advanced.debugging.openGl.glErrorHandlingMode":
|
||||||
"OpenGL Error Handling Mode",
|
"OpenGL Error Handling Mode",
|
||||||
"distanthorizons.config.client.advanced.debugging.openGl.glErrorHandlingMode.@tooltip":
|
"distanthorizons.config.client.advanced.debugging.openGl.glErrorHandlingMode.@tooltip":
|
||||||
@@ -742,6 +746,10 @@
|
|||||||
"Maximum Data Transfer Speed, KB/s",
|
"Maximum Data Transfer Speed, KB/s",
|
||||||
"distanthorizons.config.server.maxDataTransferSpeed.@tooltip":
|
"distanthorizons.config.server.maxDataTransferSpeed.@tooltip":
|
||||||
"Maximum speed for uploading LODs to the clients, in KB/s.\nValue of 0 disables the limit.",
|
"Maximum speed for uploading LODs to the clients, in KB/s.\nValue of 0 disables the limit.",
|
||||||
|
"distanthorizons.config.server.enableAdaptiveTransferSpeed":
|
||||||
|
"Enable Adaptive Transfer Speed",
|
||||||
|
"distanthorizons.config.server.enableAdaptiveTransferSpeed.@tooltip":
|
||||||
|
"Enables adaptive transfer speed based on client performance.\nIf true, DH will automatically adjust transfer rate to minimize connection lag.\nIf false, transfer speed will remain fixed.",
|
||||||
|
|
||||||
|
|
||||||
"distanthorizons.config.server.experimental":
|
"distanthorizons.config.server.experimental":
|
||||||
@@ -913,7 +921,7 @@
|
|||||||
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.FEATURES":
|
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.FEATURES":
|
||||||
"Features",
|
"Features",
|
||||||
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.INTERNAL_SERVER":
|
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.INTERNAL_SERVER":
|
||||||
"Internal Server",
|
"Full - Save Chunks",
|
||||||
|
|
||||||
"distanthorizons.config.enum.EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY":
|
"distanthorizons.config.enum.EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY":
|
||||||
"Overlay",
|
"Overlay",
|
||||||
@@ -927,9 +935,11 @@
|
|||||||
"distanthorizons.config.enum.EDhApiDataCompressionMode.UNCOMPRESSED":
|
"distanthorizons.config.enum.EDhApiDataCompressionMode.UNCOMPRESSED":
|
||||||
"Uncompressed",
|
"Uncompressed",
|
||||||
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZ4":
|
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZ4":
|
||||||
"Fast/Big - LZ4",
|
"Fastest/Big - LZ4",
|
||||||
|
"distanthorizons.config.enum.EDhApiDataCompressionMode.Z_STD":
|
||||||
|
"Fast/Small - Z_STD",
|
||||||
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZMA2":
|
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZMA2":
|
||||||
"Slow/Small - LZMA2",
|
"Slow/Smallest - LZMA2",
|
||||||
|
|
||||||
"distanthorizons.config.enum.EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS":
|
"distanthorizons.config.enum.EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS":
|
||||||
"1. Merge Same Blocks",
|
"1. Merge Same Blocks",
|
||||||
@@ -1012,6 +1022,8 @@
|
|||||||
"File: Info, Chat: Warning",
|
"File: Info, Chat: Warning",
|
||||||
"distanthorizons.config.enum.EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE":
|
"distanthorizons.config.enum.EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE":
|
||||||
"File: Info, Chat: Error",
|
"File: Info, Chat: Error",
|
||||||
|
"distanthorizons.config.enum.EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE":
|
||||||
|
"File: Warning, Chat: Error",
|
||||||
|
|
||||||
"distanthorizons.config.enum.EDhApiGpuUploadMethod.AUTO":
|
"distanthorizons.config.enum.EDhApiGpuUploadMethod.AUTO":
|
||||||
"Auto",
|
"Auto",
|
||||||
|
|||||||
@@ -8,17 +8,26 @@ uniform sampler2D gDhColorTexture;
|
|||||||
uniform sampler2D gDhDepthTexture;
|
uniform sampler2D gDhDepthTexture;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LOD application shader
|
||||||
|
*
|
||||||
|
* This merges the rendered LODs into Minecraft's texture/FBO
|
||||||
|
*/
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
fragColor = vec4(0.0);
|
fragColor = vec4(0.0);
|
||||||
|
|
||||||
float fragmentDepth = texture(gDhDepthTexture, TexCoord).r;
|
|
||||||
|
|
||||||
// a fragment depth of "1" means the fragment wasn't drawn to,
|
// a fragment depth of "1" means the fragment wasn't drawn to,
|
||||||
// only update fragments that were drawn to
|
// only update fragments that were drawn to
|
||||||
|
float fragmentDepth = texture(gDhDepthTexture, TexCoord).r;
|
||||||
if (fragmentDepth != 1)
|
if (fragmentDepth != 1)
|
||||||
{
|
{
|
||||||
fragColor = texture(gDhColorTexture, TexCoord);
|
fragColor = texture(gDhColorTexture, TexCoord);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use the original MC texture if no LODs were drawn to this fragment
|
||||||
|
discard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,18 @@ uniform sampler2D uColorTexture;
|
|||||||
uniform sampler2D uDepthTexture;
|
uniform sampler2D uDepthTexture;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fog application shader
|
||||||
|
*
|
||||||
|
* This merges the rendered fog onto DH's rendered LODs
|
||||||
|
*/
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
fragColor = vec4(1.0);
|
fragColor = vec4(0.0);
|
||||||
|
|
||||||
float fragmentDepth = textureLod(uDepthTexture, TexCoord, 0).r;
|
|
||||||
|
|
||||||
// a fragment depth of "1" means the fragment wasn't drawn to,
|
// a fragment depth of "1" means the fragment wasn't drawn to,
|
||||||
// only update fragments that were drawn to
|
// only update fragments that were drawn to
|
||||||
|
float fragmentDepth = textureLod(uDepthTexture, TexCoord, 0).r;
|
||||||
if (fragmentDepth != 1)
|
if (fragmentDepth != 1)
|
||||||
{
|
{
|
||||||
fragColor = texture(uColorTexture, TexCoord);
|
fragColor = texture(uColorTexture, TexCoord);
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
|||||||
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
|
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
|
||||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
|
//import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.text.CharacterIterator;
|
|
||||||
import java.text.StringCharacterIterator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <strong>Note:</strong>
|
* <strong>Note:</strong>
|
||||||
@@ -47,22 +47,14 @@ import java.text.StringCharacterIterator;
|
|||||||
*/
|
*/
|
||||||
public class CompressionTest
|
public class CompressionTest
|
||||||
{
|
{
|
||||||
public static String TEST_DIR = "C:\\DistantHorizonsWorkspace\\distant-horizons\\run\\saves\\Arcapelago\\data";
|
public static String TEST_DIR = "C:\\DistantHorizonsWorkspace\\distant-horizons\\run\\client\\saves\\Archipelego\\data";
|
||||||
public static String DB_FILE_NAME_PREFIX = "DistantHorizons";
|
public static String DB_FILE_NAME_PREFIX = "DistantHorizons";
|
||||||
public static String UNCOMPRESSED_DB_FILE_NAME = "DistantHorizons.sqlite";
|
public static String UNCOMPRESSED_DB_FILE_NAME = "DistantHorizons_uncompressed.sqlite";
|
||||||
|
|
||||||
/** -1 will test all of them */
|
/** -1 will test all of them */
|
||||||
public static int MAX_DTO_TEST_COUNT = -1;
|
public static int MAX_DTO_TEST_COUNT = -1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//@Test
|
|
||||||
public void NoCompression()
|
|
||||||
{
|
|
||||||
String compressorName = "Uncompressed";
|
|
||||||
this.testCompressor(compressorName, EDhApiDataCompressionMode.UNCOMPRESSED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// collapse the following commented out code when looking at tests
|
// collapse the following commented out code when looking at tests
|
||||||
|
|
||||||
//@Test
|
//@Test
|
||||||
@@ -132,17 +124,6 @@ public class CompressionTest
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
|
|
||||||
//@Test
|
|
||||||
//public void Zstd()
|
|
||||||
//{
|
|
||||||
// String compressorName = "Zstd";
|
|
||||||
//
|
|
||||||
// DhDataInputStream.CreateInputStreamFunc createInputStreamFunc = (inputStream) -> new ZstdInputStream(inputStream);
|
|
||||||
// DhDataOutputStream.CreateOutputStreamFunc createOutputStreamFunc = (outputStream) -> new ZstdOutputStream(outputStream);
|
|
||||||
//
|
|
||||||
// this.testCompressor(compressorName, createInputStreamFunc, createOutputStreamFunc);
|
|
||||||
//}
|
|
||||||
|
|
||||||
////@Test
|
////@Test
|
||||||
//public void ZstdDictionary() throws SQLException // isn't any better than normal Zstd
|
//public void ZstdDictionary() throws SQLException // isn't any better than normal Zstd
|
||||||
//{
|
//{
|
||||||
@@ -205,6 +186,13 @@ public class CompressionTest
|
|||||||
// this.testCompressor(compressorName, createInputStreamFunc, createOutputStreamFunc);
|
// this.testCompressor(compressorName, createInputStreamFunc, createOutputStreamFunc);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
//@Test
|
||||||
|
public void NoCompression()
|
||||||
|
{
|
||||||
|
String compressorName = "Uncompressed";
|
||||||
|
this.testCompressor(compressorName, EDhApiDataCompressionMode.UNCOMPRESSED);
|
||||||
|
}
|
||||||
|
|
||||||
//@Test
|
//@Test
|
||||||
public void Lz4() // fast, poor compression
|
public void Lz4() // fast, poor compression
|
||||||
{
|
{
|
||||||
@@ -213,11 +201,11 @@ public class CompressionTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
//@Test
|
//@Test
|
||||||
//public void Zstd() // middle of the road
|
public void Zstd() // middle of the road
|
||||||
//{
|
{
|
||||||
// String compressorName = "Zstd";
|
//String compressorName = "Zstd";
|
||||||
// this.testCompressor(compressorName, EDhApiDataCompressionMode.Z_STD);
|
//this.testCompressor(compressorName, EDhApiDataCompressionMode.Z_STD);
|
||||||
//}
|
}
|
||||||
|
|
||||||
//@Test
|
//@Test
|
||||||
public void LZMA2() // very slow, very good compression though
|
public void LZMA2() // very slow, very good compression though
|
||||||
@@ -254,13 +242,15 @@ public class CompressionTest
|
|||||||
long totalCompressedFileSizeInBytes;
|
long totalCompressedFileSizeInBytes;
|
||||||
|
|
||||||
|
|
||||||
|
FullDataSourceV2Repo uncompressedRepo = null;
|
||||||
|
FullDataSourceV2Repo compressedRepo = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
String uncompressedDatabaseFilePath = TEST_DIR + "/" + UNCOMPRESSED_DB_FILE_NAME;
|
String uncompressedDatabaseFilePath = TEST_DIR + "/" + UNCOMPRESSED_DB_FILE_NAME;
|
||||||
File uncompressedDatabaseFile = new File(uncompressedDatabaseFilePath);
|
File uncompressedDatabaseFile = new File(uncompressedDatabaseFilePath);
|
||||||
Assert.assertTrue(uncompressedDatabaseFile.exists());
|
Assert.assertTrue(uncompressedDatabaseFile.exists());
|
||||||
|
|
||||||
FullDataSourceV2Repo uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile);
|
uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile);
|
||||||
|
|
||||||
|
|
||||||
String compressedDatabaseFilePath = TEST_DIR + "/output/" + DB_FILE_NAME_PREFIX + "_" + compressorName + ".sqlite";
|
String compressedDatabaseFilePath = TEST_DIR + "/output/" + DB_FILE_NAME_PREFIX + "_" + compressorName + ".sqlite";
|
||||||
@@ -268,7 +258,7 @@ public class CompressionTest
|
|||||||
compressedDatabaseFile.mkdirs();
|
compressedDatabaseFile.mkdirs();
|
||||||
compressedDatabaseFile.delete();
|
compressedDatabaseFile.delete();
|
||||||
Assert.assertTrue(!compressedDatabaseFile.exists());
|
Assert.assertTrue(!compressedDatabaseFile.exists());
|
||||||
FullDataSourceV2Repo compressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile);
|
compressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", compressedDatabaseFile);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -292,47 +282,47 @@ public class CompressionTest
|
|||||||
|
|
||||||
// uncompressed input //
|
// uncompressed input //
|
||||||
|
|
||||||
FullDataSourceV2DTO uncompressedDto = uncompressedRepo.getByKey(pos);
|
try (FullDataSourceV2DTO uncompressedDto = uncompressedRepo.getByKey(pos))
|
||||||
Assert.assertEquals(uncompressedDto.compressionModeValue, EDhApiDataCompressionMode.UNCOMPRESSED.value);
|
{
|
||||||
FullDataSourceV2 uncompressedDataSource = uncompressedDto.createUnitTestDataSource();
|
Assert.assertEquals(uncompressedDto.compressionModeValue, EDhApiDataCompressionMode.UNCOMPRESSED.value);
|
||||||
|
FullDataSourceV2 uncompressedDataSource = uncompressedDto.createUnitTestDataSource();
|
||||||
|
|
||||||
long uncompressedDtoSize = uncompressedRepo.getDataSizeInBytes(pos);
|
long uncompressedDtoSize = uncompressedRepo.getDataSizeInBytes(pos);
|
||||||
minUncompressedDtoSizeInBytes = Math.min(uncompressedDtoSize, minUncompressedDtoSizeInBytes);
|
minUncompressedDtoSizeInBytes = Math.min(uncompressedDtoSize, minUncompressedDtoSizeInBytes);
|
||||||
maxUncompressedDtoSizeInBytes = Math.max(uncompressedDtoSize, maxUncompressedDtoSizeInBytes);
|
maxUncompressedDtoSizeInBytes = Math.max(uncompressedDtoSize, maxUncompressedDtoSizeInBytes);
|
||||||
avgUncompressedDtoSizeInBytes += uncompressedDtoSize;
|
avgUncompressedDtoSizeInBytes += uncompressedDtoSize;
|
||||||
|
|
||||||
|
|
||||||
|
// compress file //
|
||||||
|
|
||||||
// compress file //
|
long startWriteNanoTime = System.nanoTime();
|
||||||
|
|
||||||
long startWriteNanoTime = System.nanoTime();
|
FullDataSourceV2DTO compressedDto = FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, compressionMode);
|
||||||
|
compressedRepo.save(compressedDto);
|
||||||
|
|
||||||
FullDataSourceV2DTO compressedDto = FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, compressionMode);
|
long endWriteNanoTime = System.nanoTime();
|
||||||
compressedRepo.save(compressedDto);
|
totalWriteTimeInNano += (endWriteNanoTime - startWriteNanoTime);
|
||||||
|
|
||||||
long endWriteNanoTime = System.nanoTime();
|
|
||||||
totalWriteTimeInNano += (endWriteNanoTime - startWriteNanoTime);
|
|
||||||
|
|
||||||
|
|
||||||
long compressedDtoSize = compressedRepo.getDataSizeInBytes(pos);
|
long compressedDtoSize = compressedRepo.getDataSizeInBytes(pos);
|
||||||
minCompressedDtoSizeInBytes = Math.min(compressedDtoSize, minCompressedDtoSizeInBytes);
|
minCompressedDtoSizeInBytes = Math.min(compressedDtoSize, minCompressedDtoSizeInBytes);
|
||||||
maxCompressedDtoSizeInBytes = Math.max(compressedDtoSize, maxCompressedDtoSizeInBytes);
|
maxCompressedDtoSizeInBytes = Math.max(compressedDtoSize, maxCompressedDtoSizeInBytes);
|
||||||
avgCompressedDtoSizeInBytes += compressedDtoSize;
|
avgCompressedDtoSizeInBytes += compressedDtoSize;
|
||||||
|
|
||||||
|
|
||||||
|
// read compressed file //
|
||||||
|
|
||||||
// read compressed file //
|
long startReadNanoTime = System.nanoTime();
|
||||||
|
|
||||||
long startReadNanoTime = System.nanoTime();
|
compressedDto = compressedRepo.getByKey(pos);
|
||||||
|
FullDataSourceV2 compressedDataSource = compressedDto.createUnitTestDataSource();
|
||||||
|
|
||||||
compressedDto = compressedRepo.getByKey(pos);
|
long endReadMsTime = System.nanoTime();
|
||||||
FullDataSourceV2 compressedDataSource = compressedDto.createUnitTestDataSource();
|
totalReadTimeInNano += (endReadMsTime - startReadNanoTime);
|
||||||
|
|
||||||
long endReadMsTime = System.nanoTime();
|
|
||||||
totalReadTimeInNano += (endReadMsTime - startReadNanoTime);
|
|
||||||
|
|
||||||
|
|
||||||
processedDtoCount++;
|
processedDtoCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception | Error e)
|
catch (Exception | Error e)
|
||||||
{
|
{
|
||||||
@@ -371,6 +361,18 @@ public class CompressionTest
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Assert.fail(e.getMessage());
|
Assert.fail(e.getMessage());
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if(uncompressedRepo != null)
|
||||||
|
{
|
||||||
|
uncompressedRepo.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(compressedRepo != null)
|
||||||
|
{
|
||||||
|
compressedRepo.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ 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.sql.DatabaseUpdater;
|
import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
|
||||||
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
||||||
|
import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
@@ -218,6 +219,12 @@ public class DhRepoSqliteTest
|
|||||||
@Test
|
@Test
|
||||||
public void testRepoLeakDetection()
|
public void testRepoLeakDetection()
|
||||||
{
|
{
|
||||||
|
if (!AutoClosableTrackingWrapper.TRACK_WRAPPERS)
|
||||||
|
{
|
||||||
|
System.out.println("Skipping repo leak detection unit test. Leak tracking is disabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TestPrimaryKeyRepo primaryKeyRepo = null;
|
TestPrimaryKeyRepo primaryKeyRepo = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -298,7 +305,8 @@ public class DhRepoSqliteTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Assert.assertNotEquals(0, primaryKeyRepo.openClosables.size()); // TODO fails when built for release due to tracking being disabled
|
// TODO fails when built for release due to tracking being disabled
|
||||||
|
Assert.assertNotEquals(0, primaryKeyRepo.openClosables.size());
|
||||||
primaryKeyRepo.openClosables.clear();
|
primaryKeyRepo.openClosables.clear();
|
||||||
|
|
||||||
|
|
||||||
@@ -318,7 +326,7 @@ public class DhRepoSqliteTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Assert.assertNotEquals(0, primaryKeyRepo.openClosables.size());
|
Assert.assertNotEquals(0, primaryKeyRepo.openClosables.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (SQLException e)
|
catch (SQLException e)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
package tests;
|
package tests;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.util.objects.GLMessage;
|
import com.seibel.distanthorizons.core.util.objects.GLMessages.*;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -28,66 +28,88 @@ import java.util.ArrayList;
|
|||||||
public class GLMessageTest
|
public class GLMessageTest
|
||||||
{
|
{
|
||||||
public static final String MESSAGE_ID = "0x20071";
|
public static final String MESSAGE_ID = "0x20071";
|
||||||
public static final GLMessage.ESource MESSAGE_SOURCE = GLMessage.ESource.API;
|
public static final EGLMessageSource MESSAGE_SOURCE = EGLMessageSource.API;
|
||||||
public static final GLMessage.EType MESSAGE_TYPE = GLMessage.EType.OTHER;
|
public static final EGLMessageType MESSAGE_TYPE = EGLMessageType.OTHER;
|
||||||
public static final GLMessage.ESeverity MESSAGE_SEVERITY = GLMessage.ESeverity.NOTIFICATION;
|
public static final EGLMessageSeverity MESSAGE_SEVERITY = EGLMessageSeverity.NOTIFICATION;
|
||||||
public static final String MESSAGE = "Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations.";
|
public static final String MESSAGE = "Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations.";
|
||||||
|
|
||||||
|
|
||||||
/** This is how debug messages were sent prior to Minecraft 1.20.2 */
|
/** This is how debug messages were sent prior to Minecraft 1.20.2 */
|
||||||
private static final String[] PRE_1_20_2_MESSAGE_ARRAY =
|
private static final String[] OLD_MESSAGE_ARRAY =
|
||||||
{
|
{
|
||||||
"[LWJGL] OpenGL debug message"
|
"[LWJGL] OpenGL debug message"
|
||||||
,"ID", ":", "0x20071"
|
,"ID", ":", MESSAGE_ID
|
||||||
,"Source", ":", "API"
|
,"Source", ":", MESSAGE_SOURCE.name
|
||||||
,"Type", ":", "OTHER"
|
,"Type", ":", MESSAGE_TYPE.name
|
||||||
,"Severity", ":", "NOTIFICATION"
|
,"Severity", ":", MESSAGE_SEVERITY.name
|
||||||
,"Message", ":", "Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations."
|
,"Message", ":", MESSAGE
|
||||||
|
|
||||||
// optional addition to force the builder into noticing the message ended, shouldn't be necessary
|
// optional addition to force the builder into noticing the message ended, shouldn't be necessary
|
||||||
//,"[LWJGL] OpenGL debug message"
|
//,"[LWJGL] OpenGL debug message"
|
||||||
};
|
};
|
||||||
|
|
||||||
/** This is how debug messages were sent after (and including) Minecraft 1.20.2 */
|
/** This is how debug messages were sent after (and including) Minecraft 1.20.2 */
|
||||||
private static final String[] POST_1_20_2_MESSAGE_ARRAY =
|
private static final String[] NEW_MESSAGE_ARRAY =
|
||||||
{
|
{
|
||||||
"[LWJGL] OpenGL debug message"
|
"[LWJGL] OpenGL debug message"
|
||||||
,"ID: 0x20071"
|
,"ID: " + MESSAGE_ID
|
||||||
,"Source: API"
|
,"Source: " + MESSAGE_SOURCE.name
|
||||||
,"Type: OTHER"
|
,"Type: " + MESSAGE_TYPE.name
|
||||||
,"Severity: NOTIFICATION"
|
,"Severity: " + MESSAGE_SEVERITY.name
|
||||||
,"Message: Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations."
|
,"Message: " + MESSAGE
|
||||||
|
|
||||||
// optional addition to force the builder into noticing the message ended, shouldn't be necessary
|
// optional addition to force the builder into noticing the message ended, shouldn't be necessary
|
||||||
//,"[LWJGL] OpenGL debug message"
|
//,"[LWJGL] OpenGL debug message"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public final GLMessageBuilder messageBuilder = new GLMessageBuilder(null, null, null);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=======//
|
||||||
|
// tests //
|
||||||
|
//=======//
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preMc1_20_2()
|
public void preMc1_20_2()
|
||||||
{
|
{
|
||||||
ArrayList<GLMessage> messageList = new ArrayList<>();
|
ArrayList<GLMessage> messageList = new ArrayList<>();
|
||||||
for (String str : PRE_1_20_2_MESSAGE_ARRAY)
|
for (String str : OLD_MESSAGE_ARRAY)
|
||||||
{
|
{
|
||||||
GLMessage message = GLMessage.Builder.DEFAULT_MESSAGE_BUILDER.add(str);
|
GLMessage message = this.messageBuilder.add(str);
|
||||||
if (message != null)
|
if (message != null)
|
||||||
{
|
{
|
||||||
messageList.add(message);
|
messageList.add(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Assert.assertEquals("Incorrect message parse count.", 1, messageList.size());
|
Assert.assertEquals("Incorrect message parse count.", 1, messageList.size());
|
||||||
//testMessage(messageList.get(0));
|
messageMatchesExpected(messageList.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void mc1_20_2()
|
public void mc1_20_2()
|
||||||
{
|
{
|
||||||
// TODO
|
ArrayList<GLMessage> messageList = new ArrayList<>();
|
||||||
|
for (String str : NEW_MESSAGE_ARRAY)
|
||||||
|
{
|
||||||
|
GLMessage message = this.messageBuilder.add(str);
|
||||||
|
if (message != null)
|
||||||
|
{
|
||||||
|
messageList.add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals("Incorrect message parse count.", 1, messageList.size());
|
||||||
|
messageMatchesExpected(messageList.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// helper methods //
|
||||||
|
//================//
|
||||||
|
|
||||||
private static void messageMatchesExpected(GLMessage testMessage)
|
private static void messageMatchesExpected(GLMessage testMessage)
|
||||||
{
|
{
|
||||||
Assert.assertEquals(MESSAGE_ID, testMessage.id);
|
Assert.assertEquals(MESSAGE_ID, testMessage.id);
|
||||||
@@ -97,4 +119,6 @@ public class GLMessageTest
|
|||||||
Assert.assertEquals(MESSAGE, testMessage.message);
|
Assert.assertEquals(MESSAGE, testMessage.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.apache.logging.log4j.core.config.Configurator;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class SquareIntersectTest
|
public class SquareIntersectTest
|
||||||
{
|
{
|
||||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
|
|||||||
Reference in New Issue
Block a user