Compare commits

..

75 Commits

Author SHA1 Message Date
James Seibel ba2681d7b2 remove dev from version number 2025-10-11 20:54:00 -05:00
James Seibel 168570f21f minor lodRenderer refactor 2025-10-11 18:40:32 -05:00
James Seibel b3928d3b1f rename renderFade -> renderFadeTransparent 2025-10-11 11:14:03 -05:00
James Seibel 57aec6092c comment out delayed save cache test to improve build speed 2025-10-10 07:00:32 -05:00
James Seibel 278f4b1642 move more logic into a global RenderState 2025-10-10 06:58:47 -05:00
James Seibel 26d0b5c571 disable world gen progress display by default 2025-10-09 20:12:31 -05:00
James Seibel 3cb8bbeaa7 Fix some tasks being dropped 2025-10-09 20:12:22 -05:00
James Seibel 009cfdce93 Fix VANILLA_CHUNKS API world gen 2025-10-08 17:27:04 -05:00
James Seibel 463565384b Re-add biome blending 2025-10-05 16:23:09 -05:00
James Seibel aed5bb4163 Separate DH pool threads and new executor "Render Loader"
Having separate threads for each task behind the scenes allows for easier performance monitoring vs having a single threadpool that handles everything.
2025-10-04 20:10:10 -05:00
James Seibel bd517e54cf remove duplicate "thread" name in ticker threads 2025-10-04 19:54:19 -05:00
James Seibel b323b7e52d rename uniforms in SSAO shader 2025-10-04 13:45:18 -05:00
James Seibel 32b3eac589 add nullable attributes to world getters 2025-10-04 10:48:34 -05:00
James Seibel 569a5442a9 fix a potential null pointer on world shutdown 2025-10-04 10:26:53 -05:00
James Seibel 25213cae39 Fix noise texture only applying changes on level change 2025-10-04 10:26:34 -05:00
James Seibel 82bb5ef64e fix typo in far falloff 2025-10-03 06:58:04 -05:00
James Seibel a8748471df Handle null pointer on server shutdown 2025-10-02 20:29:42 -05:00
James Seibel 721124b886 Write custom timeout logic for DelayedDataSourceCache
This should make the code a bit more transparent vs using the CacheBuilder, plus hopefully resolve a concurrent writing issue that causes monoliths
2025-10-02 20:29:26 -05:00
James Seibel 85e52301d6 typo in ApiEventInjector 2025-10-02 18:08:47 -05:00
James Seibel 08ede3351d Add DhApiChunkProcessingEvent 2025-10-02 18:03:27 -05:00
James Seibel 9690c898b0 handle null pointer on server shutdown 2025-10-02 07:33:05 -05:00
James Seibel 328336bd29 Allow unbinding Dependencies
TODO replacing may be a better way to handle it
2025-10-02 07:32:58 -05:00
James Seibel 75f0061d97 remove unused ServerPlayerWrapper methods 2025-10-02 07:07:31 -05:00
James Seibel be87c79b1b Handle a few rendering setup edge cases 2025-10-02 07:07:22 -05:00
James Seibel 12a885aa6e Manually close compression streams to try reducing GC reliance 2025-09-29 17:21:01 -05:00
James Seibel d33be490a7 cull LOD rendering on the quad tree 2025-09-29 07:28:03 -05:00
James Seibel cb654f2429 replace IConfigEntry apiValuePresent -> apiIsOverriding 2025-09-28 16:16:31 -05:00
James Seibel 2705cb679e minor config handler refactoring 2025-09-28 16:14:22 -05:00
James Seibel 372fcedc7c add IConfigEntry.apiValuePresent 2025-09-27 20:58:15 -05:00
James Seibel 25e909203d prep for Config UI refactoring 2025-09-27 20:55:37 -05:00
s809 b312582ce4 Add global bandwidth limit setting 2025-09-26 21:45:10 +05:00
James Seibel 73324c71ec Force Mac upload method to DATA
Maybe will help with crashing/memory corruption?
Data is the most basic upload method in GL so Mac should be able to support it a lot better than BUFFER_STORAGE.
2025-09-24 07:23:14 -05:00
James Seibel 0cdb5cf0ec Remove Mac state validation option 2025-09-24 07:13:51 -05:00
James Seibel cbfb1625bc add extra logic to proof-of-concept java swing UI 2025-09-21 21:28:56 -05:00
James Seibel 25e69d03ba Make config lang test return empty string if up to date 2025-09-21 21:28:36 -05:00
James Seibel 9564f02283 maybe fix freebsd OS crashing 2025-09-20 22:40:53 -05:00
James Seibel 9e7378be63 Merge branch 'merge-bedrock' 2025-09-20 16:16:36 -05:00
James Seibel 2495c38dc2 Merge branch 'merge-bedrock' 2025-09-20 15:23:34 -05:00
James Seibel 17fcdb428c finish glproxy comment 2025-09-20 15:14:28 -05:00
James Seibel 944e4f9cb4 Add experimental option to maybe help with Mac crashing 2025-09-20 15:10:54 -05:00
James Seibel 7c0b746220 re-add notnull anotation to ClientPluginChannelApi 2025-09-20 14:21:29 -05:00
Fabian Maurer b4cb390333 Use correct Supplier interface (1.7.10)
It works on modern since
com.google.common.base.Supplier implements
java.util.function.Supplier
but that is not guaranteed
2025-09-19 14:16:36 +02:00
Fabian Maurer 15cda35434 Remove dependency on org.checkerframework (1.7.10) 2025-09-19 14:16:36 +02:00
Fabian Maurer 361d251aa2 Replace isLessSpecificThan with helper function (for 1.7.10) 2025-09-19 14:16:36 +02:00
Fabian Maurer a565e7d906 User older netty functions (1.7.10) 2025-09-19 14:16:36 +02:00
James Seibel 57bbb12b39 Fix "CUSTOM" quality preset when Iris is present 2025-09-16 07:44:18 -05:00
James Seibel df17c1cc1b include world gen chunk/sec rate in progress log 2025-09-14 08:18:37 -05:00
James Seibel a4f7aad306 change world gen progress message to reduce confusion 2025-09-14 08:18:14 -05:00
James Seibel 1b2c1a59f9 Improve world gen task queue speed slightly 2025-09-13 17:59:39 -05:00
James Seibel f0bcf88b35 cache a few repo sql strings 2025-09-13 17:06:33 -05:00
James Seibel 5dbda75c0b add a unit test for SQL update performance testing 2025-09-13 17:01:40 -05:00
James Seibel 5caa945925 remove sea level from level wrapper 2025-09-11 07:07:21 -05:00
James Seibel 6bdfee3636 remove unexplored terrain rendering 2025-09-11 07:06:15 -05:00
James Seibel 1ec536b7df Add unexplored ocean for overworld 2025-09-10 07:46:21 -05:00
James Seibel 9ffda4d43e ColumnRenderSource doesn't need to be a IDataSource 2025-09-07 16:15:26 -05:00
James Seibel 670ec28b6f improve lod load time slightly
done by caching the ClientLevelWrapper used to determine block colors
2025-09-07 16:15:05 -05:00
James Seibel 771814af98 Fix typo in config 2025-09-06 22:10:24 -05:00
James Seibel 90f1d38233 make unexplored fog slightly lighter 2025-09-06 11:59:44 -05:00
James Seibel 54a4f380bd change world gen wireframe height to match unexplored fog 2025-09-06 11:59:44 -05:00
James Seibel bab421c381 add a config for unexplored fog 2025-09-06 11:59:44 -05:00
James Seibel 9c285c17a9 lower unexplored fog slightly 2025-09-06 11:59:44 -05:00
James Seibel 470a9ce8f1 Close #1036 (LODs reloading twice on config change)
Also clean up config event handling
2025-09-06 11:59:44 -05:00
James Seibel d6b79f8b06 fix concurrency issue during unexplored fog setup 2025-09-06 11:59:44 -05:00
James Seibel 71f1dce956 Add unexplored fog 2025-09-06 11:59:44 -05:00
James Seibel 9857eb337f Add remove(obj) and remove(index) to RenderableBoxGroup 2025-09-06 11:59:44 -05:00
James Seibel bced9938f3 Add unexplored fog proof of concept 2025-09-06 11:59:25 -05:00
James Seibel 7f46257e1a add TODO to testRenderer 2025-09-06 09:33:49 -05:00
James Seibel 5f8b566486 improve generic obj render perf logging 2025-09-06 09:33:29 -05:00
James Seibel 9fe2a3fa7b minor dontMergeColoredColumns reformat and comment 2025-09-06 08:53:09 -05:00
James Seibel eb6750bb8d Merge branch 'dontMergeColoredColumns' 2025-09-06 08:38:58 -05:00
James Seibel e86487ab9d Fix LOD-only rendering mode 2025-09-06 08:38:34 -05:00
James Seibel 5423b49f3d Merge !83 (Improve Chunk Update Queue) 2025-09-05 22:23:25 -05:00
James Seibel a2c6f906fa update compression unit test file path 2025-09-05 07:10:49 -05:00
Fabian Maurer d51474a64a Don't merge blocks that get colored by blocks above into columns 2025-09-04 17:56:54 +02:00
James Seibel 5b41c7d48a add (native) ZStd compression as default compressor 2025-09-03 07:39:58 -05:00
115 changed files with 3322 additions and 1733 deletions
@@ -55,15 +55,14 @@ public enum EDhApiDataCompressionMode
*/ */
LZ4(1), LZ4(1),
///** /**
// * Decent speed and good compression. <br><br> * Decent speed and good compression. <br><br>
// * *
// * Read Speed: 9.31 MS / DTO <br> * Read Speed: 9.31 MS / DTO <br>
// * Write Speed: 15.13 MS / DTO <br> * Write Speed: 15.13 MS / DTO <br>
// * Compression ratio: 0.2606 <br> * Compression ratio: 0.2606 <br>
// */ */
////@DisallowSelectingViaConfigGui Z_STD(2),
//Z_STD(2),
/** /**
@@ -0,0 +1,197 @@
/*
* 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.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
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;
/**
* Used to override which blocks may be stored in a given chunk.
* This can be used for X-ray prevention or to replace problematic mod blocks
* that don't fit into the {@link IDhApiBlockStateWrapper} format DH requires
* (IE modded blocks that use NBT data
* to determine their model and/or texture). <br/><br/>
*
* This event is fired for each block or biome change when DH is processing a chunk.
* A change happens when DH finds a different block or biome while walking through a chunk.
* For example with the block sequence:<br/>
* <code> stone -> stone -> air -> stone </code> <br/>
* This event would be fired for the first, third, and forth blocks in the sequence
* (IE the first stone, first air, and last stone respectively). <br/> <br/>
*
* The order DH will process blocks is undefined so a specific ordering shouldn't be relied upon for your logic to function. <br/> <br/>
*
* <b>Threading note:</b> this event may be called concurrently across multiple threads. <br/>
* <b>Performance note:</b> this event will be called very frequently, avoid expensive lookups or other slow operations if possible. <br/>
*
* @see DhApiLevelLoadEvent
* @see IDhApiWrapperFactory
*
* @author James Seibel
* @version 2025-09-29
* @since API 4.1.0
*/
public abstract class DhApiChunkProcessingEvent implements IDhApiEvent<DhApiChunkProcessingEvent.EventParam>
{
public abstract void blockOrBiomeChangedDuringChunkProcessing(DhApiEventParam<EventParam> event);
//=========================//
// internal DH API methods //
//=========================//
@Override
public final void fireEvent(DhApiEventParam<EventParam> event) { this.blockOrBiomeChangedDuringChunkProcessing(event); }
//==================//
// parameter object //
//==================//
public static class EventParam implements IDhApiEventParam
{
/** The saved level. */
public final IDhApiLevelWrapper levelWrapper;
/** the processed chunk's X pos in chunk coordinates */
public final int chunkX;
/** the processed chunk's Z pos in chunk coordinates */
public final int chunkZ;
public int relativeBlockPosX;
public int blockPosY;
public int relativeBlockPosZ;
public IDhApiBlockStateWrapper currentBlock;
public IDhApiBiomeWrapper currentBiome;
private IDhApiBlockStateWrapper newBlock;
private IDhApiBiomeWrapper newBiome;
//=============//
// constructor //
//=============//
public EventParam(IDhApiLevelWrapper newLevelWrapper, int chunkX, int chunkZ)
{
this.levelWrapper = newLevelWrapper;
this.chunkX = chunkX;
this.chunkZ = chunkZ;
}
/**
* Internal method use by Distant Horizons
* to set up this event.
*/
public void updateForPosition(
int relativeBlockPosX, int blockPosY, int relativeBlockPosZ,
IDhApiBlockStateWrapper currentBlock,
IDhApiBiomeWrapper currentBiome)
{
this.relativeBlockPosX = relativeBlockPosX;
this.blockPosY = blockPosY;
this.relativeBlockPosZ = relativeBlockPosZ;
this.newBlock = null;
this.newBiome = null;
this.currentBlock = currentBlock;
this.currentBiome = currentBiome;
}
//=================//
// getters/setters //
//=================//
/**
* Sets the {@link IDhApiBlockStateWrapper} that should be used at this event's current position in the chunk.
* If you don't want to modify the block at this event's current position,
* either don't call this method or pass in null. <br>
* Passing in null will remove the override, meaning the original block will be used. <br><br>
*
* A {@link IDhApiWrapperFactory} should be used to get the {@link IDhApiBlockStateWrapper} that's returned.
* Attempting to create your own {@link IDhApiBlockStateWrapper} will cause a {@link ClassCastException}. <br/> <br/>
*
* If multiple API users are listening to this event the override may already have been set.
* With that in mind it is recommended to check if an override has already been set via
* {@link EventParam#getBlockOverride()} to handle that occurrence. <br>
* Note that the order of API events firing is undefined so a specific order shouldn't be relied upon. <br><br>
*
* @see IDhApiWrapperFactory
*/
public void setBlockOverride(IDhApiBlockStateWrapper block) { this.newBlock = block; }
/**
* Returns the currently overriding block for this position.
* This will be null if no other API event has set the override.
*/
public IDhApiBlockStateWrapper getBlockOverride() { return this.newBlock; }
/**
* Sets the {@link IDhApiBiomeWrapper} that should be used at this event's current position in the chunk.
* If you don't want to modify the biome at this event's current position,
* either don't call this method or pass in null. <br>
* Passing in null will remove the override, meaning the original biome will be used. <br><br>
*
* A {@link IDhApiWrapperFactory} should be used to get the {@link IDhApiBiomeWrapper} that's returned.
* Attempting to create your own {@link IDhApiBiomeWrapper} will cause a {@link ClassCastException}. <br/> <br/>
*
* If multiple API users are listening to this event the override may already have been set.
* With that in mind it is recommended to check if an override has already been set via
* {@link EventParam#getBiomeOverride()} ()} to handle that occurrence. <br>
* Note that the order of API events firing is undefined so a specific order shouldn't be relied upon. <br><br>
*
* @see IDhApiWrapperFactory
*/
public void setBiomeOverride(IDhApiBiomeWrapper biome) { this.newBiome = biome; }
/**
* Returns the currently overriding biome for this position.
* This will be null if no other API event has set the override.
*/
public IDhApiBiomeWrapper getBiomeOverride() { return this.newBiome; }
/**
* Returns the same instance of this event.
* Copying this event isn't recommended due to
* how often it would be called per chunk, creating
* unnecessary garbage collector pressure.
*/
@Override
public EventParam copy() { return this; }
@Override
public boolean getCopyBeforeFire() { return false; }
}
}
@@ -9,5 +9,23 @@ import com.seibel.distanthorizons.api.interfaces.util.IDhApiCopyable;
*/ */
public interface IDhApiEventParam extends IDhApiCopyable public interface IDhApiEventParam extends IDhApiCopyable
{ {
/**
* Internal DH use. <br> <br>
*
* Most API events will clone their parameters
* before firing to prevent API implementors
* from modifying the properties causing
* any subsequent listeners to see the wrong data. <br><br>
*
* However, this can be overridden for API events that shouldn't
* be cloned before firing.
* Generally that would be done for performance reasons
* where an event may fire hundreds or thousands of times
* in quick succession or where the event parameter is needed
* internally by DH after firing.
*
* @since API 4.1.0
*/
default boolean getCopyBeforeFire() { return true; }
} }
@@ -143,19 +143,23 @@ public class ApiEventInjector extends DependencyInjector<IDhApiEvent> implements
// attempt to clone the event input if possible // attempt to clone the event input if possible
// this is done to reduce the likely hood that one event listener // this is done to reduce the likelihood that one event listener
// will make change the event parameter for other listeners // will change the event parameter for other listeners
T input = eventInput; T input = eventInput;
if (eventInput instanceof IDhApiEventParam) if (eventInput instanceof IDhApiEventParam)
{ {
try IDhApiEventParam dhApiEventParam = (IDhApiEventParam) eventInput;
if (dhApiEventParam.getCopyBeforeFire())
{ {
//noinspection unchecked try
input = (T) ((IDhApiEventParam) eventInput).copy(); {
} //noinspection unchecked
catch (Exception e) input = (T) dhApiEventParam.copy();
{ }
LOGGER.error("Unable to clone event parameter ["+eventInput.getClass().getSimpleName()+"], error: ["+e.getMessage()+"].", e); catch (Exception e)
{
LOGGER.error("Unable to clone event parameter [" + eventInput.getClass().getSimpleName() + "], error: [" + e.getMessage() + "].", e);
}
} }
} }
@@ -43,11 +43,9 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
protected final boolean allowDuplicateBindings; protected final boolean allowDuplicateBindings;
public DependencyInjector(Class<BindableType> newBindableInterface) //==============//
{ // constructors //
this.bindableInterface = newBindableInterface; //==============//
this.allowDuplicateBindings = false;
}
public DependencyInjector(Class<BindableType> newBindableInterface, boolean newAllowDuplicateBindings) public DependencyInjector(Class<BindableType> newBindableInterface, boolean newAllowDuplicateBindings)
{ {
@@ -57,12 +55,16 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
//=========//
// binding //
//=========//
@Override @Override
public void bind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException public void bind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException
{ {
// duplicate check if requested // duplicate check if requested
if (this.dependencies.containsKey(dependencyInterface) && !this.allowDuplicateBindings) if (this.dependencies.containsKey(dependencyInterface)
&& !this.allowDuplicateBindings)
{ {
throw new IllegalStateException("The dependency [" + dependencyInterface.getSimpleName() + "] has already been bound."); throw new IllegalStateException("The dependency [" + dependencyInterface.getSimpleName() + "] has already been bound.");
} }
@@ -130,6 +132,54 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
public boolean checkIfClassExtends(Class<?> classToTest, Class<?> extensionToLookFor) { return extensionToLookFor.isAssignableFrom(classToTest); } public boolean checkIfClassExtends(Class<?> classToTest, Class<?> extensionToLookFor) { return extensionToLookFor.isAssignableFrom(classToTest); }
//===========//
// unbinding //
//===========//
// TODO having a bindOrReplace method would probably be better since it wouldn't have the possiblity of having nothing bound
public void unbind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException
{
// check if this object is bound
if (!this.dependencies.containsKey(dependencyInterface))
{
return;
}
// make sure the given dependency implements the necessary interfaces
boolean implementsInterface = this.checkIfClassImplements(dependencyImplementation.getClass(), dependencyInterface)
|| this.checkIfClassExtends(dependencyImplementation.getClass(), dependencyInterface);
boolean implementsBindable = this.checkIfClassImplements(dependencyImplementation.getClass(), this.bindableInterface);
// display any errors
if (!implementsInterface)
{
throw new IllegalArgumentException("The dependency [" + dependencyImplementation.getClass().getSimpleName() + "] doesn't implement or extend: [" + dependencyInterface.getSimpleName() + "].");
}
if (!implementsBindable)
{
throw new IllegalArgumentException("The dependency [" + dependencyImplementation.getClass().getSimpleName() + "] doesn't implement the interface: [" + IBindable.class.getSimpleName() + "].");
}
// make sure the hashSet has an array to hold the dependency
if (!this.dependencies.containsKey(dependencyInterface))
{
this.dependencies.put(dependencyInterface, new ArrayList<BindableType>());
}
// remove the dependency if present
this.dependencies.get(dependencyInterface).remove(dependencyImplementation);
this.dependencies.remove(dependencyInterface);
}
//=========//
// getters //
//=========//
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public <T extends BindableType> T get(Class<T> interfaceClass) throws ClassCastException public <T extends BindableType> T get(Class<T> interfaceClass) throws ClassCastException
@@ -31,14 +31,14 @@ 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 = 12; public static final int PROTOCOL_VERSION = 11;
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.5-b-dev"; public static final String VERSION = "2.3.5-b";
/** 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");
@@ -36,12 +36,15 @@ public interface IConfigEntry<T>
void setApiValue(T newApiValue); void setApiValue(T newApiValue);
T getApiValue(); T getApiValue();
/** @return true if this config is able to be overridden by the API and an API user has set it */
boolean apiIsOverriding();
/** Returns true if this config can be set via the API. */ /** Returns true if this config can be set via the API. */
boolean getAllowApiOverride(); boolean getAllowApiOverride();
void set(T newValue); void set(T newValue);
T get(); T get();
/** gets the option ignoring what the API has overridden */
T getTrueValue(); T getTrueValue();
/** Sets the value without saving */ /** Sets the value without saving */
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core; package com.seibel.distanthorizons.core;
import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory; import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater; import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
@@ -54,8 +55,9 @@ public class Initializer
{ {
// if any library isn't present in the jar its class // if any library isn't present in the jar its class
// will throw an error (not an exception) // will throw an error (not an exception)
Class<?> fastCompressor = LZ4FrameOutputStream.class; Class<?> lz4Compressor = LZ4FrameOutputStream.class;
Class<?> smallCompressor = XZOutputStream.class; Class<?> zstdCompressor = ZstdOutputStream.class;
Class<?> lzmaCompressor = XZOutputStream.class;
//Class<?> networking = ByteBuf.class; //Class<?> networking = ByteBuf.class;
Class<?> config = com.electronwill.nightconfig.core.Config.class; Class<?> config = com.electronwill.nightconfig.core.Config.class;
Class<?> oldFastUtil = it.unimi.dsi.fastutil.longs.LongArrayList.class; // available in 8.2.1 Class<?> oldFastUtil = it.unimi.dsi.fastutil.longs.LongArrayList.class; // available in 8.2.1
@@ -63,30 +65,6 @@ public class Initializer
Class<?> sqliteJava = org.sqlite.SQLiteConnection.class; Class<?> sqliteJava = org.sqlite.SQLiteConnection.class;
Class<?> sqliteNative = org.sqlite.core.NativeDB.class; Class<?> sqliteNative = org.sqlite.core.NativeDB.class;
//// maybe these lines are needed to shade SQLite, James isn't sure.
//// Although they never seemed to fail, which is a bit odd.
//try
//{
// // needed by Forge to load the Java database connection
// Class.forName("org.sqlite.JDBC");
// LOGGER.info("loaded normal SQLITE");
//}
//catch (ClassNotFoundException e)
//{
// LOGGER.warn("normal: " + e.getMessage(), e);
//}
//
//try
//{
// // needed by Forge to load the Java database connection
// Class.forName("DistantHorizons.libraries.sqlite.JDBC");
// LOGGER.info("loaded shaded SQLITE");
//}
//catch (ClassNotFoundException e)
//{
// LOGGER.warn("shaded: " + e.getMessage(), e);
//}
boolean sqliteLoaded = SQLiteJDBCLoader.initialize(); boolean sqliteLoaded = SQLiteJDBCLoader.initialize();
if (!sqliteLoaded) if (!sqliteLoaded)
{ {
@@ -503,7 +503,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
// this will throw a cast exception if the chunk object array isn't correct // this will throw a cast exception if the chunk object array isn't correct
IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(chunkObjectArray); IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(chunkObjectArray);
SharedApi.INSTANCE.applyChunkUpdate(chunk, dhLevel.getLevelWrapper(), true); SharedApi.INSTANCE.applyChunkUpdate(chunk, dhLevel.getLevelWrapper(), true, true);
return DhApiResult.createSuccess(); return DhApiResult.createSuccess();
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.api.internal.rendering.RenderState;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure; import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry; import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
@@ -399,21 +400,27 @@ public class ClientApi
//===========// //===========//
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */ /** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
public void renderLods(IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks) public void renderLods() { this.renderLodLayer(false); }
{ this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, false); }
/** /**
* 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 renderDeferredLodsForShaders(IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks) public void renderDeferredLodsForShaders() { this.renderLodLayer(true); }
{ this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, true); }
private void renderLodLayer(boolean renderingDeferredLayer)
private void renderLodLayer(
IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks,
boolean renderingDeferredLayer)
{ {
// A global render state variable is used since MC has split up their
// render prep and actual rendering into different threads/methods
// this is annoying since it's possible to start a render with only
// partially complete info, but there isn't a better option at the moment
IClientLevelWrapper levelWrapper = RENDER_STATE.clientLevelWrapper;
Mat4f mcModelViewMatrix = RENDER_STATE.mcModelViewMatrix;
Mat4f mcProjectionMatrix = RENDER_STATE.mcProjectionMatrix;
float partialTicks = RENDER_STATE.frameTime;
// logging // // logging //
this.sendQueuedChatMessages(); this.sendQueuedChatMessages();
@@ -580,8 +587,11 @@ public class ClientApi
} }
} }
/** should be called after DH and MC finish rendering so we can smooth the transition between the two */ /**
public void renderFadeOpaque(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level) * The first fade pass.
* Called after MC finishes rendering the opaque passes.
*/
public void renderFadeOpaque()
{ {
// 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
@@ -590,20 +600,32 @@ public class ClientApi
// don't fade when Iris shaders are active, otherwise the rendering can get weird // don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering()) && !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{ {
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level); FadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper);
} }
} }
/** 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) * The second fade pass.
* Called after MC finishes rendering both opaque
* and transparent passes.
*/
public void renderFadeTransparent()
{ {
// 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())
{ {
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level); boolean renderFade =
(
// only fade when requested
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() != EDhApiMcRenderingFadeMode.NONE
// or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get()
)
// don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
if (renderFade)
{
FadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper);
}
} }
} }
@@ -776,45 +798,4 @@ public class ClientApi
//================//
// 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);
}
}
}
} }
@@ -11,7 +11,7 @@ import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
@@ -65,7 +65,7 @@ public class ClientPluginChannelApi
//================// //================//
/** fired when this client connects to a server with DH support */ /** fired when this client connects to a server with DH support */
public void onJoinServer(@NonNull NetworkSession networkSession) public void onJoinServer(@NotNull NetworkSession networkSession)
{ {
Objects.requireNonNull(networkSession); Objects.requireNonNull(networkSession);
this.networkSession = networkSession; this.networkSession = networkSession;
@@ -75,12 +75,7 @@ public class ClientPluginChannelApi
private void onLevelInitMessage(LevelInitMessage msg) private void onLevelInitMessage(LevelInitMessage msg)
{ {
if (!msg.serverKey.matches(LevelInitMessage.SERVER_KEY_REGEX)) if (!msg.levelKey.matches(LevelInitMessage.VALIDATION_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.");
} }
@@ -110,12 +105,10 @@ public class ClientPluginChannelApi
this.levelUnloadHandler.accept(clientLevel); this.levelUnloadHandler.accept(clientLevel);
} }
if (existingKeyedClientLevel == null if (existingKeyedClientLevel == null || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
|| !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.serverKey, msg.levelKey); IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.levelKey);
this.levelLoadHandler.accept(keyedLevel); this.levelLoadHandler.accept(keyedLevel);
} }
}); });
@@ -136,8 +136,8 @@ public class ServerApi
// chunk modified events // // chunk modified events //
//=======================// //=======================//
public void serverChunkLoadEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, false); } public void serverChunkLoadEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, false, false); }
public void serverChunkSaveEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, true); } public void serverChunkSaveEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, true, false); }
@@ -22,6 +22,8 @@ package com.seibel.distanthorizons.core.api.internal;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldLoadEvent; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldUnloadEvent; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldUnloadEvent;
import com.seibel.distanthorizons.core.Initializer; import com.seibel.distanthorizons.core.Initializer;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.generation.DhLightingEngine;
@@ -65,20 +67,20 @@ public class SharedApi
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class); private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
private static final UpdateChunkPosManager UPDATE_POS_MANAGER = new UpdateChunkPosManager(); public static final ChunkUpdateQueueManager CHUNK_UPDATE_QUEUE_MANAGER = new ChunkUpdateQueueManager();
/** /**
* how many chunks can be queued for updating per thread + player (in multiplayer), * how many chunks can be queued for updating per thread + player (in multiplayer),
* used to prevent updates from infinitely pilling up if the user flies around extremely fast * used to prevent updates from infinitely pilling up if the user flies around extremely fast
*/ */
private static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER = 1_000; public static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER = 1_000;
/** how many milliseconds must pass before an overloaded message can be sent in chat or the log */ /** how many milliseconds must pass before an overloaded message can be sent in chat or the log */
private static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 30_000; public static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 30_000;
@Nullable
private static AbstractDhWorld currentWorld; private static AbstractDhWorld currentWorld;
private static int lastWorldGenTickDelta = 0; private static int lastWorldGenTickDelta = 0;
private static long lastOverloadedLogMessageMsTime = 0;
@@ -127,7 +129,7 @@ public class SharedApi
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed // shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
AbstractDhRepo.closeAllConnections(); AbstractDhRepo.closeAllConnections();
// needs to be closed on world shutdown to clear out un-processed chunks // needs to be closed on world shutdown to clear out un-processed chunks
UPDATE_POS_MANAGER.clear(); CHUNK_UPDATE_QUEUE_MANAGER.clear();
// recommend that the garbage collector cleans up any objects from the old world and thread pools // recommend that the garbage collector cleans up any objects from the old world and thread pools
System.gc(); System.gc();
@@ -149,6 +151,7 @@ public class SharedApi
} }
} }
@Nullable
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; } public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */ /** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */
public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld instanceof DhClientServerWorld) ? (DhClientServerWorld) currentWorld : null; } public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld instanceof DhClientServerWorld) ? (DhClientServerWorld) currentWorld : null; }
@@ -168,10 +171,10 @@ public class SharedApi
* This is important since asking MC for a chunk is slow and may block the render thread. * This is important since asking MC for a chunk is slow and may block the render thread.
*/ */
public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ) public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ)
{ return UPDATE_POS_MANAGER.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); } { return CHUNK_UPDATE_QUEUE_MANAGER.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); }
public static boolean isChunkAtChunkPosAlreadyUpdating(int chunkPosX, int chunkPosZ) public static boolean isChunkAtChunkPosAlreadyUpdating(int chunkPosX, int chunkPosZ)
{ return UPDATE_POS_MANAGER.contains(new DhChunkPos(chunkPosX, chunkPosZ)); } { return CHUNK_UPDATE_QUEUE_MANAGER.contains(new DhChunkPos(chunkPosX, chunkPosZ)); }
/** /**
* This is often fired when unloading a level. * This is often fired when unloading a level.
@@ -179,17 +182,18 @@ public class SharedApi
* rapidly changing dimensions. * rapidly changing dimensions.
* (IE prevent DH from infinitely allocating memory * (IE prevent DH from infinitely allocating memory
*/ */
public void clearQueuedChunkUpdates() { UPDATE_POS_MANAGER.clear(); } public void clearQueuedChunkUpdates() { CHUNK_UPDATE_QUEUE_MANAGER.clear(); }
public int getQueuedChunkUpdateCount() { return UPDATE_POS_MANAGER.closestQueue.size(); } public int getQueuedChunkUpdateCount() { return CHUNK_UPDATE_QUEUE_MANAGER.getQueuedCount(); }
/** handles both block place and break events */ /** handles both block place and break events */
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true); } public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, false); }
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, false); } public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, true); }
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean updateNeighborChunks) //public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks) { this.applyChunkUpdate(chunkWrapper, level, canGetNeighboringChunks, false); }
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded)
{ {
//========================// //========================//
// world and level checks // // world and level checks //
@@ -245,7 +249,7 @@ public class SharedApi
} }
// shoudln't normally happen, but just in case // shoudln't normally happen, but just in case
if (UPDATE_POS_MANAGER.contains(chunkWrapper.getChunkPos())) if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
{ {
// TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded // TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded
return; return;
@@ -257,23 +261,53 @@ public class SharedApi
// update the necessary chunk(s) // // update the necessary chunk(s) //
//===============================// //===============================//
if (!updateNeighborChunks) if (!canGetNeighboringChunks)
{ {
// only update the center chunk // only update the center chunk
queueChunkUpdate(chunkWrapper, null, dhLevel, false);
queueChunkUpdate(chunkWrapper, null, dhLevel); return;
}
ArrayList<IChunkWrapper> neighboringChunkList = getNeighborChunkListForChunk(chunkWrapper, dhLevel);
if (newlyLoaded)
{
// this means this chunkWrapper is a newly loaded chunk
// which may be missing some neighboring chunk data
// because it is bordering the render distance
// thus, only the chunks neighboring this chunkWrapper will get updated
// because those are more likely to have their full neighboring chunk data
//TODO this does not prevent those neighboring chunks from updating
// this newly loaded chunk that were just skipped
// leading to occasional lighting issues
for (IChunkWrapper neighboringChunk : neighboringChunkList)
{
if (neighboringChunk == chunkWrapper)
{
continue;
}
this.applyChunkUpdate(neighboringChunk, level, true, false);
}
} }
else else
{ {
// if not all neighboring chunk data is available, do not try to update
if (neighboringChunkList.size() < 9)
{
return;
}
// update the center with any existing neighbour chunks. // update the center with any existing neighbour chunks.
// this is done so lighting changes are propagated correctly // this is done so lighting changes are propagated correctly
queueChunkUpdate(chunkWrapper, getNeighbourChunkListForChunk(chunkWrapper,dhLevel), dhLevel); queueChunkUpdate(chunkWrapper, neighboringChunkList, dhLevel, true);
} }
} }
private static ArrayList<IChunkWrapper> getNeighbourChunkListForChunk(IChunkWrapper chunkWrapper, IDhLevel dhLevel) private static ArrayList<IChunkWrapper> getNeighborChunkListForChunk(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
{ {
// get the neighboring chunk list // get the neighboring chunk list
ArrayList<IChunkWrapper> neighbourChunkList = new ArrayList<>(9); ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
for (int xOffset = -1; xOffset <= 1; xOffset++) for (int xOffset = -1; xOffset <= 1; xOffset++)
{ {
for (int zOffset = -1; zOffset <= 1; zOffset++) for (int zOffset = -1; zOffset <= 1; zOffset++)
@@ -281,80 +315,36 @@ public class SharedApi
if (xOffset == 0 && zOffset == 0) if (xOffset == 0 && zOffset == 0)
{ {
// center chunk // center chunk
neighbourChunkList.add(chunkWrapper); neighborChunkList.add(chunkWrapper);
} }
else else
{ {
// neighboring chunk // neighboring chunk
DhChunkPos neighbourPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset); DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
IChunkWrapper neighbourChunk = dhLevel.getLevelWrapper().tryGetChunk(neighbourPos); IChunkWrapper neighborChunk = dhLevel.getLevelWrapper().tryGetChunk(neighborPos);
if (neighbourChunk != null) if (neighborChunk != null)
{ {
neighbourChunkList.add(neighbourChunk); neighborChunkList.add(neighborChunk);
} }
} }
} }
} }
return neighbourChunkList; return neighborChunkList;
} }
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel) private static void queueChunkUpdate(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighboringChunks)
{ queueChunkUpdate(chunkWrapper, neighbourChunkList, dhLevel,false); }
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel, boolean lightUpdateOnly)
{ {
int maxUpdateSizeMultiplier;
if (MC_CLIENT != null && MC_CLIENT.playerExists())
{
// Local worlds & multiplayer
UPDATE_POS_MANAGER.setCenter(MC_CLIENT.getPlayerChunkPos());
maxUpdateSizeMultiplier = MC_CLIENT.clientConnectedToDedicatedServer() ? 1 : MC_SHARED.getPlayerCount();
}
else
{
// Dedicated servers
// Also includes spawn chunks since they're likely to be intentionally utilized with updates
maxUpdateSizeMultiplier = 1 + MC_SHARED.getPlayerCount();
}
UPDATE_POS_MANAGER.maxSize = MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER
* Config.Common.MultiThreading.numberOfThreads.get()
* maxUpdateSizeMultiplier;
UpdateChunkData updateData = new UpdateChunkData(chunkWrapper, neighbourChunkList, dhLevel, lightUpdateOnly);
if(lightUpdateOnly)
{
UPDATE_POS_MANAGER.removeItem(chunkWrapper.getChunkPos());
}
int remainingCapacity = UPDATE_POS_MANAGER.addItem(chunkWrapper.getChunkPos(), updateData);
if (remainingCapacity <= 0)
{
// limit how often an overloaded message can be sent
long msBetweenLastLog = System.currentTimeMillis() - lastOverloadedLogMessageMsTime;
if (msBetweenLastLog >= MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE)
{
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
String message = "\u00A76" + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + "\u00A7r" + // return if the chunk is already queued
"\nThis may result in holes in your LODs. " + if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
"\nFix: move through the world slower, decrease your vanilla render distance, slow down your world pre-generator (IE Chunky), or increase the Distant Horizons' CPU thread counts. " + {
"\nMax queue count ["+UPDATE_POS_MANAGER.maxSize+"] (["+ MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER +"] per thread+players)."; return;
boolean showWarningInChat = Config.Common.Logging.Warning.showUpdateQueueOverloadedChatWarning.get();
if (showWarningInChat)
{
ClientApi.INSTANCE.showChatMessageNextFrame(message);
}
// Don't log warnings in singleplayer or in hosted LAN since it usually isn't a problem (and if it is it's easy to notice).
// Servers should always log since being overloaded is harder to notice.
EWorldEnvironment environment = SharedApi.getEnvironment();
if (showWarningInChat || environment == EWorldEnvironment.SERVER_ONLY)
{
LOGGER.warn(message);
}
}
} }
// add chunk update data to preUpdate queue
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, neighborChunkList, dhLevel, canGetNeighboringChunks);
CHUNK_UPDATE_QUEUE_MANAGER.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData);
// queue updates up to the number of CPU cores allocated for the job // queue updates up to the number of CPU cores allocated for the job
@@ -365,7 +355,7 @@ public class SharedApi
{ {
try try
{ {
executor.execute(SharedApi::processQueuedChunkUpdate); executor.execute(SharedApi::processQueue);
} }
catch (RejectedExecutionException ignore) catch (RejectedExecutionException ignore)
{ {
@@ -373,94 +363,158 @@ public class SharedApi
} }
} }
} }
private static void processQueue()
{
// update the center & max size of the queue manager
int maxUpdateSizeMultiplier;
if (MC_CLIENT != null && MC_CLIENT.playerExists())
{
// Local worlds & multiplayer
CHUNK_UPDATE_QUEUE_MANAGER.setCenter(MC_CLIENT.getPlayerChunkPos());
maxUpdateSizeMultiplier = MC_CLIENT.clientConnectedToDedicatedServer() ? 1 : MC_SHARED.getPlayerCount();
}
else
{
// Dedicated servers
// Also includes spawn chunks since they're likely to be intentionally utilized with updates
maxUpdateSizeMultiplier = 1 + MC_SHARED.getPlayerCount();
}
CHUNK_UPDATE_QUEUE_MANAGER.maxSize = MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER
* Config.Common.MultiThreading.numberOfThreads.get()
* maxUpdateSizeMultiplier;
//===============================//
// update the necessary chunk(s) //
//===============================//
// process preUpdate queue
processQueuedChunkPreUpdate();
// process update queue
processQueuedChunkUpdate();
// queue the next position if there are still positions to process
AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null && !CHUNK_UPDATE_QUEUE_MANAGER.isEmpty())
{
try
{
executor.execute(SharedApi::processQueue);
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
}
}
}
private static void processQueuedChunkPreUpdate()
{
ChunkUpdateData preUpdateData = CHUNK_UPDATE_QUEUE_MANAGER.preUpdateQueue.popClosest();
if (preUpdateData == null)
{
return;
}
IDhLevel dhLevel = preUpdateData.dhLevel;
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
boolean canGetNeighboringChunks = preUpdateData.canGetNeighboringChunks;
ArrayList<IChunkWrapper> neighborChunkList = preUpdateData.neighborChunkList;
try
{
// check if this chunk has been converted into an LOD already
boolean checkChunkHash = !Config.Common.LodBuilding.disableUnchangedChunkCheck.get();
if (checkChunkHash)
{
int oldChunkHash = dhLevel.getChunkHash(chunkWrapper.getChunkPos()); // shouldn't happen on the render thread since it may take a few moments to run
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
boolean hasNewChunkHash = (oldChunkHash != newChunkHash);
if (!hasNewChunkHash)
{
// do not update the chunk if the hash is the same
return;
}
// if this chunk will update and can get neighbors
// then queue neighboring chunks to update as well
// neighboring chunk will get added directly to the update queue
// so they won't queue further chunk updates
if (neighborChunkList != null
&& !neighborChunkList.isEmpty())
{
for (IChunkWrapper adjacentChunk : neighborChunkList)
{
// pulling a new chunkWrapper is necessary to prevent concurrent modification on the existing chunkWrappers
IChunkWrapper newCenterChunk = dhLevel.getLevelWrapper().tryGetChunk(adjacentChunk.getChunkPos());
if (newCenterChunk != null)
{
ChunkUpdateData newUpdateData;
if (canGetNeighboringChunks)
{
newUpdateData = new ChunkUpdateData(newCenterChunk, getNeighborChunkListForChunk(newCenterChunk, dhLevel), dhLevel, true);
}
else
{
newUpdateData = new ChunkUpdateData(newCenterChunk, null, dhLevel, false);
}
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(newCenterChunk.getChunkPos(), newUpdateData);
}
}
}
}
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData);
}
catch (Exception e)
{
LOGGER.error("Unexpected error when pre-updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
}
private static void processQueuedChunkUpdate() private static void processQueuedChunkUpdate()
{ {
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount()); //LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
UpdateChunkData updateData = UPDATE_POS_MANAGER.popClosest(); ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest();
if (updateData == null) if (updateData == null)
{ {
return; return;
} }
IChunkWrapper chunkWrapper = updateData.chunkWrapper; IChunkWrapper chunkWrapper = updateData.chunkWrapper;
@Nullable ArrayList<IChunkWrapper> neighbourChunkList = updateData.neighbourChunkList;
IDhLevel dhLevel = updateData.dhLevel; IDhLevel dhLevel = updateData.dhLevel;
// having a list of the nearby chunks is needed for lighting and beacon generation
@Nullable ArrayList<IChunkWrapper> nearbyChunkList = updateData.neighborChunkList;
// a non-null list is needed for the lighting engine
if (nearbyChunkList == null)
{
nearbyChunkList = new ArrayList<IChunkWrapper>();
nearbyChunkList.add(chunkWrapper);
}
try try
{ {
boolean checkChunkHash = !Config.Common.LodBuilding.disableUnchangedChunkCheck.get();
// check if this chunk has been converted into an LOD already
int oldChunkHash = dhLevel.getChunkHash(chunkWrapper.getChunkPos()); // shouldn't happen on the render thread since it may take a few moments to run
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
if (checkChunkHash)
{
if (oldChunkHash == newChunkHash && !updateData.lightUpdateOnly)
{
// if the chunk hashes are the same then we don't need to bother with lighting the chunk
// or creating/updating the LODs
return;
}
}
// having a list of the nearby chunks is needed for lighting and beacon generation
ArrayList<IChunkWrapper> nearbyChunkList;
if (neighbourChunkList != null)
{
nearbyChunkList = neighbourChunkList;
}
else
{
nearbyChunkList = new ArrayList<>(1);
nearbyChunkList.add(chunkWrapper);
}
// if this chunk will update its lighting
// then queue adjacent chunks to update theirs as well
// adjacent chunk will have 'lightUpdateOnly' true
// so they won't schedule further chunk updates
if (!updateData.lightUpdateOnly)
{
for (IChunkWrapper adjacentChunk : nearbyChunkList)
{
// pulling a new chunkWrapper is necessary to prevent concurrent modification on the existing chunkWrappers
IChunkWrapper newCenterChunk = dhLevel.getLevelWrapper().tryGetChunk(adjacentChunk.getChunkPos());
if (newCenterChunk != null)
{
queueChunkUpdate(newCenterChunk, getNeighbourChunkListForChunk(newCenterChunk, dhLevel), dhLevel, true);
}
}
}
// sky lighting is populated later at the data source level // sky lighting is populated later at the data source level
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList); dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash); dhLevel.updateChunkAsync(chunkWrapper, newChunkHash);
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e); LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
} }
finally
{
// queue the next position if there are still positions to process
AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null && !UPDATE_POS_MANAGER.updateDataByChunkPos.isEmpty())
{
try
{
executor.execute(SharedApi::processQueuedChunkUpdate);
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
}
}
}
} }
@@ -471,161 +525,13 @@ public class SharedApi
public String getDebugMenuString() public String getDebugMenuString()
{ {
String updatingCountStr = F3Screen.NUMBER_FORMAT.format(UPDATE_POS_MANAGER.closestQueue.size()); String preUpdatingCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.preUpdateQueue.getQueuedCount());
String maxUpdateCountStr = F3Screen.NUMBER_FORMAT.format(UPDATE_POS_MANAGER.maxSize); String updatingCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.getQueuedCount());
return "Queued chunk updates: "+updatingCountStr+" / "+maxUpdateCountStr; String queuedCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.getQueuedCount());
}
//================//
// helper classes //
//================//
/** contains the objects needed to update a chunk */
private static class UpdateChunkData
{
public IChunkWrapper chunkWrapper;
@Nullable
public ArrayList<IChunkWrapper> neighbourChunkList;
public IDhLevel dhLevel;
/** adjacent chunks will only update their light */
public boolean lightUpdateOnly;
public UpdateChunkData(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel, boolean lightUpdateOnly) String maxUpdateCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.maxSize);
{
this.chunkWrapper = chunkWrapper;
this.neighbourChunkList = neighbourChunkList;
this.dhLevel = dhLevel;
this.lightUpdateOnly = lightUpdateOnly;
}
}
/** keeps track of which chunks need to be updated */
private static class UpdateChunkPosManager
{
private final PriorityBlockingQueue<DhChunkPos> closestQueue;
private final PriorityBlockingQueue<DhChunkPos> furthestQueue;
private final ConcurrentHashMap<DhChunkPos, UpdateChunkData> updateDataByChunkPos;
private DhChunkPos center;
private int maxSize = 500;
//=============//
// constructor //
//=============//
public UpdateChunkPosManager()
{
this.closestQueue = new PriorityBlockingQueue<>(500, Comparator.comparingDouble(pos -> pos.squaredDistance(this.center)));
this.furthestQueue = new PriorityBlockingQueue<>(500, Comparator.comparingDouble(pos -> ((DhChunkPos)pos).squaredDistance(this.center)).reversed());
this.updateDataByChunkPos = new ConcurrentHashMap<>();
// defaulting to 0,0 is fine since it'll be updated once we start adding items
this.center = new DhChunkPos(0, 0);
}
//==================//
// list/set methods //
//==================//
public boolean contains(DhChunkPos pos) { return this.updateDataByChunkPos.containsKey(pos); }
public void clear()
{
this.updateDataByChunkPos.clear();
this.closestQueue.clear();
this.furthestQueue.clear();
}
public void removeItem(DhChunkPos pos)
{
this.updateDataByChunkPos.remove(pos);
this.closestQueue.remove(pos);
this.furthestQueue.remove(pos);
}
/**
* Adds an item to the queue of chunks that need to be updated.
* If there are no more slots, replaces the item furthest from the center.
*
* @return The number of remaining slots available in the queue.
*/
public int addItem(DhChunkPos pos, UpdateChunkData updateData)
{
int remainingSlots = this.maxSize - this.updateDataByChunkPos.size();
if (this.updateDataByChunkPos.containsKey(pos))
{
// Chunk is already present in queue, no need to insert
return remainingSlots;
}
// If no slots are left, get one by removing the item furthest from the center
if (remainingSlots <= 0)
{
DhChunkPos furthest = this.furthestQueue.poll();
if (furthest != null)
{
this.closestQueue.remove(furthest);
this.updateDataByChunkPos.remove(furthest);
}
}
this.updateDataByChunkPos.put(pos, updateData);
this.closestQueue.add(pos);
this.furthestQueue.add(pos);
return remainingSlots;
}
//==================//
// position methods //
//==================//
public void setCenter(DhChunkPos newCenter)
{
// if the rebuild time takes too long
// (in James' testing a queue of 500 items only took around 0.1 milliseconds)
// this equation could be changed to only update after moving 2 or 4 chunks from the center
if (newCenter.equals(this.center))
{
return;
}
this.center = newCenter;
// rebuild the priority queues to match the new center
this.closestQueue.clear();
this.furthestQueue.clear();
for (DhChunkPos pos : this.updateDataByChunkPos.keySet())
{
this.closestQueue.add(pos);
this.furthestQueue.add(pos);
}
}
public UpdateChunkData popClosest()
{
if (this.closestQueue.isEmpty())
{
return null;
}
DhChunkPos closest = this.closestQueue.poll();
if (closest == null)
{
return null;
}
this.furthestQueue.remove(closest);
return this.updateDataByChunkPos.remove(closest);
}
return "Queued chunk updates: "+"( "+preUpdatingCountStr+" + "+updatingCountStr+" ) [ "+queuedCountStr+" / "+maxUpdateCountStr+" ]";
} }
@@ -0,0 +1,125 @@
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import java.util.Comparator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.PriorityBlockingQueue;
public class ChunkPosQueue
{
private final PriorityBlockingQueue<DhChunkPos> closestQueue;
private final PriorityBlockingQueue<DhChunkPos> furthestQueue;
private final ConcurrentHashMap<DhChunkPos, ChunkUpdateData> updateDataByChunkPos;
private DhChunkPos center;
//=============//
// constructor //
//=============//
public ChunkPosQueue()
{
this.closestQueue = new PriorityBlockingQueue<>(500, Comparator.comparingDouble(pos -> pos.squaredDistance(this.center)));
this.furthestQueue = new PriorityBlockingQueue<>(500, Comparator.comparingDouble(pos -> ((DhChunkPos)pos).squaredDistance(this.center)).reversed());
this.updateDataByChunkPos = new ConcurrentHashMap<>();
// defaulting to 0,0 is fine since it'll be updated once we start adding items
this.center = new DhChunkPos(0, 0);
}
//==============//
// list methods //
//==============//
public boolean contains(DhChunkPos pos) { return this.updateDataByChunkPos.containsKey(pos); }
public void clear()
{
this.updateDataByChunkPos.clear();
this.closestQueue.clear();
this.furthestQueue.clear();
}
public void addItem(DhChunkPos pos, ChunkUpdateData updateData)
{
if (this.updateDataByChunkPos.containsKey(pos))
{
// Chunk is already present in queue, no need to insert
return;
}
this.updateDataByChunkPos.put(pos, updateData);
this.closestQueue.add(pos);
this.furthestQueue.add(pos);
}
public int getQueuedCount() { return this.updateDataByChunkPos.size(); }
public boolean isEmpty() { return this.updateDataByChunkPos.isEmpty(); }
//==================//
// position methods //
//==================//
public void setCenter(DhChunkPos newCenter)
{
// if the rebuild time takes too long
// (in James' testing a queue of 500 items only took around 0.1 milliseconds)
// this equation could be changed to only update after moving 2 or 4 chunks from the center
if (newCenter.equals(this.center))
{
return;
}
this.center = newCenter;
// rebuild the priority queues to match the new center
this.closestQueue.clear();
this.furthestQueue.clear();
for (DhChunkPos pos : this.updateDataByChunkPos.keySet())
{
this.closestQueue.add(pos);
this.furthestQueue.add(pos);
}
}
public ChunkUpdateData popClosest()
{
if (this.closestQueue.isEmpty())
{
return null;
}
DhChunkPos closest = this.closestQueue.poll();
if (closest == null)
{
return null;
}
this.furthestQueue.remove(closest);
return this.updateDataByChunkPos.remove(closest);
}
public ChunkUpdateData popFurthest()
{
if (this.furthestQueue.isEmpty())
{
return null;
}
DhChunkPos furthest = this.furthestQueue.poll();
if (furthest == null)
{
return null;
}
this.closestQueue.remove(furthest);
return this.updateDataByChunkPos.remove(furthest);
}
}
@@ -0,0 +1,26 @@
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
public class ChunkUpdateData
{
public IChunkWrapper chunkWrapper;
@Nullable
public ArrayList<IChunkWrapper> neighborChunkList;
public IDhLevel dhLevel;
public boolean canGetNeighboringChunks;
public ChunkUpdateData(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighborChunks)
{
this.chunkWrapper = chunkWrapper;
this.neighborChunkList = neighborChunkList;
this.dhLevel = dhLevel;
this.canGetNeighboringChunks = canGetNeighborChunks;
}
}
@@ -0,0 +1,145 @@
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import org.apache.logging.log4j.Logger;
public class ChunkUpdateQueueManager
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final ChunkPosQueue updateQueue;
public final ChunkPosQueue preUpdateQueue;
public int maxSize = 500;
private static long lastOverloadedLogMessageMsTime = 0;
//=============//
// constructor //
//=============//
public ChunkUpdateQueueManager()
{
this.updateQueue = new ChunkPosQueue();
this.preUpdateQueue = new ChunkPosQueue();
}
//==================//
// list/set methods //
//==================//
public boolean contains(DhChunkPos pos) { return this.updateQueue.contains(pos) || this.preUpdateQueue.contains(pos); }
public void clear()
{
this.updateQueue.clear();
this.preUpdateQueue.clear();
}
public int getQueuedCount() { return this.updateQueue.getQueuedCount() + this.preUpdateQueue.getQueuedCount(); }
public boolean isEmpty()
{
return this.updateQueue.isEmpty()
&& this.preUpdateQueue.isEmpty();
}
/**
* Adds an item to the pre-update queue of chunks that might need to be updated.
* If there are no more slots, replaces the item furthest from the center in the update queue.
*/
public void addItemToPreUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
{
int remainingSlots = this.maxSize - this.getQueuedCount();
// If no slots are left, get one by removing the item furthest from the center
if (remainingSlots <= 0)
{
if (!this.updateQueue.isEmpty())
{
this.updateQueue.popFurthest();
}
else
{
this.preUpdateQueue.popFurthest();
}
}
this.preUpdateQueue.addItem(pos, updateData);
remainingSlots = this.maxSize - this.getQueuedCount();
if (remainingSlots <= 0)
{
this.sendOverloadMessage();
}
}
public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
{
int remainingSlots = this.maxSize - this.getQueuedCount();
// If no slots are left, get one by removing the item furthest from the center
if (remainingSlots <= 0)
{
this.updateQueue.popFurthest();
}
this.updateQueue.addItem(pos,updateData);
remainingSlots = this.maxSize - this.getQueuedCount();
if (remainingSlots <= 0)
{
this.sendOverloadMessage();
}
}
private void sendOverloadMessage()
{
// limit how often an overloaded message can be sent
long msBetweenLastLog = System.currentTimeMillis() - lastOverloadedLogMessageMsTime;
if (msBetweenLastLog >= SharedApi.MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE)
{
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
String message = "\u00A76" + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + "\u00A7r" +
"\nThis may result in holes in your LODs. " +
"\nFix: move through the world slower, decrease your vanilla render distance, slow down your world pre-generator (IE Chunky), or increase the Distant Horizons' CPU thread counts. " +
"\nMax queue count [" + SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.maxSize + "] ([" + SharedApi.MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER + "] per thread+players).";
boolean showWarningInChat = Config.Common.Logging.Warning.showUpdateQueueOverloadedChatWarning.get();
if (showWarningInChat)
{
ClientApi.INSTANCE.showChatMessageNextFrame(message);
}
// Don't log warnings in singleplayer or in hosted LAN since it usually isn't a problem (and if it is it's easy to notice).
// Servers should always log since being overloaded is harder to notice.
EWorldEnvironment environment = SharedApi.getEnvironment();
if (showWarningInChat || environment == EWorldEnvironment.SERVER_ONLY)
{
LOGGER.warn(message);
}
}
}
//==================//
// position methods //
//==================//
public void setCenter(DhChunkPos newCenter)
{
this.updateQueue.setCenter(newCenter);
this.preUpdateQueue.setCenter(newCenter);
}
}
@@ -0,0 +1,72 @@
package com.seibel.distanthorizons.core.api.internal.rendering;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
/**
* Used to track the rendering state for the current frame.
*
* @see ClientApi
*/
public class RenderState
{
public Mat4f mcModelViewMatrix = null;
public Mat4f mcProjectionMatrix = null;
public float frameTime = -1;
public IClientLevelWrapper clientLevelWrapper = null;
//========//
// checks //
//========//
public String unableToRenderBecause()
{
String errorReasons = "";
// the matrix may be the identity matrix or and old/incorrect matrix
// but we did set it at least once before this
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 (this.clientLevelWrapper == null)
{
errorReasons += "no Level Wrapper, ";
}
return errorReasons;
}
public boolean canRender()
{
// separated variable to allow for easy checking with the debugger
String errorReasons = this.unableToRenderBecause();
return errorReasons.isEmpty();
}
public void canRenderOrThrow() throws IllegalStateException
{
String errorReasons = this.unableToRenderBecause();
if (!errorReasons.isEmpty())
{
throw new IllegalStateException(errorReasons);
}
}
}
@@ -131,6 +131,8 @@ public class Config
// since they aren't part of "client" config class // since they aren't part of "client" config class
// TODO determine their destination programically instead of hard coding the value // TODO determine their destination programically instead of hard coding the value
public static ConfigUIComment advancedHeader = new ConfigUIComment.Builder().setParentConfigClass(Advanced.class).build();
public static ConfigCategory graphics = new ConfigCategory.Builder().set(Graphics.class).build(); public static ConfigCategory graphics = new ConfigCategory.Builder().set(Graphics.class).build();
public static ConfigCategory worldGenerator = new ConfigCategory.Builder().set(Common.WorldGenerator.class).setDestination("common.worldGenerator").build(); public static ConfigCategory worldGenerator = new ConfigCategory.Builder().set(Common.WorldGenerator.class).setDestination("common.worldGenerator").build();
public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build(); public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build();
@@ -146,23 +148,32 @@ public class Config
public static class Graphics public static class Graphics
{ {
public static ConfigUIComment advancedGraphicsHeader = new ConfigUIComment.Builder().setParentConfigClass(Graphics.class).build();
public static ConfigCategory quality = new ConfigCategory.Builder().set(Quality.class).build(); public static ConfigCategory quality = new ConfigCategory.Builder().set(Quality.class).build();
public static ConfigUISpacer qualitySpacer = new ConfigUISpacer.Builder().build();
public static ConfigUiLinkedEntry quickEnableSsao = new ConfigUiLinkedEntry(Ssao.enableSsao); public static ConfigUiLinkedEntry quickEnableSsao = new ConfigUiLinkedEntry(Ssao.enableSsao);
public static ConfigCategory ssao = new ConfigCategory.Builder().set(Ssao.class).build(); public static ConfigCategory ssao = new ConfigCategory.Builder().set(Ssao.class).build();
public static ConfigUISpacer ssaoSpacer = new ConfigUISpacer.Builder().build();
public static ConfigUiLinkedEntry quickEnableGenericRendering = new ConfigUiLinkedEntry(GenericRendering.enableGenericRendering); public static ConfigUiLinkedEntry quickEnableGenericRendering = new ConfigUiLinkedEntry(GenericRendering.enableGenericRendering);
public static ConfigCategory genericRendering = new ConfigCategory.Builder().set(GenericRendering.class).build(); public static ConfigCategory genericRendering = new ConfigCategory.Builder().set(GenericRendering.class).build();
public static ConfigUISpacer genericRenderingSpacer = new ConfigUISpacer.Builder().build();
public static ConfigUiLinkedEntry quickEnableDhFog = new ConfigUiLinkedEntry(Fog.enableDhFog); public static ConfigUiLinkedEntry quickEnableDhFog = new ConfigUiLinkedEntry(Fog.enableDhFog);
public static ConfigUiLinkedEntry quickEnableMcFog = new ConfigUiLinkedEntry(Fog.enableVanillaFog); public static ConfigUiLinkedEntry quickEnableMcFog = new ConfigUiLinkedEntry(Fog.enableVanillaFog);
public static ConfigCategory fog = new ConfigCategory.Builder().set(Fog.class).build(); public static ConfigCategory fog = new ConfigCategory.Builder().set(Fog.class).build();
public static ConfigUISpacer fogSpacer = new ConfigUISpacer.Builder().build();
public static ConfigUiLinkedEntry quickEnableNoiseTexture = new ConfigUiLinkedEntry(NoiseTexture.enableNoiseTexture); public static ConfigUiLinkedEntry quickEnableNoiseTexture = new ConfigUiLinkedEntry(NoiseTexture.enableNoiseTexture);
public static ConfigCategory noiseTexture = new ConfigCategory.Builder().set(NoiseTexture.class).build(); public static ConfigCategory noiseTexture = new ConfigCategory.Builder().set(NoiseTexture.class).build();
public static ConfigUISpacer noiseTextureSpacer = new ConfigUISpacer.Builder().build();
public static ConfigUiLinkedEntry quickEnableCaveCulling = new ConfigUiLinkedEntry(Culling.enableCaveCulling); public static ConfigUiLinkedEntry quickEnableCaveCulling = new ConfigUiLinkedEntry(Culling.enableCaveCulling);
public static ConfigCategory culling = new ConfigCategory.Builder().set(Culling.class).build(); public static ConfigCategory culling = new ConfigCategory.Builder().set(Culling.class).build();
public static ConfigUISpacer cullingSpacer = new ConfigUISpacer.Builder().build();
public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build(); public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build();
@@ -170,6 +181,8 @@ public class Config
public static class Quality public static class Quality
{ {
public static ConfigUIComment qualityHeader = new ConfigUIComment.Builder().setParentConfigClass(Quality.class).build();
public static ConfigEntry<Integer> lodChunkRenderDistanceRadius = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> lodChunkRenderDistanceRadius = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(32, 256, 4096) .setMinDefaultMax(32, 256, 4096)
.comment("" + .comment("" +
@@ -202,6 +215,7 @@ public class Config
+ "Lowest Quality: " + EDhApiMaxHorizontalResolution.CHUNK + "\n" + "Lowest Quality: " + EDhApiMaxHorizontalResolution.CHUNK + "\n"
+ "Highest Quality: " + EDhApiMaxHorizontalResolution.BLOCK) + "Highest Quality: " + EDhApiMaxHorizontalResolution.BLOCK)
.setPerformance(EConfigEntryPerformance.MEDIUM) .setPerformance(EConfigEntryPerformance.MEDIUM)
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<EDhApiVerticalQuality> verticalQuality = new ConfigEntry.Builder<EDhApiVerticalQuality>() public static ConfigEntry<EDhApiVerticalQuality> verticalQuality = new ConfigEntry.Builder<EDhApiVerticalQuality>()
@@ -215,7 +229,7 @@ public class Config
+ "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n" + "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n"
+ "Highest Quality: " + EDhApiVerticalQuality.EXTREME) + "Highest Quality: " + EDhApiVerticalQuality.EXTREME)
.setPerformance(EConfigEntryPerformance.VERY_HIGH) .setPerformance(EConfigEntryPerformance.VERY_HIGH)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<EDhApiTransparency> transparency = new ConfigEntry.Builder<EDhApiTransparency>() public static ConfigEntry<EDhApiTransparency> transparency = new ConfigEntry.Builder<EDhApiTransparency>()
@@ -228,7 +242,7 @@ public class Config
+ EDhApiTransparency.DISABLED + ": LODs will be opaque. \n" + EDhApiTransparency.DISABLED + ": LODs will be opaque. \n"
+ "") + "")
.setPerformance(EConfigEntryPerformance.MEDIUM) .setPerformance(EConfigEntryPerformance.MEDIUM)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>() public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>()
@@ -240,7 +254,7 @@ public class Config
+ EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n" + EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n"
+ "") + "")
.setPerformance(EConfigEntryPerformance.NONE) .setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Boolean> tintWithAvoidedBlocks = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> tintWithAvoidedBlocks = new ConfigEntry.Builder<Boolean>()
@@ -252,7 +266,7 @@ public class Config
+ "False: skipped blocks will not change color of surface below them. " + "False: skipped blocks will not change color of surface below them. "
+ "") + "")
.setPerformance(EConfigEntryPerformance.NONE) .setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Double> lodBias = new ConfigEntry.Builder<Double>() public static ConfigEntry<Double> lodBias = new ConfigEntry.Builder<Double>()
@@ -273,7 +287,7 @@ public class Config
+ EDhApiLodShading.DISABLED + ": All LOD sides will be rendered with the same brightness. \n" + EDhApiLodShading.DISABLED + ": All LOD sides will be rendered with the same brightness. \n"
+ "") + "")
.setPerformance(EConfigEntryPerformance.NONE) .setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<EDhApiGrassSideRendering> grassSideRendering = new ConfigEntry.Builder<EDhApiGrassSideRendering>() public static ConfigEntry<EDhApiGrassSideRendering> grassSideRendering = new ConfigEntry.Builder<EDhApiGrassSideRendering>()
@@ -286,7 +300,7 @@ public class Config
+ EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n" + EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n"
+ "") + "")
.setPerformance(EConfigEntryPerformance.NONE) .setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Boolean> ditherDhFade = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> ditherDhFade = new ConfigEntry.Builder<Boolean>()
@@ -319,7 +333,7 @@ public class Config
+ "0 = black \n" + "0 = black \n"
+ "1 = normal \n" + "1 = normal \n"
+ "2 = near white") + "2 = near white")
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Double> saturationMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats) public static ConfigEntry<Double> saturationMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
@@ -330,26 +344,26 @@ public class Config
+ "0 = black and white \n" + "0 = black and white \n"
+ "1 = normal \n" + "1 = normal \n"
+ "2 = very saturated") + "2 = very saturated")
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
// TODO fixme public static ConfigEntry<Integer> lodBiomeBlending = new ConfigEntry.Builder<Integer>()
//public static ConfigEntry<Integer> lodBiomeBlending = new ConfigEntry.Builder<Integer>() .setMinDefaultMax(0,3,3) // going higher than 3 causes banding issues for blending across LOD borders and an exponential increase in load times
// .setMinDefaultMax(0,1,7) .comment(""
// .comment("" + "This is the same as vanilla Biome Blending settings for Lod area. \n"
// + "This is the same as vanilla Biome Blending settings for Lod area. \n" + " Note that anything other than '0' will greatly effect Lod building time. \n"
// + " Note that anything other than '0' will greatly effect Lod building time \n" + "\n"
// + " and increase triangle count. The cost on chunk generation speed is also \n" + " '0' equals to Vanilla Biome Blending of '1x1' or 'OFF', \n"
// + " quite large if set too high.\n" + " '1' equals to Vanilla Biome Blending of '3x3', \n"
// + "\n" + " '2' equals to Vanilla Biome Blending of '5x5'...")
// + " '0' equals to Vanilla Biome Blending of '1x1' or 'OFF', \n" .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
// + " '1' equals to Vanilla Biome Blending of '3x3', \n" .build();
// + " '2' equals to Vanilla Biome Blending of '5x5'...")
// .build();
} }
public static class Ssao public static class Ssao
{ {
public static ConfigUIComment ssaoHeader = new ConfigUIComment.Builder().setParentConfigClass(Ssao.class).build();
public static ConfigEntry<Boolean> enableSsao = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableSsao = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("Enable Screen Space Ambient Occlusion") .comment("Enable Screen Space Ambient Occlusion")
@@ -412,6 +426,8 @@ public class Config
public static class GenericRendering public static class GenericRendering
{ {
public static ConfigUIComment genericRendererHeader = new ConfigUIComment.Builder().setParentConfigClass(GenericRendering.class).build();
public static ConfigEntry<Boolean> enableGenericRendering = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableGenericRendering = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
@@ -459,6 +475,8 @@ public class Config
public static ConfigUIComment fogHeader = new ConfigUIComment.Builder().setParentConfigClass(Fog.class).build();
public static ConfigEntry<Boolean> enableDhFog = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableDhFog = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
@@ -560,6 +578,8 @@ public class Config
public static class HeightFog public static class HeightFog
{ {
public static ConfigUIComment heightFogHeader = new ConfigUIComment.Builder().setParentConfigClass(HeightFog.class).build();
public static ConfigEntry<EDhApiHeightFogMixMode> heightFogMixMode = new ConfigEntry.Builder<EDhApiHeightFogMixMode>() public static ConfigEntry<EDhApiHeightFogMixMode> heightFogMixMode = new ConfigEntry.Builder<EDhApiHeightFogMixMode>()
.set(EDhApiHeightFogMixMode.SPHERICAL) .set(EDhApiHeightFogMixMode.SPHERICAL)
.comment("" .comment(""
@@ -655,6 +675,8 @@ public class Config
public static class NoiseTexture public static class NoiseTexture
{ {
public static ConfigUIComment noiseTextureHeader = new ConfigUIComment.Builder().setParentConfigClass(NoiseTexture.class).build();
public static ConfigEntry<Boolean> enableNoiseTexture = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableNoiseTexture = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
@@ -689,8 +711,10 @@ public class Config
public static class Culling public static class Culling
{ {
public static ConfigUIComment cullingHeader = new ConfigUIComment.Builder().setParentConfigClass(Culling.class).build();
public static ConfigEntry<Double> overdrawPrevention = new ConfigEntry.Builder<Double>() public static ConfigEntry<Double> overdrawPrevention = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.0, 0.0, 1.0) .setMinDefaultMax(0.0, 0.0, 1.0) // TODO change -1 to auto
.comment("" .comment(""
+ "Determines how far from the camera Distant Horizons will start rendering. \n" + "Determines how far from the camera Distant Horizons will start rendering. \n"
+ "Measured as a percentage of the vanilla render distance.\n" + "Measured as a percentage of the vanilla render distance.\n"
@@ -717,7 +741,7 @@ public class Config
+ " Tweaking the caveCullingHeight, can resolve some \n" + " Tweaking the caveCullingHeight, can resolve some \n"
+ " of those issues. \n" + " of those issues. \n"
+ "") + "")
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> caveCullingHeight = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> caveCullingHeight = new ConfigEntry.Builder<Integer>()
@@ -725,7 +749,7 @@ public class Config
.comment("" .comment(""
+ "At what Y value should cave culling start? \n" + "At what Y value should cave culling start? \n"
+ "Lower this value if you get walls for areas with 0 light.") + "Lower this value if you get walls for areas with 0 light.")
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Boolean> disableBeaconDistanceCulling = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> disableBeaconDistanceCulling = new ConfigEntry.Builder<Boolean>()
@@ -813,6 +837,8 @@ public class Config
public static class Experimental public static class Experimental
{ {
public static ConfigUIComment experimentalHeader = new ConfigUIComment.Builder().setParentConfigClass(Experimental.class).build();
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 0, 5000) .setMinDefaultMax(0, 0, 5000)
.comment("" .comment(""
@@ -835,6 +861,8 @@ public class Config
public static class AutoUpdater public static class AutoUpdater
{ {
public static ConfigUIComment autoUpdaterHeader = new ConfigUIComment.Builder().setParentConfigClass(AutoUpdater.class).build();
public static ConfigEntry<Boolean> enableAutoUpdater = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableAutoUpdater = new ConfigEntry.Builder<Boolean>()
.set(!isRunningInDevEnvironment()) .set(!isRunningInDevEnvironment())
.comment("" .comment(""
@@ -862,6 +890,8 @@ public class Config
public static class Multiplayer public static class Multiplayer
{ {
public static ConfigUIComment multiplayerHeader = new ConfigUIComment.Builder().setParentConfigClass(Multiplayer.class).build();
public static ConfigEntry<EDhApiServerFolderNameMode> serverFolderNameMode = new ConfigEntry.Builder<EDhApiServerFolderNameMode>() public static ConfigEntry<EDhApiServerFolderNameMode> serverFolderNameMode = new ConfigEntry.Builder<EDhApiServerFolderNameMode>()
.set(EDhApiServerFolderNameMode.NAME_ONLY) .set(EDhApiServerFolderNameMode.NAME_ONLY)
.comment("" .comment(""
@@ -878,6 +908,8 @@ public class Config
public static class Debugging public static class Debugging
{ {
public static ConfigUIComment debuggingHeader = new ConfigUIComment.Builder().setParentConfigClass(Debugging.class).build();
public static ConfigEntry<EDhApiRendererMode> rendererMode = new ConfigEntry.Builder<EDhApiRendererMode>() public static ConfigEntry<EDhApiRendererMode> rendererMode = new ConfigEntry.Builder<EDhApiRendererMode>()
.set(EDhApiRendererMode.DEFAULT) .set(EDhApiRendererMode.DEFAULT)
.comment("" .comment(""
@@ -898,6 +930,7 @@ public class Config
+ EDhApiDebugRendering.SHOW_BLOCK_MATERIAL + ": LODs' color will be based on their material. \n" + EDhApiDebugRendering.SHOW_BLOCK_MATERIAL + ": LODs' color will be based on their material. \n"
+ EDhApiDebugRendering.SHOW_OVERLAPPING_QUADS + ": LODs will be drawn with total white, but overlapping quads will be drawn with red. \n" + EDhApiDebugRendering.SHOW_OVERLAPPING_QUADS + ": LODs will be drawn with total white, but overlapping quads will be drawn with red. \n"
+ "") + "")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Boolean> lodOnlyMode = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> lodOnlyMode = new ConfigEntry.Builder<Boolean>()
@@ -965,6 +998,8 @@ public class Config
public static class DebugWireframe public static class DebugWireframe
{ {
public static ConfigUIComment debugWireframeHeader = new ConfigUIComment.Builder().setParentConfigClass(DebugWireframe.class).build();
public static ConfigEntry<Boolean> enableRendering = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableRendering = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.comment("" .comment(""
@@ -1014,6 +1049,8 @@ public class Config
public static class OpenGl public static class OpenGl
{ {
public static ConfigUIComment openGlHeader = new ConfigUIComment.Builder().setParentConfigClass(OpenGl.class).build();
public static ConfigEntry<Boolean> overrideVanillaGLLogger = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> overrideVanillaGLLogger = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
@@ -1061,50 +1098,54 @@ public class Config
public static class ColumnBuilderDebugging public static class ColumnBuilderDebugging
{ {
public static ConfigUIComment columnBuilderDebuggingHeader = new ConfigUIComment.Builder().setParentConfigClass(ColumnBuilderDebugging.class).build();
public static ConfigEntry<Boolean> columnBuilderDebugEnable = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> columnBuilderDebugEnable = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.INSTANT_INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> columnBuilderDebugDetailLevel = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> columnBuilderDebugDetailLevel = new ConfigEntry.Builder<Integer>()
.set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) .set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.INSTANT_INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> columnBuilderDebugXPos = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> columnBuilderDebugXPos = new ConfigEntry.Builder<Integer>()
.set(0) .set(0)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.INSTANT_INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> columnBuilderDebugZPos = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> columnBuilderDebugZPos = new ConfigEntry.Builder<Integer>()
.set(0) .set(0)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.INSTANT_INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> columnBuilderDebugXRow = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> columnBuilderDebugXRow = new ConfigEntry.Builder<Integer>()
.set(-1) .set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.INSTANT_INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> columnBuilderDebugZRow = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> columnBuilderDebugZRow = new ConfigEntry.Builder<Integer>()
.set(-1) .set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.INSTANT_INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> columnBuilderDebugColumnIndex = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> columnBuilderDebugColumnIndex = new ConfigEntry.Builder<Integer>()
.set(-1) .set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(ReloadLodsConfigEventHandler.INSTANT_INSTANCE)
.build(); .build();
} }
public static class F3Screen public static class F3Screen
{ {
public static ConfigUIComment f3ScreenHeader = new ConfigUIComment.Builder().setParentConfigClass(F3Screen.class).build();
public static ConfigEntry<Boolean> showPlayerPos = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showPlayerPos = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("Shows info about each thread pool.") .comment("Shows the player's LOD position.")
.build(); .build();
public static ConfigEntry<Integer> playerPosSectionDetailLevel = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> playerPosSectionDetailLevel = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(6, 6, 16) .setMinDefaultMax(6, 6, 16)
@@ -1145,8 +1186,10 @@ public class Config
// This will throw a warning when opened in the default ui to tell you about it not showing // This will throw a warning when opened in the default ui to tell you about it not showing
public static class ExampleConfigScreen public static class ExampleConfigScreen
{ {
public static ConfigUIComment exampleConfigHeader = new ConfigUIComment.Builder().setParentConfigClass(ExampleConfigScreen.class).build();
// Defined in the lang, just a note about this screen // Defined in the lang, just a note about this screen
public static ConfigUIComment debugConfigScreenNote = new ConfigUIComment(); public static ConfigUIComment debugConfigScreenNote = new ConfigUIComment.Builder().setTextPosition(EConfigCommentTextPosition.CENTER_OF_SCREEN).build();
public static ConfigEntry<Boolean> boolTest = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> boolTest = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
@@ -1154,6 +1197,7 @@ public class Config
public static ConfigEntry<Byte> byteTest = new ConfigEntry.Builder<Byte>() public static ConfigEntry<Byte> byteTest = new ConfigEntry.Builder<Byte>()
.set((byte) 8) .set((byte) 8)
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // no GUI renderer set up currently
.build(); .build();
public static ConfigEntry<Integer> intTest = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> intTest = new ConfigEntry.Builder<Integer>()
@@ -1166,14 +1210,17 @@ public class Config
public static ConfigEntry<Short> shortTest = new ConfigEntry.Builder<Short>() public static ConfigEntry<Short> shortTest = new ConfigEntry.Builder<Short>()
.set((short) 69) .set((short) 69)
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // no GUI renderer set up currently
.build(); .build();
public static ConfigEntry<Long> longTest = new ConfigEntry.Builder<Long>() public static ConfigEntry<Long> longTest = new ConfigEntry.Builder<Long>()
.set(42069L) .set(42069L)
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // no GUI renderer set up currently
.build(); .build();
public static ConfigEntry<Float> floatTest = new ConfigEntry.Builder<Float>() public static ConfigEntry<Float> floatTest = new ConfigEntry.Builder<Float>()
.set(0.42069f) .set(0.42069f)
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // no GUI renderer set up currently
.build(); .build();
public static ConfigEntry<String> stringTest = new ConfigEntry.Builder<String>() public static ConfigEntry<String> stringTest = new ConfigEntry.Builder<String>()
@@ -1182,10 +1229,12 @@ public class Config
public static ConfigEntry<List<String>> listTest = new ConfigEntry.Builder<List<String>>() public static ConfigEntry<List<String>> listTest = new ConfigEntry.Builder<List<String>>()
.set(new ArrayList<String>(Arrays.asList("option 1", "option 2", "option 3"))) .set(new ArrayList<String>(Arrays.asList("option 1", "option 2", "option 3")))
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // no GUI renderer set up currently
.build(); .build();
public static ConfigEntry<Map<String, String>> mapTest = new ConfigEntry.Builder<Map<String, String>>() public static ConfigEntry<Map<String, String>> mapTest = new ConfigEntry.Builder<Map<String, String>>()
.set(new HashMap<String, String>()) .set(new HashMap<String, String>())
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // no GUI renderer set up currently
.build(); .build();
public static ConfigUIButton uiButtonTest = new ConfigUIButton(() -> public static ConfigUIButton uiButtonTest = new ConfigUIButton(() ->
@@ -1229,6 +1278,8 @@ public class Config
{ {
public static class WorldGenerator public static class WorldGenerator
{ {
public static ConfigUIComment worldGeneratorHeader = new ConfigUIComment.Builder().setParentConfigClass(WorldGenerator.class).build();
public static ConfigEntry<Boolean> enableDistantGeneration = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableDistantGeneration = new ConfigEntry.Builder<Boolean>()
.setChatCommandName("generation.enable") .setChatCommandName("generation.enable")
.set(true) .set(true)
@@ -1282,7 +1333,7 @@ public class Config
.build(); .build();
public static ConfigEntry<EDhApiDistantGeneratorProgressDisplayLocation> showGenerationProgress = new ConfigEntry.Builder<EDhApiDistantGeneratorProgressDisplayLocation>() public static ConfigEntry<EDhApiDistantGeneratorProgressDisplayLocation> showGenerationProgress = new ConfigEntry.Builder<EDhApiDistantGeneratorProgressDisplayLocation>()
.set(EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY) .set(EDhApiDistantGeneratorProgressDisplayLocation.DISABLED)
.comment("" .comment(""
+ "How should distant generator progress be displayed? \n" + "How should distant generator progress be displayed? \n"
+ "\n" + "\n"
@@ -1309,10 +1360,21 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Boolean> generationProgressIncludeChunksPerSecond = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "When logging generation progress also include the rate at which chunks \n"
+ "are being generated. \n"
+ "This can be useful for troubleshooting performance. \n"
+ "")
.build();
} }
public static class LodBuilding public static class LodBuilding
{ {
public static ConfigUIComment lodBuildingHeader = new ConfigUIComment.Builder().setParentConfigClass(LodBuilding.class).build();
public static ConfigEntry<Boolean> disableUnchangedChunkCheck = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> disableUnchangedChunkCheck = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
// enabling this can be quite detrimental to performance, // enabling this can be quite detrimental to performance,
@@ -1332,7 +1394,7 @@ public class Config
.build(); .build();
public static ConfigEntry<EDhApiDataCompressionMode> dataCompression = new ConfigEntry.Builder<EDhApiDataCompressionMode>() public static ConfigEntry<EDhApiDataCompressionMode> dataCompression = new ConfigEntry.Builder<EDhApiDataCompressionMode>()
.set(EDhApiDataCompressionMode.LZMA2) .set(EDhApiDataCompressionMode.Z_STD)
.comment("" .comment(""
+ "What algorithm should be used to compress new LOD data? \n" + "What algorithm should be used to compress new LOD data? \n"
+ "This setting will only affect new or updated LOD data, \n" + "This setting will only affect new or updated LOD data, \n"
@@ -1342,14 +1404,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: 3.25 milliseconds\n" + "Estimated average DTO read speed: 6.09 milliseconds\n"
+ "Estimated average DTO write speed: 5.99 milliseconds\n" + "Estimated average DTO write speed: 6.01 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.26\n" + "Expected Compression Ratio: 0.4513\n"
+ "Estimated average DTO read speed: 1.85 ms\n" + "Estimated average DTO read speed: 3.25 ms\n"
+ "Estimated average DTO write speed: 9.46 ms\n" + "Estimated average DTO write speed: 5.99 ms\n"
+ "\n"
+ EDhApiDataCompressionMode.Z_STD + " \n"
+ "A good option if you're CPU limited and have plenty of hard drive space.\n"
+ "Expected Compression Ratio: 0.2606\n"
+ "Estimated average DTO read speed: 9.31 ms\n"
+ "Estimated average DTO write speed: 15.13 ms\n"
+ "\n" + "\n"
+ EDhApiDataCompressionMode.LZMA2 + " \n" + EDhApiDataCompressionMode.LZMA2 + " \n"
+ "Slow but very good compression.\n" + "Slow but very good compression.\n"
@@ -1428,6 +1496,8 @@ public class Config
public static class Experimental public static class Experimental
{ {
public static ConfigUIComment experimentalHeader = new ConfigUIComment.Builder().setParentConfigClass(Experimental.class).build();
public static ConfigEntry<Boolean> upsampleLowerDetailLodsToFillHoles = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> upsampleLowerDetailLodsToFillHoles = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.comment("" .comment(""
@@ -1450,6 +1520,8 @@ public class Config
public static class MultiThreading public static class MultiThreading
{ {
public static ConfigUIComment multiThreadingHeader = new ConfigUIComment.Builder().setParentConfigClass(MultiThreading.class).build();
public static final ConfigEntry<Integer> numberOfThreads = new ConfigEntry.Builder<Integer>() public static final ConfigEntry<Integer> numberOfThreads = new ConfigEntry.Builder<Integer>()
.setChatCommandName("threading.numberOfThreads") .setChatCommandName("threading.numberOfThreads")
.setMinDefaultMax(1, .setMinDefaultMax(1,
@@ -1478,6 +1550,8 @@ public class Config
public static class Logging public static class Logging
{ {
public static ConfigUIComment loggingHeader = new ConfigUIComment.Builder().setParentConfigClass(Logging.class).build();
// TODO add change all option // TODO add change all option
// TODO default to error chat and info file // TODO default to error chat and info file
public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
@@ -1532,6 +1606,7 @@ public class Config
public static class Warning public static class Warning
{ {
public static ConfigUIComment warningHeader = new ConfigUIComment.Builder().setParentConfigClass(Warning.class).build();
public static ConfigEntry<Boolean> showLowMemoryWarningOnStartup = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showLowMemoryWarningOnStartup = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
@@ -1599,28 +1674,6 @@ 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("")
@@ -1727,11 +1780,19 @@ public class Config
// Common // Common
public static ConfigEntry<Integer> maxDataTransferSpeed = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> playerBandwidthLimit = new ConfigEntry.Builder<Integer>()
.setChatCommandName("common.maxDataTransferSpeed") .setChatCommandName("common.playerBandwidthLimit")
.setMinDefaultMax(0, 500, 1000000 /* 1 GB/s */) .setMinDefaultMax(0, 500, 1000000 /* 1 GB/s */)
.comment("" .comment(""
+ "Maximum speed for uploading LODs to the clients, in KB/s.\n" + "Maximum per-player speed for uploading LODs to the clients, in KB/s.\n"
+ "Value of 0 disables the limit."
+ "")
.build();
public static ConfigEntry<Integer> globalBandwidthLimit = new ConfigEntry.Builder<Integer>()
.setChatCommandName("common.globalBandwidthLimit")
.setMinDefaultMax(0, 0, 10000000 /* 10 GB/s */)
.comment(""
+ "Maximum global speed for uploading LODs to the clients, in KB/s.\n"
+ "Value of 0 disables the limit." + "Value of 0 disables the limit."
+ "") + "")
.build(); .build();
@@ -1793,7 +1854,6 @@ public class Config
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
QuickShowWorldGenProgressConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); QuickShowWorldGenProgressConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
RenderCacheConfigEventHandler.getInstance();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -19,46 +19,38 @@
package com.seibel.distanthorizons.core.config; package com.seibel.distanthorizons.core.config;
import com.seibel.distanthorizons.core.config.file.ConfigFileHandling; import com.seibel.distanthorizons.core.config.file.ConfigFileHandler;
import com.seibel.distanthorizons.core.config.types.*;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.config.types.AbstractConfigType; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.config.types.ConfigCategory;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.config.types.ConfigUiLinkedEntry;
import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import 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.lang.invoke.MethodHandles;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
/** /**
* Indexes and sets everything up for the file handling and gui * Indexes and sets everything up for the file handling and gui.
* This should be init after singletons have been bound
* *
* @author coolGi * @author coolGi
* @author Ran * @author Ran
* @version 2023-8-26 * @version 2023-8-26
*/ */
// Init the config after singletons have been blinded
public class ConfigBase public class ConfigBase
{ {
/** Our own config instance, don't modify unless you are the DH mod */ /** Our own config instance, don't modify unless you are the DH mod */
public static ConfigBase INSTANCE; public static ConfigBase INSTANCE;
public ConfigFileHandling configFileINSTANCE;
private final Logger logger;
public final String modID;
public final String modName;
public final int configVersion;
public boolean isLoaded = false;
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/** /**
* What the config works with * What the config works with
* <br> * <br>
* <br> {@link Enum} * <br> {@link Enum}
* <br> {@link Boolean} * <br> {@link Boolean}
@@ -77,61 +69,74 @@ public class ConfigBase
* <br> Map<String, T> * <br> Map<String, T>
* <br> HashMap<String, T> * <br> HashMap<String, T>
*/ */
public static final List<Class<?>> acceptableInputs = new ArrayList<Class<?>>() public static final List<Class<?>> ACCEPTABLE_INPUTS = new ArrayList<Class<?>>()
{{ {{
add(Boolean.class); this.add(Boolean.class);
add(Byte.class); this.add(Byte.class);
add(Integer.class); this.add(Integer.class);
add(Double.class); this.add(Double.class);
add(Short.class); this.add(Short.class);
add(Long.class); this.add(Long.class);
add(Float.class); this.add(Float.class);
add(String.class); this.add(String.class);
// TODO[CONFIG]: Check the type of these is valid // TODO[CONFIG]: Check the type of these is valid
add(List.class); this.add(List.class);
add(ArrayList.class); this.add(ArrayList.class);
add(Map.class); this.add(Map.class);
add(HashMap.class); this.add(HashMap.class);
}}; }};
public ConfigFileHandler configFileHandler;
public final int configVersion;
public boolean isLoaded = false;
/** Disables the minimum and maximum of any variable */ /** Disables the minimum and maximum of any variable */
public boolean disableMinMax = false; // Very fun to use, but should always be disabled by default public boolean disableMinMax = false; // Very fun to use, but should always be disabled by default
public final List<AbstractConfigType<?, ?>> entries = new ArrayList<>(); public final List<AbstractConfigType<?, ?>> entries = new ArrayList<>();
public ConfigBase(String modID, String modName, Class<?> configClass)
//=============//
// constructor //
//=============//
public static void RunFirstTimeSetup()
{ {
this(modID, modName, configClass, getConfigPath(modName), -1); if (INSTANCE != null)
} {
public ConfigBase(String modID, String modName, Class<?> configClass, Path configPath) LOGGER.debug("ConfigBase setup already run, ignoring.");
{ return;
this(modID, modName, configClass, configPath, -1); }
}
public ConfigBase(String modID, String modName, Class<?> configClass, int configVersion) INSTANCE = new ConfigBase(Config.class, ModInfo.CONFIG_FILE_VERSION);
{
this(modID, modName, configClass, getConfigPath(modName), configVersion);
} }
public ConfigBase(String modID, String modName, Class<?> configClass, Path configPath, int configVersion) private ConfigBase(Class<?> configClass, int configVersion)
{ {
this.logger = LogManager.getLogger(this.getClass().getSimpleName() + ", " + modID); LOGGER.info("Initialising config for [" + ModInfo.NAME + "]");
this.logger.info("Initialising config for " + modName);
this.modID = modID;
this.modName = modName;
this.configVersion = configVersion; this.configVersion = configVersion;
this.initNestedClass(configClass, ""); // Init root category this.initNestedClass(configClass, ""); // Init root category
// File handling (load from file) Path configPath = getConfigPath(ModInfo.NAME);
this.configFileINSTANCE = new ConfigFileHandling(this, configPath); this.configFileHandler = new ConfigFileHandler(this, configPath);
this.configFileINSTANCE.loadFromFile(); this.configFileHandler.loadFromFile();
this.isLoaded = true; this.isLoaded = true;
this.logger.info("Config for " + modName + " initialised"); LOGGER.info("Config for [" + ModInfo.NAME + "] initialised");
}
/** Gets the default config path given a mod name */
private static Path getConfigPath(String modName)
{
return SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class)
.getInstallationDirectory().toPath().resolve("config").resolve(modName + ".toml");
} }
private void initNestedClass(Class<?> configClass, String category) private void initNestedClass(Class<?> configClass, String category)
{ {
// Put all the entries in entries // Put all the entries in entries
@@ -146,7 +151,7 @@ public class ConfigBase
} }
catch (IllegalAccessException exception) catch (IllegalAccessException exception)
{ {
this.logger.warn(exception); LOGGER.warn(exception);
} }
AbstractConfigType<?, ?> entry = this.entries.get(this.entries.size() - 1); AbstractConfigType<?, ?> entry = this.entries.get(this.entries.size() - 1);
@@ -155,11 +160,12 @@ public class ConfigBase
entry.configBase = this; entry.configBase = this;
if (ConfigEntry.class.isAssignableFrom(field.getType())) if (ConfigEntry.class.isAssignableFrom(field.getType()))
{ // If item is type ConfigEntry {
// If item is type ConfigEntry
if (!isAcceptableType(entry.getType())) if (!isAcceptableType(entry.getType()))
{ {
this.logger.error("Invalid variable type at [" + (category.isEmpty() ? "" : category + ".") + field.getName() + "]."); LOGGER.error("Invalid variable type at [" + (category.isEmpty() ? "" : category + ".") + field.getName() + "].");
this.logger.error("Type [" + entry.getType() + "] is not one of these types [" + acceptableInputs.toString() + "]"); LOGGER.error("Type [" + entry.getType() + "] is not one of these types [" + ACCEPTABLE_INPUTS.toString() + "]");
this.entries.remove(this.entries.size() - 1); // Delete the entry if it is invalid so the game can still run this.entries.remove(this.entries.size() - 1); // Delete the entry if it is invalid so the game can still run
} }
} }
@@ -177,32 +183,31 @@ public class ConfigBase
} }
} }
} }
private static boolean isAcceptableType(Class<?> Clazz) private static boolean isAcceptableType(Class<?> Clazz)
{ {
if (Clazz.isEnum()) if (Clazz.isEnum())
{
return true; return true;
return acceptableInputs.contains(Clazz); }
return ACCEPTABLE_INPUTS.contains(Clazz);
} }
/** Gets the default config path given a mod name */
public static Path getConfigPath(String modName)
{
return SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class)
.getInstallationDirectory().toPath().resolve("config").resolve(modName + ".toml");
}
//===============//
// lang handling //
//===============//
/** /**
* Used for checking that all the lang files for the config exist * Used for checking that all the lang files for the config exist.
* This is just to re-format the lang or check if there is something in the lang that is missing
* *
* @param onlyShowNew If disabled then it would basically remake the config lang * @param onlyShowMissing If false then this will remake the entire config lang
* @param checkEnums Checks if all the lang for the enum's exist * @param checkEnums Checks if all the lang for the enum's exist
*/ */
// This is just to re-format the lang or check if there is something in the lang that is missing
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public String generateLang(boolean onlyShowNew, boolean checkEnums) public String generateLang(boolean onlyShowMissing, boolean checkEnums)
{ {
ILangWrapper langWrapper = SingletonInjector.INSTANCE.get(ILangWrapper.class); ILangWrapper langWrapper = SingletonInjector.INSTANCE.get(ILangWrapper.class);
List<Class<? extends Enum<?>>> enumList = new ArrayList<>(); List<Class<? extends Enum<?>>> enumList = new ArrayList<>();
@@ -213,50 +218,86 @@ public class ConfigBase
String separator = "\":\n \""; String separator = "\":\n \"";
String ending = "\",\n"; String ending = "\",\n";
// config entries
for (AbstractConfigType<?, ?> entry : this.entries) for (AbstractConfigType<?, ?> entry : this.entries)
{ {
String entryPrefix = "lod.config." + entry.getNameWCategory(); String entryPrefix = "distanthorizons.config." + entry.getNameWCategory();
if (checkEnums && entry.getType().isEnum() && !enumList.contains(entry.getType())) if (checkEnums
{ // Put it in an enum list to work with at the end && entry.getType().isEnum()
&& !enumList.contains(entry.getType()))
{
// Put it in an enum list to work with at the end
enumList.add((Class<? extends Enum<?>>) entry.getType()); enumList.add((Class<? extends Enum<?>>) entry.getType());
} }
if (!onlyShowNew || langWrapper.langExists(entryPrefix))
// config file items don't need lang entries
if (!entry.getAppearance().showInGui)
{ {
if (!ConfigUiLinkedEntry.class.isAssignableFrom(entry.getClass())) continue;
{ // If it is a linked item, dont generate the base lang }
generatedLang += starter
+ entryPrefix // some entries don't need localization
+ separator if (ConfigUiLinkedEntry.class.isAssignableFrom(entry.getClass())
+ langWrapper.getLang(entryPrefix) || ConfigUISpacer.class.isAssignableFrom(entry.getClass()))
+ ending {
; continue;
} }
// Adds tooltips
if (langWrapper.langExists(entryPrefix + ".@tooltip")) if (ConfigUIComment.class.isAssignableFrom(entry.getClass())
{ && ((ConfigUIComment)entry).parentConfigPath != null)
generatedLang += starter {
+ entryPrefix + ".@tooltip" // TODO this could potentially add the same item multiple times
+ separator entryPrefix = "distanthorizons.config." + ((ConfigUIComment)entry).parentConfigPath;
+ langWrapper.getLang(entryPrefix + ".@tooltip") }
.replaceAll("\n", "\\\\n")
.replaceAll("\"", "\\\\\"")
+ ending if (langWrapper.langExists(entryPrefix)
; && onlyShowMissing)
} {
continue;
}
generatedLang += starter
+ entryPrefix
+ separator
+ langWrapper.getLang(entryPrefix)
+ ending
;
// only add tooltips for entries that are also missing
// their primary lang
// this is done since not all menu items need a tooltip
if (!langWrapper.langExists(entryPrefix + ".@tooltip")
|| !onlyShowMissing)
{
generatedLang += starter
+ entryPrefix + ".@tooltip"
+ separator
+ langWrapper.getLang(entryPrefix + ".@tooltip")
.replaceAll("\n", "\\\\n")
.replaceAll("\"", "\\\\\"")
+ ending
;
} }
} }
// enums
if (!enumList.isEmpty()) if (!enumList.isEmpty())
{ {
generatedLang += "\n"; // Separate the main lang with the enum's generatedLang += "\n"; // Separate the main lang with the enum's
for (Class<? extends Enum> anEnum : enumList) for (Class<? extends Enum> anEnum : enumList)
{ {
for (Object enumStr : new ArrayList<>(EnumSet.allOf(anEnum))) for (Object enumStr : new ArrayList<Object>(EnumSet.allOf(anEnum)))
{ {
String enumPrefix = "lod.config.enum." + anEnum.getSimpleName() + "." + enumStr.toString(); String enumPrefix = "distanthorizons.config.enum." + anEnum.getSimpleName() + "." + enumStr.toString();
if (!onlyShowNew || langWrapper.langExists(enumPrefix)) if (!langWrapper.langExists(enumPrefix)
|| !onlyShowMissing)
{ {
generatedLang += starter generatedLang += starter
+ enumPrefix + enumPrefix
@@ -272,4 +313,6 @@ public class ConfigBase
return generatedLang; return generatedLang;
} }
} }
@@ -48,7 +48,9 @@ public class ConfigEntryWithPresetOptions<TQuickEnum, TConfig>
public HashSet<TQuickEnum> getPossibleQualitiesFromCurrentOptionValue() public HashSet<TQuickEnum> getPossibleQualitiesFromCurrentOptionValue()
{ {
TConfig inputOptionValue = this.configEntry.get(); // get true value so we can ignore API overrides,
// users find this confusing if their preset is set to "CUSTOM"
TConfig inputOptionValue = this.configEntry.getTrueValue();
HashSet<TQuickEnum> possibleQualities = new HashSet<>(); HashSet<TQuickEnum> possibleQualities = new HashSet<>();
for (TQuickEnum key : this.configOptionByQualityOption.keySet()) for (TQuickEnum key : this.configOptionByQualityOption.keySet())
@@ -22,13 +22,77 @@ package com.seibel.distanthorizons.core.config.eventHandlers;
import com.seibel.distanthorizons.api.DhApi; import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy; import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener; import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.util.TimerUtil;
import java.util.Timer;
import java.util.TimerTask;
public class ReloadLodsConfigEventHandler implements IConfigListener public class ReloadLodsConfigEventHandler implements IConfigListener
{ {
public static ReloadLodsConfigEventHandler INSTANCE = new ReloadLodsConfigEventHandler(); /**
* should be used for user facing UI options
* this allows the user a second to click through options before they're applied
*/
public static ReloadLodsConfigEventHandler DELAYED_INSTANCE = new ReloadLodsConfigEventHandler(2_000L);
/** should be used for debug options so their change can be seen instantly */
public static ReloadLodsConfigEventHandler INSTANT_INSTANCE = new ReloadLodsConfigEventHandler(0);
/** how long to wait in milliseconds before applying the config changes */
private final long timeoutInMs;
private Timer cacheClearingTimer;
//=============//
// constructor //
//=============//
public ReloadLodsConfigEventHandler(long timeoutInMs)
{
this.timeoutInMs = timeoutInMs;
}
//========//
// events //
//========//
@Override @Override
public void onConfigValueSet() public void onConfigValueSet()
{
if (this.timeoutInMs > 0)
{
this.refreshRenderDataAfterTimeout();
}
else
{
clearRenderDataCache();
}
}
/** Calling this method multiple times will reset the timer */
private synchronized void refreshRenderDataAfterTimeout() // synchronized to prevent potential threading issues when adding/removing the timer
{
// stop the previous timer if one exists
if (this.cacheClearingTimer != null)
{
this.cacheClearingTimer.cancel();
}
// create a new timer task
TimerTask timerTask = new TimerTask()
{
public void run()
{
clearRenderDataCache();
}
};
this.cacheClearingTimer = TimerUtil.CreateTimer("RenderCacheClearConfigTimer");
this.cacheClearingTimer.schedule(timerTask, this.timeoutInMs);
}
private static void clearRenderDataCache()
{ {
IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy; IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy;
if (renderProxy != null) if (renderProxy != null)
@@ -37,4 +101,5 @@ public class ReloadLodsConfigEventHandler implements IConfigListener
} }
} }
} }
@@ -1,114 +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.config.eventHandlers;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.util.TimerUtil;
import java.util.Timer;
import java.util.TimerTask;
/**
* Listens to the config and will automatically
* clear the current render cache if certain settings are changed. <br> <br>
*
* Note: if additional settings should clear the render cache, add those to this listener, don't create a new listener
*/
public class RenderCacheConfigEventHandler
{
private static RenderCacheConfigEventHandler INSTANCE;
// previous values used to check if a watched setting was actually modified
private final ConfigChangeListener<EDhApiMaxHorizontalResolution> horizontalResolutionChangeListener;
private final ConfigChangeListener<EDhApiVerticalQuality> verticalQualityChangeListener;
private final ConfigChangeListener<EDhApiTransparency> transparencyChangeListener;
private final ConfigChangeListener<EDhApiBlocksToAvoid> blocksToIgnoreChangeListener;
private final ConfigChangeListener<Boolean> tintWithAvoidedBlocksChangeListener;
private final ConfigChangeListener<Double> brightnessMultiplierChangeListener;
private final ConfigChangeListener<Double> saturationMultiplierChangeListener;
private final ConfigChangeListener<EDhApiLodShading> lodShadingChangeListener;
private final ConfigChangeListener<EDhApiGrassSideRendering> grassSideChangeListener;
private final ConfigChangeListener<EDhApiDebugRendering> debugRenderingChangeListener;
/** how long to wait in milliseconds before applying the config changes */
private static final long TIMEOUT_IN_MS = 4_000L;
private Timer cacheClearingTimer;
public static RenderCacheConfigEventHandler getInstance()
{
if (INSTANCE == null)
{
INSTANCE = new RenderCacheConfigEventHandler();
}
return INSTANCE;
}
/** private since we only ever need one handler at a time */
private RenderCacheConfigEventHandler()
{
this.horizontalResolutionChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution, (newValue) -> this.refreshRenderDataAfterTimeout());
this.verticalQualityChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.verticalQuality, (newValue) -> this.refreshRenderDataAfterTimeout());
this.transparencyChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.transparency, (newValue) -> this.refreshRenderDataAfterTimeout());
this.blocksToIgnoreChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.blocksToIgnore, (newValue) -> this.refreshRenderDataAfterTimeout());
this.tintWithAvoidedBlocksChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks, (newValue) -> this.refreshRenderDataAfterTimeout());
this.brightnessMultiplierChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.brightnessMultiplier, (newValue) -> this.refreshRenderDataAfterTimeout());
this.saturationMultiplierChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.saturationMultiplier, (newValue) -> this.refreshRenderDataAfterTimeout());
this.lodShadingChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.lodShading, (newValue) -> this.refreshRenderDataAfterTimeout());
this.grassSideChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.grassSideRendering, (newValue) -> this.refreshRenderDataAfterTimeout());
this.debugRenderingChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Debugging.debugRendering, (newValue) -> this.refreshRenderDataAfterTimeout());
}
/** Calling this method multiple times will reset the timer */
private void refreshRenderDataAfterTimeout()
{
// stop the previous timer if one exists
if (this.cacheClearingTimer != null)
{
this.cacheClearingTimer.cancel();
}
// create a new timer task
TimerTask timerTask = new TimerTask()
{
public void run()
{
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
};
this.cacheClearingTimer = TimerUtil.CreateTimer("RenderCacheClearConfigTimer");
this.cacheClearingTimer.schedule(timerTask, TIMEOUT_IN_MS);
}
}
@@ -114,6 +114,15 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, false); this.put(EDhApiQualityPreset.HIGH, false);
this.put(EDhApiQualityPreset.EXTREME, false); this.put(EDhApiQualityPreset.EXTREME, false);
}}); }});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, Integer> biomeBlending = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.lodBiomeBlending,
new HashMap<EDhApiQualityPreset, Integer>()
{{
this.put(EDhApiQualityPreset.MINIMUM, 0);
this.put(EDhApiQualityPreset.LOW, 1);
this.put(EDhApiQualityPreset.MEDIUM, 3);
this.put(EDhApiQualityPreset.HIGH, 3);
this.put(EDhApiQualityPreset.EXTREME, 3);
}});
@@ -133,6 +142,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.configList.add(this.vanillaFade); this.configList.add(this.vanillaFade);
this.configList.add(this.dhDither); this.configList.add(this.dhDither);
this.configList.add(this.caveCulling); this.configList.add(this.caveCulling);
this.configList.add(this.biomeBlending);
for (ConfigEntryWithPresetOptions<EDhApiQualityPreset, ?> config : this.configList) for (ConfigEntryWithPresetOptions<EDhApiQualityPreset, ?> config : this.configList)
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -41,10 +42,9 @@ import java.util.concurrent.locks.ReentrantLock;
* @author coolGi * @author coolGi
* @version 2023-8-26 * @version 2023-8-26
*/ */
public class ConfigFileHandling public class ConfigFileHandler
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
public final ConfigBase configBase; public final ConfigBase configBase;
@@ -64,9 +64,9 @@ public class ConfigFileHandling
// constructor // // constructor //
//=============// //=============//
public ConfigFileHandling(ConfigBase configBase, Path configPath) public ConfigFileHandler(ConfigBase configBase, Path configPath)
{ {
this.logger = LogManager.getLogger(this.getClass().getSimpleName() + ", " + configBase.modID); this.logger = LogManager.getLogger(this.getClass().getSimpleName() + ", " + ModInfo.ID);
this.configBase = configBase; this.configBase = configBase;
this.configPath = configPath; this.configPath = configPath;
@@ -80,6 +80,9 @@ public class ConfigFileHandling
//====================//
// entire config file //
//====================//
/** Saves the entire config to the file */ /** Saves the entire config to the file */
public void saveToFile() { this.saveToFile(this.nightConfig); } public void saveToFile() { this.saveToFile(this.nightConfig); }
@@ -161,7 +164,7 @@ public class ConfigFileHandling
} }
else // if (currentCfgVersion < configBase.configVersion) else // if (currentCfgVersion < configBase.configVersion)
{ {
this.logger.warn(this.configBase.modName + " config is of an older version, currently there is no config updater... so resetting config"); this.logger.warn(ModInfo.NAME + " config is of an older version, currently there is no config updater... so resetting config");
try try
{ {
Files.delete(this.configPath); Files.delete(this.configPath);
@@ -223,6 +226,9 @@ public class ConfigFileHandling
//=======================//
// single config entries //
//=======================//
// Save an entry when only given the entry // Save an entry when only given the entry
public void saveEntry(ConfigEntry<?> entry) public void saveEntry(ConfigEntry<?> entry)
@@ -240,7 +246,7 @@ public class ConfigFileHandling
else if (entry.getTrueValue() == null) else if (entry.getTrueValue() == null)
{ {
// TODO when can this happen? // TODO when can this happen?
throw new IllegalArgumentException("Entry [" + entry.getNameWCategory() + "] is null, this may be a problem with [" + this.configBase.modName + "]. Please contact the authors."); throw new IllegalArgumentException("Entry [" + entry.getNameWCategory() + "] is null, this may be a problem with [" + ModInfo.NAME + "]. Please contact the authors.");
} }
workConfig.set(entry.getNameWCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue())); workConfig.set(entry.getNameWCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue()));
@@ -319,10 +325,12 @@ public class ConfigFileHandling
//=============//
// nightconfig //
//=============//
/** /**
* Uses {@link ConfigFileHandling#nightConfig} to do {@link CommentedFileConfig#load()} but with error checking * Uses {@link ConfigFileHandler#nightConfig} to do {@link CommentedFileConfig#load()} but with error checking
* *
* @apiNote This overwrites any value currently stored in the config * @apiNote This overwrites any value currently stored in the config
*/ */
@@ -361,6 +369,10 @@ public class ConfigFileHandling
//===============//
// file handling //
//===============//
public static void reCreateFile(Path path) public static void reCreateFile(Path path)
{ {
try try
@@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.KeyListener; import java.awt.event.KeyListener;
@@ -35,6 +36,8 @@ public class JavaScreenHandlerScreen extends AbstractScreen
public static boolean firstRun = true; public static boolean firstRun = true;
public final Component jComponent; public final Component jComponent;
static static
{ {
// Note: this code can cause Mac // Note: this code can cause Mac
@@ -54,19 +57,25 @@ public class JavaScreenHandlerScreen extends AbstractScreen
public void init() public void init()
{ {
if (firstRun) if (firstRun)
{
frame = EmbeddedFrameUtil.embeddedFrameCreate(this.minecraftWindow); // Don't call this multiple times frame = EmbeddedFrameUtil.embeddedFrameCreate(this.minecraftWindow); // Don't call this multiple times
}
frame.add(jComponent); frame.add(this.jComponent);
frame.setBackground(new Color(0, 125, 155));
JavaScreenHandlerScreen thiss = this; JavaScreenHandlerScreen thiss = this;
frame.addKeyListener(new KeyListener() { frame.addKeyListener(new KeyListener()
{
@Override @Override
public void keyPressed(KeyEvent keyEvent) public void keyPressed(KeyEvent keyEvent)
{ {
System.out.println("Key pressed code=" + keyEvent.getKeyCode() + ", char=" + keyEvent.getKeyChar()); System.out.println("Key pressed code=" + keyEvent.getKeyCode() + ", char=" + keyEvent.getKeyChar());
if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE)
{
thiss.close = true; thiss.close = true;
}
} }
@Override @Override
@@ -80,8 +89,8 @@ public class JavaScreenHandlerScreen extends AbstractScreen
EmbeddedFrameUtil.embeddedFrameSetBounds(frame, 0, 0, this.width, this.height); EmbeddedFrameUtil.embeddedFrameSetBounds(frame, 0, 0, this.width, this.height);
firstRun = false; firstRun = false;
} }
else
EmbeddedFrameUtil.showFrame(frame); EmbeddedFrameUtil.showFrame(frame);
} }
/** A testing/debug screen */ /** A testing/debug screen */
@@ -89,13 +98,43 @@ public class JavaScreenHandlerScreen extends AbstractScreen
{ {
public ExampleScreen() public ExampleScreen()
{ {
setLayout(new GridBagLayout()); this.setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints(); this.setBackground(new Color(255, 0, 0)); // doesn't appear to be used
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 0.5; GridBagConstraints helloWorldConstraints = new GridBagConstraints();
constraints.gridx = 0; helloWorldConstraints.weightx = 0.5;
constraints.gridy = 0; helloWorldConstraints.gridx = 0;
add(new JLabel("Hello World!"), constraints); helloWorldConstraints.gridy = 0;
//helloWorldConstraints.fill = GridBagConstraints.BOTH;
this.add(new JLabel("Hello World!"), helloWorldConstraints);
GridBagConstraints buttonConstraints = new GridBagConstraints();
buttonConstraints.weightx = 0.5;
buttonConstraints.gridx = 0;
buttonConstraints.gridy = 1;
//buttonConstraints.fill = GridBagConstraints.BOTH;
JButton button = new JButton();
button.setBackground(Color.GREEN);
button.setFocusable(false); // otherwise we can't use escape to leave
button.setAction(new ExampleButtonEventHandler("Button text"));
this.add(button, buttonConstraints);
}
private class ExampleButtonEventHandler extends AbstractAction
{
public ExampleButtonEventHandler(String text)
{
super(text);
//this.putValue(SHORT_DESCRIPTION, text);
//this.putValue(MNEMONIC_KEY, text);
}
@Override
public void actionPerformed(ActionEvent e)
{
System.out.println("button pressed");
}
} }
} }
@@ -116,8 +155,10 @@ public class JavaScreenHandlerScreen extends AbstractScreen
@Override @Override
public void onClose() public void onClose()
{ {
frame.remove(jComponent); frame.remove(this.jComponent);
EmbeddedFrameUtil.hideFrame(frame); EmbeddedFrameUtil.hideFrame(frame);
} }
} }
@@ -1,126 +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.config.gui;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.AbstractVertexAttribute;
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import org.lwjgl.opengl.GL32;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* @author coolGi
*/
public class OpenGLConfigScreen extends AbstractScreen
{
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private ShaderProgram basicShader;
private GLVertexBuffer sameContextBuffer;
private GLVertexBuffer sharedContextBuffer;
private AbstractVertexAttribute va;
@Override
public void init()
{
System.out.println("init");
this.va = AbstractVertexAttribute.create();
this.va.bind();
// Pos
this.va.setVertexAttribute(0, 0, VertexPointer.addVec2Pointer(false));
// Color
this.va.setVertexAttribute(0, 1, VertexPointer.addVec4Pointer(false));
this.va.completeAndCheck(Float.BYTES * 6);
this.basicShader = new ShaderProgram("shaders/test/vert.vert", "shaders/test/frag.frag",
"fragColor", new String[]{"vPosition", "color"});
this.createBuffer();
}
// Render a square with uv color
private static final float[] vertices = {
// PosX,Y, ColorR,G,B,A
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
0.4f, -0.4f, 1.0f, 0.0f, 0.0f, 1.0f,
0.3f, 0.3f, 1.0f, 1.0f, 0.0f, 0.0f,
-0.2f, 0.2f, 0.0f, 1.0f, 1.0f, 1.0f
};
private static GLVertexBuffer createTextingBuffer()
{
ByteBuffer buffer = ByteBuffer.allocateDirect(vertices.length * Float.BYTES);
// Fill buffer with the vertices.
buffer = buffer.order(ByteOrder.nativeOrder());
buffer.asFloatBuffer().put(vertices);
buffer.rewind();
GLVertexBuffer vbo = new GLVertexBuffer(false);
vbo.bind();
vbo.uploadBuffer(buffer, 4, EDhApiGpuUploadMethod.DATA, vertices.length * Float.BYTES);
return vbo;
}
private void createBuffer()
{
this.sharedContextBuffer = createTextingBuffer();
this.sameContextBuffer = createTextingBuffer();
}
@Override
public void render(float delta)
{
System.out.println("Updated config screen with the delta of " + delta);
GL32.glViewport(0, 0, this.width, this.height);
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GLMC.disableFaceCulling();
GLMC.disableDepthTest();
GLMC.disableBlend();
this.basicShader.bind();
this.va.bind();
// Switch between the two buffers per second
if (System.currentTimeMillis() % 2000 < 1000)
{
this.sameContextBuffer.bind();
this.va.bindBufferToAllBindingPoints(this.sameContextBuffer.getId());
}
else
{
this.sameContextBuffer.bind();
this.va.bindBufferToAllBindingPoints(this.sharedContextBuffer.getId());
}
// Render the square
GL32.glDrawArrays(GL32.GL_TRIANGLE_FAN, 0, 4);
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
}
@Override
public void tick() { System.out.println("Ticked"); }
}
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.config.types;
import com.seibel.distanthorizons.core.config.NumberUtil; import com.seibel.distanthorizons.core.config.NumberUtil;
import com.seibel.distanthorizons.core.config.file.ConfigFileHandler;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener; import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance; import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
@@ -94,13 +95,15 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
} }
@Override @Override
public T getApiValue() { return this.apiValue; } public T getApiValue() { return this.apiValue; }
@Override
public boolean apiIsOverriding() { return this.allowApiOverride && this.apiValue != null; }
@Override @Override
public boolean getAllowApiOverride() { return this.allowApiOverride; } public boolean getAllowApiOverride() { return this.allowApiOverride; }
/** /**
* DONT USE THIS IN YOUR CODE <br> * DONT USE THIS IN YOUR CODE <br>
* Sets the value without informing the rest of the code (ie, doesnt call listeners, or saves the value). <br> * Sets the value without informing the rest of the code (ie, doesnt call listeners, or saves the value). <br>
* Should only be used when loading the config from the file (in places like the {@link com.seibel.distanthorizons.core.config.file.ConfigFileHandling} or {@link com.seibel.distanthorizons.core.config.ConfigBase}) * Should only be used when loading the config from the file (in places like the {@link ConfigFileHandler} or {@link com.seibel.distanthorizons.core.config.ConfigBase})
*/ */
public void pureSet(T newValue) { public void pureSet(T newValue) {
super.set(newValue); super.set(newValue);
@@ -305,9 +308,9 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
} }
/** This should normally not be called since set() automatically calls this */ /** This should normally not be called since set() automatically calls this */
public void save() { configBase.configFileINSTANCE.saveEntry(this); } public void save() { configBase.configFileHandler.saveEntry(this); }
/** This should normally not be called except for special circumstances */ /** This should normally not be called except for special circumstances */
public void load() { configBase.configFileINSTANCE.loadEntry(this); } public void load() { configBase.configFileHandler.loadEntry(this); }
@Override @Override
@@ -19,7 +19,13 @@
package com.seibel.distanthorizons.core.config.types; package com.seibel.distanthorizons.core.config.types;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.types.enums.EConfigCommentTextPosition;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance; import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/** /**
* Adds something like a ConfigEntry but without a button to change the input * Adds something like a ConfigEntry but without a button to change the input
@@ -28,11 +34,25 @@ import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance
*/ */
public class ConfigUIComment extends AbstractConfigType<String, ConfigUIComment> public class ConfigUIComment extends AbstractConfigType<String, ConfigUIComment>
{ {
public ConfigUIComment() private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public String parentConfigPath = null;
@Nullable
public EConfigCommentTextPosition textPosition = null;
public ConfigUIComment() { this(null, null); }
public ConfigUIComment(String parentConfigPath, EConfigCommentTextPosition textPosition)
{ {
super(EConfigEntryAppearance.ONLY_IN_GUI, ""); super(EConfigEntryAppearance.ONLY_IN_GUI, "");
this.parentConfigPath = parentConfigPath;
this.textPosition = textPosition;
} }
/** Appearance shouldn't be changed */ /** Appearance shouldn't be changed */
@Override @Override
public void setAppearance(EConfigEntryAppearance newAppearance) { } public void setAppearance(EConfigEntryAppearance newAppearance) { }
@@ -41,26 +61,102 @@ public class ConfigUIComment extends AbstractConfigType<String, ConfigUIComment>
@Override @Override
public void set(String newValue) { } public void set(String newValue) { }
public static class Builder extends AbstractConfigType.Builder<String, Builder> public static class Builder extends AbstractConfigType.Builder<String, Builder>
{ {
public String tempParentConfigPath = null;
public EConfigCommentTextPosition tempTextPosition = null;
/** Appearance shouldn't be changed */ /** Appearance shouldn't be changed */
@Deprecated
@Override @Override
public Builder setAppearance(EConfigEntryAppearance newAppearance) public Builder setAppearance(EConfigEntryAppearance newAppearance) { return this; }
{
return this;
}
/** Pointless to set the value */ /** Pointless to set the value */
@Deprecated
@Override @Override
public Builder set(String newValue) public Builder set(String newValue)
{ return this; }
public Builder setParentConfigClass(@NotNull Class<?> parentConfigClass)
{ {
// expected format: "Config.Client.Advanced"
String packageName = parentConfigClass.getPackage().getName(); // com.seibel.distanthorizons.core.config
String fullName = parentConfigClass.getName(); // com.seibel.distanthorizons.core.config.Config$Common$MultiThreading
try
{
String configPath = fullName.substring(
packageName.length() + // "com.seibel.distanthorizons.core.config"
1 + // "." before "Config"
Config.class.getSimpleName().length() + // "Config"
1); // "$" before the inner class name
// configPath after substring:
// Config$Common$MultiThreading
this.tempParentConfigPath = convertPackageNameToLangPath(configPath); // client.advanced.graphics.Quality
}
catch (Exception e)
{
this.tempParentConfigPath = parentConfigClass.getSimpleName();
LOGGER.warn("Failed to parse config class: ["+fullName+"], error: ["+e.getMessage()+"], defaulting to: ["+this.tempParentConfigPath+"].", e);
}
return this; return this;
} }
/**
* example:
* input: "Client$Advanced$multiThreading"
* output: "client.advanced.multiThreading"
*/
public static String convertPackageNameToLangPath(String input)
{
StringBuilder result = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++)
{
char ch = input.charAt(i);
if (i == 0)
{
result.append(Character.toLowerCase(ch));
continue;
}
// replace '$' -> '.' to match lang path naming
if (ch == '$')
{
result.append('.');
continue;
}
char lastCh = input.charAt(i-1);
if (lastCh == '$')
{
result.append(Character.toLowerCase(ch));
continue;
}
result.append(ch);
}
return result.toString();
}
public Builder setTextPosition(EConfigCommentTextPosition textPosition)
{
this.tempTextPosition = textPosition;
return this;
}
public ConfigUIComment build() public ConfigUIComment build()
{ { return new ConfigUIComment(this.tempParentConfigPath, this.tempTextPosition); }
return new ConfigUIComment();
}
} }
@@ -0,0 +1,59 @@
/*
* 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.config.types;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
/**
* Adds empty space the height of a button.
* Useful for separating different categories.
*/
public class ConfigUISpacer extends AbstractConfigType<String, ConfigUISpacer>
{
public ConfigUISpacer()
{ super(EConfigEntryAppearance.ONLY_IN_GUI, ""); }
/** Appearance shouldn't be changed */
@Override
public void setAppearance(EConfigEntryAppearance newAppearance) { }
/** Pointless to set the value */
@Override
public void set(String newValue) { }
public static class Builder extends AbstractConfigType.Builder<String, Builder>
{
/** Appearance shouldn't be changed */
@Override
public Builder setAppearance(EConfigEntryAppearance newAppearance) { return this; }
/** Pointless to set the value */
@Override
public Builder set(String newValue) { return this; }
public ConfigUISpacer build() { return new ConfigUISpacer(); }
}
}
@@ -27,7 +27,6 @@ import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance
* *
* @author coolGi * @author coolGi
*/ */
@Deprecated // FIXME doesn't work with localization
public class ConfigUiLinkedEntry extends AbstractConfigType<AbstractConfigType<?, ?>, ConfigUiLinkedEntry> public class ConfigUiLinkedEntry extends AbstractConfigType<AbstractConfigType<?, ?>, ConfigUiLinkedEntry>
{ {
public ConfigUiLinkedEntry(AbstractConfigType<?, ?> value) public ConfigUiLinkedEntry(AbstractConfigType<?, ?> value)
@@ -0,0 +1,32 @@
/*
* 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.config.types.enums;
/**
* RIGHT_OVER_BUTTONS <br/>
* CENTER_OF_SCREEN <br/>
* CENTERED_OVER_BUTTONS <br/>
*/
public enum EConfigCommentTextPosition
{
RIGHT_JUSTIFIED,
CENTER_OF_SCREEN,
CENTERED_OVER_BUTTONS,
}
@@ -116,7 +116,7 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); } public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); }
@Override @Override
public Long getPos() { return this.pos; } public long getPos() { return this.pos; }
public void resizeDataStructuresForRepopulation(long pos) public void resizeDataStructuresForRepopulation(long pos)
{ {
@@ -29,12 +29,16 @@ import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler;
import com.seibel.distanthorizons.core.file.IDataSource; import com.seibel.distanthorizons.core.file.IDataSource;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListParent; import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.util.*; import com.seibel.distanthorizons.core.util.*;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
@@ -53,7 +57,7 @@ import java.util.List;
* @see FullDataSourceV1 * @see FullDataSourceV1
*/ */
public class FullDataSourceV2 public class FullDataSourceV2
extends PhantomArrayListParent extends AbstractPhantomArrayList
implements IDataSource<IDhLevel>, IDhApiFullDataSource implements IDataSource<IDhLevel>, IDhApiFullDataSource
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -132,7 +136,7 @@ public class FullDataSourceV2
// constructors // // constructors //
//==============// //==============//
public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper) { return LodDataBuilder.createFromChunk(chunkWrapper); } public static FullDataSourceV2 createFromChunk(ILevelWrapper levelWrapper, IChunkWrapper chunkWrapper) { return LodDataBuilder.createFromChunk(levelWrapper, chunkWrapper); }
public static FullDataSourceV2 createFromLegacyDataSourceV1(FullDataSourceV1 legacyData) public static FullDataSourceV2 createFromLegacyDataSourceV1(FullDataSourceV1 legacyData)
{ {
@@ -279,10 +283,83 @@ public class FullDataSourceV2
//======// //======//
// data // // getters //
//======// //======//
public LongArrayList get(int relX, int relZ) throws IndexOutOfBoundsException { return this.dataPoints[relativePosToIndex(relX, relZ)]; } public LongArrayList get(int relX, int relZ) throws IndexOutOfBoundsException
{ return this.dataPoints[relativePosToIndex(relX, relZ)]; }
/**
* returns {@link FullDataPointUtil#EMPTY_DATA_POINT} if the given {@link DhBlockPos}
* is outside this data source's boundaries.
*/
public long getAtBlockPos(DhBlockPos blockPos)
{
DhLodPos requestedPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPos.getX(), blockPos.getZ());
// stop if the requested blockPos is outside this datasource
{
// get the detail levels for this request
byte requestedDetailLevel = requestedPos.detailLevel;
byte requestedSectionDetailLevel = (byte) (requestedDetailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
// get the positions for this request
long sectionPos = requestedPos.getSectionPosWithSectionDetailLevel(requestedSectionDetailLevel);
if (!DhSectionPos.contains(this.pos, sectionPos))
{
return FullDataPointUtil.EMPTY_DATA_POINT;
}
}
// get the relative data source position
byte requestDetailLevel = (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
DhLodPos relativePos = requestedPos.getDhSectionRelativePositionForDetailLevel(requestDetailLevel);
// get the data column
LongArrayList dataColumn = this.get(relativePos.x, relativePos.z);
if (dataColumn == null)
{
return FullDataPointUtil.EMPTY_DATA_POINT;
}
// search for a datapoint that contains the given block y position
long dataPoint;
for (int i = 0; i < dataColumn.size(); i++)
{
dataPoint = dataColumn.getLong(i);
// we are looking for a specific datapoint,
// don't look at null ones
if (dataPoint == FullDataPointUtil.EMPTY_DATA_POINT)
{
continue;
}
int requestedY = blockPos.getY();
int bottomY = FullDataPointUtil.getBottomY(dataPoint) + this.levelMinY;
int height = FullDataPointUtil.getHeight(dataPoint);
int topY = bottomY + height;
// does this datapoint contain the requested Y position?
if (bottomY <= requestedY
&& requestedY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY
{
return dataPoint;
}
}
return FullDataPointUtil.EMPTY_DATA_POINT;
}
//==========//
// updating //
//==========//
@Override @Override
public boolean update(@NotNull FullDataSourceV2 inputDataSource, @Nullable IDhLevel level) { return this.update(inputDataSource); } public boolean update(@NotNull FullDataSourceV2 inputDataSource, @Nullable IDhLevel level) { return this.update(inputDataSource); }
@@ -989,6 +1066,7 @@ public class FullDataSourceV2
} }
//================// //================//
// helper methods // // helper methods //
//================// //================//
@@ -1073,7 +1151,7 @@ public class FullDataSourceV2
//=====================// //=====================//
@Override @Override
public Long getPos() { return this.pos; } public long getPos() { return this.pos; }
@Override @Override
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); } public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); }
@@ -1163,6 +1241,15 @@ public class FullDataSourceV2
//============//
// unit tests //
//============//
public PhantomArrayListCheckout getPhantomArrayCheckoutForUnitTesting()
{ return this.pooledArraysCheckout; }
//================// //================//
// base overrides // // base overrides //
//================// //================//
@@ -1,11 +1,6 @@
package com.seibel.distanthorizons.core.dataObjects.render; package com.seibel.distanthorizons.core.dataObjects.render;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -19,20 +19,13 @@
package com.seibel.distanthorizons.core.dataObjects.render; package com.seibel.distanthorizons.core.dataObjects.render;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.file.IDataSource;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListParent; import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.ListUtil;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnQuadView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnQuadView;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
@@ -46,9 +39,7 @@ import java.util.concurrent.atomic.AtomicLong;
* *
* @see RenderDataPointUtil * @see RenderDataPointUtil
*/ */
public class ColumnRenderSource public class ColumnRenderSource extends AbstractPhantomArrayList
extends PhantomArrayListParent
implements IDataSource<IDhClientLevel>
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -134,80 +125,12 @@ public class ColumnRenderSource
//=============//
// data update //
//=============//
@Override
public boolean update(FullDataSourceV2 inputFullDataSource, IDhClientLevel level)
{
final String errorMessagePrefix = "Unable to complete update for RenderSource pos: [" + this.pos + "] and pos: [" + inputFullDataSource.getPos() + "]. Error:";
boolean dataChanged = false;
if (DhSectionPos.getDetailLevel(inputFullDataSource.getPos()) == DhSectionPos.getDetailLevel(this.pos))
{
try
{
if (Thread.interrupted())
{
LOGGER.warn(errorMessagePrefix + "write interrupted.");
return false;
}
DhBlockPos2D centerBlockPos = DhSectionPos.getCenterBlockPos(inputFullDataSource.getPos());
int halfBlockWidth = DhSectionPos.getBlockWidth(inputFullDataSource.getPos()) / 2;
DhBlockPos2D minBlockPos = new DhBlockPos2D(centerBlockPos.x - halfBlockWidth, centerBlockPos.z - halfBlockWidth);
for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
{
for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
{
ColumnArrayView columnArrayView = this.getVerticalDataPointView(x, z);
int columnHash = columnArrayView.getDataHash();
LongArrayList dataColumn = inputFullDataSource.get(x, z);
EDhApiWorldGenerationStep worldGenStep = inputFullDataSource.getWorldGenStepAtRelativePos(x, z);
if (dataColumn != null && worldGenStep != EDhApiWorldGenerationStep.EMPTY)
{
FullDataToRenderDataTransformer.updateOrReplaceRenderDataViewColumnWithFullDataColumn(
level, inputFullDataSource.mapping,
minBlockPos.x + x,
minBlockPos.z + z,
columnArrayView, dataColumn);
dataChanged |= columnHash != columnArrayView.getDataHash();
this.fillDebugFlag(x, z, 1, 1, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
}
}
}
catch (Exception e)
{
LOGGER.error(errorMessagePrefix + e.getMessage(), e);
}
}
if (dataChanged)
{
this.localVersion.incrementAndGet();
this.markNotEmpty();
}
return dataChanged;
}
//=====================// //=====================//
// data helper methods // // data helper methods //
//=====================// //=====================//
public Long getPos() { return this.pos; } public Long getPos() { return this.pos; }
@Override
public Long getKey() { return this.pos; } public Long getKey() { return this.pos; }
@Override
public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); } public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); }
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - SECTION_SIZE_OFFSET); } public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - SECTION_SIZE_OFFSET); }
@@ -284,7 +284,7 @@ public class ColumnRenderBufferBuilder
}// for z }// for z
}// for x }// for x
quadBuilder.finalizeData(); quadBuilder.mergeQuads();
} }
private static void addLodToBuffer( private static void addLodToBuffer(
IDhClientLevel clientLevel, IDhClientLevel clientLevel,
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import com.seibel.distanthorizons.api.enums.config.EDhApiGrassSideRendering; import com.seibel.distanthorizons.api.enums.config.EDhApiGrassSideRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial; import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
@@ -172,19 +173,6 @@ public class LodQuadBuilder
BufferQuad quad = new BufferQuad(minX, maxY, minZ, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP); BufferQuad quad = new BufferQuad(minX, maxY, minZ, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255); boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255);
ArrayList<BufferQuad> quadList = isTransparent ? this.transparentQuads[EDhDirection.UP.ordinal()] : this.opaqueQuads[EDhDirection.UP.ordinal()]; ArrayList<BufferQuad> quadList = isTransparent ? this.transparentQuads[EDhDirection.UP.ordinal()] : this.opaqueQuads[EDhDirection.UP.ordinal()];
// attempt to merge this quad with adjacent ones
if (!quadList.isEmpty() &&
(
quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|| quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
)
{
this.premergeCount++;
return;
}
quadList.add(quad); quadList.add(quad);
} }
@@ -193,14 +181,6 @@ public class LodQuadBuilder
BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN); BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN);
ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255) ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255)
? transparentQuads[EDhDirection.DOWN.ordinal()] : opaqueQuads[EDhDirection.DOWN.ordinal()]; ? transparentQuads[EDhDirection.DOWN.ordinal()] : opaqueQuads[EDhDirection.DOWN.ordinal()];
if (!qs.isEmpty()
&& (qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|| qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
)
{
premergeCount++;
return;
}
qs.add(quad); qs.add(quad);
} }
@@ -210,9 +190,6 @@ public class LodQuadBuilder
// data finalizing // // data finalizing //
//=================// //=================//
/** runs any final data cleanup, merging, etc. */
public void finalizeData() { this.mergeQuads(); }
/** Uses Greedy meshing to merge this builder's Quads. */ /** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads() public void mergeQuads()
{ {
@@ -251,7 +228,9 @@ public class LodQuadBuilder
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection) private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
{ {
if (list[directionIndex].size() <= 1) if (list[directionIndex].size() <= 1)
{
return 0; return 0;
}
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection)); list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
@@ -26,7 +26,6 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
@@ -37,6 +36,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
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.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
@@ -66,13 +66,13 @@ public class FullDataToRenderDataTransformer
//==============================// //==============================//
@Nullable @Nullable
public static ColumnRenderSource transformFullDataToRenderSource(@Nullable FullDataSourceV2 fullDataSource, @Nullable IDhClientLevel level) public static ColumnRenderSource transformFullDataToRenderSource(@Nullable FullDataSourceV2 fullDataSource, @Nullable IClientLevelWrapper levelWrapper)
{ {
if (fullDataSource == null) if (fullDataSource == null)
{ {
return null; return null;
} }
else if (level == null) else if (levelWrapper == null)
{ {
// if the client is no longer loaded in the world, render sources cannot be created // if the client is no longer loaded in the world, render sources cannot be created
return null; return null;
@@ -81,7 +81,7 @@ public class FullDataToRenderDataTransformer
try try
{ {
return transformCompleteFullDataToColumnData(level, fullDataSource); return transformCompleteFullDataToColumnData(levelWrapper, fullDataSource);
} }
catch (InterruptedException e) catch (InterruptedException e)
{ {
@@ -102,7 +102,7 @@ public class FullDataToRenderDataTransformer
* @throws InterruptedException Can be caused by interrupting the thread upstream. * @throws InterruptedException Can be caused by interrupting the thread upstream.
* Generally thrown if the method is running after the client leaves the current world. * Generally thrown if the method is running after the client leaves the current world.
*/ */
private static ColumnRenderSource transformCompleteFullDataToColumnData(IDhClientLevel level, FullDataSourceV2 fullDataSource) throws InterruptedException private static ColumnRenderSource transformCompleteFullDataToColumnData(IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource) throws InterruptedException
{ {
final long pos = fullDataSource.getPos(); final long pos = fullDataSource.getPos();
final byte dataDetail = fullDataSource.getDataDetailLevel(); final byte dataDetail = fullDataSource.getDataDetailLevel();
@@ -111,7 +111,7 @@ public class FullDataToRenderDataTransformer
final ColumnRenderSource columnSource = ColumnRenderSource.createEmpty(pos, vertSize, level.getMinY()); final ColumnRenderSource columnSource = ColumnRenderSource.createEmpty(pos, vertSize, levelWrapper.getMinHeight());
if (fullDataSource.isEmpty) if (fullDataSource.isEmpty)
{ {
return columnSource; return columnSource;
@@ -121,9 +121,9 @@ public class FullDataToRenderDataTransformer
int baseX = DhSectionPos.getMinCornerBlockX(pos); int baseX = DhSectionPos.getMinCornerBlockX(pos);
int baseZ = DhSectionPos.getMinCornerBlockZ(pos); int baseZ = DhSectionPos.getMinCornerBlockZ(pos);
for (int x = 0; x < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); x++) for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
{ {
for (int z = 0; z < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); z++) for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
{ {
throwIfThreadInterrupted(); throwIfThreadInterrupted();
@@ -131,7 +131,7 @@ public class FullDataToRenderDataTransformer
LongArrayList dataColumn = fullDataSource.get(x, z); LongArrayList dataColumn = fullDataSource.get(x, z);
updateOrReplaceRenderDataViewColumnWithFullDataColumn( updateOrReplaceRenderDataViewColumnWithFullDataColumn(
level, fullDataSource.mapping, levelWrapper, fullDataSource,
// bitshift is to account for LODs with a detail level greater than 0 so the block pos is correct // bitshift is to account for LODs with a detail level greater than 0 so the block pos is correct
baseX + BitShiftUtil.pow(x,dataDetail), baseZ + BitShiftUtil.pow(z,dataDetail), baseX + BitShiftUtil.pow(x,dataDetail), baseZ + BitShiftUtil.pow(z,dataDetail),
columnArrayView, dataColumn); columnArrayView, dataColumn);
@@ -145,13 +145,14 @@ public class FullDataToRenderDataTransformer
/** Updates the given {@link ColumnArrayView} to match the incoming Full data {@link LongArrayList} */ /** Updates the given {@link ColumnArrayView} to match the incoming Full data {@link LongArrayList} */
public static void updateOrReplaceRenderDataViewColumnWithFullDataColumn( public static void updateOrReplaceRenderDataViewColumnWithFullDataColumn(
IDhClientLevel level, IClientLevelWrapper levelWrapper,
FullDataPointIdMap fullDataMapping, int blockX, int blockZ, FullDataSourceV2 fullDataSource, int blockX, int blockZ,
ColumnArrayView columnArrayView, ColumnArrayView columnArrayView,
LongArrayList fullDataColumn) LongArrayList fullDataColumn)
{ {
// we can't do anything if the full data is missing or empty // we can't do anything if the full data is missing or empty
if (fullDataColumn == null || fullDataColumn.size() == 0) if (fullDataColumn == null
|| fullDataColumn.size() == 0)
{ {
return; return;
} }
@@ -160,7 +161,7 @@ public class FullDataToRenderDataTransformer
if (fullDataLength <= columnArrayView.verticalSize()) if (fullDataLength <= columnArrayView.verticalSize())
{ {
// Directly use the arrayView since it fits. // Directly use the arrayView since it fits.
setRenderColumnView(level, fullDataMapping, blockX, blockZ, columnArrayView, fullDataColumn); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn);
} }
else else
{ {
@@ -171,7 +172,7 @@ public class FullDataToRenderDataTransformer
{ {
// expand the ColumnArrayView to fit the new larger max vertical size // expand the ColumnArrayView to fit the new larger max vertical size
ColumnArrayView newColumnArrayView = new ColumnArrayView(dataArrayList, fullDataLength, 0, fullDataLength); ColumnArrayView newColumnArrayView = new ColumnArrayView(dataArrayList, fullDataLength, 0, fullDataLength);
setRenderColumnView(level, fullDataMapping, blockX, blockZ, newColumnArrayView, fullDataColumn); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, newColumnArrayView, fullDataColumn);
columnArrayView.changeVerticalSizeFrom(newColumnArrayView); columnArrayView.changeVerticalSizeFrom(newColumnArrayView);
} }
finally finally
@@ -181,7 +182,7 @@ public class FullDataToRenderDataTransformer
} }
} }
private static void setRenderColumnView( private static void setRenderColumnView(
IDhClientLevel level, FullDataPointIdMap fullDataMapping, IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource,
int blockX, int blockZ, int blockX, int blockZ,
ColumnArrayView renderColumnData, LongArrayList fullColumnData) ColumnArrayView renderColumnData, LongArrayList fullColumnData)
{ {
@@ -192,18 +193,18 @@ public class FullDataToRenderDataTransformer
boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING); boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get(); boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get();
HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(level.getLevelWrapper()); HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper);
HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(level.getLevelWrapper()); HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper);
int caveCullingMaxY = Config.Client.Advanced.Graphics.Culling.caveCullingHeight.get() - level.getMinY(); int caveCullingMaxY = Config.Client.Advanced.Graphics.Culling.caveCullingHeight.get() - levelWrapper.getMinHeight();
boolean caveCullingEnabled = boolean caveCullingEnabled =
Config.Client.Advanced.Graphics.Culling.enableCaveCulling.get() Config.Client.Advanced.Graphics.Culling.enableCaveCulling.get()
&& ( && (
// dimensions with a ceiling will be all caves so we don't want cave culling // dimensions with a ceiling will be all caves so we don't want cave culling
!level.getLevelWrapper().hasCeiling() !levelWrapper.hasCeiling()
// the end has a lot of overhangs with 0 lighting above the void, which look broken with // the end has a lot of overhangs with 0 lighting above the void, which look broken with
// the current cave culling logic (this could probably be improved, but just skipping it works best for now) // the current cave culling logic (this could probably be improved, but just skipping it works best for now)
&& !level.getLevelWrapper().getDimensionType().isTheEnd() && !levelWrapper.getDimensionType().isTheEnd()
); );
boolean isColumnVoid = true; boolean isColumnVoid = true;
@@ -222,6 +223,8 @@ public class FullDataToRenderDataTransformer
// convert full data to render data // // convert full data to render data //
//==================================// //==================================//
FullDataPointIdMap fullDataMapping = fullDataSource.mapping;
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable(blockX, 0, blockZ); DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable(blockX, 0, blockZ);
// goes from the top down // goes from the top down
@@ -236,7 +239,7 @@ public class FullDataToRenderDataTransformer
int blockLight = FullDataPointUtil.getBlockLight(fullData); int blockLight = FullDataPointUtil.getBlockLight(fullData);
int skyLight = FullDataPointUtil.getSkyLight(fullData); int skyLight = FullDataPointUtil.getSkyLight(fullData);
mutableBlockPos.setY(bottomY + level.getMinY()); mutableBlockPos.setY(bottomY + levelWrapper.getMinHeight());
IBiomeWrapper biome; IBiomeWrapper biome;
IBlockStateWrapper block; IBlockStateWrapper block;
@@ -250,7 +253,7 @@ public class FullDataToRenderDataTransformer
if (!brokenPos.contains(fullDataMapping.getPos())) if (!brokenPos.contains(fullDataMapping.getPos()))
{ {
brokenPos.add(fullDataMapping.getPos()); brokenPos.add(fullDataMapping.getPos());
String levelId = level.getLevelWrapper().getDhIdentifier(); String levelId = levelWrapper.getDhIdentifier();
LOGGER.warn("Unable to get data point with id ["+id+"] " + LOGGER.warn("Unable to get data point with id ["+id+"] " +
"(Max possible ID: ["+fullDataMapping.getMaxValidId()+"]) " + "(Max possible ID: ["+fullDataMapping.getMaxValidId()+"]) " +
"for pos ["+fullDataMapping.getPos()+"] in level ["+levelId+"]. " + "for pos ["+fullDataMapping.getPos()+"] in level ["+levelId+"]. " +
@@ -320,11 +323,14 @@ public class FullDataToRenderDataTransformer
//=======================// //=======================//
if (ignoreNonCollidingBlocks if (ignoreNonCollidingBlocks
&& !block.isSolid() && !block.isLiquid() && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE) && !block.isSolid()
&& !block.isLiquid()
&& block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE)
{ {
if (colorBelowWithAvoidedBlocks) if (colorBelowWithAvoidedBlocks)
{ {
int tempColor = level.computeBaseColor(mutableBlockPos, biome, block); int tempColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
// don't transfer the color when alpha is 0 // don't transfer the color when alpha is 0
// this prevents issues if grass is transparent // this prevents issues if grass is transparent
if (ColorUtil.getAlpha(tempColor) != 0) if (ColorUtil.getAlpha(tempColor) != 0)
@@ -344,7 +350,7 @@ public class FullDataToRenderDataTransformer
if (colorToApplyToNextBlock == -1) if (colorToApplyToNextBlock == -1)
{ {
// use this block's color // use this block's color
color = level.computeBaseColor(mutableBlockPos, biome, block); color = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
} }
else else
{ {
@@ -24,15 +24,15 @@ import java.util.List;
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode; import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkProcessingEvent;
import com.seibel.distanthorizons.api.objects.data.DhApiChunk; import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint; import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
@@ -43,6 +43,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper;
import 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.coreapi.DependencyInjection.ApiEventInjector;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -50,7 +52,8 @@ import org.jetbrains.annotations.Nullable;
public class LodDataBuilder public class LodDataBuilder
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IBlockStateWrapper AIR = SingletonInjector.INSTANCE.get(IWrapperFactory.class).getAirBlockStateWrapper(); private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final IBlockStateWrapper AIR = WRAPPER_FACTORY.getAirBlockStateWrapper();
private static boolean getTopErrorLogged = false; private static boolean getTopErrorLogged = false;
@@ -60,10 +63,13 @@ public class LodDataBuilder
// converters // // converters //
//============// //============//
public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper) public static FullDataSourceV2 createFromChunk(ILevelWrapper levelWrapper, IChunkWrapper chunkWrapper)
{ {
// 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(), "Provided chunk's DH Block lighting hasn't been baked.");
int chunkPosX = chunkWrapper.getChunkPos().getX();
int chunkPosZ = chunkWrapper.getChunkPos().getZ();
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX()); int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX());
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ()); int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ());
@@ -119,6 +125,9 @@ public class LodDataBuilder
IMutableBlockPosWrapper mcBlockPos = chunkWrapper.getMutableBlockPosWrapper(); IMutableBlockPosWrapper mcBlockPos = chunkWrapper.getMutableBlockPosWrapper();
IBlockStateWrapper previousBlockState = null; IBlockStateWrapper previousBlockState = null;
DhApiChunkProcessingEvent.EventParam mutableChunkProcessedEventParam
= new DhApiChunkProcessingEvent.EventParam(levelWrapper, chunkPosX, chunkPosZ);
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight(); int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
int exclusiveMaxBuildHeight = chunkWrapper.getExclusiveMaxBuildHeight(); int exclusiveMaxBuildHeight = chunkWrapper.getExclusiveMaxBuildHeight();
int inclusiveMinBuildHeight = chunkWrapper.getInclusiveMinBuildHeight(); int inclusiveMinBuildHeight = chunkWrapper.getInclusiveMinBuildHeight();
@@ -144,9 +153,9 @@ public class LodDataBuilder
} }
int lastY = exclusiveMaxBuildHeight; int lastY = exclusiveMaxBuildHeight;
IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ); IBiomeWrapper currentBiome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
IBlockStateWrapper blockState = AIR; IBlockStateWrapper currentBlockState = AIR;
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState); int mappedId = dataSource.mapping.addIfNotPresentAndGetId(currentBiome, currentBlockState);
// Determine lighting (we are at the height limit. There are no torches here, and sky is not obscured.) // TODO: Per face lighting someday? // 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 blockLight = LodUtil.MIN_MC_LIGHT;
@@ -162,7 +171,8 @@ public class LodDataBuilder
// 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 < exclusiveMaxBuildHeight) while (!topBlockState.isAir()
&& y < exclusiveMaxBuildHeight)
{ {
try try
{ {
@@ -185,6 +195,7 @@ public class LodDataBuilder
} }
// Process blocks from top to bottom // Process blocks from top to bottom
boolean forceSingleBlock = false;
for (; y >= minBuildHeight; y--) for (; y >= minBuildHeight; y--)
{ {
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ); IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
@@ -193,13 +204,46 @@ public class LodDataBuilder
byte newSkyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, y + 1, relBlockZ); byte newSkyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, y + 1, relBlockZ);
// Save the biome/block change if different from previous // Save the biome/block change if different from previous
if (!newBiome.equals(biome) || !newBlockState.equals(blockState)) if (!newBiome.equals(currentBiome)
|| !newBlockState.equals(currentBlockState)
|| forceSingleBlock)
{ {
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - inclusiveMinBuildHeight, blockLight, skyLight)); // if the previous block potentially colors this block
biome = newBiome; // make this block a single entry, aka add the next block even if it is the same
blockState = newBlockState; // this is done to allow fire, snow, flowers, etc. to properly color the top of columns vs the whole column
forceSingleBlock =
!currentBlockState.isAir()
&& !currentBlockState.isSolid()
&& !currentBlockState.isLiquid()
&& currentBlockState.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE;
mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
// check for API overrides
{
mutableChunkProcessedEventParam.updateForPosition(relBlockX, y, relBlockZ, newBlockState, newBiome);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiChunkProcessingEvent.class, mutableChunkProcessedEventParam);
// did the API user override this block?
if (mutableChunkProcessedEventParam.getBlockOverride() != null)
{
// API users shouldn't be creating their own IBlockStateWrapper objects
newBlockState = (IBlockStateWrapper)mutableChunkProcessedEventParam.getBlockOverride();
}
// did the API user override this biome?
if (mutableChunkProcessedEventParam.getBiomeOverride() != null)
{
// API users shouldn't be creating their own IBlockStateWrapper objects
newBiome = (IBiomeWrapper) mutableChunkProcessedEventParam.getBiomeOverride();
}
}
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - inclusiveMinBuildHeight, blockLight, skyLight));
currentBiome = newBiome;
currentBlockState = newBlockState;
mappedId = dataSource.mapping.addIfNotPresentAndGetId(currentBiome, currentBlockState);
blockLight = newBlockLight; blockLight = newBlockLight;
skyLight = newSkyLight; skyLight = newSkyLight;
lastY = y; lastY = y;
@@ -275,6 +319,12 @@ public class LodDataBuilder
continue; continue;
} }
// the lowest/bedrock segment should not be culled
if (centerIndex + 1 == centerColumn.size())
{
continue;
}
posXIndex = checkOcclusion(dataSource, currentPoint, posXColumn, posXIndex); posXIndex = checkOcclusion(dataSource, currentPoint, posXColumn, posXIndex);
if (posXIndex < 0) if (posXIndex < 0)
{ {
@@ -528,11 +578,11 @@ public class LodDataBuilder
//================// //==================//
// helper methods // // internal helpers //
//================// //==================//
public static int getXOrZSectionPosFromChunkPos(int chunkXOrZPos) private static int getXOrZSectionPosFromChunkPos(int chunkXOrZPos)
{ {
// get the section position // get the section position
int sectionPos = chunkXOrZPos; int sectionPos = chunkXOrZPos;
@@ -541,4 +591,6 @@ public class LodDataBuilder
return sectionPos; return sectionPos;
} }
} }
@@ -14,7 +14,7 @@ import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
*/ */
public interface IDataSource<TDhLevel extends IDhLevel> extends IBaseDTO<Long>, AutoCloseable public interface IDataSource<TDhLevel extends IDhLevel> extends IBaseDTO<Long>, AutoCloseable
{ {
Long getPos(); long getPos();
/** @return true if the data was changed */ /** @return true if the data was changed */
boolean update(FullDataSourceV2 chunkData, TDhLevel level); boolean update(FullDataSourceV2 chunkData, TDhLevel level);
@@ -1,9 +1,5 @@
package com.seibel.distanthorizons.core.file.fullDatafile; package com.seibel.distanthorizons.core.file.fullDatafile;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalNotification;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
@@ -14,6 +10,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration;
import java.util.Set; import java.util.Set;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@@ -38,7 +35,7 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
private final Cache<Long, FullDataSourceV2> dataSourceByPosition; private final ConcurrentHashMap<Long, DataSourceSavedTimePair> dataSourceByPosition = new ConcurrentHashMap<Long, DataSourceSavedTimePair>();
/* don't let two threads load the same position at the same time */ /* don't let two threads load the same position at the same time */
protected final KeyedLockContainer<Long> saveLockContainer = new KeyedLockContainer<>(); protected final KeyedLockContainer<Long> saveLockContainer = new KeyedLockContainer<>();
@@ -60,16 +57,15 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
public DelayedFullDataSourceSaveCache(@NotNull ISaveDataSourceFunc onSaveTimeoutAsyncFunc, int saveDelayInMs) public DelayedFullDataSourceSaveCache(@NotNull ISaveDataSourceFunc onSaveTimeoutAsyncFunc, int saveDelayInMs)
{ {
this.onSaveTimeoutAsyncFunc = onSaveTimeoutAsyncFunc; this.onSaveTimeoutAsyncFunc = onSaveTimeoutAsyncFunc;
// we can't clean items faster than the cleanup timer fires
if (saveDelayInMs < CLEANUP_CHECK_TIME_IN_MS)
{
LOGGER.warn("The save delay ["+saveDelayInMs+"] shouldn't be less than the cleanup check timer interval ["+CLEANUP_CHECK_TIME_IN_MS+"].");
}
this.saveDelayInMs = saveDelayInMs; this.saveDelayInMs = saveDelayInMs;
this.dataSourceByPosition =
CacheBuilder.newBuilder()
.expireAfterAccess(this.saveDelayInMs, TimeUnit.MILLISECONDS)
.expireAfterWrite(this.saveDelayInMs, TimeUnit.MILLISECONDS)
.removalListener(this::handleDataSourceRemoval)
.<Long, FullDataSourceV2>build();
SAVE_CACHE_SET.add(new WeakReference<>(this)); SAVE_CACHE_SET.add(new WeakReference<>(this));
} }
@@ -83,61 +79,61 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
* Writing into memory is done synchronously so inputDataSource can * Writing into memory is done synchronously so inputDataSource can
* be closed after this method finishes. * be closed after this method finishes.
*/ */
public void writeDataSourceToMemoryAndQueueSave(FullDataSourceV2 inputDataSource) public void writeDataSourceToMemoryAndQueueSave(@NotNull FullDataSourceV2 inputDataSource)
{ {
long inputPos = inputDataSource.getPos(); long inputPos = inputDataSource.getPos();
ReentrantLock lock = this.saveLockContainer.getLockForPos(inputPos); ReentrantLock lockForPos = this.saveLockContainer.getLockForPos(inputPos);
try try
{ {
lock.lock(); lockForPos.lock();
FullDataSourceV2 memoryDataSource = this.dataSourceByPosition.getIfPresent(inputPos); FullDataSourceV2 memoryDataSource;
if (memoryDataSource == null)
DataSourceSavedTimePair pair = this.dataSourceByPosition.getOrDefault(inputPos, null);
if (pair == null)
{ {
// no data currently in the memory cache for this position
memoryDataSource = FullDataSourceV2.createEmpty(inputPos); memoryDataSource = FullDataSourceV2.createEmpty(inputPos);
} pair = new DataSourceSavedTimePair(memoryDataSource);
memoryDataSource.update(inputDataSource); this.dataSourceByPosition.put(inputPos, pair);
this.dataSourceByPosition.put(inputPos, memoryDataSource);
}
finally
{
lock.unlock();
}
}
public void handleDataSourceRemoval(RemovalNotification<Long, FullDataSourceV2> removalNotification)
{
RemovalCause cause = removalNotification.getCause();
if (cause == RemovalCause.EXPIRED
|| cause == RemovalCause.COLLECTED
|| cause == RemovalCause.EXPLICIT
|| cause == RemovalCause.SIZE)
{
// close the data source after it has expired from the cache
FullDataSourceV2 dataSource = removalNotification.getValue();
if (dataSource != null)
{
this.onSaveTimeoutAsyncFunc.saveAsync(dataSource)
.handle((voidObj, throwable) ->
{
try
{
dataSource.close();
}
catch (Exception e)
{
LOGGER.error("Unable to close datasource ["+ DhSectionPos.toString(dataSource.getPos()) +"], removal cause: ["+cause+"], error: ["+e.getMessage()+"].", e);
}
return null;
});
} }
else else
{ {
LOGGER.error("Unable to close null cached data source."); memoryDataSource = pair.dataSource;
} }
// write the new data into memory
memoryDataSource.update(inputDataSource);
// keep track of when the last time we saved something was
pair.updateLastWrittenTimestamp();
} }
finally
{
lockForPos.unlock();
}
}
/** when this method is called the datasource should no longer be in the memory cache */
public void handleDataSourceRemoval(@NotNull FullDataSourceV2 removedDataSource)
{
this.onSaveTimeoutAsyncFunc.saveAsync(removedDataSource)
.handle((voidObj, throwable) ->
{
try
{
// if this close method is fired multiple times
// monoliths can appear due to concurrent writing to the
// backend arrays
removedDataSource.close();
}
catch (Exception e)
{
LOGGER.error("Unable to close datasource ["+ DhSectionPos.toString(removedDataSource.getPos()) +"], error: ["+e.getMessage()+"].", e);
}
return null;
});
} }
@@ -146,26 +142,36 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
// List methods // // List methods //
//==============// //==============//
public int getUnsavedCount() { return (int)this.dataSourceByPosition.size(); } public int getUnsavedCount() { return this.dataSourceByPosition.size(); }
public void flush() { this.cleanUp(true); }
/** Removes everything from the memory cache and fires the {@link DelayedFullDataSourceSaveCache#onSaveTimeoutAsyncFunc} for each. */ /** Removes everything from the memory cache and fires the {@link DelayedFullDataSourceSaveCache#onSaveTimeoutAsyncFunc} for each. */
public void flush() public void cleanUp(boolean flushAll)
{ {
Set<Long> keySet = this.dataSourceByPosition.asMap().keySet(); Enumeration<Long> keyIterator = this.dataSourceByPosition.keys();
for (Long pos : keySet) while (keyIterator.hasMoreElements())
{ {
ReentrantLock lock = this.saveLockContainer.getLockForPos(pos); Long pos = keyIterator.nextElement();
ReentrantLock posLock = this.saveLockContainer.getLockForPos(pos);
try try
{ {
lock.lock(); posLock.lock();
this.dataSourceByPosition.invalidate(pos); DataSourceSavedTimePair savedPair = this.dataSourceByPosition.getOrDefault(pos, null);
if (savedPair != null)
{
if (flushAll
|| savedPair.dataSourceHasTimedOut(this.saveDelayInMs))
{
this.dataSourceByPosition.remove(pos);
this.handleDataSourceRemoval(savedPair.dataSource);
}
}
} }
finally finally
{ {
lock.unlock(); posLock.unlock();
} }
} }
} }
@@ -197,7 +203,7 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
} }
else else
{ {
cache.dataSourceByPosition.cleanUp(); cache.cleanUp(false);
} }
}); });
} }
@@ -240,4 +246,36 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
CompletableFuture<Void> saveAsync(FullDataSourceV2 inputDataSource); CompletableFuture<Void> saveAsync(FullDataSourceV2 inputDataSource);
} }
/**
* used to keep track of when data sources
* were written to so we can flush them once
* enough time has passed.
*/
private static class DataSourceSavedTimePair
{
@NotNull
public final FullDataSourceV2 dataSource;
/** the last unix millisecond time this data source was written to */
public long lastWrittenDateTimeMs;
public DataSourceSavedTimePair(@NotNull FullDataSourceV2 dataSource)
{
this.dataSource = dataSource;
this.lastWrittenDateTimeMs = System.currentTimeMillis();
}
public void updateLastWrittenTimestamp()
{ this.lastWrittenDateTimeMs = System.currentTimeMillis(); }
public boolean dataSourceHasTimedOut(long msTillTimeout)
{
long currentTime = System.currentTimeMillis();
long timeSinceUpdate = currentTime - this.lastWrittenDateTimeMs;
return (timeSinceUpdate > msTillTimeout);
}
}
} }
@@ -9,6 +9,7 @@ import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV1Repo; import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV1Repo;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -21,7 +22,6 @@ import java.util.ArrayList;
import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
public class FullDataSourceProviderV1<TDhLevel extends IDhLevel> public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
@@ -79,7 +79,10 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException, DataCorruptedException protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException, DataCorruptedException
{ {
FullDataSourceV1 dataSource = FullDataSourceV1.createEmpty(dto.pos); FullDataSourceV1 dataSource = FullDataSourceV1.createEmpty(dto.pos);
dataSource.populateFromStream(dto, dto.getInputStream(), this.level); try (DhDataInputStream inputStream = dto.getInputStream())
{
dataSource.populateFromStream(dto, inputStream, this.level);
}
return dataSource; return dataSource;
} }
@@ -210,8 +210,18 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
} }
PriorityTaskPicker.Executor fileExecutor = ThreadPoolUtil.getFileHandlerExecutor(); PriorityTaskPicker.Executor renderLoadExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
if (fileExecutor == null || fileExecutor.getQueueSize() >= getMaxUpdateTaskCount() / 2) if (renderLoadExecutor == null
|| renderLoadExecutor.getQueueSize() >= getMaxUpdateTaskCount() / 2)
{
// don't queue additional world gen requests if the render loader handler is overwhelmed,
// otherwise LODs may not load in properly
return false;
}
PriorityTaskPicker.Executor fileHandlerExecutor = ThreadPoolUtil.getFileHandlerExecutor();
if (fileHandlerExecutor == null
|| fileHandlerExecutor.getQueueSize() >= getMaxUpdateTaskCount() / 2)
{ {
// don't queue additional world gen requests if the file handler is overwhelmed, // don't queue additional world gen requests if the file handler is overwhelmed,
// otherwise LODs may not load in properly // otherwise LODs may not load in properly
@@ -244,6 +254,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
return false; return false;
} }
int availableTaskSlots = maxWorldGenQueueCount - worldGenQueue.getWaitingTaskCount(); int availableTaskSlots = maxWorldGenQueueCount - worldGenQueue.getWaitingTaskCount();
if (availableTaskSlots <= 0) if (availableTaskSlots <= 0)
{ {
@@ -81,20 +81,13 @@ 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() + "].");
// This world was identified by the server directly, so we can know for sure which folder to use.
String serverKey = keyedClientLevel.getServerKey(); saveFolder = getSaveFolderByLevelId(keyedClientLevel.getServerLevelKey());
if (serverKey.isEmpty())
{
serverKey = getServerFolderName();
}
// This world was identified by the server directly, so we can know for sure which folder to use.
saveFolder = getSaveFolderByLevelId(serverKey, keyedClientLevel.getServerLevelKey());
} }
else else
{ {
// get the default folder // get the default folder
saveFolder = getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDhIdentifier()); saveFolder = getSaveFolderByLevelId(levelWrapper.getDhIdentifier());
} }
// Allow API users to override the save folder // Allow API users to override the save folder
@@ -123,7 +116,7 @@ public class ClientOnlySaveStructure implements ISaveStructure
return this.getSaveFolder(levelWrapper); return this.getSaveFolder(levelWrapper);
} }
return getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDimensionType().getName()); return getSaveFolderByLevelId(levelWrapper.getDimensionType().getName());
} }
@@ -180,11 +173,11 @@ public class ClientOnlySaveStructure implements ISaveStructure
} }
private static File getSaveFolderByLevelId(String folderName, String dimensionName) private static File getSaveFolderByLevelId(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
+ folderName + File.separatorChar + getServerFolderName() + File.separatorChar
+ dimensionName.replaceAll(":", "@@"); + dimensionName.replaceAll(":", "@@");
return new File(path); return new File(path);
@@ -26,12 +26,12 @@ import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource; import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.InProgressWorldGenTaskGroup; import com.seibel.distanthorizons.core.generation.tasks.InProgressWorldGenTaskGroup;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenTask; import com.seibel.distanthorizons.core.generation.tasks.WorldGenTask;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenTaskGroup; import com.seibel.distanthorizons.core.generation.tasks.WorldGenTaskGroup;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
@@ -76,6 +76,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private final IDhApiWorldGenerator generator; private final IDhApiWorldGenerator generator;
private final IDhServerLevel level;
/** contains the positions that need to be generated */ /** contains the positions that need to be generated */
private final ConcurrentHashMap<Long, WorldGenTask> waitingTasks = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Long, WorldGenTask> waitingTasks = new ConcurrentHashMap<>();
@@ -113,10 +114,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// constructors // // constructors //
//==============// //==============//
public WorldGenerationQueue(IDhApiWorldGenerator generator) public WorldGenerationQueue(IDhApiWorldGenerator generator, IDhServerLevel level)
{ {
LOGGER.info("Creating world gen queue"); LOGGER.info("Creating world gen queue");
this.generator = generator; this.generator = generator;
this.level = level;
this.lowestDataDetail = generator.getLargestDataDetailLevel(); this.lowestDataDetail = generator.getLargestDataDetailLevel();
this.highestDataDetail = generator.getSmallestDataDetailLevel(); this.highestDataDetail = generator.getSmallestDataDetailLevel();
@@ -185,45 +187,40 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// update the target pos // update the target pos
this.generationTargetPos = targetPos; this.generationTargetPos = targetPos;
// ensure the queuing thread is running // needs to be called at least once to start the queue
if (!this.generationQueueRunning) this.tryQueueNewWorldGenRequestsAsync();
{
this.startWorldGenQueuingThread();
}
} }
private void startWorldGenQueuingThread() private synchronized void tryQueueNewWorldGenRequestsAsync()
{ {
if (!DhApiWorldProxy.INSTANCE.worldLoaded()
|| DhApiWorldProxy.INSTANCE.getReadOnly())
{
return;
}
if (this.generationQueueRunning)
{
return;
}
this.generationQueueRunning = true; this.generationQueueRunning = true;
// queue world generation tasks on its own thread since this process is very slow and would lag the server thread // queue world generation tasks on its own thread since this process is very slow and would lag the server thread
this.queueingThread.execute(() -> this.queueingThread.execute(() ->
{ {
try try
{ {
// loop until the generator is shutdown this.generator.preGeneratorTaskStart();
while (!Thread.interrupted() && DhApiWorldProxy.INSTANCE.worldLoaded() && !DhApiWorldProxy.INSTANCE.getReadOnly())
// queue generation tasks until the generator is full, or there are no more tasks to generate
boolean taskStarted = true;
while (!this.isGeneratorBusy()
&& taskStarted)
{ {
this.generator.preGeneratorTaskStart(); taskStarted = this.startNextWorldGenTask(this.generationTargetPos);
// queue generation tasks until the generator is full, or there are no more tasks to generate
boolean taskStarted = true;
while (!this.isGeneratorBusy() && taskStarted)
{
taskStarted = this.startNextWorldGenTask(this.generationTargetPos);
if (!taskStarted)
{
int debugPointOne = 0;
}
}
// if there aren't any new tasks, wait a second before checking again // TODO replace with a listener instead
Thread.sleep(1000);
} }
} }
catch (InterruptedException e)
{
/* do nothing, this means the thread is being shut down */
}
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("queueing exception: " + e.getMessage(), e); LOGGER.error("queueing exception: " + e.getMessage(), e);
@@ -234,7 +231,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
} }
}); });
} }
public boolean isGeneratorBusy() private boolean isGeneratorBusy()
{ {
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getWorldGenExecutor(); PriorityTaskPicker.Executor executor = ThreadPoolUtil.getWorldGenExecutor();
if (executor == null) if (executor == null)
@@ -247,7 +244,6 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD; int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD;
return executor.getQueueSize() > maxWorldGenTaskCount; return executor.getQueueSize() > maxWorldGenTaskCount;
} }
/** /**
* @param targetPos the position to center the generation around * @param targetPos the position to center the generation around
* @return false if no tasks were found to generate * @return false if no tasks were found to generate
@@ -380,6 +376,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{ {
LOGGER.error("Unexpected error completing world gen task at pos: ["+DhSectionPos.toString(taskPos)+"].", e); LOGGER.error("Unexpected error completing world gen task at pos: ["+DhSectionPos.toString(taskPos)+"].", e);
} }
finally
{
this.tryQueueNewWorldGenRequestsAsync();
}
}); });
this.inProgressGenTasksByLodPos.put(taskPos, newTaskGroup); this.inProgressGenTasksByLodPos.put(taskPos, newTaskGroup);
@@ -410,8 +410,15 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{ {
try try
{ {
IChunkWrapper chunk = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray); IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
try (FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(chunk))
// TODO light data should be pulled (if possible) from the ChunkAccess object itself via ChunkFileReader.readLight
// but this should work for now
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<IChunkWrapper>();
nearbyChunkList.add(chunkWrapper);
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, this.level.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
try (FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(this.level.getLevelWrapper(), chunkWrapper))
{ {
LodUtil.assertTrue(dataSource != null); LodUtil.assertTrue(dataSource != null);
dataSourceConsumer.accept(dataSource); dataSourceConsumer.accept(dataSource);
@@ -465,6 +472,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
pooledDataSource.setRunApiChunkValidation(this.generator.runApiValidation()); pooledDataSource.setRunApiChunkValidation(this.generator.runApiValidation());
// only apply to children if we aren't at the bottom of the tree // only apply to children if we aren't at the bottom of the tree
pooledDataSource.applyToChildren = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL; pooledDataSource.applyToChildren = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
pooledDataSource.applyToParent = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12; pooledDataSource.applyToParent = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12;
@@ -655,8 +663,25 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
@Override @Override
public void debugRender(DebugRenderer renderer) public void debugRender(DebugRenderer renderer)
{ {
this.waitingTasks.keySet().forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.blue)); }); // show the wireframe a bit lower than world max height,
this.inProgressGenTasksByLodPos.forEach((pos, t) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.red)); }); // since most worlds don't render all the way up to the max height
int levelHeightRange = (this.level.getMaxY() - this.level.getMinY());
int maxY = this.level.getMaxY() - (levelHeightRange / 2);
// blue - queued
this.waitingTasks.keySet().forEach((pos) ->
{
renderer.renderBox(
new DebugRenderer.Box(pos, this.level.getMinY(), maxY, 0.05f, Color.blue));
});
// red - in progress
this.inProgressGenTasksByLodPos.forEach((pos, t) ->
{
renderer.renderBox(
new DebugRenderer.Box(pos, this.level.getMinY(), maxY, 0.05f, Color.red));
});
} }
@@ -76,7 +76,7 @@ public enum EPlatform
{ // For MacOS it should either output "Mac OS X" or "Darwin" depending on the version of MacOS { // For MacOS it should either output "Mac OS X" or "Darwin" depending on the version of MacOS
current = MACOS; current = MACOS;
} }
else if (osName.startsWith("bsd")) else if (osName.startsWith("bsd") || osName.startsWith("freebsd"))
{ // Depending on the BSD distro this will be different { // Depending on the BSD distro this will be different
current = BSD; current = BSD;
} }
@@ -19,7 +19,14 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache; import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.generation.DhLightingEngine;
@@ -29,18 +36,24 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.renderer.generic.CloudRenderHandler; import com.seibel.distanthorizons.core.render.renderer.generic.CloudRenderHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO; import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo; import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.KeyedLockContainer; import com.seibel.distanthorizons.core.util.KeyedLockContainer;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File; import java.io.File;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -73,13 +86,15 @@ public abstract class AbstractDhLevel implements IDhLevel
@Nullable @Nullable
protected CloudRenderHandler cloudRenderHandler; protected CloudRenderHandler cloudRenderHandler;
private IDhApiRenderableBoxGroup unexploredFogRenderableBoxGroup;
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
protected AbstractDhLevel() { } protected AbstractDhLevel() { }
/** /**
* Creating the repos requires access to the level file, which isn't * Creating the repos requires access to the level file, which isn't
@@ -141,7 +156,7 @@ public abstract class AbstractDhLevel implements IDhLevel
@Override @Override
public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash) public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash)
{ {
try (FullDataSourceV2 dataSource = FullDataSourceV2.createFromChunk(chunkWrapper)) try (FullDataSourceV2 dataSource = FullDataSourceV2.createFromChunk(this.getLevelWrapper(), chunkWrapper))
{ {
if (dataSource == null) if (dataSource == null)
{ {
@@ -377,6 +392,15 @@ public abstract class AbstractDhLevel implements IDhLevel
this.beaconBeamRepo.close(); this.beaconBeamRepo.close();
} }
GenericObjectRenderer genericRenderer = this.getGenericRenderer();
if (genericRenderer != null
&& this.unexploredFogRenderableBoxGroup != null)
{
genericRenderer.remove(this.unexploredFogRenderableBoxGroup.getId());
}
this.delayedFullDataSourceSaveCache.close(); this.delayedFullDataSourceSaveCache.close();
} }
@@ -328,6 +328,8 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
@Override @Override
public int getMinY() { return this.getLevelWrapper().getMinHeight(); } public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
@Override
public int getMaxY() { return this.getLevelWrapper().getMaxHeight(); }
@Override @Override
public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; } public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
@@ -36,17 +36,14 @@ import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue
import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource; import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.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.util.threading.ThreadPoolUtil;
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.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.LogManager;
@@ -297,9 +294,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
// getters // // getters //
//=========// //=========//
@Override
public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block) { return this.levelWrapper.getBlockColor(pos, biome, block); }
@Override @Override
public IClientLevelWrapper getClientLevelWrapper() { return this.levelWrapper; } public IClientLevelWrapper getClientLevelWrapper() { return this.levelWrapper; }
@@ -314,6 +308,8 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Override @Override
public int getMinY() { return this.levelWrapper.getMinHeight(); } public int getMinY() { return this.levelWrapper.getMinHeight(); }
@Override
public int getMaxY() { return this.levelWrapper.getMaxHeight(); }
@Override @Override
public FullDataSourceProviderV2 getFullDataProvider() { return this.dataFileHandler; } public FullDataSourceProviderV2 getFullDataProvider() { return this.dataFileHandler; }
@@ -94,20 +94,6 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
// level handling // // level handling //
//================// //================//
@Override //FIXME this can fail if the clientLevel isn't available yet, maybe in that case we could return -1 and handle it upstream?
public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block)
{
IClientLevelWrapper clientLevel = this.getClientLevelWrapper();
if (clientLevel == null)
{
return 0;
}
else
{
return clientLevel.getBlockColor(pos, biome, block);
}
}
@Nullable @Nullable
@Override @Override
public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); } public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); }
@@ -34,8 +34,6 @@ public interface IDhClientLevel extends IDhLevel
void render(DhApiRenderParam renderEventParam, IProfilerWrapper profiler); void render(DhApiRenderParam renderEventParam, IProfilerWrapper profiler);
void renderDeferred(DhApiRenderParam renderEventParam, IProfilerWrapper profiler); void renderDeferred(DhApiRenderParam renderEventParam, IProfilerWrapper profiler);
int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block);
@Nullable @Nullable
IClientLevelWrapper getClientLevelWrapper(); IClientLevelWrapper getClientLevelWrapper();
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
@@ -29,6 +31,7 @@ import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRend
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -41,6 +44,7 @@ public interface IDhLevel extends AutoCloseable, GeneratedFullDataSourceProvider
void worldGenTick(); void worldGenTick();
int getMinY(); int getMinY();
int getMaxY();
/** /**
* May return either a client or server level wrapper. <br> * May return either a client or server level wrapper. <br>
@@ -60,6 +64,7 @@ public interface IDhLevel extends AutoCloseable, GeneratedFullDataSourceProvider
void updateBeaconBeamsForChunkPos(DhChunkPos chunkPos, List<BeaconBeamDTO> activeBeamList); void updateBeaconBeamsForChunkPos(DhChunkPos chunkPos, List<BeaconBeamDTO> activeBeamList);
void updateBeaconBeamsForSectionPos(long sectionPos, List<BeaconBeamDTO> activeBeamList); void updateBeaconBeamsForSectionPos(long sectionPos, List<BeaconBeamDTO> activeBeamList);
/** @return null on server-only levels */
@Nullable @Nullable
BeaconBeamRepo getBeaconBeamRepo(); BeaconBeamRepo getBeaconBeamRepo();
@@ -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 serverKey, String levelKey); IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String levelKey);
void clearKeyedLevel(); void clearKeyedLevel();
boolean isEnabled(); boolean isEnabled();
@@ -24,9 +24,6 @@ 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();
@@ -85,7 +85,7 @@ public class ServerLevelModule implements AutoCloseable
// since core world generator's should have the lowest override priority // since core world generator's should have the lowest override priority
WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator); WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator);
} }
this.worldGenerationQueue = new WorldGenerationQueue(worldGenerator); this.worldGenerationQueue = new WorldGenerationQueue(worldGenerator, level);
} }
} }
@@ -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 is loading chunks. " + remainingChunkCountStr + " left."; String message = "DH is generating 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;
@@ -312,7 +312,12 @@ public class WorldGenModule implements Closeable
if (chunksPerSec > 0) if (chunksPerSec > 0)
{ {
long estimatedRemainingTime = (long) (remainingChunkCount / chunksPerSec); long estimatedRemainingTime = (long) (remainingChunkCount / chunksPerSec);
message += " ETA: " + FormatUtil.formatEta(Duration.ofSeconds(estimatedRemainingTime));//+ " at " + F3Screen.NUMBER_FORMAT.format(chunksPerSec) + " chunks/sec"; message += " ETA: " + FormatUtil.formatEta(Duration.ofSeconds(estimatedRemainingTime));
if (Config.Common.WorldGenerator.generationProgressIncludeChunksPerSecond.get())
{
message += " at " + F3Screen.NUMBER_FORMAT.format(chunksPerSec) + " chunks/sec";
}
} }
// only log if there are chunks needing to be generated // only log if there are chunks needing to be generated
@@ -62,6 +62,11 @@ public class ConfigBasedLogger
loggers.add(new WeakReference<>(this)); loggers.add(new WeakReference<>(this));
} }
private static boolean isLessSpecificThan(Level _this, Level other)
{
return _this.intLevel() >= other.intLevel();
}
private String _throwableToDetailString(Throwable t) private String _throwableToDetailString(Throwable t)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -95,16 +100,16 @@ public class ConfigBasedLogger
: this.logger.getMessageFactory().newMessage("{}", str); : this.logger.getMessageFactory().newMessage("{}", str);
String msgStr = msg.getFormattedMessage(); String msgStr = msg.getFormattedMessage();
if (mode.levelForFile.isLessSpecificThan(level)) if (isLessSpecificThan(mode.levelForFile, level))
{ {
Level logLevel = level.isLessSpecificThan(Level.INFO) ? Level.INFO : level; Level logLevel = isLessSpecificThan(level, Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length - 1] instanceof Throwable) if (param.length > 0 && param[param.length - 1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable) param[param.length - 1]); logger.log(logLevel, msgStr, (Throwable) param[param.length - 1]);
else else
logger.log(logLevel, msgStr); logger.log(logLevel, msgStr);
} }
if (MC != null && mode.levelForChat.isLessSpecificThan(level)) if (MC != null && isLessSpecificThan(mode.levelForChat, level))
{ {
if (param.length > 0 && param[param.length - 1] instanceof Throwable) if (param.length > 0 && param[param.length - 1] instanceof Throwable)
MC.logToChat(level, msgStr + "\n" + MC.logToChat(level, msgStr + "\n" +
@@ -67,6 +67,11 @@ public class ConfigBasedSpamLogger
loggers.add(new WeakReference<>(this)); loggers.add(new WeakReference<>(this));
} }
private static boolean isLessSpecificThan(Level _this, Level other)
{
return _this.intLevel() >= other.intLevel();
}
public void reset() public void reset()
{ {
logTries.set(0); logTries.set(0);
@@ -105,15 +110,15 @@ public class ConfigBasedSpamLogger
Message msg = logger.getMessageFactory().newMessage(str, param); Message msg = logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage(); String msgStr = msg.getFormattedMessage();
if (mode.levelForFile.isLessSpecificThan(level)) if (isLessSpecificThan(mode.levelForFile, level))
{ {
Level logLevel = level.isLessSpecificThan(Level.INFO) ? Level.INFO : level; Level logLevel = isLessSpecificThan(level, Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length - 1] instanceof Throwable) if (param.length > 0 && param[param.length - 1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable) param[param.length - 1]); logger.log(logLevel, msgStr, (Throwable) param[param.length - 1]);
else else
logger.log(logLevel, msgStr); logger.log(logLevel, msgStr);
} }
if (mode.levelForChat.isLessSpecificThan(level)) if (isLessSpecificThan(mode.levelForChat, level))
{ {
if (param.length > 0 && param[param.length - 1] instanceof Throwable) if (param.length > 0 && param[param.length - 1] instanceof Throwable)
MC.logToChat(level, msgStr + "\n" + MC.logToChat(level, msgStr + "\n" +
@@ -160,15 +165,15 @@ public class ConfigBasedSpamLogger
Message msg = logger.getMessageFactory().newMessage(str, param); Message msg = logger.getMessageFactory().newMessage(str, param);
String msgStr = msg.getFormattedMessage(); String msgStr = msg.getFormattedMessage();
if (mode.levelForFile.isLessSpecificThan(level)) if (isLessSpecificThan(mode.levelForFile, level))
{ {
Level logLevel = level.isLessSpecificThan(Level.INFO) ? Level.INFO : level; Level logLevel = isLessSpecificThan(level, Level.INFO) ? Level.INFO : level;
if (param.length > 0 && param[param.length - 1] instanceof Throwable) if (param.length > 0 && param[param.length - 1] instanceof Throwable)
logger.log(logLevel, msgStr, (Throwable) param[param.length - 1]); logger.log(logLevel, msgStr, (Throwable) param[param.length - 1]);
else else
logger.log(logLevel, msgStr); logger.log(logLevel, msgStr);
} }
if (mode.levelForChat.isLessSpecificThan(level)) if (isLessSpecificThan(mode.levelForChat, level))
{ {
if (param.length > 0 && param[param.length - 1] instanceof Throwable) if (param.length > 0 && param[param.length - 1] instanceof Throwable)
MC.logToChat(level, msgStr + "\n" + MC.logToChat(level, msgStr + "\n" +
@@ -56,6 +56,11 @@ public class SpamReducedLogger
loggers.add(new WeakReference<SpamReducedLogger>(this)); loggers.add(new WeakReference<SpamReducedLogger>(this));
} }
private static boolean isLessSpecificThan(Level _this, Level other)
{
return _this.intLevel() >= other.intLevel();
}
public void reset() public void reset()
{ {
logTries.set(0); logTries.set(0);
@@ -70,7 +75,7 @@ public class SpamReducedLogger
{ {
if (logTries.get() >= maxLogCount) if (logTries.get() >= maxLogCount)
return; return;
LOGGER.log(level.isLessSpecificThan(Level.INFO) ? Level.INFO : level, str, param); LOGGER.log(isLessSpecificThan(level, Level.INFO) ? Level.INFO : level, str, param);
} }
public void error(String str, Object... param) public void error(String str, Object... param)
@@ -107,7 +112,7 @@ public class SpamReducedLogger
{ {
if (logTries.getAndIncrement() >= maxLogCount) if (logTries.getAndIncrement() >= maxLogCount)
return; return;
LOGGER.log(level.isLessSpecificThan(Level.INFO) ? Level.INFO : level, str, param); LOGGER.log(isLessSpecificThan(level, Level.INFO) ? Level.INFO : level, str, param);
} }
public void errorInc(String str, Object... param) public void errorInc(String str, Object... param)
@@ -82,6 +82,7 @@ public class F3Screen
// multi thread pools // multi thread pools
PriorityTaskPicker.Executor worldGenPool = ThreadPoolUtil.getWorldGenExecutor(); PriorityTaskPicker.Executor worldGenPool = ThreadPoolUtil.getWorldGenExecutor();
PriorityTaskPicker.Executor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor(); PriorityTaskPicker.Executor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor();
PriorityTaskPicker.Executor renderLoadingPool = ThreadPoolUtil.getRenderLoadingExecutor();
PriorityTaskPicker.Executor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor(); PriorityTaskPicker.Executor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor();
PriorityTaskPicker.Executor lodBuilderPool = ThreadPoolUtil.getChunkToLodBuilderExecutor(); PriorityTaskPicker.Executor lodBuilderPool = ThreadPoolUtil.getChunkToLodBuilderExecutor();
PriorityTaskPicker.Executor networkPool = ThreadPoolUtil.getNetworkCompressionExecutor(); PriorityTaskPicker.Executor networkPool = ThreadPoolUtil.getNetworkCompressionExecutor();
@@ -92,6 +93,11 @@ public class F3Screen
ThreadPoolExecutor migrationPool = ThreadPoolUtil.getFullDataMigrationExecutor(); ThreadPoolExecutor migrationPool = ThreadPoolUtil.getFullDataMigrationExecutor();
AbstractDhWorld world = SharedApi.getAbstractDhWorld(); AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world == null)
{
return;
}
Iterable<? extends IDhLevel> levelIterator = world.getAllLoadedLevels(); Iterable<? extends IDhLevel> levelIterator = world.getAllLoadedLevels();
@@ -124,6 +130,7 @@ public class F3Screen
{ {
// multi thread pools // multi thread pools
messageList.add(getThreadPoolStatString("World Gen/Import", worldGenPool)); messageList.add(getThreadPoolStatString("World Gen/Import", worldGenPool));
messageList.add(getThreadPoolStatString("Render Load", renderLoadingPool));
messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool)); messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool));
messageList.add(getThreadPoolStatString("Update Propagator", updatePool)); messageList.add(getThreadPoolStatString("Update Propagator", updatePool));
messageList.add(getThreadPoolStatString("LOD Builder", lodBuilderPool)); messageList.add(getThreadPoolStatString("LOD Builder", lodBuilderPool));
@@ -153,7 +153,7 @@ public class ClientNetworkState implements Closeable
if (Config.Server.enableAdaptiveTransferSpeed.get()) if (Config.Server.enableAdaptiveTransferSpeed.get())
{ {
sessionConfig.constrainValue(Config.Server.maxDataTransferSpeed, this.congestionControl.getDesiredRate()); sessionConfig.constrainValue(Config.Server.playerBandwidthLimit, this.congestionControl.getDesiredRate());
} }
if (blocking) if (blocking)
@@ -45,7 +45,7 @@ public class SessionConfig implements INetworkObject
registerConfigEntry(Config.Server.maxSyncOnLoadRequestDistance, Math::min); registerConfigEntry(Config.Server.maxSyncOnLoadRequestDistance, Math::min);
registerConfigEntry(Config.Server.syncOnLoadRateLimit, Math::min); registerConfigEntry(Config.Server.syncOnLoadRateLimit, Math::min);
registerConfigEntry(Config.Server.maxDataTransferSpeed, (x, y) -> { registerConfigEntry(Config.Server.playerBandwidthLimit, (x, y) -> {
if (x == 0 && y == 0) if (x == 0 && y == 0)
{ {
return 0; return 0;
@@ -80,7 +80,7 @@ public class SessionConfig implements INetworkObject
public int getMaxSyncOnLoadDistance() { return this.getValue(Config.Server.maxSyncOnLoadRequestDistance); } public int getMaxSyncOnLoadDistance() { return this.getValue(Config.Server.maxSyncOnLoadRequestDistance); }
public int getSyncOnLoginRateLimit() { return this.getValue(Config.Server.syncOnLoadRateLimit); } public int getSyncOnLoginRateLimit() { return this.getValue(Config.Server.syncOnLoadRateLimit); }
public int getMaxDataTransferSpeed() { return this.getValue(Config.Server.maxDataTransferSpeed); } public int getPlayerBandwidthLimit() { return this.getValue(Config.Server.playerBandwidthLimit); }
@@ -11,6 +11,7 @@ import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import java.util.Objects; import java.util.Objects;
@@ -38,7 +39,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
{ {
if (message.isFirst) if (message.isFirst)
{ {
composite = Unpooled.compositeBuffer(); composite = UnpooledByteBufAllocator.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)
@@ -47,7 +48,8 @@ public class FullDataPayloadReceiver implements AutoCloseable
return null; return null;
} }
composite.addComponent(true, message.buffer); composite.addComponent(message.buffer);
composite.writerIndex(composite.writerIndex() + message.buffer.writerIndex());
LOGGER.debug("Updated full data buffer [" + message.bufferId + "]: [" + composite + "]."); LOGGER.debug("Updated full data buffer [" + message.bufferId + "]: [" + composite + "].");
return composite; return composite;
}); });
@@ -8,7 +8,6 @@ import io.netty.buffer.ByteBuf;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.*; import java.util.function.*;
public class FullDataPayloadSender implements AutoCloseable public class FullDataPayloadSender implements AutoCloseable
@@ -26,11 +25,14 @@ public class FullDataPayloadSender implements AutoCloseable
private final IntSupplier maxKBpsSupplier; private final IntSupplier maxKBpsSupplier;
private final ConcurrentLinkedQueue<PendingTransfer> transferQueue = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue<PendingTransfer> transferQueue = new ConcurrentLinkedQueue<>();
private final SharedBandwidthLimit sharedBandwidthLimit;
public FullDataPayloadSender(NetworkSession session, IntSupplier maxKBpsSupplier)
public FullDataPayloadSender(NetworkSession session, IntSupplier maxKBpsSupplier, SharedBandwidthLimit sharedBandwidthLimit)
{ {
this.session = session; this.session = session;
this.maxKBpsSupplier = maxKBpsSupplier; this.maxKBpsSupplier = maxKBpsSupplier;
this.sharedBandwidthLimit = sharedBandwidthLimit;
UPLOAD_TIMER.scheduleAtFixedRate(this.tickTimerTask, 0, 1000 / TICK_RATE); UPLOAD_TIMER.scheduleAtFixedRate(this.tickTimerTask, 0, 1000 / TICK_RATE);
} }
@@ -48,11 +50,16 @@ public class FullDataPayloadSender implements AutoCloseable
private void tick() private void tick()
{ {
int convertedMaxRate = this.maxKBpsSupplier.getAsInt(); int bandwidthShare = this.sharedBandwidthLimit.getBandwidthShare();
convertedMaxRate = convertedMaxRate > 0 ? convertedMaxRate : Integer.MAX_VALUE / 1000; int maxPlayerRate = Math.min(this.maxKBpsSupplier.getAsInt(), bandwidthShare);
// + 1 to account for rounding errors on values of < 4 // + 1 to account for rounding errors on values of < 4
int bytesToSend = (convertedMaxRate * 1000) / TICK_RATE + 1; int bytesToSend = maxPlayerRate > 0
? (maxPlayerRate * 1000) / TICK_RATE + 1
: Integer.MAX_VALUE;
this.sharedBandwidthLimit.setSenderActive(this, bytesToSend > 0);
while (bytesToSend > 0) while (bytesToSend > 0)
{ {
PendingTransfer pendingTransfer = this.transferQueue.peek(); PendingTransfer pendingTransfer = this.transferQueue.peek();
@@ -64,7 +71,7 @@ public class FullDataPayloadSender implements AutoCloseable
int chunkSize = Math.min(Math.min(bytesToSend, FULL_DATA_SPLIT_SIZE_IN_BYTES), pendingTransfer.buffer.readableBytes()); int chunkSize = Math.min(Math.min(bytesToSend, FULL_DATA_SPLIT_SIZE_IN_BYTES), pendingTransfer.buffer.readableBytes());
boolean isFirstChunk = pendingTransfer.buffer.readerIndex() == 0; boolean isFirstChunk = pendingTransfer.buffer.readerIndex() == 0;
FullDataSplitMessage chunkMessage = new FullDataSplitMessage(pendingTransfer.bufferId, pendingTransfer.buffer.readRetainedSlice(chunkSize), isFirstChunk); FullDataSplitMessage chunkMessage = new FullDataSplitMessage(pendingTransfer.bufferId, pendingTransfer.buffer.readSlice(chunkSize).retain(), isFirstChunk);
this.session.sendMessage(chunkMessage); this.session.sendMessage(chunkMessage);
bytesToSend -= chunkSize; bytesToSend -= chunkSize;
@@ -0,0 +1,36 @@
package com.seibel.distanthorizons.core.multiplayer.fullData;
import com.seibel.distanthorizons.core.config.Config;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class SharedBandwidthLimit
{
private final Set<FullDataPayloadSender> senders = Collections.newSetFromMap(new ConcurrentHashMap<>());
public void setSenderActive(FullDataPayloadSender sender, boolean active)
{
if (active)
{
this.senders.add(sender);
}
else
{
this.senders.remove(sender);
}
}
public int getBandwidthShare()
{
int globalBandwidthLimit = Config.Server.globalBandwidthLimit.get();
if (globalBandwidthLimit == 0)
{
globalBandwidthLimit = Integer.MAX_VALUE;
}
return globalBandwidthLimit / Math.max(this.senders.size(), 1);
}
}
@@ -5,6 +5,7 @@ import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel; import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig; import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadSender; import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadSender;
import com.seibel.distanthorizons.core.multiplayer.fullData.SharedBandwidthLimit;
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage; import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
@@ -27,10 +28,6 @@ public class ServerPlayerState implements Closeable
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage); private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
private final String serverKeyWithoutId = Config.Server.serverKey.get();
private final String serverKey = (this.serverKeyWithoutId.isEmpty() ? "" : Config.Server.serverId.get() + "_" + this.serverKeyWithoutId.trim())
.replaceAll("[^" + LevelInitMessage.PART_ALLOWED_CHARS_REGEX + " ]", "")
.replaceAll(" ", "_");
private String lastLevelKey = ""; private String lastLevelKey = "";
@@ -52,10 +49,10 @@ public class ServerPlayerState implements Closeable
// constructors // // constructors //
//==============// //==============//
public ServerPlayerState(IServerPlayerWrapper serverPlayer) public ServerPlayerState(IServerPlayerWrapper serverPlayer, SharedBandwidthLimit sharedBandwidthLimit)
{ {
this.networkSession = new NetworkSession(serverPlayer); this.networkSession = new NetworkSession(serverPlayer);
this.fullDataPayloadSender = new FullDataPayloadSender(this.networkSession, this.sessionConfig::getMaxDataTransferSpeed); this.fullDataPayloadSender = new FullDataPayloadSender(this.networkSession, this.sessionConfig::getPlayerBandwidthLimit, sharedBandwidthLimit);
this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) -> this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) ->
{ {
@@ -93,7 +90,7 @@ public class ServerPlayerState implements Closeable
if (!levelKey.equals(this.lastLevelKey)) if (!levelKey.equals(this.lastLevelKey))
{ {
this.lastLevelKey = levelKey; this.lastLevelKey = levelKey;
this.networkSession.sendMessage(new LevelInitMessage(this.serverKey, levelKey)); this.networkSession.sendMessage(new LevelInitMessage(levelKey));
} }
} }
} }
@@ -1,5 +1,6 @@
package com.seibel.distanthorizons.core.multiplayer.server; package com.seibel.distanthorizons.core.multiplayer.server;
import com.seibel.distanthorizons.core.multiplayer.fullData.SharedBandwidthLimit;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -16,6 +17,7 @@ public class ServerPlayerStateManager
private final ConcurrentMap<IServerPlayerWrapper, ServerPlayerState> connectedPlayerStateByPlayerWrapper = new ConcurrentHashMap<>(); private final ConcurrentMap<IServerPlayerWrapper, ServerPlayerState> connectedPlayerStateByPlayerWrapper = new ConcurrentHashMap<>();
private final ConcurrentMap<IServerPlayerWrapper, MessageQueueState> messageQueueByPlayerWrapper = new ConcurrentHashMap<>(); private final ConcurrentMap<IServerPlayerWrapper, MessageQueueState> messageQueueByPlayerWrapper = new ConcurrentHashMap<>();
private final SharedBandwidthLimit sharedBandwidthLimit = new SharedBandwidthLimit();
//========================// //========================//
@@ -24,7 +26,7 @@ public class ServerPlayerStateManager
public ServerPlayerState registerJoinedPlayer(IServerPlayerWrapper serverPlayer) public ServerPlayerState registerJoinedPlayer(IServerPlayerWrapper serverPlayer)
{ {
ServerPlayerState playerState = new ServerPlayerState(serverPlayer); ServerPlayerState playerState = new ServerPlayerState(serverPlayer, this.sharedBandwidthLimit);
this.connectedPlayerStateByPlayerWrapper.put(serverPlayer, playerState); this.connectedPlayerStateByPlayerWrapper.put(serverPlayer, playerState);
return playerState; return playerState;
} }
@@ -10,18 +10,12 @@ 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 LEVEL_KEY_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$", public static final String VALIDATION_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;
@@ -32,9 +26,8 @@ public class LevelInitMessage extends AbstractNetworkMessage
//==============// //==============//
public LevelInitMessage() { } public LevelInitMessage() { }
public LevelInitMessage(String serverKey, String levelKey) public LevelInitMessage(String levelKey)
{ {
this.serverKey = serverKey;
this.levelKey = levelKey; this.levelKey = levelKey;
this.serverTime = System.currentTimeMillis(); this.serverTime = System.currentTimeMillis();
} }
@@ -48,7 +41,6 @@ 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);
} }
@@ -56,7 +48,6 @@ 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();
} }
@@ -71,7 +62,6 @@ 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);
} }
@@ -16,13 +16,13 @@ import java.lang.ref.PhantomReference;
* @see PhantomArrayListCheckout * @see PhantomArrayListCheckout
* @see PhantomArrayListPool * @see PhantomArrayListPool
*/ */
public abstract class PhantomArrayListParent implements AutoCloseable public abstract class AbstractPhantomArrayList implements AutoCloseable
{ {
private static final Logger LOGGER = LogManager.getLogger(); private static final Logger LOGGER = LogManager.getLogger();
private final PhantomArrayListPool phantomArrayListPool; private final PhantomArrayListPool phantomArrayListPool;
private final PhantomReference<PhantomArrayListParent> phantomReference; private final PhantomReference<AbstractPhantomArrayList> phantomReference;
/** /**
* It's recommended to set this as null after the child's constructor * It's recommended to set this as null after the child's constructor
@@ -37,7 +37,7 @@ public abstract class PhantomArrayListParent implements AutoCloseable
//=============// //=============//
/** The Array counts can be 0 or greater. */ /** The Array counts can be 0 or greater. */
public PhantomArrayListParent(PhantomArrayListPool phantomArrayListPool, int byteArrayCount, int shortArrayCount, int longArrayCount) public AbstractPhantomArrayList(PhantomArrayListPool phantomArrayListPool, int byteArrayCount, int shortArrayCount, int longArrayCount)
{ {
if (byteArrayCount < 0 || shortArrayCount < 0 || longArrayCount < 0) if (byteArrayCount < 0 || shortArrayCount < 0 || longArrayCount < 0)
{ {
@@ -10,13 +10,12 @@ import org.jetbrains.annotations.Nullable;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* This keeps track of all the poolable * This keeps track of all the poolable
* arrays that can be retrieved via the {@link PhantomArrayListPool}. * arrays that can be retrieved via the {@link PhantomArrayListPool}.
* *
* @see PhantomArrayListParent * @see AbstractPhantomArrayList
* @see PhantomArrayListPool * @see PhantomArrayListPool
*/ */
public class PhantomArrayListCheckout implements AutoCloseable public class PhantomArrayListCheckout implements AutoCloseable
@@ -33,18 +33,18 @@ import java.util.concurrent.atomic.AtomicInteger;
* we pool these arrays when possible. <br><br> * we pool these arrays when possible. <br><br>
* *
* How pooled arrays can be returned: <br> * How pooled arrays can be returned: <br>
* 1. <b> Closing the {@link PhantomArrayListParent} </b> <br> * 1. <b> Closing the {@link AbstractPhantomArrayList} </b> <br>
* The fastest and most efficient method of returning pooled arrays * The fastest and most efficient method of returning pooled arrays
* is to call {@link AutoCloseable#close()}. <br><br> * is to call {@link AutoCloseable#close()}. <br><br>
* *
* 2. <b> {@link PhantomArrayListParent} Garbage Collection </b> <br> * 2. <b> {@link AbstractPhantomArrayList} Garbage Collection </b> <br>
* Some objects are used across many different threads and * Some objects are used across many different threads and
* cleanly closing them is impossible, so when the {@link PhantomArrayListParent} * cleanly closing them is impossible, so when the {@link AbstractPhantomArrayList}
* is automatically garbage collected we recover and recycle any * is automatically garbage collected we recover and recycle any
* arrays it checked out. * arrays it checked out.
* This is less efficient since it may allow a lot of additional arrays to * This is less efficient since it may allow a lot of additional arrays to
* be created while we wait for the garbage collector to run, but * be created while we wait for the garbage collector to run, but
* does prevent any leaks from {@link PhantomArrayListParent} that weren't closed. * does prevent any leaks from {@link AbstractPhantomArrayList} that weren't closed.
* *
* <br><br> * <br><br>
* <strong>Use Notes: </strong><br> * <strong>Use Notes: </strong><br>
@@ -83,9 +83,9 @@ public class PhantomArrayListPool
*/ */
public final boolean logGarbageCollectedStacks; public final boolean logGarbageCollectedStacks;
public final ConcurrentHashMap<Reference<? extends PhantomArrayListParent>, PhantomArrayListCheckout> public final ConcurrentHashMap<Reference<? extends AbstractPhantomArrayList>, PhantomArrayListCheckout>
phantomRefToCheckout = new ConcurrentHashMap<>(); phantomRefToCheckout = new ConcurrentHashMap<>();
public final ReferenceQueue<PhantomArrayListParent> phantomRefQueue = new ReferenceQueue<>(); public final ReferenceQueue<AbstractPhantomArrayList> phantomRefQueue = new ReferenceQueue<>();
private final ConcurrentLinkedQueue<SoftReference<PhantomArrayListCheckout>> pooledCheckoutsRefs = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue<SoftReference<PhantomArrayListCheckout>> pooledCheckoutsRefs = new ConcurrentLinkedQueue<>();
@@ -274,7 +274,7 @@ public class PhantomArrayListPool
allocationStackTraceCountPairList.clear(); allocationStackTraceCountPairList.clear();
Reference<? extends PhantomArrayListParent> phantomRef = pool.phantomRefQueue.poll(); Reference<? extends AbstractPhantomArrayList> phantomRef = pool.phantomRefQueue.poll();
while (phantomRef != null) while (phantomRef != null)
{ {
// return the pooled arrays // return the pooled arrays
@@ -378,7 +378,7 @@ public class PhantomArrayListPool
// return checkout // // return checkout //
//=================// //=================//
public void returnParentPhantomRef(@NotNull PhantomReference<PhantomArrayListParent> parentRef) public void returnParentPhantomRef(@NotNull PhantomReference<AbstractPhantomArrayList> parentRef)
{ {
try try
{ {
@@ -221,11 +221,27 @@ public class DhSectionPos
byte offset = (byte) (detailLevel - returnDetailLevel); byte offset = (byte) (detailLevel - returnDetailLevel);
return BitShiftUtil.powerOfTwo(offset); return BitShiftUtil.powerOfTwo(offset);
} }
/** @return how wide this section is in blocks */
public static int getBlockWidth(long pos) { return BitShiftUtil.powerOfTwo(getDetailLevel(pos)); }
/** @return how wide this section is in chunks */ /** @return how wide this section is in chunks */
public static int getChunkWidth(long pos) { return DhSectionPos.getBlockWidth(pos) / LodUtil.CHUNK_WIDTH; } public static int getChunkWidth(long pos) { return DhSectionPos.getBlockWidth(pos) / LodUtil.CHUNK_WIDTH; }
/** @see DhSectionPos#getDetailLevelWidthInBlocks(byte) */
public static int getBlockWidth(long pos) { return getDetailLevelWidthInBlocks(getDetailLevel(pos)); }
/**
* Returns how many blocks wide a single LOD at the given detail level would be in blocks. <br>
* IE: <br>
* <code>
* 0 => 1 <br>
* 1 => 2 <br>
* 2 => 4 <br>
* 3 => 8 <br>
* 4 => 16 <br>
* 5 => 32 <br>
* 6 => 64 <br>
* etc. <br>
* </code>
*/
public static int getDetailLevelWidthInBlocks(byte detailLevel) { return BitShiftUtil.powerOfTwo(detailLevel); }
public static DhBlockPos2D getCenterBlockPos(long pos) { return new DhBlockPos2D(getCenterBlockPosX(pos), getCenterBlockPosZ(pos)); } public static DhBlockPos2D getCenterBlockPos(long pos) { return new DhBlockPos2D(getCenterBlockPosX(pos), getCenterBlockPosZ(pos)); }
@@ -21,8 +21,6 @@ package com.seibel.distanthorizons.core.render;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalNotification;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.CachedColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.CachedColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
@@ -245,8 +243,10 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
} }
// full data retrieval (world gen) // queue full data retrieval (world gen) requests if needed
if (!this.fullDataRetrievalQueueRunning.get()) if (nodesNeedingRetrieval.size() != 0
&& !this.fullDataRetrievalQueueRunning.get()
&& this.fullDataSourceProvider.canQueueRetrieval())
{ {
this.fullDataRetrievalQueueRunning.set(true); this.fullDataRetrievalQueueRunning.set(true);
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() -> this.queueFullDataRetrievalTasks(playerPos, nodesNeedingRetrieval)); FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() -> this.queueFullDataRetrievalTasks(playerPos, nodesNeedingRetrieval));
@@ -342,7 +342,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// outdated when child LODs are updated. // outdated when child LODs are updated.
// (They'd have to be reloaded from file anyway during an update) // (They'd have to be reloaded from file anyway during an update)
long parentPos = renderSection.pos; long parentPos = renderSection.pos;
while (DhSectionPos.getDetailLevel(parentPos) <= this.treeMinDetailLevel) while (DhSectionPos.getDetailLevel(parentPos) <= this.treeRootDetailLevel)
{ {
QuadNode<LodRenderSection> parentNode = this.getNode(parentPos); QuadNode<LodRenderSection> parentNode = this.getNode(parentPos);
if (parentNode != null) if (parentNode != null)
@@ -579,7 +579,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// If not done corners may not be flush with the other LODs, which looks bad. // If not done corners may not be flush with the other LODs, which looks bad.
byte minSectionDetailLevel = this.getDetailLevelFromDistance(this.blockRenderDistanceDiameter); // get the minimum allowed detail level byte minSectionDetailLevel = this.getDetailLevelFromDistance(this.blockRenderDistanceDiameter); // get the minimum allowed detail level
minSectionDetailLevel -= 1; // -1 so corners can't render lower than their adjacent neighbors. space minSectionDetailLevel -= 1; // -1 so corners can't render lower than their adjacent neighbors. space
minSectionDetailLevel = (byte) Math.min(minSectionDetailLevel, this.treeMinDetailLevel); // don't allow rendering lower detail sections than what the tree contains minSectionDetailLevel = (byte) Math.min(minSectionDetailLevel, this.treeRootDetailLevel); // don't allow rendering lower detail sections than what the tree contains
this.minRenderDetailLevel = (byte) Math.max(minSectionDetailLevel, this.maxRenderDetailLevel); // respect the user's selected max resolution if it is lower detail (IE they want 2x2 block, but minSectionDetailLevel is specifically for 1x1 block render resolution) this.minRenderDetailLevel = (byte) Math.max(minSectionDetailLevel, this.maxRenderDetailLevel); // respect the user's selected max resolution if it is lower detail (IE they want 2x2 block, but minSectionDetailLevel is specifically for 1x1 block render resolution)
} }
@@ -685,9 +685,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
{ {
try try
{ {
// add a slight delay since we don't need to check the world gen queue every tick
Thread.sleep(WORLD_GEN_QUEUE_UPDATE_DELAY_IN_MS);
// sort the nodes from nearest to farthest // sort the nodes from nearest to farthest
ArrayList<LodRenderSection> nodeList = new ArrayList<>(nodesNeedingRetrieval); ArrayList<LodRenderSection> nodeList = new ArrayList<>(nodesNeedingRetrieval);
nodeList.sort((a, b) -> nodeList.sort((a, b) ->
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.render; package com.seibel.distanthorizons.core.render;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers; import com.google.common.base.Suppliers;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
@@ -46,6 +47,7 @@ import com.seibel.distanthorizons.core.util.KeyedLockContainer;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -57,7 +59,6 @@ import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
/** /**
* A render section represents an area that could be rendered. * A render section represents an area that could be rendered.
@@ -73,6 +74,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public final long pos; public final long pos;
private final IDhClientLevel level; private final IDhClientLevel level;
private final IClientLevelWrapper levelWrapper;
@WillNotClose @WillNotClose
private final FullDataSourceProviderV2 fullDataSourceProvider; private final FullDataSourceProviderV2 fullDataSourceProvider;
private final LodQuadTree quadTree; private final LodQuadTree quadTree;
@@ -149,12 +151,13 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.cachedRenderSourceByPos = cachedRenderSourceByPos; this.cachedRenderSourceByPos = cachedRenderSourceByPos;
this.renderLoadLockContainer = renderLoadLockContainer; this.renderLoadLockContainer = renderLoadLockContainer;
this.level = level; this.level = level;
this.levelWrapper = level.getClientLevelWrapper();
this.fullDataSourceProvider = fullDataSourceProvider; this.fullDataSourceProvider = fullDataSourceProvider;
this.uploadTaskCountRef = uploadTaskCountRef; this.uploadTaskCountRef = uploadTaskCountRef;
this.beaconRenderHandler = this.quadTree.beaconRenderHandler; this.beaconRenderHandler = this.quadTree.beaconRenderHandler;
this.beaconBeamRepo = this.level.getBeaconBeamRepo(); this.beaconBeamRepo = this.level.getBeaconBeamRepo();
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus); DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
} }
@@ -180,7 +183,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return false; return false;
} }
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getFileHandlerExecutor(); PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor();
if (executor == null || executor.isTerminated()) if (executor == null || executor.isTerminated())
{ {
return false; return false;
@@ -264,6 +267,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.SOUTH)); adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.SOUTH));
adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.EAST)); adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.EAST));
adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.WEST)); adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(DhSectionPos.getAdjacentPos(this.pos, EDhDirection.WEST));
//adjacentLoadFutures[0] = CompletableFuture.completedFuture(null);
//adjacentLoadFutures[1] = CompletableFuture.completedFuture(null);
//adjacentLoadFutures[2] = CompletableFuture.completedFuture(null);
//adjacentLoadFutures[3] = CompletableFuture.completedFuture(null);
return CompletableFuture.allOf(adjacentLoadFutures).thenRun(() -> return CompletableFuture.allOf(adjacentLoadFutures).thenRun(() ->
{ {
try (CachedColumnRenderSource northRenderSource = adjacentLoadFutures[0].get(); try (CachedColumnRenderSource northRenderSource = adjacentLoadFutures[0].get();
@@ -326,7 +333,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getFileHandlerExecutor(); PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor();
if (executor == null || executor.isTerminated()) if (executor == null || executor.isTerminated())
{ {
// should only happen if the threadpool is actively being re-sized // should only happen if the threadpool is actively being re-sized
@@ -342,7 +349,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// generate new render source // generate new render source
try (FullDataSourceV2 fullDataSource = this.fullDataSourceProvider.get(pos)) try (FullDataSourceV2 fullDataSource = this.fullDataSourceProvider.get(pos))
{ {
newCachedRenderSource.columnRenderSource = FullDataToRenderDataTransformer.transformFullDataToRenderSource(fullDataSource, this.level); newCachedRenderSource.columnRenderSource = FullDataToRenderDataTransformer.transformFullDataToRenderSource(fullDataSource, this.levelWrapper);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -503,7 +510,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public void tryQueuingMissingLodRetrieval() public void tryQueuingMissingLodRetrieval()
{ {
if (this.fullDataSourceProvider.canRetrieveMissingDataSources() && this.fullDataSourceProvider.canQueueRetrieval()) if (this.fullDataSourceProvider.canRetrieveMissingDataSources()
&& this.fullDataSourceProvider.canQueueRetrieval())
{ {
// calculate the missing positions if not already done // calculate the missing positions if not already done
if (this.missingGenerationPosFunc == null) if (this.missingGenerationPosFunc == null)
@@ -623,7 +631,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
//==============// //==============//
// base methods // // base methods //
//==============// //==============//
@@ -686,7 +693,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{ {
// remove the task from our executor if present // remove the task from our executor if present
// note: don't cancel the task since that prevents cleanup, we just don't want it to run // note: don't cancel the task since that prevents cleanup, we just don't want it to run
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getFileHandlerExecutor(); PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor();
if (executor != null && !executor.isTerminated()) if (executor != null && !executor.isTerminated())
{ {
Runnable runnable = this.getAndBuildRenderDataRunnable; Runnable runnable = this.getAndBuildRenderDataRunnable;
@@ -33,14 +33,13 @@ import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.Pos2D; import com.seibel.distanthorizons.core.pos.Pos2D;
import com.seibel.distanthorizons.core.render.glObject.buffer.QuadElementBuffer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.objects.SortedArraySet; import com.seibel.distanthorizons.core.util.objects.SortedArraySet;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode; import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
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 com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
@@ -176,50 +175,17 @@ public class RenderBufferHandler implements AutoCloseable
} }
} }
Pos2D cPos = this.lodQuadTree.getCenterBlockPos().toPos2D(); Pos2D centerPos = this.lodQuadTree.getCenterBlockPos().toPos2D();
// Now that we have the axis directions, we can sort the render list // Now that we have the axis directions, we can sort the render list
Comparator<LoadedRenderBuffer> farToNearComparator = (loadedBufferA, loadedBufferB) -> Comparator<LoadedRenderBuffer> farToNearComparator = (loadedBufferA, loadedBufferB) ->
{ {
Pos2D aPos = DhSectionPos.getCenterBlockPos(loadedBufferA.pos).toPos2D(); Pos2D aPos = DhSectionPos.getCenterBlockPos(loadedBufferA.pos).toPos2D();
Pos2D bPos = DhSectionPos.getCenterBlockPos(loadedBufferB.pos).toPos2D(); Pos2D bPos = DhSectionPos.getCenterBlockPos(loadedBufferB.pos).toPos2D();
if (true)
{
int aManhattanDistance = aPos.manhattanDist(cPos);
int bManhattanDistance = bPos.manhattanDist(cPos);
return bManhattanDistance - aManhattanDistance;
}
for (EDhDirection axisDirection : axisDirections) int aManhattanDistance = aPos.manhattanDist(centerPos);
{ int bManhattanDistance = bPos.manhattanDist(centerPos);
if (axisDirection.getAxis().isVertical()) return bManhattanDistance - aManhattanDistance;
{
continue; // We only sort in the horizontal direction
}
int abPosDifference;
if (axisDirection.getAxis().equals(EDhDirection.Axis.X))
{
abPosDifference = aPos.getX() - bPos.getX();
}
else
{
abPosDifference = aPos.getY() - bPos.getY();
}
if (abPosDifference == 0)
{
continue;
}
if (axisDirection.getAxisDirection().equals(EDhDirection.AxisDirection.NEGATIVE))
{
abPosDifference = -abPosDifference; // Reverse the sign
}
return abPosDifference;
}
return DhSectionPos.getDetailLevel(loadedBufferA.pos) - DhSectionPos.getDetailLevel(loadedBufferB.pos); // If all else fails, sort by detail
}; };
this.loadedNearToFarBuffers = new SortedArraySet<>((a, b) -> -farToNearComparator.compare(a, b)); // TODO is the comparator named wrong? this.loadedNearToFarBuffers = new SortedArraySet<>((a, b) -> -farToNearComparator.compare(a, b)); // TODO is the comparator named wrong?
@@ -279,19 +245,21 @@ public class RenderBufferHandler implements AutoCloseable
this.culledBufferCount = 0; this.culledBufferCount = 0;
} }
boolean rebuildAllBuffers = this.rebuildAllBuffers.getAndSet(false); // setup iterator with culling frustum
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.lodQuadTree.nodeIterator(); Iterator<QuadNode<LodRenderSection>> nodeIterator = this.lodQuadTree.nodeIteratorWithStoppingFilter((QuadNode<LodRenderSection> node) ->
while (nodeIterator.hasNext())
{ {
QuadNode<LodRenderSection> node = nodeIterator.next(); if (node == null)
{
return true;
}
long sectionPos = node.sectionPos;
LodRenderSection renderSection = node.value; LodRenderSection renderSection = node.value;
if (renderSection == null) if (renderSection == null)
{ {
continue; return false;
} }
try try
{ {
if (enableFrustumCulling) if (enableFrustumCulling)
@@ -311,22 +279,48 @@ public class RenderBufferHandler implements AutoCloseable
this.culledBufferCount++; this.culledBufferCount++;
} }
continue; return true;
} }
} }
return false;
}
catch (Exception e)
{
LOGGER.error("Unexpected issue during culling for node pos: ["+DhSectionPos.toString(node.sectionPos)+"], error: ["+e.getMessage()+"].", e);
// don't cull if there was an unexpected issue
return false;
}
});
while (nodeIterator.hasNext())
{
QuadNode<LodRenderSection> node = nodeIterator.next();
long sectionPos = node.sectionPos;
LodRenderSection renderSection = node.value;
if (renderSection == null)
{
continue;
}
try
{
ColumnRenderBuffer buffer = renderSection.renderBuffer; ColumnRenderBuffer buffer = renderSection.renderBuffer;
if (buffer == null || !renderSection.getRenderingEnabled()) if (buffer == null
|| !renderSection.getRenderingEnabled())
{ {
continue; continue;
} }
this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(buffer, sectionPos)); this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(buffer, sectionPos));
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Error updating QuadTree render source at " + renderSection.pos + ".", e); LOGGER.error("Error updating QuadTree render source at [" + DhSectionPos.toString(renderSection.pos) + "], error: ["+e.getMessage()+"].", e);
} }
} }
@@ -23,12 +23,12 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiGLErrorHandlingMode;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.jar.EPlatform;
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.GLMessages.*; import com.seibel.distanthorizons.core.util.objects.GLMessages.*;
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;
@@ -39,7 +39,6 @@ import org.lwjgl.opengl.GLUtil;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
@@ -173,17 +172,26 @@ public class GLProxy
// get the best automatic upload method // get the best automatic upload method
String vendor = GL32.glGetString(GL32.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION" String vendor = GL32.glGetString(GL32.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE")) if (EPlatform.get() != EPlatform.MACOS)
{ {
// NVIDIA card if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
this.preferredUploadMethod = this.bufferStorageSupported ? EDhApiGpuUploadMethod.BUFFER_STORAGE : EDhApiGpuUploadMethod.SUB_DATA; {
// NVIDIA card
this.preferredUploadMethod = this.bufferStorageSupported ? EDhApiGpuUploadMethod.BUFFER_STORAGE : EDhApiGpuUploadMethod.SUB_DATA;
}
else
{
// AMD or Intel card
this.preferredUploadMethod = this.bufferStorageSupported ? EDhApiGpuUploadMethod.BUFFER_STORAGE : EDhApiGpuUploadMethod.DATA;
}
} }
else else
{ {
// AMD or Intel card // Mac may have an issue with Buffer Storage, so default to the most basic
this.preferredUploadMethod = this.bufferStorageSupported ? EDhApiGpuUploadMethod.BUFFER_STORAGE : EDhApiGpuUploadMethod.DATA; // form of uploading
this.preferredUploadMethod = EDhApiGpuUploadMethod.DATA;
} }
GL_LOGGER.info("GPU Vendor [" + vendor + "], Preferred upload method is [" + this.preferredUploadMethod + "]."); GL_LOGGER.info("GPU Vendor [" + vendor + "] with OS [" + EPlatform.get().getName() + "], Preferred upload method is [" + this.preferredUploadMethod + "].");
@@ -202,7 +210,8 @@ public class GLProxy
//=========// //=========//
public static boolean hasInstance() { return instance != null; } public static boolean hasInstance() { return instance != null; }
public static GLProxy getInstance() /** @throws IllegalStateException if the Proxy hasn't been created yet and this is called outside the render thread */
public static GLProxy getInstance() throws IllegalStateException
{ {
if (instance == null) if (instance == null)
{ {
@@ -132,15 +132,6 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
throw e; throw e;
} }
if (this.uEarthRadius != -1) this.setUniform(this.uEarthRadius,
/*6371KM*/ 6371000.0f / Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get());
// Noise Uniforms
this.setUniform(this.uNoiseEnabled, Config.Client.Advanced.Graphics.NoiseTexture.enableNoiseTexture.get());
this.setUniform(this.uNoiseSteps, Config.Client.Advanced.Graphics.NoiseTexture.noiseSteps.get());
this.setUniform(this.uNoiseIntensity, Config.Client.Advanced.Graphics.NoiseTexture.noiseIntensity.get().floatValue());
this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get());
} }
@@ -191,6 +182,15 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
if (this.uDitherDhRendering != -1) this.setUniform(this.uDitherDhRendering, Config.Client.Advanced.Graphics.Quality.ditherDhFade.get()); if (this.uDitherDhRendering != -1) this.setUniform(this.uDitherDhRendering, Config.Client.Advanced.Graphics.Quality.ditherDhFade.get());
if (this.uEarthRadius != -1) this.setUniform(this.uEarthRadius,
/*6371KM*/ 6371000.0f / Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get());
// Noise Uniforms
this.setUniform(this.uNoiseEnabled, Config.Client.Advanced.Graphics.NoiseTexture.enableNoiseTexture.get());
this.setUniform(this.uNoiseSteps, Config.Client.Advanced.Graphics.NoiseTexture.noiseSteps.get());
this.setUniform(this.uNoiseIntensity, Config.Client.Advanced.Graphics.NoiseTexture.noiseIntensity.get().floatValue());
this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get());
// Debug // Debug
this.setUniform(this.uWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get()); this.setUniform(this.uWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.render.renderer; package com.seibel.distanthorizons.core.render.renderer;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.render.glObject.GLState; import com.seibel.distanthorizons.core.render.glObject.GLState;
import com.seibel.distanthorizons.core.render.renderer.shaders.FadeApplyShader; import com.seibel.distanthorizons.core.render.renderer.shaders.FadeApplyShader;
import com.seibel.distanthorizons.core.render.renderer.shaders.FadeShader; import com.seibel.distanthorizons.core.render.renderer.shaders.FadeShader;
@@ -106,7 +106,7 @@ public class LodRenderer
// The shader program // The shader program
private IDhApiShaderProgram lodRenderProgram = null; private IDhApiShaderProgram lodRenderProgram = null;
public QuadElementBuffer quadIBO = null; public QuadElementBuffer quadIBO = null;
public boolean isSetupComplete = false; private boolean isSetupComplete = false;
// frameBuffer and texture ID's for this renderer // frameBuffer and texture ID's for this renderer
private IDhApiFramebuffer framebuffer; private IDhApiFramebuffer framebuffer;
@@ -252,6 +252,11 @@ public class LodRenderer
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderSetupEvent.class, renderEventParam); ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderSetupEvent.class, renderEventParam);
this.setupGLStateAndRenderObjects(profiler, renderEventParam, renderingFirstPass); this.setupGLStateAndRenderObjects(profiler, renderEventParam, renderingFirstPass);
if (!this.isSetupComplete)
{
// this shouldn't normally happen, but just in case
return;
}
lightmap.bind(); lightmap.bind();
this.quadIBO.bind(); this.quadIBO.bind();
@@ -542,17 +547,17 @@ public class LodRenderer
activeFrameBuffer = framebufferOverride; activeFrameBuffer = framebufferOverride;
} }
this.setActiveFramebufferId(activeFrameBuffer.getId()); activeFramebufferId = activeFrameBuffer.getId();
this.setActiveDepthTextureId(this.depthTexture.getTextureId()); activeDepthTextureId = this.depthTexture.getTextureId();
if (this.nullableColorTexture != null) if (this.nullableColorTexture != null)
{ {
this.setActiveColorTextureId(this.nullableColorTexture.getTextureId()); activeColorTextureId = this.nullableColorTexture.getTextureId();
} }
else else
{ {
// get MC's color texture // get MC's color texture
int mcColorTextureId = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME); int mcColorTextureId = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
this.setActiveColorTextureId(mcColorTextureId); activeColorTextureId = mcColorTextureId;
} }
// Bind LOD frame buffer // Bind LOD frame buffer
activeFrameBuffer.bind(); activeFrameBuffer.bind();
@@ -633,7 +638,7 @@ public class LodRenderer
EVENT_LOGGER.warn("Renderer setup called but it has already completed setup!"); EVENT_LOGGER.warn("Renderer setup called but it has already completed setup!");
return; return;
} }
if (GLProxy.getInstance() == null) if (!GLProxy.hasInstance())
{ {
// shouldn't normally happen, but just in case // shouldn't normally happen, but just in case
EVENT_LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!"); EVENT_LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
@@ -646,7 +651,6 @@ public class LodRenderer
EVENT_LOGGER.info("Setting up renderer"); EVENT_LOGGER.info("Setting up renderer");
this.isSetupComplete = true;
this.lodRenderProgram = new DhTerrainShaderProgram(); this.lodRenderProgram = new DhTerrainShaderProgram();
this.quadIBO = new QuadElementBuffer(); this.quadIBO = new QuadElementBuffer();
@@ -675,9 +679,11 @@ public class LodRenderer
{ {
// 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
EVENT_LOGGER.warn("FrameBuffer ["+this.framebuffer.getId()+"] isn't complete."); EVENT_LOGGER.warn("FrameBuffer ["+this.framebuffer.getId()+"] isn't complete.");
return;
} }
this.isSetupComplete = true;
EVENT_LOGGER.info("Renderer setup complete"); EVENT_LOGGER.info("Renderer setup complete");
} }
finally finally
@@ -744,15 +750,12 @@ public class LodRenderer
// API functions // // API functions //
//===============// //===============//
private void setActiveFramebufferId(int frameBufferId) { activeFramebufferId = frameBufferId; }
/** Returns -1 if no frame buffer has been bound yet */ /** Returns -1 if no frame buffer has been bound yet */
public static int getActiveFramebufferId() { return activeFramebufferId; } public static int getActiveFramebufferId() { return activeFramebufferId; }
private void setActiveColorTextureId(int colorTextureId) { activeColorTextureId = colorTextureId; }
/** Returns -1 if no texture has been bound yet */ /** Returns -1 if no texture has been bound yet */
public static int getActiveColorTextureId() { return activeColorTextureId; } public static int getActiveColorTextureId() { return activeColorTextureId; }
private void setActiveDepthTextureId(int depthTextureId) { activeDepthTextureId = depthTextureId; }
/** /**
* FIXME it's possible for this to return an invalid texture ID if the renderer is being re-built at the same time * 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 * Returns -1 if no texture has been bound yet
@@ -771,7 +774,7 @@ public class LodRenderer
*/ */
private void cleanup() private void cleanup()
{ {
if (GLProxy.getInstance() == null) if (!GLProxy.hasInstance())
{ {
// shouldn't normally happen, but just in case // shouldn't normally happen, but just in case
EVENT_LOGGER.warn("Renderer Cleanup called but the GLProxy has never been initialized!"); EVENT_LOGGER.warn("Renderer Cleanup called but the GLProxy has never been initialized!");
@@ -804,8 +807,9 @@ public class LodRenderer
if (this.depthTexture != null) if (this.depthTexture != null)
this.depthTexture.destroy(); this.depthTexture.destroy();
this.setActiveDepthTextureId(-1); activeFramebufferId = -1;
this.setActiveColorTextureId(-1); activeColorTextureId = -1;
activeDepthTextureId = -1;
EVENT_LOGGER.info("Renderer Cleanup Complete"); EVENT_LOGGER.info("Renderer Cleanup Complete");
}); });
@@ -106,6 +106,7 @@ public class TestRenderer
public void render() public void render()
{ {
// TODO fix for MC 1.21.5
this.init(); this.init();
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer()); GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer());
@@ -76,6 +76,8 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
private static final ISodiumAccessor SODIUM = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class); private static final ISodiumAccessor SODIUM = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class);
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class); private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private static final DhApiRenderableBoxGroupShading DEFAULT_SHADING = DhApiRenderableBoxGroupShading.getUnshaded();
/** /**
* Can be used to troubleshoot the renderer. * Can be used to troubleshoot the renderer.
* If enabled several debug objects will render around (0,150,0). * If enabled several debug objects will render around (0,150,0).
@@ -519,13 +521,13 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
{ {
// update instance data // // update instance data //
profiler.push("setup"); profiler.push("vertex setup");
boxGroup.updateVertexAttributeData(); boxGroup.updateVertexAttributeData();
DhApiRenderableBoxGroupShading shading = boxGroup.shading; DhApiRenderableBoxGroupShading shading = boxGroup.shading;
if (shading == null) if (shading == null)
{ {
shading = DhApiRenderableBoxGroupShading.getUnshaded(); shading = DEFAULT_SHADING;
} }
shaderProgram.fillIndirectUniformData( shaderProgram.fillIndirectUniformData(
@@ -536,6 +538,7 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
// Bind instance data // // Bind instance data //
profiler.popPush("binding");
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceColorVbo); GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceColorVbo);
GL32.glEnableVertexAttribArray(1); GL32.glEnableVertexAttribArray(1);
@@ -211,6 +211,10 @@ public class RenderableBoxGroup
@Override @Override
public boolean removeIf(Predicate<? super DhApiRenderableBox> filter) { return this.boxList.removeIf(filter); } public boolean removeIf(Predicate<? super DhApiRenderableBox> filter) { return this.boxList.removeIf(filter); }
@Override @Override
public boolean remove(Object obj) { return this.boxList.remove(obj); }
@Override
public DhApiRenderableBox remove(int index) { return this.boxList.remove(index); }
@Override
public void replaceAll(UnaryOperator<DhApiRenderableBox> operator) { this.boxList.replaceAll(operator); } public void replaceAll(UnaryOperator<DhApiRenderableBox> operator) { this.boxList.replaceAll(operator); }
@Override @Override
public void sort(Comparator<? super DhApiRenderableBox> comparator) { this.boxList.sort(comparator); } public void sort(Comparator<? super DhApiRenderableBox> comparator) { this.boxList.sort(comparator); }
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.render.renderer.shaders; package com.seibel.distanthorizons.core.render.renderer.shaders;
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.render.glObject.GLState; 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;
@@ -36,7 +37,6 @@ public class FadeShader extends AbstractShaderRenderer
{ {
public static FadeShader INSTANCE = new FadeShader(); public static FadeShader INSTANCE = new FadeShader();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class); private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
@@ -60,9 +60,9 @@ public class FadeShader extends AbstractShaderRenderer
public int uStartFadeBlockDistance = -1; public int uStartFadeBlockDistance = -1;
public int uEndFadeBlockDistance = -1; public int uEndFadeBlockDistance = -1;
public int uMaxLevelHeight = -1; public int uMaxLevelHeight = -1;
public int uOnlyRenderLods = -1;
@@ -87,16 +87,17 @@ public class FadeShader extends AbstractShaderRenderer
this.uDhInvMvmProj = this.shader.tryGetUniformLocation("uDhInvMvmProj"); this.uDhInvMvmProj = this.shader.tryGetUniformLocation("uDhInvMvmProj");
this.uMcInvMvmProj = this.shader.tryGetUniformLocation("uMcInvMvmProj"); this.uMcInvMvmProj = this.shader.tryGetUniformLocation("uMcInvMvmProj");
this.uMcDepthTexture = this.shader.tryGetUniformLocation("uMcDepthMap"); this.uMcDepthTexture = this.shader.tryGetUniformLocation("uMcDepthTexture");
this.uDhDepthTexture = this.shader.tryGetUniformLocation("uDhDepthTexture"); this.uDhDepthTexture = this.shader.tryGetUniformLocation("uDhDepthTexture");
this.uCombinedMcDhColorTexture = this.shader.tryGetUniformLocation("uCombinedMcDhColorTexture"); this.uCombinedMcDhColorTexture = this.shader.tryGetUniformLocation("uCombinedMcDhColorTexture");
this.uDhColorTexture = this.shader.tryGetUniformLocation("uDhColorTexture"); this.uDhColorTexture = this.shader.tryGetUniformLocation("uDhColorTexture");
this.uStartFadeBlockDistance = this.shader.tryGetUniformLocation("uStartFadeBlockDistance"); this.uStartFadeBlockDistance = this.shader.tryGetUniformLocation("uStartFadeBlockDistance");
this.uEndFadeBlockDistance = this.shader.tryGetUniformLocation("uEndFadeBlockDistance"); this.uEndFadeBlockDistance = this.shader.tryGetUniformLocation("uEndFadeBlockDistance");
this.uMaxLevelHeight = this.shader.tryGetUniformLocation("uMaxLevelHeight"); this.uMaxLevelHeight = this.shader.tryGetUniformLocation("uMaxLevelHeight");
this.uOnlyRenderLods = this.shader.tryGetUniformLocation("uOnlyRenderLods");
} }
@@ -108,8 +109,8 @@ public class FadeShader extends AbstractShaderRenderer
@Override @Override
protected void onApplyUniforms(float partialTicks) protected void onApplyUniforms(float partialTicks)
{ {
if (this.inverseMcMvmProjMatrix != null) this.shader.setUniform(this.uMcInvMvmProj, this.inverseMcMvmProjMatrix); this.shader.setUniform(this.uMcInvMvmProj, this.inverseMcMvmProjMatrix);
if (this.inverseDhMvmProjMatrix != null) this.shader.setUniform(this.uDhInvMvmProj, this.inverseDhMvmProjMatrix); this.shader.setUniform(this.uDhInvMvmProj, this.inverseDhMvmProjMatrix);
float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(partialTicks); float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(partialTicks);
@@ -122,10 +123,12 @@ public class FadeShader extends AbstractShaderRenderer
float fadeStartDistance = dhNearClipDistance * 1.5f; float fadeStartDistance = dhNearClipDistance * 1.5f;
float fadeEndDistance = dhNearClipDistance * 1.9f; float fadeEndDistance = dhNearClipDistance * 1.9f;
if (this.uStartFadeBlockDistance != -1) this.shader.setUniform(this.uStartFadeBlockDistance, fadeStartDistance); this.shader.setUniform(this.uStartFadeBlockDistance, fadeStartDistance);
if (this.uEndFadeBlockDistance != -1) this.shader.setUniform(this.uEndFadeBlockDistance, fadeEndDistance); this.shader.setUniform(this.uEndFadeBlockDistance, fadeEndDistance);
if (this.uMaxLevelHeight != -1) this.shader.setUniform(this.uMaxLevelHeight, this.levelMaxHeight); this.shader.setUniform(this.uMaxLevelHeight, this.levelMaxHeight);
this.shader.setUniform(this.uOnlyRenderLods, Config.Client.Advanced.Debugging.lodOnlyMode.get());
} }
public void setProjectionMatrix(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks) public void setProjectionMatrix(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
@@ -28,7 +28,6 @@ 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;
@@ -50,14 +50,14 @@ public class SSAOShader extends AbstractShaderRenderer
// uniforms // uniforms
public int gProjUniform; public int uProj;
public int gInvProjUniform; public int uInvProj;
public int gSampleCountUniform; public int uSampleCount;
public int gRadiusUniform; public int uRadius;
public int gStrengthUniform; public int uStrength;
public int gMinLightUniform; public int uMinLight;
public int gBiasUniform; public int uBias;
public int gDepthMapUniform; public int uDepthMap;
@@ -73,14 +73,14 @@ public class SSAOShader extends AbstractShaderRenderer
); );
// uniform setup // uniform setup
this.gProjUniform = this.shader.getUniformLocation("gProj"); this.uProj = this.shader.getUniformLocation("uProj");
this.gInvProjUniform = this.shader.getUniformLocation("gInvProj"); this.uInvProj = this.shader.getUniformLocation("uInvProj");
this.gSampleCountUniform = this.shader.getUniformLocation("gSampleCount"); this.uSampleCount = this.shader.getUniformLocation("uSampleCount");
this.gRadiusUniform = this.shader.getUniformLocation("gRadius"); this.uRadius = this.shader.getUniformLocation("uRadius");
this.gStrengthUniform = this.shader.getUniformLocation("gStrength"); this.uStrength = this.shader.getUniformLocation("uStrength");
this.gMinLightUniform = this.shader.getUniformLocation("gMinLight"); this.uMinLight = this.shader.getUniformLocation("uMinLight");
this.gBiasUniform = this.shader.getUniformLocation("gBias"); this.uBias = this.shader.getUniformLocation("uBias");
this.gDepthMapUniform = this.shader.getUniformLocation("gDepthMap"); this.uDepthMap = this.shader.getUniformLocation("uDepthMap");
} }
@@ -100,28 +100,26 @@ public class SSAOShader extends AbstractShaderRenderer
@Override @Override
protected void onApplyUniforms(float partialTicks) protected void onApplyUniforms(float partialTicks)
{ {
this.shader.setUniform(this.gProjUniform, this.projection); this.shader.setUniform(this.uProj, this.projection);
this.shader.setUniform(this.gInvProjUniform, this.invertedProjection); this.shader.setUniform(this.uInvProj, this.invertedProjection);
this.shader.setUniform(this.gSampleCountUniform, this.shader.setUniform(this.uSampleCount, Config.Client.Advanced.Graphics.Ssao.sampleCount.get());
Config.Client.Advanced.Graphics.Ssao.sampleCount.get());
// Implicit Number cast needs to be done to prevent issues with the default value being a int // Explicit Number casts need to be done to prevent issues with the default value being an int
Number radius = Config.Client.Advanced.Graphics.Ssao.radius.get(); Number radius = Config.Client.Advanced.Graphics.Ssao.radius.get();
this.shader.setUniform(this.gRadiusUniform, radius.floatValue()); this.shader.setUniform(this.uRadius, radius.floatValue());
Number strength = Config.Client.Advanced.Graphics.Ssao.strength.get(); Number strength = Config.Client.Advanced.Graphics.Ssao.strength.get();
this.shader.setUniform(this.gStrengthUniform, strength.floatValue()); this.shader.setUniform(this.uStrength, strength.floatValue());
Number minLight = Config.Client.Advanced.Graphics.Ssao.minLight.get(); Number minLight = Config.Client.Advanced.Graphics.Ssao.minLight.get();
this.shader.setUniform(this.gMinLightUniform, minLight.floatValue()); this.shader.setUniform(this.uMinLight, minLight.floatValue());
Number bias = Config.Client.Advanced.Graphics.Ssao.bias.get(); Number bias = Config.Client.Advanced.Graphics.Ssao.bias.get();
this.shader.setUniform(this.gBiasUniform, bias.floatValue()); this.shader.setUniform(this.uBias, bias.floatValue());
GL32.glUniform1i(this.gDepthMapUniform, 0); GL32.glUniform1i(this.uDepthMap, 0);
} }
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListParent; import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.network.INetworkObject; import com.seibel.distanthorizons.core.network.INetworkObject;
@@ -47,7 +47,7 @@ import java.io.*;
/** handles storing {@link FullDataSourceV2}'s in the database. */ /** handles storing {@link FullDataSourceV2}'s in the database. */
public class FullDataSourceV2DTO public class FullDataSourceV2DTO
extends PhantomArrayListParent extends AbstractPhantomArrayList
implements IBaseDTO<Long>, INetworkObject, AutoCloseable implements IBaseDTO<Long>, INetworkObject, AutoCloseable
{ {
public static final boolean VALIDATE_INPUT_DATAPOINTS = true; public static final boolean VALIDATE_INPUT_DATAPOINTS = true;
@@ -237,63 +237,63 @@ public class FullDataSourceV2DTO
// normally a DhStream should be the topmost stream to prevent closing the stream accidentally, // normally a DhStream should be the topmost stream to prevent closing the stream accidentally,
// but since this stream will be closed immediately after writing anyway, it won't be an issue // but since this stream will be closed immediately after writing anyway, it won't be an issue
DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum); try (DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
// write the data
int dataArrayLength = FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH;
for (int xz = 0; xz < dataArrayLength; xz++)
{ {
LongArrayList dataColumn = inputDataArray[xz]; // write the data
int dataArrayLength = FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH;
// write column length for (int xz = 0; xz < dataArrayLength; xz++)
short columnLength = (dataColumn != null) ? (short) dataColumn.size() : 0;
// a short is used instead of an int because at most we store 4096 vertical slices and a
// short fits that with less wasted spaces vs an int (short has max value of 32,767 vs int's max of 2 billion)
compressedOut.writeShort(columnLength);
// write column data (will be skipped if no data was present)
for (int y = 0; y < columnLength; y++)
{ {
compressedOut.writeLong(dataColumn.getLong(y)); LongArrayList dataColumn = inputDataArray[xz];
// write column length
short columnLength = (dataColumn != null) ? (short) dataColumn.size() : 0;
// a short is used instead of an int because at most we store 4096 vertical slices and a
// short fits that with less wasted spaces vs an int (short has max value of 32,767 vs int's max of 2 billion)
compressedOut.writeShort(columnLength);
// write column data (will be skipped if no data was present)
for (int y = 0; y < columnLength; y++)
{
compressedOut.writeLong(dataColumn.getLong(y));
}
} }
// generate the checksum
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
} }
// generate the checksum
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
} }
private static void readBlobToDataSourceDataArray(ByteArrayList inputCompressedDataByteArray, LongArrayList[] outputDataLongArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException private static void readBlobToDataSourceDataArray(ByteArrayList inputCompressedDataByteArray, LongArrayList[] outputDataLongArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{ {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements()); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements());
DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum); try (DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
// read the data
int dataArrayLength = FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH;
for (int xz = 0; xz < dataArrayLength; xz++)
{ {
// read the column length // read the data
short dataColumnLength = compressedIn.readShort(); // separate variables are used for debugging and in case validation wants to be added later int dataArrayLength = FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH;
if (dataColumnLength < 0) for (int xz = 0; xz < dataArrayLength; xz++)
{ {
throw new DataCorruptedException("Read DataSource Blob data at index ["+xz+"], column length ["+dataColumnLength+"] should be greater than zero."); // read the column length
} short dataColumnLength = compressedIn.readShort(); // separate variables are used for debugging and in case validation wants to be added later
if (dataColumnLength < 0)
LongArrayList dataColumn = outputDataLongArray[xz];
ListUtil.clearAndSetSize(dataColumn, dataColumnLength);
// read column data (will be skipped if no data was present)
for (int y = 0; y < dataColumnLength; y++)
{
long dataPoint = compressedIn.readLong();
if (VALIDATE_INPUT_DATAPOINTS)
{ {
FullDataPointUtil.validateDatapoint(dataPoint); throw new DataCorruptedException("Read DataSource Blob data at index [" + xz + "], column length [" + dataColumnLength + "] should be greater than zero.");
}
LongArrayList dataColumn = outputDataLongArray[xz];
ListUtil.clearAndSetSize(dataColumn, dataColumnLength);
// read column data (will be skipped if no data was present)
for (int y = 0; y < dataColumnLength; y++)
{
long dataPoint = compressedIn.readLong();
if (VALIDATE_INPUT_DATAPOINTS)
{
FullDataPointUtil.validateDatapoint(dataPoint);
}
dataColumn.set(y, dataPoint);
} }
dataColumn.set(y, dataPoint);
} }
} }
} }
@@ -302,23 +302,23 @@ public class FullDataSourceV2DTO
private static void writeGenerationStepsToBlob(ByteArrayList inputColumnGenStepByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException private static void writeGenerationStepsToBlob(ByteArrayList inputColumnGenStepByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
{ {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum); try (DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
for (int i = 0; i < inputColumnGenStepByteArray.size(); i++)
{ {
compressedOut.writeByte(inputColumnGenStepByteArray.getByte(i)); for (int i = 0; i < inputColumnGenStepByteArray.size(); i++)
{
compressedOut.writeByte(inputColumnGenStepByteArray.getByte(i));
}
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
} }
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
} }
private static void readBlobToGenerationSteps(ByteArrayList inputCompressedDataByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException private static void readBlobToGenerationSteps(ByteArrayList inputCompressedDataByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{ {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements()); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements());
DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum);
try try(DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
{ {
compressedIn.readFully(outputByteArray.elements(), 0, FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH); compressedIn.readFully(outputByteArray.elements(), 0, FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH);
} }
@@ -332,23 +332,23 @@ public class FullDataSourceV2DTO
private static void writeWorldCompressionModeToBlob(ByteArrayList inputWorldCompressionModeByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException private static void writeWorldCompressionModeToBlob(ByteArrayList inputWorldCompressionModeByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
{ {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum); try (DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
for (int i = 0; i < inputWorldCompressionModeByteArray.size(); i++)
{ {
compressedOut.write(inputWorldCompressionModeByteArray.getByte(i)); for (int i = 0; i < inputWorldCompressionModeByteArray.size(); i++)
{
compressedOut.write(inputWorldCompressionModeByteArray.getByte(i));
}
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
} }
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
} }
private static void readBlobToWorldCompressionMode(ByteArrayList inputCompressedDataByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException private static void readBlobToWorldCompressionMode(ByteArrayList inputCompressedDataByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{ {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements()); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements());
DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum);
try try(DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
{ {
compressedIn.readFully(outputByteArray.elements(), 0, FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH); compressedIn.readFully(outputByteArray.elements(), 0, FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH);
} }
@@ -362,21 +362,23 @@ public class FullDataSourceV2DTO
private static void writeDataMappingToBlob(FullDataPointIdMap mapping, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException private static void writeDataMappingToBlob(FullDataPointIdMap mapping, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
{ {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum); try(DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
{
mapping.serialize(compressedOut); mapping.serialize(compressedOut);
compressedOut.flush(); compressedOut.flush();
byteArrayOutputStream.close(); byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray()); outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
}
} }
private static FullDataPointIdMap readBlobToDataMapping(ByteArrayList compressedMappingByteArray, long pos, @NotNull ILevelWrapper levelWrapper, EDhApiDataCompressionMode compressionModeEnum) throws IOException, InterruptedException, DataCorruptedException private static FullDataPointIdMap readBlobToDataMapping(ByteArrayList compressedMappingByteArray, long pos, @NotNull ILevelWrapper levelWrapper, EDhApiDataCompressionMode compressionModeEnum) throws IOException, InterruptedException, DataCorruptedException
{ {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressedMappingByteArray.elements()); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressedMappingByteArray.elements());
DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum); try (DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
{
FullDataPointIdMap mapping = FullDataPointIdMap.deserialize(compressedIn, pos, levelWrapper); FullDataPointIdMap mapping = FullDataPointIdMap.deserialize(compressedIn, pos, levelWrapper);
return mapping; return mapping;
}
} }
@@ -35,7 +35,6 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO> public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO>
{ {
@@ -96,20 +95,20 @@ public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO>
return dto; return dto;
} }
private final String insertSqlTemplate =
"INSERT INTO "+this.getTableName() + " (\n" +
" BlockPosX, BlockPosY, BlockPosZ, \n" +
" ColorR, ColorG, ColorB, \n" +
" LastModifiedUnixDateTime, CreatedUnixDateTime) \n" +
"VALUES( \n" +
" ?, ?, ?, \n" +
" ?, ?, ?, \n" +
" ?, ? \n" +
");";
@Override @Override
public PreparedStatement createInsertStatement(BeaconBeamDTO dto) throws SQLException public PreparedStatement createInsertStatement(BeaconBeamDTO dto) throws SQLException
{ {
String sql = PreparedStatement statement = this.createPreparedStatement(this.insertSqlTemplate);
"INSERT INTO "+this.getTableName() + " (\n" +
" BlockPosX, BlockPosY, BlockPosZ, \n" +
" ColorR, ColorG, ColorB, \n" +
" LastModifiedUnixDateTime, CreatedUnixDateTime) \n" +
"VALUES( \n" +
" ?, ?, ?, \n" +
" ?, ?, ?, \n" +
" ?, ? \n" +
");";
PreparedStatement statement = this.createPreparedStatement(sql);
if (statement == null) if (statement == null)
{ {
return null; return null;
@@ -131,16 +130,16 @@ public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO>
return statement; return statement;
} }
private final String updateSqlTemplate =
"UPDATE "+this.getTableName()+" \n" +
"SET \n" +
" ColorR = ?, ColorG = ?, ColorB = ?, \n" +
" LastModifiedUnixDateTime = ? \n" +
"WHERE BlockPosX = ? AND BlockPosY = ? AND BlockPosZ = ?";
@Override @Override
public PreparedStatement createUpdateStatement(BeaconBeamDTO dto) throws SQLException public PreparedStatement createUpdateStatement(BeaconBeamDTO dto) throws SQLException
{ {
String sql = PreparedStatement statement = this.createPreparedStatement(this.updateSqlTemplate);
"UPDATE "+this.getTableName()+" \n" +
"SET \n" +
" ColorR = ?, ColorG = ?, ColorB = ?, \n" +
" LastModifiedUnixDateTime = ? \n" +
"WHERE BlockPosX = ? AND BlockPosY = ? AND BlockPosZ = ?";
PreparedStatement statement = this.createPreparedStatement(sql);
if (statement == null) if (statement == null)
{ {
return null; return null;
@@ -21,7 +21,6 @@ package com.seibel.distanthorizons.core.sql.repo;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO; import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -30,7 +29,6 @@ import java.io.File;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Map;
public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO> public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO>
{ {
@@ -87,20 +85,25 @@ public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO>
return dto; return dto;
} }
private final String insertSqlTemplate =
"INSERT INTO "+this.getTableName() + " (\n" +
" ChunkPosX, ChunkPosZ, \n" +
" ChunkHash, \n" +
" LastModifiedUnixDateTime, CreatedUnixDateTime) \n" +
"VALUES( \n" +
" ?, ?, \n" +
" ?, \n" +
" ?, ? \n" +
");";
@Override @Override
public PreparedStatement createInsertStatement(ChunkHashDTO dto) throws SQLException public PreparedStatement createInsertStatement(ChunkHashDTO dto) throws SQLException
{ {
String sql = PreparedStatement statement = this.createPreparedStatement(this.insertSqlTemplate);
"INSERT INTO "+this.getTableName() + " (\n" + if (statement == null)
" ChunkPosX, ChunkPosZ, \n" + {
" ChunkHash, \n" + return null;
" LastModifiedUnixDateTime, CreatedUnixDateTime) \n" + }
"VALUES( \n" +
" ?, ?, \n" +
" ?, \n" +
" ?, ? \n" +
");";
PreparedStatement statement = this.createPreparedStatement(sql);
int i = 1; int i = 1;
statement.setObject(i++, dto.pos.getX()); statement.setObject(i++, dto.pos.getX());
@@ -114,16 +117,21 @@ public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO>
return statement; return statement;
} }
private final String updateSqlTemplate =
"UPDATE "+this.getTableName()+" \n" +
"SET \n" +
" ChunkHash = ? \n" +
" ,LastModifiedUnixDateTime = ? \n" +
"WHERE ChunkPosX = ? AND ChunkPosZ = ?";
@Override @Override
public PreparedStatement createUpdateStatement(ChunkHashDTO dto) throws SQLException public PreparedStatement createUpdateStatement(ChunkHashDTO dto) throws SQLException
{ {
String sql = PreparedStatement statement = this.createPreparedStatement(updateSqlTemplate);
"UPDATE "+this.getTableName()+" \n" + if (statement == null)
"SET \n" + {
" ChunkHash = ? \n" + return null;
" ,LastModifiedUnixDateTime = ? \n" + }
"WHERE ChunkPosX = ? AND ChunkPosZ = ?";
PreparedStatement statement = this.createPreparedStatement(sql);
int i = 1; int i = 1;
statement.setObject(i++, dto.chunkHash); statement.setObject(i++, dto.chunkHash);
@@ -312,22 +312,22 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
/** should be be very similar to {@link FullDataSourceV2Repo#getChildPositionsToUpdateSql} */ /** should be be very similar to {@link FullDataSourceV2Repo#getChildPositionsToUpdateSql} */
private final String getParentPositionsToUpdateSql = private final String getParentPositionsToUpdateSql =
"SELECT DetailLevel, PosX, PosZ, " + "SELECT DetailLevel, PosX, PosZ, " +
" abs((PosX << (6 + DetailLevel)) - ?) + abs((PosZ << (6 + DetailLevel)) - ?) AS Distance " + " abs((PosX << (6 + DetailLevel)) - ?) + abs((PosZ << (6 + DetailLevel)) - ?) AS Distance " +
"FROM " + this.getTableName() + " " + "FROM " + this.getTableName() + " " +
"WHERE ApplyToParent = 1 " + "WHERE ApplyToParent = 1 " +
"ORDER BY DetailLevel ASC, Distance ASC " + "ORDER BY DetailLevel ASC, Distance ASC " +
"LIMIT ?; "; "LIMIT ?; ";
public LongArrayList getPositionsToUpdate(int targetBlockPosX, int targetBlockPosZ, int returnCount) public LongArrayList getPositionsToUpdate(int targetBlockPosX, int targetBlockPosZ, int returnCount)
{ return this.getPositionsToUpdate(targetBlockPosX, targetBlockPosZ, returnCount, true); } { return this.getPositionsToUpdate(targetBlockPosX, targetBlockPosZ, returnCount, true); }
/** should be be very similar to {@link FullDataSourceV2Repo#getParentPositionsToUpdateSql} */ /** should be be very similar to {@link FullDataSourceV2Repo#getParentPositionsToUpdateSql} */
private final String getChildPositionsToUpdateSql = private final String getChildPositionsToUpdateSql =
"SELECT DetailLevel, PosX, PosZ, " + "SELECT DetailLevel, PosX, PosZ, " +
" abs((PosX << (6 + DetailLevel)) - ?) + abs((PosZ << (6 + DetailLevel)) - ?) AS Distance " + " abs((PosX << (6 + DetailLevel)) - ?) + abs((PosZ << (6 + DetailLevel)) - ?) AS Distance " +
"FROM " + this.getTableName() + " " + "FROM " + this.getTableName() + " " +
"WHERE ApplyToChildren = 1 " + "WHERE ApplyToChildren = 1 " +
"ORDER BY DetailLevel ASC, Distance ASC " + "ORDER BY DetailLevel ASC, Distance ASC " +
"LIMIT ?; "; "LIMIT ?; ";
public LongArrayList getChildPositionsToUpdate(int targetBlockPosX, int targetBlockPosZ, int returnCount) public LongArrayList getChildPositionsToUpdate(int targetBlockPosX, int targetBlockPosZ, int returnCount)
{ return this.getPositionsToUpdate(targetBlockPosX, targetBlockPosZ, returnCount, false); } { return this.getPositionsToUpdate(targetBlockPosX, targetBlockPosZ, returnCount, false); }
@@ -410,10 +410,9 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
byte compressionModeEnumValue = result.getByte("CompressionMode"); byte compressionModeEnumValue = result.getByte("CompressionMode");
EDhApiDataCompressionMode compressionModeEnum = EDhApiDataCompressionMode.getFromValue(compressionModeEnumValue); EDhApiDataCompressionMode compressionModeEnum = EDhApiDataCompressionMode.getFromValue(compressionModeEnumValue);
try // decompress the data
try(DhDataInputStream compressedIn = new DhDataInputStream(result.getBinaryStream("ColumnGenerationStep"), compressionModeEnum))
{ {
// decompress the data
DhDataInputStream compressedIn = new DhDataInputStream(result.getBinaryStream("ColumnGenerationStep"), compressionModeEnum);
putAllBytes(compressedIn, outputByteArray); putAllBytes(compressedIn, outputByteArray);
} }
catch (IOException e) catch (IOException e)
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.util;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.IColumnDataView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.IColumnDataView;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListParent; import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException; import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
@@ -46,7 +46,7 @@ import it.unimi.dsi.fastutil.shorts.ShortArrays;
* *
* @author Builderb0y * @author Builderb0y
*/ */
public class RenderDataPointReducingList extends PhantomArrayListParent public class RenderDataPointReducingList extends AbstractPhantomArrayList
{ {
/** /**
@@ -19,9 +19,10 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams; package com.seibel.distanthorizons.core.util.objects.dataStreams;
import com.github.luben.zstd.RecyclingBufferPool;
import com.github.luben.zstd.ZstdInputStream;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode; import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
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;
@@ -42,7 +43,6 @@ 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();
@@ -61,8 +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: case Z_STD:
// return new ZstdCompressorInputStream(stream, ZSTD_RESETTABLE_ARRAY_CACHE_GETTER.get()); return new ZstdInputStream(stream, RecyclingBufferPool.INSTANCE);
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();
@@ -113,7 +113,10 @@ public class DhDataInputStream extends DataInputStream
} }
} }
@Override // TODO at one point closing the streams caused errors, is that due to a bug with LZMA streams or some bug in DH's code that was since fixed?
public void close() throws IOException { /* Do nothing. */ } // if streams aren't closed that cause cause higher-than-expected native memory use if the GC decides
// it doesn't want to clear the stream objects
//@Override
//public void close() throws IOException { /* Do nothing. */ }
} }
@@ -19,11 +19,11 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams; package com.seibel.distanthorizons.core.util.objects.dataStreams;
import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode; 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.*;
@@ -55,8 +55,8 @@ public class DhDataOutputStream extends DataOutputStream
case UNCOMPRESSED: case UNCOMPRESSED:
return stream; return stream;
//case Z_STD: case Z_STD:
// return new ZstdCompressorOutputStream(stream, 3, true, true); return new ZstdOutputStream(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,
@@ -88,7 +88,11 @@ public class DhDataOutputStream extends DataOutputStream
} }
} }
@Override
public void close() throws IOException { /* Do nothing. */ } // TODO at one point closing the streams caused errors, is that due to a bug with LZMA streams or some bug in DH's code that was since fixed?
// if streams aren't closed that cause cause higher-than-expected native memory use if the GC decides
// it doesn't want to clear the stream objects
//@Override
//public void close() throws IOException { /* Do nothing. */ }
} }
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.util.objects.quadTree.iterators.QuadNodeD
import com.seibel.distanthorizons.core.util.objects.quadTree.iterators.QuadTreeNodeIterator; import com.seibel.distanthorizons.core.util.objects.quadTree.iterators.QuadTreeNodeIterator;
import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongIterator;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.util.Iterator; import java.util.Iterator;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -37,7 +38,11 @@ public class QuadNode<T>
public final long sectionPos; public final long sectionPos;
public final byte minimumDetailLevel; /**
* this is the highest detail level this tree can provide.
* IE the detail levels that the root nodes in the tree are.
*/
public final byte parentTreeLeafDetailLevel;
public T value; public T value;
@@ -68,10 +73,10 @@ public class QuadNode<T>
public QuadNode(long sectionPos, byte minimumDetailLevel) public QuadNode(long sectionPos, byte parentTreeLeafDetailLevel)
{ {
this.sectionPos = sectionPos; this.sectionPos = sectionPos;
this.minimumDetailLevel = minimumDetailLevel; this.parentTreeLeafDetailLevel = parentTreeLeafDetailLevel;
} }
@@ -191,12 +196,12 @@ public class QuadNode<T>
if (DhSectionPos.getDetailLevel(inputSectionPos) == DhSectionPos.getDetailLevel(this.sectionPos) && inputSectionPos != this.sectionPos) if (DhSectionPos.getDetailLevel(inputSectionPos) == DhSectionPos.getDetailLevel(this.sectionPos) && inputSectionPos != this.sectionPos)
{ {
throw new IllegalArgumentException("Node and input detail level are equal, however positions are not; this tree doesn't contain the requested position. Node pos: " + this.sectionPos + ", input pos: " + inputSectionPos); throw new IllegalArgumentException("Node and input detail level are equal, however positions are not; this tree doesn't contain the requested position. Node pos: " + this.sectionPos + ", input pos: " + DhSectionPos.toString(inputSectionPos));
} }
if (DhSectionPos.getDetailLevel(inputSectionPos) < this.minimumDetailLevel) if (DhSectionPos.getDetailLevel(inputSectionPos) < this.parentTreeLeafDetailLevel)
{ {
throw new IllegalArgumentException("Input position is requesting a detail level lower than what this node can provide. Node minimum detail level: " + this.minimumDetailLevel + ", input pos: " + inputSectionPos); throw new IllegalArgumentException("Input position is requesting a detail level lower than what this node can provide. Tree leaf detail level: " + this.parentTreeLeafDetailLevel + ", input pos: " + DhSectionPos.toString(inputSectionPos));
} }
@@ -231,7 +236,7 @@ public class QuadNode<T>
if (replaceValue && this.nwChild == null) if (replaceValue && this.nwChild == null)
{ {
// if no node exists for this position, but we want to insert a new value at this position, create a new node // if no node exists for this position, but we want to insert a new value at this position, create a new node
this.nwChild = new QuadNode<>(nwPos, this.minimumDetailLevel); this.nwChild = new QuadNode<>(nwPos, this.parentTreeLeafDetailLevel);
} }
childNode = this.nwChild; childNode = this.nwChild;
@@ -244,7 +249,7 @@ public class QuadNode<T>
if (replaceValue && this.swChild == null) if (replaceValue && this.swChild == null)
{ {
// if no node exists for this position, but we want to insert a new value at this position, create a new node // if no node exists for this position, but we want to insert a new value at this position, create a new node
this.swChild = new QuadNode<>(swPos, this.minimumDetailLevel); this.swChild = new QuadNode<>(swPos, this.parentTreeLeafDetailLevel);
} }
childNode = this.swChild; childNode = this.swChild;
@@ -257,7 +262,7 @@ public class QuadNode<T>
if (replaceValue && this.neChild == null) if (replaceValue && this.neChild == null)
{ {
// if no node exists for this position, but we want to insert a new value at this position, create a new node // if no node exists for this position, but we want to insert a new value at this position, create a new node
this.neChild = new QuadNode<>(nePos, this.minimumDetailLevel); this.neChild = new QuadNode<>(nePos, this.parentTreeLeafDetailLevel);
} }
childNode = this.neChild; childNode = this.neChild;
@@ -270,7 +275,7 @@ public class QuadNode<T>
if (replaceValue && this.seChild == null) if (replaceValue && this.seChild == null)
{ {
// if no node exists for this position, but we want to insert a new value at this position, create a new node // if no node exists for this position, but we want to insert a new value at this position, create a new node
this.seChild = new QuadNode<>(sePos, this.minimumDetailLevel); this.seChild = new QuadNode<>(sePos, this.parentTreeLeafDetailLevel);
} }
childNode = this.seChild; childNode = this.seChild;
@@ -290,8 +295,9 @@ public class QuadNode<T>
// iterators // // iterators //
//===========// //===========//
public Iterator<QuadNode<T>> getNodeIterator() { return new QuadTreeNodeIterator<>(this, false); } public Iterator<QuadNode<T>> getNodeIterator() { return new QuadTreeNodeIterator<>(this, false, null); }
public Iterator<QuadNode<T>> getLeafNodeIterator() { return new QuadTreeNodeIterator<>(this, true); } public Iterator<QuadNode<T>> getNodeIterator(@Nullable QuadTree.INodeIteratorStoppingFunc<T> stopIteratingFunc) { return new QuadTreeNodeIterator<>(this, false, stopIteratingFunc); }
public Iterator<QuadNode<T>> getLeafNodeIterator() { return new QuadTreeNodeIterator<>(this, true, null); }
/** positions can point to null children */ /** positions can point to null children */
public LongIterator getChildPosIterator() { return new QuadNodeDirectChildPosIterator<>(this); } public LongIterator getChildPosIterator() { return new QuadNodeDirectChildPosIterator<>(this); }
@@ -35,6 +35,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.LongConsumer; import java.util.function.LongConsumer;
/** /**
@@ -50,12 +51,12 @@ public class QuadTree<T>
* The largest numerical detail level this tree supports. <br> * The largest numerical detail level this tree supports. <br>
* IE: the detail level used by the root nodes. * IE: the detail level used by the root nodes.
*/ */
public final byte treeMinDetailLevel; public final byte treeRootDetailLevel;
/** /**
* The smallest numerical detail level this tree supports. <br> * The smallest numerical detail level this tree supports. <br>
* IE: the detail level used by the leaf nodes. * IE: the detail level used by the leaf nodes.
*/ */
public final byte treeMaxDetailLevel; public final byte treeLeafDetailLevel;
private final int diameterInBlocks; // diameterInBlocks private final int diameterInBlocks; // diameterInBlocks
@@ -71,21 +72,21 @@ public class QuadTree<T>
* *
* @param diameterInBlocks equivalent to the distance between the two opposing sides * @param diameterInBlocks equivalent to the distance between the two opposing sides
*/ */
public QuadTree(int diameterInBlocks, DhBlockPos2D centerBlockPos, byte treeMaxDetailLevel) public QuadTree(int diameterInBlocks, DhBlockPos2D centerBlockPos, byte treeLeafDetailLevel)
{ {
this.centerBlockPos = centerBlockPos; this.centerBlockPos = centerBlockPos;
this.diameterInBlocks = diameterInBlocks; this.diameterInBlocks = diameterInBlocks;
this.treeMaxDetailLevel = treeMaxDetailLevel; this.treeLeafDetailLevel = treeLeafDetailLevel;
// the min detail level must be greater than 0 (to prevent divide by 0 errors) and greater than the maximum detail level // the min detail level must be greater than 0 (to prevent divide by 0 errors) and greater than the maximum detail level
this.treeMinDetailLevel = (byte) Math.max(Math.max(1, this.treeMaxDetailLevel), MathUtil.log2(diameterInBlocks)); this.treeRootDetailLevel = (byte) Math.max(Math.max(1, this.treeLeafDetailLevel), MathUtil.log2(diameterInBlocks));
int halfSizeInRootNodes = Math.floorDiv(this.diameterInBlocks, 2) / BitShiftUtil.powerOfTwo(this.treeMinDetailLevel); int halfSizeInRootNodes = Math.floorDiv(this.diameterInBlocks, 2) / BitShiftUtil.powerOfTwo(this.treeRootDetailLevel);
halfSizeInRootNodes = halfSizeInRootNodes + 1; // always add 1 so nodes will always have a parent, even if the tree's center is offset from the root node grid halfSizeInRootNodes = halfSizeInRootNodes + 1; // always add 1 so nodes will always have a parent, even if the tree's center is offset from the root node grid
Pos2D ringListCenterPos = new Pos2D( Pos2D ringListCenterPos = new Pos2D(
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeMinDetailLevel), BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeRootDetailLevel),
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeMinDetailLevel)); BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeRootDetailLevel));
this.topRingList = new MovableGridRingList<>(halfSizeInRootNodes, ringListCenterPos.getX(), ringListCenterPos.getY()); this.topRingList = new MovableGridRingList<>(halfSizeInRootNodes, ringListCenterPos.getX(), ringListCenterPos.getY());
} }
@@ -129,12 +130,12 @@ public class QuadTree<T>
int radius = this.diameterInBlocks() / 2; int radius = this.diameterInBlocks() / 2;
DhBlockPos2D minPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius)); DhBlockPos2D minPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
DhBlockPos2D maxPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius)); DhBlockPos2D maxPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius));
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeMaxDetailLevel + ", max detail level: " + this.treeMinDetailLevel + ". Given Position: [" + DhSectionPos.toString(pos) + "] = block pos: " + DhSectionPos.convertToDetailLevel(pos, LodUtil.BLOCK_DETAIL_LEVEL)); throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeLeafDetailLevel + ", max detail level: " + this.treeRootDetailLevel + ". Given Position: [" + DhSectionPos.toString(pos) + "] = block pos: " + DhSectionPos.convertToDetailLevel(pos, LodUtil.BLOCK_DETAIL_LEVEL));
} }
long rootPos = DhSectionPos.convertToDetailLevel(pos, this.treeMinDetailLevel); long rootPos = DhSectionPos.convertToDetailLevel(pos, this.treeRootDetailLevel);
int ringListPosX = DhSectionPos.getX(rootPos); int ringListPosX = DhSectionPos.getX(rootPos);
int ringListPosZ = DhSectionPos.getZ(rootPos); int ringListPosZ = DhSectionPos.getZ(rootPos);
@@ -146,7 +147,7 @@ public class QuadTree<T>
return null; return null;
} }
topQuadNode = new QuadNode<T>(rootPos, this.treeMaxDetailLevel); topQuadNode = new QuadNode<T>(rootPos, this.treeLeafDetailLevel);
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, topQuadNode); boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, topQuadNode);
if (!successfullyAdded) if (!successfullyAdded)
{ {
@@ -171,7 +172,7 @@ public class QuadTree<T>
public boolean isSectionPosInBounds(long testPos) public boolean isSectionPosInBounds(long testPos)
{ {
// check if the testPos is within the detail level limits of the tree // check if the testPos is within the detail level limits of the tree
boolean detailLevelWithinBounds = this.treeMaxDetailLevel <= DhSectionPos.getDetailLevel(testPos) && DhSectionPos.getDetailLevel(testPos) <= this.treeMinDetailLevel; boolean detailLevelWithinBounds = this.treeLeafDetailLevel <= DhSectionPos.getDetailLevel(testPos) && DhSectionPos.getDetailLevel(testPos) <= this.treeRootDetailLevel;
if (!detailLevelWithinBounds) if (!detailLevelWithinBounds)
{ {
return false; return false;
@@ -237,10 +238,12 @@ public class QuadTree<T>
//===========// //===========//
/** can include null nodes */ /** can include null nodes */
public LongIterator rootNodePosIterator() { return new QuadTreeRootPosIterator(true); } public LongIterator rootNodePosIterator() { return new QuadTreeRootPosIterator(true, null); }
public Iterator<QuadNode<T>> nodeIterator() { return new QuadTreeNodeIterator(false); } /** @see INodeIteratorStoppingFunc */
public Iterator<QuadNode<T>> leafNodeIterator() { return new QuadTreeNodeIterator(true); } public Iterator<QuadNode<T>> nodeIteratorWithStoppingFilter(INodeIteratorStoppingFunc<T> stoppingFilterFunc) { return new QuadTreeNodeIterator(false, stoppingFilterFunc); }
public Iterator<QuadNode<T>> nodeIterator() { return new QuadTreeNodeIterator(false, null); }
public Iterator<QuadNode<T>> leafNodeIterator() { return new QuadTreeNodeIterator(true, null); }
@@ -254,8 +257,8 @@ public class QuadTree<T>
this.centerBlockPos = newCenterPos; this.centerBlockPos = newCenterPos;
Pos2D expectedCenterPos = new Pos2D( Pos2D expectedCenterPos = new Pos2D(
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeMinDetailLevel), BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeRootDetailLevel),
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeMinDetailLevel)); BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeRootDetailLevel));
if (this.topRingList.getCenter().equals(expectedCenterPos)) if (this.topRingList.getCenter().equals(expectedCenterPos))
{ {
@@ -320,16 +323,14 @@ public class QuadTree<T>
//==============// //==============//
// base methods // // base methods //
//==============// //==============//
public boolean isEmpty() { return this.count() == 0; } // TODO this should be rewritten to short-circuit public boolean isEmpty() { return this.nodeCount() == 0; } // this should be rewritten to short-circuit
/** @return the number of non-null nodes in the tree */ /** @return the number of non-null nodes in the tree */
public int count() public int nodeCount()
{ {
int count = 0; int count = 0;
for (QuadNode<T> node : this.topRingList) for (QuadNode<T> node : this.topRingList)
@@ -394,7 +395,13 @@ public class QuadTree<T>
// } // }
@Override @Override
public String toString() { return "center block: " + this.centerBlockPos + ", block width: " + this.diameterInBlocks + ", detail level range: [" + this.treeMaxDetailLevel + "-" + this.treeMinDetailLevel + "], leaf #: " + this.leafNodeCount(); } public String toString()
{
return "center block: " + this.centerBlockPos +
", block width: " + this.diameterInBlocks +
", detail level range: [" + this.treeLeafDetailLevel + "-" + this.treeRootDetailLevel + "], " +
"leaf #: " + this.leafNodeCount();
}
@@ -402,19 +409,39 @@ public class QuadTree<T>
// iterator classes // // iterator classes //
//==================// //==================//
/** @see INodeIteratorStoppingFunc#iteratorShouldStop(QuadNode) */
@FunctionalInterface
public interface INodeIteratorStoppingFunc<T>
{
/** if this function returns true then the iterator will stop walking down the tree */
boolean iteratorShouldStop(QuadNode<T> node);
}
private class QuadTreeRootPosIterator implements LongIterator private class QuadTreeRootPosIterator implements LongIterator
{ {
private final LongArrayFIFOQueue iteratorPosQueue = new LongArrayFIFOQueue(); private final LongArrayFIFOQueue iteratorPosQueue;
@Nullable
private final INodeIteratorStoppingFunc<T> stopIteratingFunc;
public QuadTreeRootPosIterator(boolean includeNullNodes) public QuadTreeRootPosIterator(boolean includeNullNodes, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
{ {
this.iteratorPosQueue = new LongArrayFIFOQueue();
this.stopIteratingFunc = stopIteratingFunc;
QuadTree.this.topRingList.forEachPosOrdered((node, pos2D) -> QuadTree.this.topRingList.forEachPosOrdered((node, pos2D) ->
{ {
if (node != null || includeNullNodes) if (this.stopIteratingFunc != null
&& this.stopIteratingFunc.iteratorShouldStop(node))
{ {
long rootPos = DhSectionPos.encode(QuadTree.this.treeMinDetailLevel, pos2D.getX(), pos2D.getY()); return;
}
if (node != null
|| includeNullNodes)
{
long rootPos = DhSectionPos.encode(QuadTree.this.treeRootDetailLevel, pos2D.getX(), pos2D.getY());
if (QuadTree.this.isSectionPosInBounds(rootPos)) if (QuadTree.this.isSectionPosInBounds(rootPos))
{ {
this.iteratorPosQueue.enqueue(rootPos); this.iteratorPosQueue.enqueue(rootPos);
@@ -459,13 +486,17 @@ public class QuadTree<T>
private QuadNode<T> lastNode = null; private QuadNode<T> lastNode = null;
private final boolean onlyReturnLeaves; private final boolean onlyReturnLeaves;
@Nullable
private final INodeIteratorStoppingFunc<T> stopIteratingFunc;
public QuadTreeNodeIterator(boolean onlyReturnLeaves) public QuadTreeNodeIterator(boolean onlyReturnLeaves, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
{ {
this.rootNodeIterator = new QuadTreeRootPosIterator(false); this.rootNodeIterator = new QuadTreeRootPosIterator(false, stopIteratingFunc);
this.onlyReturnLeaves = onlyReturnLeaves; this.onlyReturnLeaves = onlyReturnLeaves;
this.stopIteratingFunc = stopIteratingFunc;
} }
@@ -473,23 +504,28 @@ public class QuadTree<T>
@Override @Override
public boolean hasNext() public boolean hasNext()
{ {
if (!this.rootNodeIterator.hasNext() && this.currentNodeIterator != null && !this.currentNodeIterator.hasNext()) if (!this.rootNodeIterator.hasNext()
&& this.currentNodeIterator != null
&& !this.currentNodeIterator.hasNext())
{ {
return false; return false;
} }
if (this.currentNodeIterator == null || !this.currentNodeIterator.hasNext()) if (this.currentNodeIterator == null
|| !this.currentNodeIterator.hasNext())
{ {
this.currentNodeIterator = this.getNextChildNodeIterator(); this.currentNodeIterator = this.getNextChildNodeIterator();
} }
return this.currentNodeIterator != null && this.currentNodeIterator.hasNext(); return this.currentNodeIterator != null && this.currentNodeIterator.hasNext();
} }
@Override @Override
public QuadNode<T> next() public QuadNode<T> next()
{ {
if (this.currentNodeIterator == null || !this.currentNodeIterator.hasNext()) if (this.currentNodeIterator == null
|| !this.currentNodeIterator.hasNext())
{ {
this.currentNodeIterator = this.getNextChildNodeIterator(); this.currentNodeIterator = this.getNextChildNodeIterator();
} }
@@ -503,13 +539,14 @@ public class QuadTree<T>
private Iterator<QuadNode<T>> getNextChildNodeIterator() private Iterator<QuadNode<T>> getNextChildNodeIterator()
{ {
Iterator<QuadNode<T>> nodeIterator = null; Iterator<QuadNode<T>> nodeIterator = null;
while ((nodeIterator == null || !nodeIterator.hasNext()) && this.rootNodeIterator.hasNext()) while ((nodeIterator == null || !nodeIterator.hasNext())
&& this.rootNodeIterator.hasNext())
{ {
long sectionPos = this.rootNodeIterator.nextLong(); long sectionPos = this.rootNodeIterator.nextLong();
QuadNode<T> rootNode = QuadTree.this.getNode(sectionPos); QuadNode<T> rootNode = QuadTree.this.getNode(sectionPos);
if (rootNode != null) if (rootNode != null)
{ {
nodeIterator = this.onlyReturnLeaves ? rootNode.getLeafNodeIterator() : rootNode.getNodeIterator(); nodeIterator = this.onlyReturnLeaves ? rootNode.getLeafNodeIterator() : rootNode.getNodeIterator(this.stopIteratingFunc);
} }
} }
return nodeIterator; return nodeIterator;
@@ -34,7 +34,7 @@ public class QuadNodeChildIndexIterator<T> implements Iterator<Integer>
public QuadNodeChildIndexIterator(QuadNode<T> parentNode, boolean returnNullChildPos) public QuadNodeChildIndexIterator(QuadNode<T> parentNode, boolean returnNullChildPos)
{ {
// only get the children if this section isn't at the bottom of the tree // only get the children if this section isn't at the bottom of the tree
if (DhSectionPos.getDetailLevel(parentNode.sectionPos) > parentNode.minimumDetailLevel) if (DhSectionPos.getDetailLevel(parentNode.sectionPos) > parentNode.parentTreeLeafDetailLevel)
{ {
// go over each child pos // go over each child pos
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
@@ -21,6 +21,8 @@ package com.seibel.distanthorizons.core.util.objects.quadTree.iterators;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode; import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -36,31 +38,36 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
private byte iteratorDetailLevel = 0; private byte iteratorDetailLevel = 0;
private final boolean onlyReturnLeafValues; private final boolean onlyReturnLeafValues;
@Nullable
private final QuadTree.INodeIteratorStoppingFunc<T> stopIteratingFunc;
public QuadTreeNodeIterator(
public QuadTreeNodeIterator(QuadNode<T> rootNode, boolean onlyReturnLeafValues) QuadNode<T> rootNode,
boolean onlyReturnLeafValues, @Nullable QuadTree.INodeIteratorStoppingFunc<T> stopIteratingFunc)
{ {
this.onlyReturnLeafValues = onlyReturnLeafValues; this.onlyReturnLeafValues = onlyReturnLeafValues;
this.stopIteratingFunc = stopIteratingFunc;
// TODO the naming conversion for these are flipped in a lot of places // TODO the naming conversion for these are flipped in a lot of places
this.highestDetailLevel = rootNode.minimumDetailLevel; this.highestDetailLevel = rootNode.parentTreeLeafDetailLevel;
this.iteratorDetailLevel = DhSectionPos.getDetailLevel(rootNode.sectionPos); this.iteratorDetailLevel = DhSectionPos.getDetailLevel(rootNode.sectionPos);
if (!this.onlyReturnLeafValues) if (!this.onlyReturnLeafValues)
{ {
// return all nodes
// set the start for the iterator // set the start for the iterator
this.validNodesForDetailLevel.add(rootNode); this.validNodesForDetailLevel.add(rootNode);
this.iteratorNodeQueue.add(rootNode); this.iteratorNodeQueue.add(rootNode);
} }
else else
{ {
// fully populate the iterator // return leaf nodes
// fully populate the iterator
// this isn't the best way to do this, especially for large trees, // this isn't the best way to do this, especially for large trees,
// but it is simple and functions well enough for now // but it is simple and functions well enough for now
Queue<QuadNode<T>> parentNodeQueue = new ArrayDeque<>(); Queue<QuadNode<T>> parentNodeQueue = new ArrayDeque<>();
parentNodeQueue.add(rootNode); parentNodeQueue.add(rootNode);
@@ -68,6 +75,13 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
while (parentNodeQueue.peek() != null) while (parentNodeQueue.peek() != null)
{ {
QuadNode<T> parentNode = parentNodeQueue.poll(); QuadNode<T> parentNode = parentNodeQueue.poll();
if (stopIteratingFunc != null
&& stopIteratingFunc.iteratorShouldStop(parentNode))
{
continue;
}
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
QuadNode<T> childNode = parentNode.getChildByIndex(i); QuadNode<T> childNode = parentNode.getChildByIndex(i);
@@ -112,7 +126,8 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
// the iterator queue should be fully populated for leaf values, // the iterator queue should be fully populated for leaf values,
// for all values, it needs to be populated for each detail level // for all values, it needs to be populated for each detail level
if (this.iteratorNodeQueue.size() == 0 && !onlyReturnLeafValues) if (this.iteratorNodeQueue.size() == 0
&& !this.onlyReturnLeafValues)
{ {
// populate the next detail level list // populate the next detail level list
@@ -123,17 +138,32 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
Queue<QuadNode<T>> parentNodes = new LinkedList<>(this.validNodesForDetailLevel); Queue<QuadNode<T>> parentNodes = new LinkedList<>(this.validNodesForDetailLevel);
this.validNodesForDetailLevel.clear(); this.validNodesForDetailLevel.clear();
// populate the list of nodes for this level // populate the list of nodes for this detail level
for (QuadNode<T> parentNode : parentNodes) for (QuadNode<T> parentNode : parentNodes)
{ {
if (this.stopIteratingFunc != null
&& this.stopIteratingFunc.iteratorShouldStop(parentNode))
{
continue;
}
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
QuadNode<T> childNode = parentNode.getChildByIndex(i); QuadNode<T> childNode = parentNode.getChildByIndex(i);
if (childNode != null) if (childNode == null)
{ {
this.iteratorNodeQueue.add(childNode); continue;
this.validNodesForDetailLevel.add(childNode);
} }
if (this.stopIteratingFunc != null
&& this.stopIteratingFunc.iteratorShouldStop(childNode))
{
continue;
}
this.iteratorNodeQueue.add(childNode);
this.validNodesForDetailLevel.add(childNode);
} }
} }
} }
@@ -1,7 +1,7 @@
package com.seibel.distanthorizons.core.util.threading; package com.seibel.distanthorizons.core.util.threading;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.types.ConfigEntry; import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -13,111 +13,131 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Stream;
/**
* This handles dividing work DH needs to do across
* DH's thread pool.
*/
public class PriorityTaskPicker public class PriorityTaskPicker
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final ConfigEntry<Integer> threadCountConfig = Config.Common.MultiThreading.numberOfThreads; /** the list of currently registered executors */
private final RateLimitedThreadPoolExecutor threadPoolExecutor = new RateLimitedThreadPoolExecutor(
this.threadCountConfig.getMax(),
new DhThreadFactory("PriorityTaskPicker", Thread.MIN_PRIORITY, false),
new ArrayBlockingQueue<>(this.threadCountConfig.getMax())
);
// List of executors
private final ArrayList<Executor> executors = new ArrayList<>(); private final ArrayList<Executor> executors = new ArrayList<>();
// Lock to ensure task picking logic is thread-safe /** Lock to ensure task picking logic is thread-safe */
private final ReentrantLock taskPickerLock = new ReentrantLock(); private final ReentrantLock taskPickerLock = new ReentrantLock();
// Tracks the number of active threads /** Tracks the number of active threads */
private final AtomicInteger occupiedThreads = new AtomicInteger(0); private final AtomicInteger occupiedThreadsRef = new AtomicInteger(0);
private final AtomicBoolean isShutDownRef = new AtomicBoolean(false); private final AtomicBoolean isShutDownRef = new AtomicBoolean(false);
//==================// //==========//
// executor methods // // executor //
//==================// //==========//
/** public Executor createExecutor(String name)
* Creates an executor.
*
* @return a newly created Executor
*/
public Executor createExecutor()
{ {
Executor executor = new Executor(); Executor executor = new Executor(this, name);
this.executors.add(executor); this.executors.add(executor);
return executor; return executor;
} }
/** /**
* Tries to start the next task by iterating over executors in the queue. * Tries to start the next queued task
* Ensures thread limits are respected and only one thread iterates over the executorQueue at a time. * for one of the available executors.
*/ */
private void tryStartNextTask() private void tryStartNextTask()
{ {
if (this.taskPickerLock.tryLock()) // only let one thread start the next task to prevent concurrency errors
if (!this.taskPickerLock.tryLock())
{ {
try return;
}
try
{
// Limit how many tasks can be queued for a given pool before moving to the next pool.
// This allows the picker to spread out the work a little more vs having the threads
// only work on a single executor's queue at a time
int maxQueuedBeforeOverflow = Math.max(1, Config.Common.MultiThreading.numberOfThreads.get() / 2);
// fill up executors that have run for less time first,
// this prevents long-running tasks from taking up all the CPU time
Iterator<Executor> iterator = this.getExecutorIteratorSortedByShortestTotalRunTime();
while (iterator.hasNext())
{ {
// TODO limit based on thread count so visual VM is easier to parse Executor executor = iterator.next();
for (Executor executor : (Iterable<? extends Executor>) this.executors.stream().sorted(Comparator.comparingLong(executor -> executor.totalRuntimeNanos.get()))::iterator) int queuedTaskCount = 0;
TrackedRunnable task;
// start tasks until we're running as many threads as acceptable by the config,
// or until this executor is empty,
// or until we should move on to the next executor
while (this.occupiedThreadsRef.get() < Config.Common.MultiThreading.numberOfThreads.get()
&& queuedTaskCount <= maxQueuedBeforeOverflow
&& (task = executor.taskQueue.poll()) != null)
{ {
TrackedRunnable task; queuedTaskCount++;
while (this.occupiedThreads.get() < this.threadCountConfig.get() && (task = executor.tasks.poll()) != null) try
{ {
try executor.runTask(task);
this.occupiedThreadsRef.getAndIncrement();
}
catch (RejectedExecutionException e)
{
if (this.isShutDownRef.get())
{ {
// Attempt to start another task // Clear this executor's tasks since we no longer expect anything to execute.
this.threadPoolExecutor.execute(task); executor.taskQueue.clear();
// Update variables related to task status
this.occupiedThreads.getAndIncrement();
executor.runningTasks.getAndIncrement();
} }
catch (RejectedExecutionException e) else
{ {
if (this.isShutDownRef.get()) throw e;
{
// Clear executor's tasks since we no longer expect anything to execute
// Tasks from other executors will be cleared by the outer for loop
executor.tasks.clear();
}
else
{
throw e;
}
} }
} }
} }
} }
finally }
{ finally
this.taskPickerLock.unlock(); {
this.taskPickerLock.unlock();
// If someone else manages to pick up a lock before us, we'll leave early, and they will do our work
}
} }
} }
private Iterator<Executor> getExecutorIteratorSortedByShortestTotalRunTime()
{
Stream<Executor> stream = this.executors.stream();
// returns smaller numbers first
stream = stream.sorted(Comparator.comparingLong((executor) -> executor.totalRuntimeNanos.get()));
return stream.iterator();
}
/** Shuts down the thread pool immediately, stopping all tasks. */ /** Blocking, shuts down the thread pool immediately, stopping all tasks. */
public void shutdown() public void shutdownNow()
{ {
LOGGER.info("Shutting down PriorityTaskPicker thread pool..."); LOGGER.info("Shutting down PriorityTaskPicker thread pool...");
this.isShutDownRef.set(true); this.isShutDownRef.set(true);
try try
{ {
this.threadPoolExecutor.shutdown(); for (int i = 0; i < this.executors.size(); i++)
if (!this.threadPoolExecutor.awaitTermination(5, TimeUnit.SECONDS))
{ {
this.threadPoolExecutor.shutdownNow(); Executor executor = this.executors.get(i);
if (executor != null)
{
executor.shutdown();
if (!executor.awaitTermination(5, TimeUnit.SECONDS))
{
executor.shutdownNow();
}
}
} }
} }
catch (InterruptedException e) catch (InterruptedException e)
@@ -132,68 +152,158 @@ public class PriorityTaskPicker
// helper classes // // helper classes //
//================// //================//
public class Executor extends AbstractExecutorService /**
* Each executor handles a specific type of work that DH needs done.
* By separating out task into its own executor it allows for easier performance monitoring
* via a tool like Visual VM and fairly spreading out CPU time between tasks.
*/
public static class Executor extends AbstractExecutorService implements IConfigListener
{ {
private final Queue<TrackedRunnable> tasks = new ConcurrentLinkedQueue<>(); private final PriorityTaskPicker parentTaskPicker;
private final String name;
private final Queue<TrackedRunnable> taskQueue = new ConcurrentLinkedQueue<>();
private final AtomicInteger runningTasksRef = new AtomicInteger(0);
private final AtomicInteger completedTasksRef = new AtomicInteger(0);
private final AtomicInteger runningTasks = new AtomicInteger(0);
private final AtomicInteger completedTasks = new AtomicInteger(0);
private final RollingAverage runTimeInMsRollingAverage = new RollingAverage(200);
private final AtomicLong totalRuntimeNanos = new AtomicLong(0); private final AtomicLong totalRuntimeNanos = new AtomicLong(0);
/** used for performance logging */
private final RollingAverage runTimeInMsRollingAverage = new RollingAverage(200);
/** holds the threads this {@link Executor} can run */
private RateLimitedThreadPoolExecutor threadPoolExecutor;
//=============//
// constructor //
//=============//
public Executor(PriorityTaskPicker parentTaskPicker, String name)
{
this.parentTaskPicker = parentTaskPicker;
this.name = name;
this.threadPoolExecutor = this.createThreadPool();
Config.Common.MultiThreading.numberOfThreads.addListener(this);
}
private RateLimitedThreadPoolExecutor createThreadPool()
{
return new RateLimitedThreadPoolExecutor(
Config.Common.MultiThreading.numberOfThreads.get(),
new DhThreadFactory(this.name, Thread.MIN_PRIORITY, false),
new ArrayBlockingQueue<>(Runtime.getRuntime().availableProcessors())
);
}
//=================//
// config handling //
//=================//
@Override
public void onConfigValueSet()
{
RateLimitedThreadPoolExecutor oldExecutor = this.threadPoolExecutor;
this.threadPoolExecutor = this.createThreadPool();
// shut down the old executor after replacing it with the new one
// to make sure no tasks are lost in the transfer
if (oldExecutor != null)
{
oldExecutor.shutdown();
}
}
//=====================//
// task queue handling //
//=====================//
@Override @Override
public void execute(@NotNull Runnable command) public void execute(@NotNull Runnable command)
{ {
this.tasks.add(new TrackedRunnable(this, command)); this.taskQueue.add(new TrackedRunnable(this.parentTaskPicker, this, command));
// Attempt to pick up the task immediately // Attempt to start the task immediately
PriorityTaskPicker.this.tryStartNextTask(); this.parentTaskPicker.tryStartNextTask();
}
/** The passed in {@link Runnable} must be exactly the same as the one passed into {@link PriorityTaskPicker.Executor#execute(Runnable)} */
public void remove(@NotNull Runnable command) { this.taskQueue.removeIf(trackedRunnable -> trackedRunnable.command == command); }
public void runTask(@NotNull Runnable command)
{
this.threadPoolExecutor.execute(command);
this.runningTasksRef.getAndIncrement();
} }
public int getQueueSize() { return this.tasks.size(); } public int getQueueSize() { return this.taskQueue.size(); }
public int getPoolSize() { return PriorityTaskPicker.this.threadCountConfig.get(); } public int getPoolSize() { return Config.Common.MultiThreading.numberOfThreads.get(); }
public int getRunningTaskCount() { return this.runningTasks.get(); } public int getRunningTaskCount() { return this.runningTasksRef.get(); }
public int getCompletedTaskCount() { return this.completedTasks.get(); } public int getCompletedTaskCount() { return this.completedTasksRef.get(); }
/** Will return NaN if nothing has been submitted yet */ /** Will return NaN if nothing has been submitted yet */
public double getAverageRunTimeInMs() { return this.runTimeInMsRollingAverage.getAverage(); } public double getAverageRunTimeInMs() { return this.runTimeInMsRollingAverage.getAverage(); }
/** The passed in {@link Runnable} must be exactly the same as the one passed into {@link PriorityTaskPicker.Executor#execute(Runnable)} */
public void remove(@NotNull Runnable command) { this.tasks.removeIf(trackedRunnable -> trackedRunnable.command == command); } //==========//
// shutdown //
//==========//
@Override @Override
public void shutdown() { throw new UnsupportedOperationException(); } public void shutdown() { this.threadPoolExecutor.shutdown(); }
@Override @Override
public @NotNull List<Runnable> shutdownNow() { throw new UnsupportedOperationException(); } public @NotNull List<Runnable> shutdownNow() { return this.threadPoolExecutor.shutdownNow(); }
@Override @Override
public boolean isShutdown() { return false; } public boolean isShutdown() { return this.threadPoolExecutor.isShutdown(); }
@Override @Override
public boolean isTerminated() { return false; } public boolean isTerminated() { return this.threadPoolExecutor.isTerminated(); }
@Override @Override
public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) { return false; } public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException
{ return this.threadPoolExecutor.awaitTermination(timeout, unit); }
} }
/** used so we can {@link PriorityTaskPicker.Executor#remove(Runnable)} using the original {@link Runnable} */ /** used so we can {@link PriorityTaskPicker.Executor#remove(Runnable)} using the original {@link Runnable} */
private class TrackedRunnable implements Runnable private static class TrackedRunnable implements Runnable
{ {
private final PriorityTaskPicker parentTaskPicker;
private final Executor executor; private final Executor executor;
/** the runnable passed into {@link PriorityTaskPicker.Executor#execute(Runnable)} */ /** the runnable passed into {@link PriorityTaskPicker.Executor#execute(Runnable)} */
public final Runnable command; public final Runnable command;
public TrackedRunnable(Executor executor, Runnable command)
//=============//
// constructor //
//=============//
public TrackedRunnable(PriorityTaskPicker parentTaskPicker, Executor executor, Runnable command)
{ {
this.parentTaskPicker = parentTaskPicker;
this.executor = executor; this.executor = executor;
this.command = command; this.command = command;
} }
//=========//
// running //
//=========//
@Override @Override
public void run() public void run()
{ {
@@ -208,16 +318,17 @@ public class PriorityTaskPicker
this.executor.runTimeInMsRollingAverage.addValue(TimeUnit.NANOSECONDS.toMillis(timeElapsed)); this.executor.runTimeInMsRollingAverage.addValue(TimeUnit.NANOSECONDS.toMillis(timeElapsed));
// Update variables related to task status // Update variables related to task status
PriorityTaskPicker.this.occupiedThreads.getAndDecrement(); this.parentTaskPicker.occupiedThreadsRef.getAndDecrement();
this.executor.runningTasks.getAndDecrement(); this.executor.runningTasksRef.getAndDecrement();
this.executor.completedTasks.getAndIncrement(); this.executor.completedTasksRef.getAndIncrement();
this.executor.totalRuntimeNanos.addAndGet(timeElapsed); this.executor.totalRuntimeNanos.addAndGet(timeElapsed);
// Attempt to start another task this.parentTaskPicker.tryStartNextTask();
PriorityTaskPicker.this.tryStartNextTask();
} }
} }
} }
} }
@@ -96,7 +96,7 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
* Deprecated since most of the time this doesn't do what we want or need. * Deprecated since most of the time this doesn't do what we want or need.
* In James testing any tasks started with {@link CompletableFuture#runAsync(Runnable, Executor)} * In James testing any tasks started with {@link CompletableFuture#runAsync(Runnable, Executor)}
* or {@link CompletableFuture#supplyAsync(Supplier, Executor)} converted the {@link Runnable} * or {@link CompletableFuture#supplyAsync(Supplier, Executor)} converted the {@link Runnable}
* and {@link CompletableFuture} into objects that didn't support being cancled and removed * and {@link CompletableFuture} into objects that didn't support being canceled and removed
* from the queue. The canceled tasks were correctly never run, but couldn't be purged. * from the queue. The canceled tasks were correctly never run, but couldn't be purged.
*/ */
@Deprecated @Deprecated
@@ -45,6 +45,10 @@ public class ThreadPoolUtil
@Nullable @Nullable
public static PriorityTaskPicker.Executor getFileHandlerExecutor() { return fileHandlerThreadPool; } public static PriorityTaskPicker.Executor getFileHandlerExecutor() { return fileHandlerThreadPool; }
private static PriorityTaskPicker.Executor renderSectionLoadThreadPool;
@Nullable
public static PriorityTaskPicker.Executor getRenderLoadingExecutor() { return renderSectionLoadThreadPool; }
private static PriorityTaskPicker.Executor updatePropagatorThreadPool; private static PriorityTaskPicker.Executor updatePropagatorThreadPool;
@Nullable @Nullable
public static PriorityTaskPicker.Executor getUpdatePropagatorExecutor() { return updatePropagatorThreadPool; } public static PriorityTaskPicker.Executor getUpdatePropagatorExecutor() { return updatePropagatorThreadPool; }
@@ -95,15 +99,16 @@ public class ThreadPoolUtil
if (taskPicker != null) if (taskPicker != null)
{ {
taskPicker.shutdown(); taskPicker.shutdownNow();
} }
taskPicker = new PriorityTaskPicker(); taskPicker = new PriorityTaskPicker();
networkCompressionThreadPool = taskPicker.createExecutor(); networkCompressionThreadPool = taskPicker.createExecutor("Network");
fileHandlerThreadPool = taskPicker.createExecutor(); fileHandlerThreadPool = taskPicker.createExecutor("IO");
chunkToLodBuilderThreadPool = taskPicker.createExecutor(); renderSectionLoadThreadPool = taskPicker.createExecutor("Render Loader");
updatePropagatorThreadPool = taskPicker.createExecutor(); chunkToLodBuilderThreadPool = taskPicker.createExecutor("LOD Builder");
worldGenThreadPool = taskPicker.createExecutor(); updatePropagatorThreadPool = taskPicker.createExecutor("Update Propagator");
worldGenThreadPool = taskPicker.createExecutor("World Gen");
@@ -128,7 +133,7 @@ public class ThreadPoolUtil
public static void shutdownThreadPools() public static void shutdownThreadPools()
{ {
// standalone threads // standalone threads
taskPicker.shutdown(); taskPicker.shutdownNow();
beaconCullingThreadPool.shutdown(); beaconCullingThreadPool.shutdown();
fullDataMigrationThreadPool.shutdown(); fullDataMigrationThreadPool.shutdown();
} }
@@ -67,7 +67,21 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
@Override @Override
public void removePlayer(IServerPlayerWrapper serverPlayer) public void removePlayer(IServerPlayerWrapper serverPlayer)
{ {
this.getLevel(serverPlayer.getLevel()).removePlayer(serverPlayer); IServerLevelWrapper playerLevel = serverPlayer.getLevel();
if (playerLevel == null)
{
// can happen during server shutdown
return;
}
TDhServerLevel serverLevel = this.getLevel(playerLevel);
if (serverLevel == null)
{
// can happen during server shutdown
return;
}
serverLevel.removePlayer(serverPlayer);
this.serverPlayerStateManager.unregisterLeftPlayer(serverPlayer); this.serverPlayerStateManager.unregisterLeftPlayer(serverPlayer);
// If player's left, session is already closed // If player's left, session is already closed

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