Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into refactor/remove-tcp-connection
This commit is contained in:
Binary file not shown.
@@ -73,13 +73,14 @@ public class ClientApi
|
||||
public static boolean prefLoggerEnabled = false;
|
||||
|
||||
public static final ClientApi INSTANCE = new ClientApi();
|
||||
public static TestRenderer testRenderer = new TestRenderer();
|
||||
public static final TestRenderer TEST_RENDERER = new TestRenderer();
|
||||
|
||||
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||
|
||||
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
|
||||
|
||||
private boolean configOverrideReminderPrinted = false;
|
||||
private boolean lowMemoryWarningPrinted = false;
|
||||
|
||||
private final Queue<String> chatMessageQueueForNextFrame = new LinkedBlockingQueue<>();
|
||||
|
||||
@@ -357,29 +358,7 @@ public class ClientApi
|
||||
{
|
||||
// logging //
|
||||
|
||||
// dev build
|
||||
if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC.playerExists())
|
||||
{
|
||||
this.configOverrideReminderPrinted = true;
|
||||
|
||||
// remind the user that this is a development build
|
||||
MC.sendChatMessage("Distant Horizons nightly/unstable build, version: [" + ModInfo.VERSION+"].");
|
||||
MC.sendChatMessage("Issues may occur with this version.");
|
||||
MC.sendChatMessage("Here be dragons!");
|
||||
MC.sendChatMessage("");
|
||||
}
|
||||
|
||||
// generic messages
|
||||
while (!this.chatMessageQueueForNextFrame.isEmpty())
|
||||
{
|
||||
String message = this.chatMessageQueueForNextFrame.poll();
|
||||
if (message == null)
|
||||
{
|
||||
// done to prevent potential null pointers
|
||||
message = "";
|
||||
}
|
||||
MC.sendChatMessage(message);
|
||||
}
|
||||
this.sendChatMessagesNow();
|
||||
|
||||
IProfilerWrapper profiler = MC.getProfiler();
|
||||
profiler.pop(); // get out of "terrain"
|
||||
@@ -466,7 +445,7 @@ public class ClientApi
|
||||
else if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEBUG)
|
||||
{
|
||||
profiler.push("Render Debug");
|
||||
ClientApi.testRenderer.render();
|
||||
ClientApi.TEST_RENDERER.render();
|
||||
profiler.pop();
|
||||
}
|
||||
}
|
||||
@@ -546,6 +525,53 @@ public class ClientApi
|
||||
}
|
||||
}
|
||||
|
||||
private void sendChatMessagesNow()
|
||||
{
|
||||
// dev build
|
||||
if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC.playerExists())
|
||||
{
|
||||
this.configOverrideReminderPrinted = true;
|
||||
|
||||
// remind the user that this is a development build
|
||||
MC.sendChatMessage("\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r");
|
||||
MC.sendChatMessage("Issues may occur with this version.");
|
||||
MC.sendChatMessage("Here be dragons!");
|
||||
MC.sendChatMessage("");
|
||||
}
|
||||
|
||||
// memory
|
||||
if (!this.lowMemoryWarningPrinted && Config.Client.Advanced.Logging.showLowMemoryWarningOnStartup.get())
|
||||
{
|
||||
this.lowMemoryWarningPrinted = true;
|
||||
|
||||
// 4 GB
|
||||
long minimumRecommendedMemoryInBytes = 4L * 1_000_000_000L;
|
||||
|
||||
// Java returned 17,171,480,576 for 16 GB so it might be slightly off what you'd expect
|
||||
long maxMemoryInBytes = Runtime.getRuntime().maxMemory();
|
||||
if (maxMemoryInBytes < minimumRecommendedMemoryInBytes)
|
||||
{
|
||||
MC.sendChatMessage("\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r");
|
||||
MC.sendChatMessage("Stuttering or low FPS may occur.");
|
||||
MC.sendChatMessage("Please increase Minecraft's available memory to 4 gigabytes.");
|
||||
MC.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging.");
|
||||
MC.sendChatMessage("");
|
||||
}
|
||||
}
|
||||
|
||||
// generic messages
|
||||
while (!this.chatMessageQueueForNextFrame.isEmpty())
|
||||
{
|
||||
String message = this.chatMessageQueueForNextFrame.poll();
|
||||
if (message == null)
|
||||
{
|
||||
// done to prevent potential null pointers
|
||||
message = "";
|
||||
}
|
||||
MC.sendChatMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the given message to appear in chat the next valid frame.
|
||||
* Useful for queueing up messages that may be triggered before the user has loaded into the world.
|
||||
|
||||
@@ -41,10 +41,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
@@ -68,23 +65,13 @@ public class SharedApi
|
||||
private static int lastWorldGenTickDelta = 0;
|
||||
private static long lastOverloadedLogMessageMsTime = 0;
|
||||
|
||||
public F3Screen.DynamicMessage f3Message;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
private SharedApi()
|
||||
{
|
||||
this.f3Message = new F3Screen.DynamicMessage(() ->
|
||||
{
|
||||
int maxUpdateCount = MAX_UPDATING_CHUNK_COUNT_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get();
|
||||
return LodUtil.formatLog("Queued chunk updates: " + UPDATING_CHUNK_POS_SET.size() + " / " + maxUpdateCount);
|
||||
});
|
||||
}
|
||||
|
||||
private SharedApi() { }
|
||||
public static void init() { Initializer.init(); }
|
||||
|
||||
|
||||
@@ -228,7 +215,7 @@ public class SharedApi
|
||||
if (msBetweenLastLog >= MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE)
|
||||
{
|
||||
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
|
||||
LOGGER.warn("Too many chunks queued for updating, max queue count ["+maxQueueCount+"] (["+MAX_UPDATING_CHUNK_COUNT_PER_THREAD+"] per thread). Some LODs may not be updated or may be missing. Please move through the world slower, decrease your vanilla render distance, or increase the CPU load config.");
|
||||
LOGGER.warn("Too many chunks queued for updating, max queue count ["+maxQueueCount+"] (["+MAX_UPDATING_CHUNK_COUNT_PER_THREAD+"] per thread). This may result in holes in your LODs. Please move through the world slower, decrease your vanilla render distance, slow down your world pre-generator, or increase the CPU load config.");
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -307,6 +294,23 @@ public class SharedApi
|
||||
|
||||
try
|
||||
{
|
||||
// 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 (oldChunkHash == newChunkHash)
|
||||
{
|
||||
// if the chunk hashes are the same then we don't need to bother with lighting the chunk
|
||||
// or creating/updating the LODs
|
||||
//LOGGER.info("skipping: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//LOGGER.info("g: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Save or populate the chunk wrapper's lighting
|
||||
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
|
||||
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
|
||||
@@ -315,7 +319,7 @@ public class SharedApi
|
||||
try
|
||||
{
|
||||
// If MC's lighting engine isn't thread safe this may cause the server thread to lag
|
||||
chunkWrapper.bakeDhLightingUsingMcLightingEngine();
|
||||
chunkWrapper.bakeDhLightingUsingMcLightingEngine(); // TODO handle unlit chunks, would pulling in the chunk from disk be a good idea? Look at ChunkLoader in the world gen code for an example
|
||||
}
|
||||
catch (IllegalStateException e)
|
||||
{
|
||||
@@ -341,6 +345,7 @@ public class SharedApi
|
||||
}
|
||||
|
||||
dhLevel.updateChunkAsync(chunkWrapper);
|
||||
dhLevel.setChunkHash(chunkWrapper.getChunkPos(), newChunkHash);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -371,4 +376,18 @@ public class SharedApi
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// F3 Menu //
|
||||
//=========//
|
||||
|
||||
public String getDebugMenuString()
|
||||
{
|
||||
int maxUpdateCount = MAX_UPDATING_CHUNK_COUNT_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get();
|
||||
String updatingCountStr = F3Screen.NUMBER_FORMAT.format(UPDATING_CHUNK_POS_SET.size());
|
||||
String maxUpdateCountStr = F3Screen.NUMBER_FORMAT.format(maxUpdateCount);
|
||||
return "Queued chunk updates: "+updatingCountStr+" / "+maxUpdateCountStr;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -809,12 +809,12 @@ public class Config
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Boolean> showMigrationChatWarning = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
+ "Determines if a message should be displayed in the chat when LOD migration starts. \n"
|
||||
+ "")
|
||||
.build();
|
||||
//public static ConfigEntry<Boolean> showMigrationChatWarning = new ConfigEntry.Builder<Boolean>()
|
||||
// .set(true)
|
||||
// .comment(""
|
||||
// + "Determines if a message should be displayed in the chat when LOD migration starts. \n"
|
||||
// + "")
|
||||
// .build();
|
||||
|
||||
}
|
||||
|
||||
@@ -1242,6 +1242,14 @@ public class Config
|
||||
+ "This can be useful for debugging.")
|
||||
.build();
|
||||
|
||||
|
||||
public static ConfigEntry<Boolean> showLowMemoryWarningOnStartup = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
+ "If enabled, a chat message will be displayed if Java doesn't have enough \n"
|
||||
+ "memory allocated to run DH well.")
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
public static class Debugging
|
||||
|
||||
+23
-24
@@ -37,28 +37,27 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
public static final ThreadPresetConfigEventHandler INSTANCE = new ThreadPresetConfigEventHandler();
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final boolean LOW_THREAD_COUNT_CPU = (Runtime.getRuntime().availableProcessors() <= 4);
|
||||
|
||||
|
||||
|
||||
public static int getWorldGenDefaultThreadCount() { return getThreadCountByPercent(0.15); }
|
||||
public static int getWorldGenDefaultThreadCount() { return getThreadCountByPercent(0.1); }
|
||||
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> worldGenThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads,
|
||||
new HashMap<EDhApiThreadPreset, Integer>()
|
||||
{{
|
||||
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
|
||||
this.put(EDhApiThreadPreset.LOW_IMPACT, getWorldGenDefaultThreadCount());
|
||||
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.25));
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.5));
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.15));
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.25));
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(0.5));
|
||||
}});
|
||||
public static double getWorldGenDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.5 : 0.75; }
|
||||
public static double getWorldGenDefaultRunTimeRatio() { return 0.5; }
|
||||
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> worldGenRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads,
|
||||
new HashMap<EDhApiThreadPreset, Double>()
|
||||
{{
|
||||
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, LOW_THREAD_COUNT_CPU ? 0.1 : 0.25);
|
||||
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.1);
|
||||
this.put(EDhApiThreadPreset.LOW_IMPACT, getWorldGenDefaultRunTimeRatio());
|
||||
this.put(EDhApiThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.5 : 0.75);
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, LOW_THREAD_COUNT_CPU ? 0.75 : 1.0);
|
||||
this.put(EDhApiThreadPreset.BALANCED, 0.75);
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, 1.0);
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
}});
|
||||
|
||||
@@ -71,13 +70,13 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
this.put(EDhApiThreadPreset.LOW_IMPACT, getFileHandlerDefaultThreadCount());
|
||||
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.2));
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.2));
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(0.5));
|
||||
}});
|
||||
public static double getFileHandlerDefaultRunTimeRatio() { return 0.75; }
|
||||
public static double getFileHandlerDefaultRunTimeRatio() { return 0.5; }
|
||||
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> fileHandlerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads,
|
||||
new HashMap<EDhApiThreadPreset, Double>()
|
||||
{{
|
||||
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.50);
|
||||
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.25);
|
||||
this.put(EDhApiThreadPreset.LOW_IMPACT, getFileHandlerDefaultRunTimeRatio());
|
||||
this.put(EDhApiThreadPreset.BALANCED, 1.0);
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, 1.0);
|
||||
@@ -85,24 +84,24 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
}});
|
||||
|
||||
|
||||
public static int getUpdatePropagatorDefaultThreadCount() { return getThreadCountByPercent(0.25); }
|
||||
public static int getUpdatePropagatorDefaultThreadCount() { return getThreadCountByPercent(0.10); }
|
||||
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> UpdatePropagatorThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfUpdatePropagatorThreads,
|
||||
new HashMap<EDhApiThreadPreset, Integer>()
|
||||
{{
|
||||
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
|
||||
this.put(EDhApiThreadPreset.LOW_IMPACT, getUpdatePropagatorDefaultThreadCount());
|
||||
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.5));
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.75));
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.25));
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.50));
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(0.75));
|
||||
}});
|
||||
public static double getUpdatePropagatorDefaultRunTimeRatio() { return 0.5; }
|
||||
public static double getUpdatePropagatorDefaultRunTimeRatio() { return 0.25; }
|
||||
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> UpdatePropagatorRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads,
|
||||
new HashMap<EDhApiThreadPreset, Double>()
|
||||
{{
|
||||
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.25);
|
||||
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.1);
|
||||
this.put(EDhApiThreadPreset.LOW_IMPACT, getUpdatePropagatorDefaultRunTimeRatio());
|
||||
this.put(EDhApiThreadPreset.BALANCED, 0.75);
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, 1.0);
|
||||
this.put(EDhApiThreadPreset.BALANCED, 0.50);
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, 0.75);
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
}});
|
||||
|
||||
@@ -115,16 +114,16 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
this.put(EDhApiThreadPreset.LOW_IMPACT, getLodBuilderDefaultThreadCount());
|
||||
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.2));
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.4));
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(0.6));
|
||||
}});
|
||||
public static double getLodBuilderDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.25 : 0.5; }
|
||||
public static double getLodBuilderDefaultRunTimeRatio() { return 0.25; }
|
||||
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> lodBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads,
|
||||
new HashMap<EDhApiThreadPreset, Double>()
|
||||
{{
|
||||
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.1);
|
||||
this.put(EDhApiThreadPreset.LOW_IMPACT, getLodBuilderDefaultRunTimeRatio());
|
||||
this.put(EDhApiThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.5 : 0.75);
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, 1.0);
|
||||
this.put(EDhApiThreadPreset.BALANCED, 0.5);
|
||||
this.put(EDhApiThreadPreset.AGGRESSIVE, 0.75);
|
||||
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
}});
|
||||
|
||||
|
||||
+11
-9
@@ -75,7 +75,7 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
|
||||
/** A flattened 2D array (for the X and Z directions) containing an array for the Y direction. */
|
||||
private final long[][] dataArrays;
|
||||
|
||||
private long sectionPos;
|
||||
private long pos;
|
||||
|
||||
private boolean isEmpty = true;
|
||||
|
||||
@@ -86,11 +86,11 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
|
||||
//==============//
|
||||
|
||||
public static FullDataSourceV1 createEmpty(long pos) { return new FullDataSourceV1(pos); }
|
||||
private FullDataSourceV1(long sectionPos)
|
||||
private FullDataSourceV1(long pos)
|
||||
{
|
||||
this.dataArrays = new long[WIDTH * WIDTH][0];
|
||||
this.mapping = new FullDataPointIdMap(sectionPos);
|
||||
this.sectionPos = sectionPos;
|
||||
this.mapping = new FullDataPointIdMap(pos);
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,19 +111,21 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
|
||||
//=====================//
|
||||
|
||||
@Override
|
||||
public Long getKey() { return this.sectionPos; }
|
||||
public Long getKey() { return this.pos; }
|
||||
@Override
|
||||
public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); }
|
||||
|
||||
@Override
|
||||
public Long getPos() { return this.sectionPos; }
|
||||
public Long getPos() { return this.pos; }
|
||||
|
||||
public void resizeDataStructuresForRepopulation(long pos)
|
||||
{
|
||||
// no data structures need to be changed, only the source's position
|
||||
this.sectionPos = pos;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.sectionPos) - SECTION_SIZE_OFFSET); }
|
||||
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - SECTION_SIZE_OFFSET); }
|
||||
|
||||
public boolean isEmpty() { return this.isEmpty; }
|
||||
|
||||
@@ -370,7 +372,7 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
|
||||
throw new IOException("Invalid data content end guard for ID mapping");
|
||||
}
|
||||
|
||||
return FullDataPointIdMap.deserialize(inputStream, this.sectionPos, levelWrapper);
|
||||
return FullDataPointIdMap.deserialize(inputStream, this.pos, levelWrapper);
|
||||
}
|
||||
public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); }
|
||||
|
||||
|
||||
+2
@@ -74,6 +74,8 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
||||
private long pos;
|
||||
@Override
|
||||
public Long getKey() { return this.pos; }
|
||||
@Override
|
||||
public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); }
|
||||
|
||||
|
||||
public final FullDataPointIdMap mapping;
|
||||
|
||||
+2
@@ -218,6 +218,8 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
|
||||
public Long getPos() { return this.pos; }
|
||||
@Override
|
||||
public Long getKey() { return this.pos; }
|
||||
@Override
|
||||
public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); }
|
||||
|
||||
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - SECTION_SIZE_OFFSET); }
|
||||
|
||||
|
||||
+38
-26
@@ -95,6 +95,7 @@ public class FullDataSourceProviderV2
|
||||
|
||||
protected long legacyDeletionCount = -1;
|
||||
protected long migrationCount = -1;
|
||||
protected boolean migrationStoppedWithError = false;
|
||||
|
||||
/**
|
||||
* Tracks which positions are currently being updated
|
||||
@@ -447,38 +448,39 @@ public class FullDataSourceProviderV2
|
||||
{
|
||||
this.showMigrationStartMessage();
|
||||
|
||||
|
||||
// keep going until every data source has been migrated
|
||||
int progressCount = 0;
|
||||
while (!legacyDataSourceList.isEmpty() && this.migrationThreadRunning.get())
|
||||
try
|
||||
{
|
||||
LOGGER.info("Migrating [" + dimensionName + "] - [" + progressCount + "/" + totalMigrationCount + "]...");
|
||||
|
||||
ArrayList<CompletableFuture<Void>> updateFutureList = new ArrayList<>();
|
||||
for (int i = 0; i < legacyDataSourceList.size() && this.migrationThreadRunning.get(); i++)
|
||||
// keep going until every data source has been migrated
|
||||
int progressCount = 0;
|
||||
while (!legacyDataSourceList.isEmpty() && this.migrationThreadRunning.get())
|
||||
{
|
||||
FullDataSourceV1 legacyDataSource = legacyDataSourceList.get(i);
|
||||
LOGGER.info("Migrating [" + dimensionName + "] - [" + progressCount + "/" + totalMigrationCount + "]...");
|
||||
|
||||
try
|
||||
ArrayList<CompletableFuture<Void>> updateFutureList = new ArrayList<>();
|
||||
for (int i = 0; i < legacyDataSourceList.size() && this.migrationThreadRunning.get(); i++)
|
||||
{
|
||||
// convert the legacy data source to the new format,
|
||||
// this is a relatively cheap operation
|
||||
FullDataSourceV2 newDataSource = FullDataSourceV2.createFromLegacyDataSourceV1(legacyDataSource);
|
||||
newDataSource.applyToParent = true;
|
||||
FullDataSourceV1 legacyDataSource = legacyDataSourceList.get(i);
|
||||
|
||||
// the actual update process can be moderately expensive due to having to update
|
||||
// the render data along with the full data, so running it async on the update threads gains us a good bit of speed
|
||||
CompletableFuture<Void> future = this.updateDataSourceAsync(newDataSource);
|
||||
updateFutureList.add(future);
|
||||
future.thenRun(() ->
|
||||
try
|
||||
{
|
||||
// after the update finishes the legacy data source can be safely deleted
|
||||
this.legacyFileHandler.repo.deleteWithKey(legacyDataSource.getPos());
|
||||
// convert the legacy data source to the new format,
|
||||
// this is a relatively cheap operation
|
||||
FullDataSourceV2 newDataSource = FullDataSourceV2.createFromLegacyDataSourceV1(legacyDataSource);
|
||||
newDataSource.applyToParent = true;
|
||||
|
||||
try
|
||||
// the actual update process can be moderately expensive due to having to update
|
||||
// the render data along with the full data, so running it async on the update threads gains us a good bit of speed
|
||||
CompletableFuture<Void> future = this.updateDataSourceAsync(newDataSource);
|
||||
updateFutureList.add(future);
|
||||
future.thenRun(() ->
|
||||
{
|
||||
newDataSource.close();
|
||||
}
|
||||
// after the update finishes the legacy data source can be safely deleted
|
||||
this.legacyFileHandler.repo.deleteWithKey(legacyDataSource.getPos());
|
||||
|
||||
try
|
||||
{
|
||||
newDataSource.close();
|
||||
}
|
||||
catch (Exception ignore)
|
||||
{
|
||||
}
|
||||
@@ -513,8 +515,15 @@ public class FullDataSourceProviderV2
|
||||
progressCount += legacyDataSourceList.size();
|
||||
this.migrationCount -= legacyDataSourceList.size();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.info("migration stopped due to error for: ["+dimensionName+"]-["+this.saveDir+"], error: ["+e.getMessage()+"].", e);
|
||||
this.showMigrationEndMessage(false);
|
||||
this.migrationStoppedWithError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (this.migrationThreadRunning.get())
|
||||
{
|
||||
LOGGER.info("migration complete for: [" + dimensionName + "]-[" + this.saveDir + "].");
|
||||
@@ -525,6 +534,8 @@ public class FullDataSourceProviderV2
|
||||
{
|
||||
LOGGER.info("migration stopped for: [" + dimensionName + "]-[" + this.saveDir + "].");
|
||||
this.showMigrationEndMessage(false);
|
||||
this.migrationStoppedWithError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -537,6 +548,7 @@ public class FullDataSourceProviderV2
|
||||
|
||||
public long getLegacyDeletionCount() { return this.legacyDeletionCount; }
|
||||
public long getTotalMigrationCount() { return this.migrationCount; }
|
||||
public boolean getMigrationStoppedWithError() { return this.migrationStoppedWithError; }
|
||||
|
||||
|
||||
private void showMigrationStartMessage()
|
||||
|
||||
-2
@@ -45,8 +45,6 @@ public abstract class AbstractSaveStructure implements AutoCloseable
|
||||
*/
|
||||
public abstract File getLevelFolder(ILevelWrapper wrapper);
|
||||
|
||||
/** Will return null if no parent folder exists for the given {@link ILevelWrapper}. */
|
||||
public abstract File getRenderCacheFolder(ILevelWrapper world);
|
||||
/** Will return null if no parent folder exists for the given {@link ILevelWrapper}. */
|
||||
public abstract File getFullDataFolder(ILevelWrapper world);
|
||||
|
||||
|
||||
-12
@@ -168,18 +168,6 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public File getRenderCacheFolder(ILevelWrapper level)
|
||||
{
|
||||
File levelFolder = this.levelWrapperToFileMap.get(level);
|
||||
if (levelFolder == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return levelFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFullDataFolder(ILevelWrapper level)
|
||||
{
|
||||
|
||||
-8
@@ -51,14 +51,6 @@ public class LocalSaveStructure extends AbstractSaveStructure
|
||||
return serverSide.getSaveFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getRenderCacheFolder(ILevelWrapper level)
|
||||
{
|
||||
IServerLevelWrapper serverSide = (IServerLevelWrapper) level;
|
||||
this.debugPath = serverSide.getSaveFolder();
|
||||
return serverSide.getSaveFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFullDataFolder(ILevelWrapper level)
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* This logic was roughly based on
|
||||
@@ -67,7 +68,7 @@ public class DhLightingEngine
|
||||
* @param nearbyChunkList should also contain centerChunk
|
||||
* @param maxSkyLight should be a value between 0 and 15
|
||||
*/
|
||||
public void lightChunk(IChunkWrapper centerChunk, ArrayList<IChunkWrapper> nearbyChunkList, int maxSkyLight)
|
||||
public void lightChunk(@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList, int maxSkyLight)
|
||||
{
|
||||
DhChunkPos centerChunkPos = centerChunk.getChunkPos();
|
||||
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk);
|
||||
@@ -104,7 +105,6 @@ public class DhLightingEngine
|
||||
for (int chunkIndex = 0; chunkIndex < nearbyChunkList.size(); chunkIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
|
||||
{
|
||||
IChunkWrapper chunk = nearbyChunkList.get(chunkIndex);
|
||||
|
||||
if (chunk != null && requestedAdjacentPositions.contains(chunk.getChunkPos()))
|
||||
{
|
||||
// remove the newly found position
|
||||
@@ -179,7 +179,7 @@ public class DhLightingEngine
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// block light
|
||||
this.propagateLightPosList(blockLightPosQueue, adjacentChunkHolder,
|
||||
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.seibel.distanthorizons.core.jar.installer.WebDownloader;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||
import com.seibel.distanthorizons.coreapi.util.jar.DeleteOnUnlock;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -104,7 +105,7 @@ public class SelfUpdater
|
||||
}
|
||||
if (!ModrinthGetter.mcVersions.contains(mcVersion))
|
||||
{
|
||||
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Modrinth, only findable versions are ["+ ModrinthGetter.mcVersions.toString() +"]");
|
||||
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Modrinth, only findable versions are ["+ StringUtil.join(",", ModrinthGetter.mcVersions) +"]");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ public class SelfUpdater
|
||||
|
||||
if (!GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).containsKey(mcVersion))
|
||||
{
|
||||
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Gitlab, findable versions are ["+ GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).keySet().toArray().toString() +"].");
|
||||
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Gitlab, findable versions are ["+ StringUtil.join(",", GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).keySet().toArray()) +"].");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,18 +23,30 @@ import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkMo
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.dataObjects.transformers.ChunkToLodBuilder;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
|
||||
import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public abstract class AbstractDhLevel implements IDhLevel
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
public final ChunkToLodBuilder chunkToLodBuilder;
|
||||
|
||||
/** if this is null then the other handler is probably null too, but just in case */
|
||||
@Nullable
|
||||
public ChunkHashRepo chunkHashRepo;
|
||||
|
||||
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 2_000);
|
||||
/** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */
|
||||
protected final ConcurrentHashMap<Long, HashSet<DhChunkPos>> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>();
|
||||
@@ -47,6 +59,20 @@ public abstract class AbstractDhLevel implements IDhLevel
|
||||
|
||||
protected AbstractDhLevel() { this.chunkToLodBuilder = new ChunkToLodBuilder(); }
|
||||
|
||||
protected void createAndSetChunkHashRepo(String databaseFilePath)
|
||||
{
|
||||
ChunkHashRepo newChunkHashRepo = null;
|
||||
try
|
||||
{
|
||||
newChunkHashRepo = new ChunkHashRepo("jdbc:sqlite", databaseFilePath);
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
LOGGER.error("Unable to create [ChunkHashRepo], error: ["+e.getMessage()+"].", e);
|
||||
}
|
||||
this.chunkHashRepo = newChunkHashRepo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
@@ -99,6 +125,32 @@ public abstract class AbstractDhLevel implements IDhLevel
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getChunkHash(DhChunkPos pos)
|
||||
{
|
||||
if (this.chunkHashRepo == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ChunkHashDTO dto = this.chunkHashRepo.getByKey(pos);
|
||||
return (dto != null) ? dto.chunkHash : 0;
|
||||
}
|
||||
@Override
|
||||
public void setChunkHash(DhChunkPos pos, int chunkHash)
|
||||
{
|
||||
if (this.chunkHashRepo != null)
|
||||
{
|
||||
this.chunkHashRepo.save(new ChunkHashDTO(pos, chunkHash));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// base overrides //
|
||||
//================//
|
||||
|
||||
@Override
|
||||
public void close() { this.chunkToLodBuilder.close(); }
|
||||
|
||||
|
||||
@@ -57,8 +57,6 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
|
||||
public final FullDataSourceProviderV2 fullDataSourceProvider;
|
||||
public final AtomicReference<ClientRenderState> ClientRenderStateRef = new AtomicReference<>();
|
||||
|
||||
public final F3Screen.NestedMessage f3Message;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
@@ -68,7 +66,6 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
|
||||
public ClientLevelModule(IDhClientLevel clientLevel)
|
||||
{
|
||||
this.clientLevel = clientLevel;
|
||||
this.f3Message = new F3Screen.NestedMessage(this::f3Log);
|
||||
|
||||
this.fullDataSourceProvider = this.clientLevel.getFullDataProvider();
|
||||
this.fullDataSourceProvider.dateSourceUpdateListeners.add(this);
|
||||
@@ -246,8 +243,6 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
|
||||
}
|
||||
|
||||
this.fullDataSourceProvider.dateSourceUpdateListeners.remove(this);
|
||||
|
||||
this.f3Message.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -256,53 +251,6 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
|
||||
// misc helper functions //
|
||||
//=======================//
|
||||
|
||||
private String[] f3Log()
|
||||
{
|
||||
String dimName = this.clientLevel.getLevelWrapper().getDimensionType().getDimensionName();
|
||||
boolean rendererActive = this.ClientRenderStateRef.get() != null;
|
||||
|
||||
ThreadPoolExecutor fileExecutor = ThreadPoolUtil.getFileHandlerExecutor();
|
||||
String fileQueueSize = (fileExecutor != null) ? fileExecutor.getQueue().size()+"" : "-";
|
||||
String fileCompletedTaskSize = (fileExecutor != null) ? fileExecutor.getCompletedTaskCount()+"" : "-";
|
||||
|
||||
ThreadPoolExecutor updateExecutor = ThreadPoolUtil.getUpdatePropagatorExecutor();
|
||||
String updateQueueSize = (updateExecutor != null) ? updateExecutor.getQueue().size()+"" : "-";
|
||||
String updateCompletedTaskSize = (updateExecutor != null) ? updateExecutor.getCompletedTaskCount()+"" : "-";
|
||||
|
||||
int unsavedDataSourceCount = this.fullDataSourceProvider.getUnsavedDataSourceCount();
|
||||
long legacyDeletionCount = this.fullDataSourceProvider.getLegacyDeletionCount();
|
||||
long migrationCount = this.fullDataSourceProvider.getTotalMigrationCount();
|
||||
|
||||
|
||||
|
||||
ArrayList<String> lines = new ArrayList<>();
|
||||
lines.add("");
|
||||
lines.add("level [" + dimName + "] rendering: " + (rendererActive ? "Active" : "Inactive"));
|
||||
// TODO a lot of these items only need to be rendered once, but we don't currently have a way of doing that, so only add them for the rendered level
|
||||
if (rendererActive)
|
||||
{
|
||||
lines.add("File Handler [" + dimName + "]");
|
||||
lines.add(" File thread pool tasks: " + fileQueueSize + " (completed: " + fileCompletedTaskSize + ")");
|
||||
if (legacyDeletionCount > 0)
|
||||
{
|
||||
lines.add(" Legacy Deletion #: " + legacyDeletionCount);
|
||||
}
|
||||
if (migrationCount > 0)
|
||||
{
|
||||
lines.add(" Legacy Migration #: " + migrationCount);
|
||||
}
|
||||
lines.add(" Update thread pool tasks: " + updateQueueSize + " (completed: " + updateCompletedTaskSize + ")");
|
||||
lines.add(" Level Unsaved #: " + this.clientLevel.getUnsavedDataSourceCount());
|
||||
if (unsavedDataSourceCount != -1)
|
||||
{
|
||||
lines.add(" File Handler Unsaved #: " + unsavedDataSourceCount);
|
||||
}
|
||||
lines.add(" Parent Update #: " + this.fullDataSourceProvider.parentUpdatingPosSet.size());
|
||||
}
|
||||
|
||||
return lines.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public void clearRenderCache()
|
||||
{
|
||||
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
|
||||
|
||||
@@ -49,6 +49,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import javax.annotation.CheckForNull;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/** The level used when connected to a server */
|
||||
@@ -115,6 +117,9 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
||||
this.worldGenModule = new WorldGenModule(this);
|
||||
|
||||
this.clientside = new ClientLevelModule(this);
|
||||
|
||||
this.createAndSetChunkHashRepo(this.dataFileHandler.repo.databaseLocation);
|
||||
|
||||
if (enableRendering)
|
||||
{
|
||||
this.clientside.startRenderer(clientLevelWrapper);
|
||||
@@ -246,6 +251,34 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
||||
@Override
|
||||
public int getMinY() { return this.levelWrapper.getMinHeight(); }
|
||||
|
||||
@Override
|
||||
public void addDebugMenuStringsToList(List<String> messageList)
|
||||
{
|
||||
String dimName = this.levelWrapper.getDimensionType().getDimensionName();
|
||||
boolean rendering = this.clientside.isRendering();
|
||||
messageList.add("["+dimName+"] rendering: "+(rendering ? "yes" : "no"));
|
||||
|
||||
|
||||
boolean migrationErrored = this.dataFileHandler.getMigrationStoppedWithError();
|
||||
if (!migrationErrored)
|
||||
{
|
||||
long legacyDeletionCount = this.dataFileHandler.getLegacyDeletionCount();
|
||||
if (legacyDeletionCount > 0)
|
||||
{
|
||||
messageList.add(" Migrating - Deleting #: " + legacyDeletionCount);
|
||||
}
|
||||
long migrationCount = this.dataFileHandler.getTotalMigrationCount();
|
||||
if (migrationCount > 0)
|
||||
{
|
||||
messageList.add(" Migrating - Conversion #: " + migrationCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
messageList.add(" Migration Failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
|
||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
@@ -38,6 +39,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapp
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/** The level used on a singleplayer world */
|
||||
@@ -53,6 +56,10 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper)
|
||||
{
|
||||
if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
|
||||
@@ -62,6 +69,8 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
|
||||
this.serverLevelWrapper = serverLevelWrapper;
|
||||
this.serverside = new ServerLevelModule(this, saveStructure);
|
||||
this.clientside = new ClientLevelModule(this);
|
||||
this.createAndSetChunkHashRepo(this.serverside.fullDataFileHandler.repo.databaseLocation);
|
||||
|
||||
LOGGER.info("Started " + DhClientServerLevel.class.getSimpleName() + " for " + serverLevelWrapper + " with saves at " + saveStructure);
|
||||
}
|
||||
|
||||
@@ -72,10 +81,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
|
||||
//==============//
|
||||
|
||||
@Override
|
||||
public void clientTick()
|
||||
{
|
||||
clientside.clientTick();
|
||||
}
|
||||
public void clientTick() { this.clientside.clientTick(); }
|
||||
|
||||
@Override
|
||||
public void render(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
|
||||
@@ -124,6 +130,8 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//========//
|
||||
// render //
|
||||
//========//
|
||||
@@ -132,6 +140,8 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
|
||||
|
||||
public void stopRenderer() { this.clientside.stopRenderer(); }
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// level handling //
|
||||
//================//
|
||||
@@ -180,7 +190,52 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
|
||||
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.clientside.updateDataSourcesAsync(data); }
|
||||
|
||||
@Override
|
||||
public int getMinY() { return getLevelWrapper().getMinHeight(); }
|
||||
public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// debugging //
|
||||
//===========//
|
||||
|
||||
@Override
|
||||
public void addDebugMenuStringsToList(List<String> messageList)
|
||||
{
|
||||
// header
|
||||
String dimName = this.serverLevelWrapper.getDimensionType().getDimensionName();
|
||||
boolean rendering = this.clientside.isRendering();
|
||||
messageList.add("["+dimName+"] rendering: "+(rendering ? "yes" : "no"));
|
||||
|
||||
|
||||
// migration
|
||||
boolean migrationErrored = this.serverside.fullDataFileHandler.getMigrationStoppedWithError();
|
||||
if (!migrationErrored)
|
||||
{
|
||||
long legacyDeletionCount = this.serverside.fullDataFileHandler.getLegacyDeletionCount();
|
||||
if (legacyDeletionCount > 0)
|
||||
{
|
||||
messageList.add(" Migrating - Deleting #: " + F3Screen.NUMBER_FORMAT.format(legacyDeletionCount));
|
||||
}
|
||||
long migrationCount = this.serverside.fullDataFileHandler.getTotalMigrationCount();
|
||||
if (migrationCount > 0)
|
||||
{
|
||||
messageList.add(" Migrating - Conversion #: " + F3Screen.NUMBER_FORMAT.format(migrationCount));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
messageList.add(" Migration Failed");
|
||||
}
|
||||
|
||||
|
||||
// world gen
|
||||
WorldGenModule worldGenState = this.serverside.worldGenModule;
|
||||
String worldGenDisplayString = worldGenState.getDebugMenuString();
|
||||
if (worldGenDisplayString != null)
|
||||
{
|
||||
messageList.add(worldGenDisplayString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
@@ -61,6 +63,8 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
|
||||
public final ServerLevelModule serverside;
|
||||
private final IServerLevelWrapper serverLevelWrapper;
|
||||
|
||||
|
||||
|
||||
private final RemotePlayerConnectionHandler remotePlayerConnectionHandler;
|
||||
|
||||
private final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenLoopingQueue = new ConcurrentLinkedQueue<>();
|
||||
@@ -75,6 +79,8 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
|
||||
}
|
||||
this.serverLevelWrapper = serverLevelWrapper;
|
||||
this.serverside = new ServerLevelModule(this, saveStructure);
|
||||
this.createAndSetChunkHashRepo(this.serverside.fullDataFileHandler.repo.databaseLocation);
|
||||
|
||||
LOGGER.info("Started DHLevel for {} with saves at {}", serverLevelWrapper, saveStructure);
|
||||
|
||||
this.remotePlayerConnectionHandler = remotePlayerConnectionHandler;
|
||||
@@ -172,6 +178,8 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public <T extends PluginChannelMessage> Consumer<T> currentLevelOnly(Consumer<T> next)
|
||||
{
|
||||
return msg ->
|
||||
@@ -378,6 +386,19 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// debugging //
|
||||
//===========//
|
||||
|
||||
@Override
|
||||
public void addDebugMenuStringsToList(List<String> messageList)
|
||||
{
|
||||
String dimName = this.serverLevelWrapper.getDimensionType().getDimensionName();
|
||||
messageList.add("["+dimName+"]");
|
||||
}
|
||||
|
||||
private static class IncompleteDataSourceEntry
|
||||
{
|
||||
@CheckForNull
|
||||
|
||||
@@ -22,7 +22,6 @@ package com.seibel.distanthorizons.core.level;
|
||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||
import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
|
||||
@@ -22,9 +22,11 @@ package com.seibel.distanthorizons.core.level;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
|
||||
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface IDhLevel extends AutoCloseable
|
||||
@@ -37,6 +39,9 @@ public interface IDhLevel extends AutoCloseable
|
||||
*/
|
||||
ILevelWrapper getLevelWrapper();
|
||||
|
||||
/** @return 0 if no hash is known */
|
||||
int getChunkHash(DhChunkPos pos);
|
||||
void setChunkHash(DhChunkPos pos, int chunkHash);
|
||||
void updateChunkAsync(IChunkWrapper chunk);
|
||||
|
||||
FullDataSourceProviderV2 getFullDataProvider();
|
||||
@@ -53,4 +58,7 @@ public interface IDhLevel extends AutoCloseable
|
||||
*/
|
||||
int getUnsavedDataSourceCount();
|
||||
|
||||
void addDebugMenuStringsToList(List<String> messageList);
|
||||
|
||||
|
||||
}
|
||||
@@ -43,6 +43,10 @@ public class ServerLevelModule implements AutoCloseable
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public ServerLevelModule(IDhServerLevel parentServerLevel, AbstractSaveStructure saveStructure)
|
||||
{
|
||||
this.parentServerLevel = parentServerLevel;
|
||||
@@ -54,6 +58,10 @@ public class ServerLevelModule implements AutoCloseable
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// base overrides //
|
||||
//================//
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
|
||||
@@ -37,30 +37,16 @@ public class WorldGenModule implements Closeable
|
||||
private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener;
|
||||
|
||||
private final AtomicReference<AbstractWorldGenState> worldGenStateRef = new AtomicReference<>();
|
||||
private final F3Screen.DynamicMessage worldGenF3Message;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public WorldGenModule(GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener)
|
||||
{
|
||||
this.onWorldGenCompleteListener = onWorldGenCompleteListener;
|
||||
this.worldGenF3Message = new F3Screen.DynamicMessage(() ->
|
||||
{
|
||||
AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
|
||||
if (worldGenState != null)
|
||||
{
|
||||
int waitingCount = worldGenState.worldGenerationQueue.getWaitingTaskCount();
|
||||
int inProgressCount = worldGenState.worldGenerationQueue.getInProgressTaskCount();
|
||||
int totalCountEstimate = worldGenState.worldGenerationQueue.getEstimatedTotalTaskCount();
|
||||
|
||||
return "World Gen Tasks: "+waitingCount+"/"+totalCountEstimate+", (in progress: "+inProgressCount+")";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "World Gen Disabled";
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +100,12 @@ public class WorldGenModule implements Closeable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=======================//
|
||||
// base method overrides //
|
||||
//=======================//
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
@@ -135,8 +127,6 @@ public class WorldGenModule implements Closeable
|
||||
worldGenState.closeAsync(true).join(); //TODO: Make it async.
|
||||
}
|
||||
}
|
||||
|
||||
this.worldGenF3Message.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -147,6 +137,22 @@ public class WorldGenModule implements Closeable
|
||||
|
||||
public boolean isWorldGenRunning() { return this.worldGenStateRef.get() != null; }
|
||||
|
||||
public String getDebugMenuString()
|
||||
{
|
||||
AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
|
||||
if (worldGenState == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
String waitingCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getWaitingTaskCount());
|
||||
String inProgressCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getInProgressTaskCount());
|
||||
String totalCountEstimateStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getEstimatedTotalTaskCount());
|
||||
|
||||
return "World Gen Tasks: "+waitingCountStr+"/"+totalCountEstimateStr+" (in progress: "+inProgressCountStr+")";
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
|
||||
@@ -19,159 +19,127 @@
|
||||
|
||||
package com.seibel.distanthorizons.core.logging.f3;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
public class F3Screen
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static final String[] DEFAULT_STRING = {
|
||||
"", // blank line for spacing
|
||||
ModInfo.READABLE_NAME + " version: " + ModInfo.VERSION
|
||||
};
|
||||
private static final List<Message> SELF_UPDATE_MESSAGE_LIST = Collections.synchronizedList(new LinkedList<>());
|
||||
public static final NumberFormat NUMBER_FORMAT = NumberFormat.getIntegerInstance();
|
||||
|
||||
public static void addStringToDisplay(List<String> list)
|
||||
|
||||
|
||||
//============//
|
||||
// properties //
|
||||
//============//
|
||||
|
||||
private static WeakReference<RenderBufferHandler> renderBufferHandlerRef = new WeakReference<>(null);
|
||||
public static void setRenderBufferHandler(@Nullable RenderBufferHandler renderBufferHandler)
|
||||
{
|
||||
list.addAll(Arrays.asList(DEFAULT_STRING));
|
||||
synchronized (SELF_UPDATE_MESSAGE_LIST)
|
||||
if (renderBufferHandler != null && renderBufferHandlerRef.get() != null)
|
||||
{
|
||||
Iterator<Message> iterator = SELF_UPDATE_MESSAGE_LIST.iterator();
|
||||
while (iterator.hasNext())
|
||||
LOGGER.warn("multiple RenderBufferHandlers are active at once, the F3 menu may not be accurate.");
|
||||
}
|
||||
|
||||
renderBufferHandlerRef = new WeakReference<>(renderBufferHandler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// injection point //
|
||||
//=================//
|
||||
|
||||
/**
|
||||
* F3 menu example: <br>
|
||||
<code>
|
||||
Distant Horizons v: 2.1.1-a-dev <br><br>
|
||||
|
||||
Queued chunk updates: 0 / 1000 <br>
|
||||
World Gen Tasks: 40/5304, (in progress: 7) <br><br>
|
||||
|
||||
File thread pool tasks: 0 (complete: 759) <br>
|
||||
Update thread pool tasks: 10 (complete: 24) <br>
|
||||
Level Unsaved #: 0 <br>
|
||||
File Handler Unsaved #: 0 <br>
|
||||
Parent Update #: 12 <br><br>
|
||||
|
||||
Client_Server World with 3 levels <br>
|
||||
[overworld] rendering: Active <br>
|
||||
[the_end] rendering: Inactive <br>
|
||||
[the_nether] rendering: Inactive <br><br>
|
||||
|
||||
VBO Render Count: 199/374 <br>
|
||||
</code>
|
||||
*/
|
||||
public static void addStringToDisplay(List<String> messageList)
|
||||
{
|
||||
ThreadPoolExecutor worldGenPool = ThreadPoolUtil.getWorldGenExecutor();
|
||||
ThreadPoolExecutor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor();
|
||||
ThreadPoolExecutor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor();
|
||||
|
||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||
Iterable<? extends IDhLevel> levelIterator = world.getAllLoadedLevels();
|
||||
|
||||
|
||||
messageList.add("");
|
||||
messageList.add(ModInfo.READABLE_NAME+": "+ModInfo.VERSION);
|
||||
messageList.add("");
|
||||
// thread pools
|
||||
messageList.add(getThreadPoolStatString("World Gen", worldGenPool));//"World Gen Tasks: 40/5304, (in progress: 7)");
|
||||
messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool));
|
||||
messageList.add(getThreadPoolStatString("Update Propagator", updatePool));
|
||||
messageList.add("");
|
||||
// chunk updates
|
||||
messageList.add(SharedApi.INSTANCE.getDebugMenuString());
|
||||
messageList.add("");
|
||||
// rendering
|
||||
RenderBufferHandler renderBufferHandler = renderBufferHandlerRef.get();
|
||||
if (renderBufferHandler != null)
|
||||
{
|
||||
messageList.add(renderBufferHandler.getVboRenderDebugMenuString());
|
||||
String showPassString = renderBufferHandler.getShadowPassRenderDebugMenuString();
|
||||
if (showPassString != null)
|
||||
{
|
||||
Message message = iterator.next();
|
||||
if (message == null)
|
||||
{
|
||||
iterator.remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
message.printTo(list);
|
||||
}
|
||||
messageList.add(showPassString);
|
||||
}
|
||||
messageList.add("");
|
||||
}
|
||||
// world / levels
|
||||
messageList.add(world.GetDebugMenuString());
|
||||
for (IDhLevel level : levelIterator)
|
||||
{
|
||||
level.addDebugMenuStringsToList(messageList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
// we are using Closeable instead of AutoCloseable because the close method should never throw exceptions
|
||||
// and because this class shouldn't be used in a try {} block.
|
||||
public static abstract class Message implements Closeable
|
||||
private static String getThreadPoolStatString(String name, ThreadPoolExecutor pool)
|
||||
{
|
||||
protected Message()
|
||||
{
|
||||
SELF_UPDATE_MESSAGE_LIST.add(this);
|
||||
}
|
||||
|
||||
public abstract void printTo(List<String> output);
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
boolean removed = SELF_UPDATE_MESSAGE_LIST.remove(this);
|
||||
}
|
||||
String queueSize = (pool != null) ? NUMBER_FORMAT.format(pool.getQueue().size()) : "-";
|
||||
String completedCount = (pool != null) ? NUMBER_FORMAT.format(pool.getCompletedTaskCount()) : "-";
|
||||
|
||||
return name+", tasks: "+queueSize+", complete: "+completedCount;
|
||||
}
|
||||
|
||||
public static class StaticMessage extends Message
|
||||
{
|
||||
private final String[] lines;
|
||||
|
||||
public StaticMessage(String... lines) { this.lines = lines; }
|
||||
|
||||
@Override
|
||||
public void printTo(List<String> output) { output.addAll(Arrays.asList(this.lines)); }
|
||||
|
||||
}
|
||||
|
||||
public static class DynamicMessage extends Message
|
||||
{
|
||||
private final Supplier<String> supplier;
|
||||
|
||||
public DynamicMessage(Supplier<String> message) { this.supplier = message; }
|
||||
|
||||
public void printTo(List<String> list)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
String message = this.supplier.get();
|
||||
if (message != null)
|
||||
{
|
||||
list.add(message);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected Exception in F3 ["+DynamicMessage.class.getSimpleName()+"], error: "+e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class MultiDynamicMessage extends Message
|
||||
{
|
||||
private final Supplier<String>[] supplierList;
|
||||
|
||||
@SafeVarargs
|
||||
public MultiDynamicMessage(Supplier<String>... suppliers) { this.supplierList = suppliers; }
|
||||
|
||||
public void printTo(List<String> list)
|
||||
{
|
||||
for (Supplier<String> supplier : this.supplierList)
|
||||
{
|
||||
try
|
||||
{
|
||||
String message = supplier.get();
|
||||
if (message != null)
|
||||
{
|
||||
list.add(message);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected Exception in F3 ["+DynamicMessage.class.getSimpleName()+"], error: "+e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class NestedMessage extends Message
|
||||
{
|
||||
private final Supplier<String[]> supplier;
|
||||
|
||||
public NestedMessage(Supplier<String[]> message)
|
||||
{
|
||||
this.supplier = message;
|
||||
}
|
||||
|
||||
public void printTo(List<String> list)
|
||||
{
|
||||
try
|
||||
{
|
||||
String[] message = this.supplier.get();
|
||||
if (message != null)
|
||||
{
|
||||
list.addAll(Arrays.asList(message));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected Exception in F3 ["+DynamicMessage.class.getSimpleName()+"], error: "+e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.seibel.distanthorizons.core.network.exceptions;
|
||||
|
||||
public class SessionClosedException {
|
||||
|
||||
}
|
||||
@@ -302,13 +302,10 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
boolean allChildrenSectionsAreLoaded = true;
|
||||
|
||||
// recursively update all child render sections
|
||||
LongIterator childPosIterator = quadNode.getChildPosIterator();
|
||||
while (childPosIterator.hasNext())
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
long childPos = childPosIterator.nextLong();
|
||||
QuadNode<LodRenderSection> childNode = rootNode.getNode(childPos);
|
||||
|
||||
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, thisPosIsRendering || parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
|
||||
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), thisPosIsRendering || parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
|
||||
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
|
||||
}
|
||||
|
||||
@@ -335,13 +332,10 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
renderSection.renderingEnabled = false;
|
||||
|
||||
// walk back down the tree and enable the child sections //TODO there are probably more efficient ways of doing this, but this will work for now
|
||||
childPosIterator = quadNode.getChildPosIterator();
|
||||
while (childPosIterator.hasNext())
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
long childPos = childPosIterator.nextLong();
|
||||
QuadNode<LodRenderSection> childNode = rootNode.getNode(childPos);
|
||||
|
||||
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
|
||||
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
|
||||
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
|
||||
}
|
||||
if (!allChildrenSectionsAreLoaded)
|
||||
|
||||
+33
-28
@@ -73,8 +73,6 @@ public class RenderBufferHandler implements AutoCloseable
|
||||
|
||||
private final AtomicBoolean rebuildAllBuffers = new AtomicBoolean(false);
|
||||
|
||||
public F3Screen.MultiDynamicMessage f3Message;
|
||||
|
||||
private int visibleBufferCount;
|
||||
private int culledBufferCount;
|
||||
private int shadowVisibleBufferCount;
|
||||
@@ -104,31 +102,7 @@ public class RenderBufferHandler implements AutoCloseable
|
||||
}
|
||||
|
||||
|
||||
this.f3Message = new F3Screen.MultiDynamicMessage(
|
||||
() ->
|
||||
{
|
||||
String countText = this.visibleBufferCount + "";
|
||||
if (!Config.Client.Advanced.Graphics.AdvancedGraphics.disableFrustumCulling.get())
|
||||
{
|
||||
countText += "/" + (this.visibleBufferCount + this.culledBufferCount);
|
||||
}
|
||||
return LodUtil.formatLog("Rendered Buffer Count: " + countText);
|
||||
},
|
||||
() ->
|
||||
{
|
||||
boolean hasIrisShaders = (IRIS_ACCESSOR != null && IRIS_ACCESSOR.isShaderPackInUse());
|
||||
if (!hasIrisShaders)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
String countText = this.shadowVisibleBufferCount + "";
|
||||
if (!Config.Client.Advanced.Graphics.AdvancedGraphics.disableFrustumCulling.get())
|
||||
{
|
||||
countText += "/" + (this.shadowVisibleBufferCount + this.shadowCulledBufferCount);
|
||||
}
|
||||
return LodUtil.formatLog("Shadow Buffer Count: " + countText);
|
||||
});
|
||||
F3Screen.setRenderBufferHandler(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -396,6 +370,37 @@ public class RenderBufferHandler implements AutoCloseable
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// F3 menu //
|
||||
//=========//
|
||||
|
||||
public String getVboRenderDebugMenuString()
|
||||
{
|
||||
String countText = F3Screen.NUMBER_FORMAT.format(this.visibleBufferCount);
|
||||
if (!Config.Client.Advanced.Graphics.AdvancedGraphics.disableFrustumCulling.get())
|
||||
{
|
||||
countText += "/" + F3Screen.NUMBER_FORMAT.format(this.visibleBufferCount + this.culledBufferCount);
|
||||
}
|
||||
return LodUtil.formatLog("VBO Render Count: " + countText);
|
||||
}
|
||||
public String getShadowPassRenderDebugMenuString()
|
||||
{
|
||||
boolean hasIrisShaders = (IRIS_ACCESSOR != null && IRIS_ACCESSOR.isShaderPackInUse());
|
||||
if (!hasIrisShaders)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
String countText = F3Screen.NUMBER_FORMAT.format(this.shadowVisibleBufferCount);
|
||||
if (!Config.Client.Advanced.Graphics.AdvancedGraphics.disableFrustumCulling.get())
|
||||
{
|
||||
countText += "/" + F3Screen.NUMBER_FORMAT.format(this.shadowVisibleBufferCount + this.shadowCulledBufferCount);
|
||||
}
|
||||
return LodUtil.formatLog("Shadow VBO Render Count: " + countText);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// cleanup //
|
||||
//=========//
|
||||
@@ -413,7 +418,7 @@ public class RenderBufferHandler implements AutoCloseable
|
||||
}
|
||||
}
|
||||
|
||||
this.f3Message.close();
|
||||
F3Screen.setRenderBufferHandler(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2023 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.sql.dto;
|
||||
|
||||
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
|
||||
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
|
||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
||||
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
|
||||
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.Adler32;
|
||||
import java.util.zip.CheckedOutputStream;
|
||||
|
||||
/** handles storing {@link FullDataSourceV2}'s in the database. */
|
||||
public class ChunkHashDTO implements IBaseDTO<DhChunkPos>
|
||||
{
|
||||
public DhChunkPos pos;
|
||||
public int chunkHash;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public ChunkHashDTO(DhChunkPos pos, int chunkHash)
|
||||
{
|
||||
this.pos = pos;
|
||||
this.chunkHash = chunkHash;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// overrides //
|
||||
//===========//
|
||||
|
||||
@Override
|
||||
public DhChunkPos getKey() { return this.pos; }
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.sql.dto;
|
||||
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
|
||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -81,6 +82,8 @@ public class FullDataSourceV1DTO implements IBaseDTO<Long>
|
||||
|
||||
@Override
|
||||
public Long getKey() { return this.pos; }
|
||||
@Override
|
||||
public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
|
||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.network.protocol.INetworkObject;
|
||||
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
@@ -416,6 +417,8 @@ public class FullDataSourceV2DTO implements IBaseDTO<Long>, INetworkObject
|
||||
|
||||
@Override
|
||||
public Long getKey() { return this.pos; }
|
||||
@Override
|
||||
public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); }
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
|
||||
@@ -26,6 +26,8 @@ package com.seibel.distanthorizons.core.sql.dto;
|
||||
public interface IBaseDTO<TKey>
|
||||
{
|
||||
TKey getKey();
|
||||
/** Can be used for keys that don't have a clean human readable toString() method. */
|
||||
default String getKeyDisplayString() { return this.getKey().toString(); }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
|
||||
}
|
||||
catch (DbConnectionClosedException ignored)
|
||||
{
|
||||
LOGGER.warn("Attempted to insert [" + this.dtoClass.getSimpleName() + "] with primary key [" + (dto != null ? dto.getKey() : "NULL") + "] on closed repo [" + this.connectionString + "].");
|
||||
LOGGER.warn("Attempted to insert ["+this.dtoClass.getSimpleName()+"] with primary key ["+(dto != null ? dto.getKeyDisplayString() : "NULL")+"] on closed repo ["+this.connectionString+"].");
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
@@ -190,7 +190,7 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
|
||||
}
|
||||
catch (DbConnectionClosedException e)
|
||||
{
|
||||
LOGGER.warn("Attempted to update [" + this.dtoClass.getSimpleName() + "] with primary key [" + (dto != null ? dto.getKey() : "NULL") + "] on closed repo [" + this.connectionString + "].");
|
||||
LOGGER.warn("Attempted to update ["+this.dtoClass.getSimpleName()+"] with primary key ["+(dto != null ? dto.getKeyDisplayString() : "NULL")+"] on closed repo ["+this.connectionString+"].");
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2023 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.sql.repo;
|
||||
|
||||
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO>
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public ChunkHashRepo(String databaseType, String databaseLocation) throws SQLException
|
||||
{
|
||||
super(databaseType, databaseLocation, ChunkHashDTO.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// overrides //
|
||||
//===========//
|
||||
|
||||
@Override
|
||||
public String getTableName() { return "ChunkHash"; }
|
||||
|
||||
@Override
|
||||
public String createWhereStatement(DhChunkPos pos) { return "ChunkPosX = '"+pos.x+"' AND ChunkPosZ = '"+pos.z+"'"; }
|
||||
|
||||
|
||||
|
||||
//=======================//
|
||||
// repo required methods //
|
||||
//=======================//
|
||||
|
||||
@Override
|
||||
public ChunkHashDTO convertDictionaryToDto(Map<String, Object> objectMap) throws ClassCastException
|
||||
{
|
||||
int posX = (Integer) objectMap.get("ChunkPosX");
|
||||
int posZ = (Integer) objectMap.get("ChunkPosZ");
|
||||
|
||||
int chunkHash = (Integer) objectMap.get("ChunkHash");
|
||||
|
||||
|
||||
ChunkHashDTO dto = new ChunkHashDTO(new DhChunkPos(posX, posZ), chunkHash);
|
||||
return dto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreparedStatement createInsertStatement(ChunkHashDTO dto) throws SQLException
|
||||
{
|
||||
String sql =
|
||||
"INSERT INTO "+this.getTableName() + " (\n" +
|
||||
" ChunkPosX, ChunkPosZ, \n" +
|
||||
" ChunkHash, \n" +
|
||||
" LastModifiedUnixDateTime, CreatedUnixDateTime) \n" +
|
||||
"VALUES( \n" +
|
||||
" ?, ?, \n" +
|
||||
" ?, \n" +
|
||||
" ?, ? \n" +
|
||||
");";
|
||||
PreparedStatement statement = this.createPreparedStatement(sql);
|
||||
|
||||
int i = 1;
|
||||
statement.setObject(i++, dto.pos.x);
|
||||
statement.setObject(i++, dto.pos.z);
|
||||
|
||||
statement.setObject(i++, dto.chunkHash);
|
||||
|
||||
statement.setObject(i++, System.currentTimeMillis()); // last modified unix time
|
||||
statement.setObject(i++, System.currentTimeMillis()); // created unix time
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreparedStatement createUpdateStatement(ChunkHashDTO dto) throws SQLException
|
||||
{
|
||||
String sql =
|
||||
"UPDATE "+this.getTableName()+" \n" +
|
||||
"SET \n" +
|
||||
" ChunkHash = ? \n" +
|
||||
" ,LastModifiedUnixDateTime = ? \n" +
|
||||
"WHERE ChunkPosX = ? AND ChunkPosZ = ?";
|
||||
PreparedStatement statement = this.createPreparedStatement(sql);
|
||||
|
||||
int i = 1;
|
||||
statement.setObject(i++, dto.chunkHash);
|
||||
statement.setObject(i++, System.currentTimeMillis()); // last modified unix time
|
||||
|
||||
statement.setObject(i++, dto.pos.x);
|
||||
statement.setObject(i++, dto.pos.z);
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -30,6 +30,13 @@ public class ArrayGridList<T> extends ArrayList<T>
|
||||
{
|
||||
public final int gridSize;
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
|
||||
/** @param filler the function called for each index to set the initial values */
|
||||
public ArrayGridList(int gridSize, BiFunction<Integer, Integer, T> filler)
|
||||
{
|
||||
super((gridSize) * (gridSize));
|
||||
@@ -67,6 +74,12 @@ public class ArrayGridList<T> extends ArrayList<T>
|
||||
// "==========================================\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// methods //
|
||||
//=========//
|
||||
|
||||
protected int _indexOf(int x, int y)
|
||||
{
|
||||
return x + y * gridSize;
|
||||
|
||||
+2
-5
@@ -22,15 +22,12 @@ package com.seibel.distanthorizons.core.util.objects.quadTree.iterators;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Queue;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class QuadNodeChildIndexIterator<T> implements Iterator<Integer>
|
||||
{
|
||||
private final Queue<Integer> iteratorQueue = new LinkedList<>();
|
||||
private final Queue<Integer> iteratorQueue = new ArrayDeque<>();
|
||||
|
||||
|
||||
|
||||
|
||||
+4
-7
@@ -22,10 +22,7 @@ package com.seibel.distanthorizons.core.util.objects.quadTree.iterators;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Queue;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
@@ -34,8 +31,8 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
private final byte highestDetailLevel;
|
||||
|
||||
|
||||
private final Queue<QuadNode<T>> validNodesForDetailLevel = new LinkedList<>();
|
||||
private final Queue<QuadNode<T>> iteratorNodeQueue = new LinkedList<>();
|
||||
private final Queue<QuadNode<T>> validNodesForDetailLevel = new ArrayDeque<>();
|
||||
private final Queue<QuadNode<T>> iteratorNodeQueue = new ArrayDeque<>();
|
||||
private byte iteratorDetailLevel = 0;
|
||||
|
||||
private final boolean onlyReturnLeafValues;
|
||||
@@ -64,7 +61,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
// but it is simple and functions well enough for now
|
||||
|
||||
|
||||
Queue<QuadNode<T>> parentNodeQueue = new LinkedList<>();
|
||||
Queue<QuadNode<T>> parentNodeQueue = new ArrayDeque<>();
|
||||
parentNodeQueue.add(rootNode);
|
||||
|
||||
// walk through the whole tree and add each leaf node to the iterator queue
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.Closeable;
|
||||
@@ -36,11 +37,22 @@ public abstract class AbstractDhWorld implements IDhWorld, Closeable
|
||||
|
||||
|
||||
|
||||
// constructor //
|
||||
|
||||
protected AbstractDhWorld(EWorldEnvironment environment) { this.environment = environment; }
|
||||
|
||||
|
||||
// remove the "throws IOException"
|
||||
|
||||
// abstract methods //
|
||||
|
||||
// removes the "throws IOException"
|
||||
@Override
|
||||
public abstract void close();
|
||||
|
||||
|
||||
|
||||
// helper methods //
|
||||
|
||||
public String GetDebugMenuString() { return this.environment + " World with " + F3Screen.NUMBER_FORMAT.format(this.getLoadedLevelCount()) + " levels"; }
|
||||
|
||||
}
|
||||
|
||||
@@ -46,8 +46,6 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client Server World Ticker Thread", 2);
|
||||
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop
|
||||
|
||||
public F3Screen.DynamicMessage f3Message;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
@@ -57,10 +55,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
public DhClientServerWorld()
|
||||
{
|
||||
super(EWorldEnvironment.Client_Server);
|
||||
|
||||
LOGGER.info("Started DhWorld of type " + this.environment);
|
||||
|
||||
this.f3Message = new F3Screen.DynamicMessage(() -> LodUtil.formatLog(this.environment + " World with " + this.dhLevels.size() + " levels"));
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +108,8 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
|
||||
@Override
|
||||
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.dhLevels; }
|
||||
@Override
|
||||
public int getLoadedLevelCount() { return this.dhLevels.size(); }
|
||||
|
||||
@Override
|
||||
public void unloadLevel(@NotNull ILevelWrapper wrapper)
|
||||
@@ -154,13 +151,16 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
|
||||
public void doWorldGen() { this.dhLevels.forEach(DhClientServerLevel::doWorldGen); }
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// base overrides //
|
||||
//================//
|
||||
|
||||
/** synchronized to prevent a rare issue where the server tries closing the same world multiple times in rapid succession. */
|
||||
@Override
|
||||
public synchronized void close()
|
||||
{
|
||||
this.f3Message.close();
|
||||
|
||||
|
||||
// clear dhLevels to prevent concurrent modification errors
|
||||
HashSet<DhClientServerLevel> levelsToClose = new HashSet<>(this.dhLevels);
|
||||
this.dhLevels.clear();
|
||||
|
||||
@@ -99,6 +99,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||
|
||||
@Override
|
||||
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.levels.values(); }
|
||||
@Override
|
||||
public int getLoadedLevelCount() { return this.levels.size(); }
|
||||
|
||||
@Override
|
||||
public void unloadLevel(@NotNull ILevelWrapper wrapper)
|
||||
|
||||
@@ -119,6 +119,8 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
|
||||
|
||||
@Override
|
||||
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.levels.values(); }
|
||||
@Override
|
||||
public int getLoadedLevelCount() { return this.levels.size(); }
|
||||
|
||||
@Override
|
||||
public void unloadLevel(@NotNull ILevelWrapper wrapper)
|
||||
@@ -146,6 +148,12 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
|
||||
this.levels.values().forEach(DhServerLevel::doWorldGen);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// base overrides //
|
||||
//================//
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@ public interface IDhWorld
|
||||
IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper);
|
||||
IDhLevel getLevel(@NotNull ILevelWrapper wrapper);
|
||||
Iterable<? extends IDhLevel> getAllLoadedLevels();
|
||||
int getLoadedLevelCount();
|
||||
|
||||
void unloadLevel(@NotNull ILevelWrapper levelWrapper);
|
||||
|
||||
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2023 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.wrapperInterfaces.chunk;
|
||||
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Compact, efficient storage for light levels.
|
||||
* all blocks only take up 4 bits in total,
|
||||
* and if a 16x16x16 area is detected to have the same light level in all positions,
|
||||
* then we store a single byte for that light level, instead of 2 kilobytes.
|
||||
*
|
||||
* @author Builderb0y
|
||||
*/
|
||||
public class ChunkLightStorage
|
||||
{
|
||||
/** the minimum Y level in the chunk which this storage is storing light levels for (inclusive). */
|
||||
public int minY;
|
||||
/** the maximum Y level in the chunk which this storage is storing light levels for (exclusive). */
|
||||
public int maxY;
|
||||
|
||||
/** the data stored in this storage, split up into 16x16x16 areas. */
|
||||
public LightSection[] lightSections;
|
||||
|
||||
/**
|
||||
* If the get method is called on a Y position above what's stored
|
||||
* this value will be returned. <br><br>
|
||||
*
|
||||
* This needs to be manually defined since sky and block lights behave differently
|
||||
* for values both above and below what's defined.
|
||||
*/
|
||||
public int aboveMaxYValue;
|
||||
/** @see ChunkLightStorage#aboveMaxYValue */
|
||||
public int belowMinYValue;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public static ChunkLightStorage createSkyLightStorage(IChunkWrapper chunkWrapper) { return createSkyLightStorage(chunkWrapper.getMinBuildHeight(), chunkWrapper.getMaxBuildHeight()); }
|
||||
public static ChunkLightStorage createSkyLightStorage(int minY, int maxY)
|
||||
{
|
||||
return new ChunkLightStorage(
|
||||
minY, maxY,
|
||||
// positions above should be lit but positions below should be unlit
|
||||
LodUtil.MAX_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
|
||||
}
|
||||
public static ChunkLightStorage createBlockLightStorage(IChunkWrapper chunkWrapper) { return createBlockLightStorage(chunkWrapper.getMinBuildHeight(), chunkWrapper.getMaxBuildHeight()); }
|
||||
public static ChunkLightStorage createBlockLightStorage(int minY, int maxY)
|
||||
{
|
||||
return new ChunkLightStorage(
|
||||
minY, maxY,
|
||||
// positions above and below the handled area should be unlit
|
||||
LodUtil.MIN_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
|
||||
}
|
||||
|
||||
public ChunkLightStorage(int minY, int maxY, int aboveMaxYValue, int belowMinYValue)
|
||||
{
|
||||
this.minY = minY;
|
||||
this.maxY = maxY;
|
||||
|
||||
this.aboveMaxYValue = aboveMaxYValue;
|
||||
this.belowMinYValue = belowMinYValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// getters and setters //
|
||||
//=====================//
|
||||
|
||||
public int get(int x, int y, int z)
|
||||
{
|
||||
if (y < this.minY)
|
||||
{
|
||||
return this.belowMinYValue;
|
||||
}
|
||||
else if (y >= this.maxY)
|
||||
{
|
||||
return this.aboveMaxYValue;
|
||||
}
|
||||
|
||||
|
||||
if (this.lightSections != null)
|
||||
{
|
||||
LightSection lightSection = this.lightSections[BitShiftUtil.divideByPowerOfTwo(y - this.minY, 4)];
|
||||
if (lightSection != null)
|
||||
{
|
||||
return lightSection.get(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void set(int x, int y, int z, int lightLevel)
|
||||
{
|
||||
if (y < this.minY || y >= this.maxY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//populate array if it doesn't exist.
|
||||
if (this.lightSections == null)
|
||||
{
|
||||
this.lightSections = new LightSection[BitShiftUtil.divideByPowerOfTwo(this.maxY - this.minY, 4)];
|
||||
}
|
||||
|
||||
int index = (y - this.minY) >> 4;
|
||||
LightSection lightSection = this.lightSections[index];
|
||||
|
||||
//populate lightSection in array if it doesn't exist.
|
||||
if (lightSection == null)
|
||||
{
|
||||
lightSection = new LightSection(0);
|
||||
this.lightSections[index] = lightSection;
|
||||
}
|
||||
lightSection.set(x, y, z, lightLevel);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
public static class LightSection
|
||||
{
|
||||
public byte constantValue;
|
||||
public long[] data;
|
||||
public short[] counts;
|
||||
|
||||
public LightSection(int initialValue)
|
||||
{
|
||||
this.constantValue = (byte) (initialValue);
|
||||
this.counts = new short[16];
|
||||
this.counts[initialValue] = 16 * 16 * 16;
|
||||
}
|
||||
|
||||
public int get(int x, int y, int z)
|
||||
{
|
||||
if (this.constantValue >= 0)
|
||||
{
|
||||
return this.constantValue;
|
||||
}
|
||||
|
||||
x &= 15;
|
||||
y &= 15;
|
||||
z &= 15;
|
||||
long bits = this.data[(z << 4) | x];
|
||||
return ((int) (bits >>> (y << 2))) & 15;
|
||||
}
|
||||
|
||||
public void set(int x, int y, int z, int lightLevel)
|
||||
{
|
||||
int oldLightLevel = -1;
|
||||
if (this.constantValue >= 0)
|
||||
{
|
||||
oldLightLevel = this.constantValue;
|
||||
|
||||
//if the light level didn't change, then there's nothing to do.
|
||||
if (oldLightLevel == lightLevel) return;
|
||||
|
||||
//if we are a constant value and need to change something,
|
||||
//then that means we need to convert to a non-constant value.
|
||||
this.data = DataRecycler.get();
|
||||
|
||||
//repeat oldLightLevel 16 times as a bit pattern.
|
||||
long payload = oldLightLevel;
|
||||
payload |= payload << 4;
|
||||
payload |= payload << 8;
|
||||
payload |= payload << 16;
|
||||
payload |= payload << 32;
|
||||
|
||||
//fill our data with our constant value.
|
||||
Arrays.fill(this.data, payload);
|
||||
|
||||
//we are no longer a constant value.
|
||||
this.constantValue = -1;
|
||||
}
|
||||
|
||||
x &= 15;
|
||||
y &= 15;
|
||||
z &= 15;
|
||||
int index = (z << 4) | x;
|
||||
long bits = this.data[index];
|
||||
//if we weren't a constant value before, now's the time to initialize oldLightLevel.
|
||||
if (oldLightLevel < 0)
|
||||
{
|
||||
oldLightLevel = ((int) (bits >>> (y << 2))) & 15;
|
||||
}
|
||||
//clear the 4 bits that correspond to the light level at x, y, z...
|
||||
bits &= ~(15L << (y << 2));
|
||||
//...and then re-populate those bits with the new light level.
|
||||
bits |= ((long) (lightLevel)) << (y << 2);
|
||||
//store the updated bits in our data.
|
||||
this.data[index] = bits;
|
||||
|
||||
//we have one less of the old light level...
|
||||
this.counts[oldLightLevel]--;
|
||||
//...and one more of the new level.
|
||||
//if the number associated with the new level is now 4096 (AKA 16 ^ 3),
|
||||
//then this implies every position in this section has the same light level,
|
||||
//and therefore we can convert back to a constant value.
|
||||
if (++this.counts[lightLevel] == 4096)
|
||||
{
|
||||
this.constantValue = (byte) (lightLevel);
|
||||
DataRecycler.reclaim(this.data);
|
||||
this.data = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class DataRecycler
|
||||
{
|
||||
private static final ArrayList<long[]> recycled = new ArrayList<>(256);
|
||||
|
||||
static synchronized long[] get()
|
||||
{
|
||||
if (recycled.isEmpty())
|
||||
{
|
||||
return new long[256];
|
||||
}
|
||||
else
|
||||
{
|
||||
return recycled.remove(recycled.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static synchronized void reclaim(long[] data) { if (recycled.size() < 256) recycled.add(data); }
|
||||
}
|
||||
|
||||
}
|
||||
+118
-27
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
@@ -31,6 +32,11 @@ import java.util.ArrayList;
|
||||
|
||||
public interface IChunkWrapper extends IBindable
|
||||
{
|
||||
/** useful for debugging, but can slow down chunk operations quite a bit due to being called every time. */
|
||||
boolean RUN_RELATIVE_POS_INDEX_VALIDATION = ModInfo.IS_DEV_BUILD;
|
||||
|
||||
|
||||
|
||||
DhChunkPos getChunkPos();
|
||||
|
||||
default int getHeight() { return this.getMaxBuildHeight() - this.getMinBuildHeight(); }
|
||||
@@ -61,8 +67,6 @@ public interface IChunkWrapper extends IBindable
|
||||
int getMinBlockX();
|
||||
int getMinBlockZ();
|
||||
|
||||
long getLongChunkPos();
|
||||
|
||||
void setIsDhLightCorrect(boolean isDhLightCorrect);
|
||||
void setUseDhLighting(boolean useDhLighting);
|
||||
boolean isLightCorrect();
|
||||
@@ -77,6 +81,65 @@ public interface IChunkWrapper extends IBindable
|
||||
int getBlockLight(int relX, int relY, int relZ);
|
||||
int getSkyLight(int relX, int relY, int relZ);
|
||||
|
||||
|
||||
ArrayList<DhBlockPos> getBlockLightPosList();
|
||||
|
||||
|
||||
default boolean blockPosInsideChunk(DhBlockPos blockPos) { return this.blockPosInsideChunk(blockPos.x, blockPos.y, blockPos.z); }
|
||||
default boolean blockPosInsideChunk(int x, int y, int z)
|
||||
{
|
||||
return (x >= this.getMinBlockX() && x <= this.getMaxBlockX()
|
||||
&& y >= this.getMinBuildHeight() && y < this.getMaxBuildHeight()
|
||||
&& z >= this.getMinBlockZ() && z <= this.getMaxBlockZ());
|
||||
}
|
||||
default boolean blockPosInsideChunk(DhBlockPos2D blockPos)
|
||||
{
|
||||
return (blockPos.x >= this.getMinBlockX() && blockPos.x <= this.getMaxBlockX()
|
||||
&& blockPos.z >= this.getMinBlockZ() && blockPos.z <= this.getMaxBlockZ());
|
||||
}
|
||||
|
||||
boolean doNearbyChunksExist();
|
||||
String toString();
|
||||
|
||||
|
||||
default IBlockStateWrapper getBlockState(DhBlockPos pos) { return this.getBlockState(pos.x, pos.y, pos.z); }
|
||||
IBlockStateWrapper getBlockState(int relX, int relY, int relZ);
|
||||
|
||||
IBiomeWrapper getBiome(int relX, int relY, int relZ);
|
||||
|
||||
boolean isStillValid();
|
||||
|
||||
|
||||
|
||||
//========================//
|
||||
// default helper methods //
|
||||
//========================//
|
||||
|
||||
/** used to prevent accidentally attempting to get/set values outside this chunk's boundaries */
|
||||
default void throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(int x, int y, int z) throws IndexOutOfBoundsException
|
||||
{
|
||||
if (!RUN_RELATIVE_POS_INDEX_VALIDATION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// FIXME +1 is to handle the fact that LodDataBuilder adds +1 to all block lighting calculations, also done in the constructor
|
||||
int minHeight = this.getMinBuildHeight();
|
||||
int maxHeight = this.getMaxBuildHeight() + 1;
|
||||
|
||||
if (x < 0 || x >= LodUtil.CHUNK_WIDTH
|
||||
|| z < 0 || z >= LodUtil.CHUNK_WIDTH
|
||||
|| y < minHeight || y > maxHeight)
|
||||
{
|
||||
String errorMessage = "Relative position [" + x + "," + y + "," + z + "] out of bounds. \n" +
|
||||
"X/Z must be between 0 and 15 (inclusive) \n" +
|
||||
"Y must be between [" + minHeight + "] and [" + maxHeight + "] (inclusive).";
|
||||
throw new IndexOutOfBoundsException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populates DH's saved lighting using MC's lighting engine.
|
||||
* This is generally done in cases where MC's lighting is correct now, but may not be later (like when a chunk is unloading).
|
||||
@@ -108,27 +171,38 @@ public interface IChunkWrapper extends IBindable
|
||||
}
|
||||
|
||||
|
||||
|
||||
ArrayList<DhBlockPos> getBlockLightPosList();
|
||||
|
||||
|
||||
default boolean blockPosInsideChunk(DhBlockPos blockPos) { return this.blockPosInsideChunk(blockPos.x, blockPos.y, blockPos.z); }
|
||||
default boolean blockPosInsideChunk(int x, int y, int z)
|
||||
/**
|
||||
* Converts a 3D position into a 1D array index. <br><br>
|
||||
*
|
||||
* Source: <br>
|
||||
* <a href="https://stackoverflow.com/questions/7367770/how-to-flatten-or-index-3d-array-in-1d-array">stackoverflow</a>
|
||||
*/
|
||||
default int relativeBlockPosToIndex(int xRel, int y, int zRel)
|
||||
{
|
||||
return (x >= this.getMinBlockX() && x <= this.getMaxBlockX()
|
||||
&& y >= this.getMinBuildHeight() && y < this.getMaxBuildHeight()
|
||||
&& z >= this.getMinBlockZ() && z <= this.getMaxBlockZ());
|
||||
}
|
||||
default boolean blockPosInsideChunk(DhBlockPos2D blockPos)
|
||||
{
|
||||
return (blockPos.x >= this.getMinBlockX() && blockPos.x <= this.getMaxBlockX()
|
||||
&& blockPos.z >= this.getMinBlockZ() && blockPos.z <= this.getMaxBlockZ());
|
||||
int yRel = y - this.getMinBuildHeight();
|
||||
return (zRel * LodUtil.CHUNK_WIDTH * this.getHeight()) + (yRel * LodUtil.CHUNK_WIDTH) + xRel;
|
||||
}
|
||||
|
||||
boolean doNearbyChunksExist();
|
||||
String toString();
|
||||
/**
|
||||
* Converts a 3D position into a 1D array index. <br><br>
|
||||
*
|
||||
* Source: <br>
|
||||
* <a href="https://stackoverflow.com/questions/7367770/how-to-flatten-or-index-3d-array-in-1d-array">stackoverflow</a>
|
||||
*/
|
||||
default DhBlockPos indexToRelativeBlockPos(int index)
|
||||
{
|
||||
final int zRel = index / (LodUtil.CHUNK_WIDTH * this.getHeight());
|
||||
index -= (zRel * LodUtil.CHUNK_WIDTH * this.getHeight());
|
||||
|
||||
final int y = index / LodUtil.CHUNK_WIDTH;
|
||||
final int yRel = y + this.getMinBuildHeight();
|
||||
|
||||
final int xRel = index % LodUtil.CHUNK_WIDTH;
|
||||
return new DhBlockPos(xRel, yRel, zRel);
|
||||
}
|
||||
|
||||
/** This is a bad hash algorithm, but can be used for rough debugging. */
|
||||
|
||||
/** This is a bad hash algorithm since it only uses the heightmap, but can be used for rough debugging. */
|
||||
default int roughHashCode()
|
||||
{
|
||||
int hash = 31;
|
||||
@@ -138,7 +212,31 @@ public interface IChunkWrapper extends IBindable
|
||||
{
|
||||
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
|
||||
{
|
||||
hash = hash * primeMultiplier + Integer.hashCode(this.getLightBlockingHeightMapValue(x, z));
|
||||
hash = (hash * primeMultiplier) + Integer.hashCode(this.getLightBlockingHeightMapValue(x, z));
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
default int getBlockBiomeHashCode()
|
||||
{
|
||||
int hash = 31;
|
||||
int primeBlockMultiplier = 227;
|
||||
int primeBiomeMultiplier = 701;
|
||||
|
||||
int minBuildHeight = this.getMinBuildHeight();
|
||||
int maxBuildHeight = this.getMaxBuildHeight();
|
||||
|
||||
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
|
||||
{
|
||||
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
|
||||
{
|
||||
for (int y = minBuildHeight; y < maxBuildHeight; y++)
|
||||
{
|
||||
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode();
|
||||
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,11 +244,4 @@ public interface IChunkWrapper extends IBindable
|
||||
}
|
||||
|
||||
|
||||
default IBlockStateWrapper getBlockState(DhBlockPos pos) { return this.getBlockState(pos.x, pos.y, pos.z); }
|
||||
IBlockStateWrapper getBlockState(int relX, int relY, int relZ);
|
||||
|
||||
IBiomeWrapper getBiome(int relX, int relY, int relZ);
|
||||
|
||||
boolean isStillValid();
|
||||
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfFileHandlerThreads":
|
||||
"NO. of file handler threads",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfFileHandlerThreads.@tooltip":
|
||||
"The number of threads used when building vertex buffers \n(The things sent to your GPU to draw the LODs). \nCan only be between 1 and your CPU's processor count.",
|
||||
"The number of threads used when reading/writing LOD data to/from the disk. \nCan only be between 1 and your CPU's processor count.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForFileHandlerThreads":
|
||||
"Runtime % for file handler threads",
|
||||
|
||||
@@ -545,6 +545,8 @@
|
||||
"File Sub Dimension Events",
|
||||
"distanthorizons.config.client.advanced.logging.logNetworkEvent":
|
||||
"Network Events",
|
||||
"distanthorizons.config.client.advanced.logging.showLowMemoryWarningOnStartup":
|
||||
"Show Low Memory Warning",
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
CREATE TABLE ChunkHash(
|
||||
-- compound primary key
|
||||
ChunkPosX INT NOT NULL
|
||||
,ChunkPosZ INT NOT NULL
|
||||
|
||||
,ChunkHash INT NOT NULL
|
||||
|
||||
,LastModifiedUnixDateTime BIGINT NOT NULL -- in GMT 0
|
||||
,CreatedUnixDateTime BIGINT NOT NULL -- in GMT 0
|
||||
|
||||
,PRIMARY KEY (ChunkPosX, ChunkPosZ)
|
||||
);
|
||||
@@ -5,3 +5,4 @@
|
||||
0031-sqlite-useSqliteWalJournaling.sql
|
||||
0040-sqlite-removeRenderCache.sql
|
||||
0050-sqlite-addApplyToParentIndex.sql
|
||||
0060-sqlite-createChunkHashTable.sql
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2023 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 testItems.lightingEngine;
|
||||
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import tests.LightingEngineTest;
|
||||
|
||||
/**
|
||||
* @see LightingEngineTest
|
||||
* @see LightingTestChunkWrapper
|
||||
*/
|
||||
public class LightingTestBlockStateWrapper implements IBlockStateWrapper
|
||||
{
|
||||
private int opacity = -1;
|
||||
private int lightEmission = -1;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public LightingTestBlockStateWrapper(int opacity, int lightEmission)
|
||||
{
|
||||
this.opacity = opacity;
|
||||
this.lightEmission = lightEmission;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// wrapper methods //
|
||||
//=================//
|
||||
|
||||
@Override
|
||||
public int getOpacity() { return this.opacity; }
|
||||
|
||||
@Override
|
||||
public int getLightEmission() { return this.lightEmission; }
|
||||
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// unimplemented //
|
||||
//===============//
|
||||
|
||||
//@Override
|
||||
//public boolean equals(Object obj)
|
||||
//{
|
||||
// if (this == obj)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// if (obj == null || this.getClass() != obj.getClass())
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// BlockStateTestWrapper that = (BlockStateTestWrapper) obj;
|
||||
// // the serialized value is used so we can test the contents instead of the references
|
||||
// return Objects.equals(this.getSerialString(), that.getSerialString());
|
||||
//}
|
||||
|
||||
//@Override
|
||||
//public int hashCode() { return this.hashCode; }
|
||||
//@Override
|
||||
//public String toString() { return this.getSerialString(); }
|
||||
|
||||
|
||||
@Override
|
||||
public String getSerialString() { throw new UnsupportedOperationException("Not Implemented"); }
|
||||
|
||||
@Override
|
||||
public Object getWrappedMcObject() { throw new UnsupportedOperationException("Not Implemented"); }
|
||||
|
||||
@Override
|
||||
public boolean isAir() { throw new UnsupportedOperationException("Not Implemented"); }
|
||||
|
||||
@Override
|
||||
public boolean isSolid() { throw new UnsupportedOperationException("Not Implemented"); }
|
||||
|
||||
@Override
|
||||
public boolean isLiquid() { throw new UnsupportedOperationException("Not Implemented"); }
|
||||
|
||||
@Override
|
||||
public byte getIrisBlockMaterialId() { throw new UnsupportedOperationException("Not Implemented"); }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2023 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 testItems.lightingEngine;
|
||||
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import tests.LightingEngineTest;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @see LightingEngineTest
|
||||
* @see LightingTestBlockStateWrapper
|
||||
*/
|
||||
public class LightingTestChunkWrapper implements IChunkWrapper
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
|
||||
// chunk values //
|
||||
|
||||
private final DhChunkPos chunkPos;
|
||||
private ChunkLightStorage blockLightStorage;
|
||||
private ChunkLightStorage skyLightStorage;
|
||||
|
||||
private ArrayList<DhBlockPos> blockLightPosList = null;
|
||||
|
||||
private boolean useDhLighting;
|
||||
|
||||
private int minNonEmptyHeight = Integer.MIN_VALUE;
|
||||
private int maxNonEmptyHeight = Integer.MAX_VALUE;
|
||||
|
||||
|
||||
// test values //
|
||||
|
||||
private final Int2IntOpenHashMap blockOpacityStorage;
|
||||
private final Int2IntOpenHashMap blockEmissionStorage;
|
||||
private final int[][] solidHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH];
|
||||
private final int[][] lightBlockingHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH];
|
||||
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public LightingTestChunkWrapper(IChunkWrapper chunkWrapper)
|
||||
{
|
||||
this.chunkPos = chunkWrapper.getChunkPos();
|
||||
|
||||
this.blockOpacityStorage = new Int2IntOpenHashMap();
|
||||
this.blockEmissionStorage = new Int2IntOpenHashMap();
|
||||
this.blockLightPosList = new ArrayList<>();
|
||||
|
||||
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
|
||||
{
|
||||
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
|
||||
{
|
||||
for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++)
|
||||
{
|
||||
IBlockStateWrapper block = chunkWrapper.getBlockState(x,y,z);
|
||||
|
||||
int opacity = block.getOpacity();
|
||||
if (opacity >= IBlockStateWrapper.FULLY_OPAQUE)
|
||||
{
|
||||
opacity = 3;
|
||||
}
|
||||
|
||||
this.blockOpacityStorage.put(new DhBlockPos(x, y, z).hashCode(), opacity);
|
||||
this.blockEmissionStorage.put(new DhBlockPos(x, y, z).hashCode(), block.getLightEmission());
|
||||
|
||||
if (block.getLightEmission() != 0)
|
||||
{
|
||||
this.blockLightPosList.add(new DhBlockPos(x,y,z));
|
||||
}
|
||||
}
|
||||
|
||||
this.lightBlockingHeightMap[x][z] = chunkWrapper.getLightBlockingHeightMapValue(x, z);
|
||||
this.solidHeightMap[x][z] = chunkWrapper.getSolidHeightMapValue(x, z);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// file handling //
|
||||
//===============//
|
||||
|
||||
public LightingTestChunkWrapper(DhChunkPos pos, File saveFile) throws DataCorruptedException
|
||||
{
|
||||
this.chunkPos = pos;
|
||||
|
||||
this.blockOpacityStorage = new Int2IntOpenHashMap();
|
||||
this.blockEmissionStorage = new Int2IntOpenHashMap();
|
||||
this.blockLightPosList = new ArrayList<>();
|
||||
|
||||
try(FileInputStream inputStream = new FileInputStream(saveFile);
|
||||
BufferedInputStream bufferedStream = new BufferedInputStream(inputStream);
|
||||
DataInputStream stream = new DataInputStream(bufferedStream))
|
||||
{
|
||||
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
|
||||
{
|
||||
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
|
||||
{
|
||||
for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++)
|
||||
{
|
||||
this.blockOpacityStorage.put(new DhBlockPos(x, y, z).hashCode(), stream.readInt());
|
||||
|
||||
int blockEmission = stream.readInt();
|
||||
this.blockEmissionStorage.put(new DhBlockPos(x, y, z).hashCode(), blockEmission);
|
||||
if (blockEmission != 0)
|
||||
{
|
||||
this.blockLightPosList.add(new DhBlockPos(x,y,z));
|
||||
}
|
||||
}
|
||||
|
||||
if (stream.readChar() != ';')
|
||||
{
|
||||
throw new DataCorruptedException("bad height map");
|
||||
}
|
||||
|
||||
this.lightBlockingHeightMap[x][z] = stream.readInt();
|
||||
this.solidHeightMap[x][z] = stream.readInt();
|
||||
|
||||
if (stream.readChar() != '\n')
|
||||
{
|
||||
throw new DataCorruptedException(" bad col ending");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOGGER.error("Unable to write to file: ["+e.getMessage()+"].", e);
|
||||
}
|
||||
}
|
||||
public void writeToFile(File file)
|
||||
{
|
||||
try(FileOutputStream fileStream = new FileOutputStream(file);
|
||||
BufferedOutputStream bufferedStream = new BufferedOutputStream(fileStream);
|
||||
DataOutputStream stream = new DataOutputStream(bufferedStream))
|
||||
{
|
||||
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
|
||||
{
|
||||
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
|
||||
{
|
||||
for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++)
|
||||
{
|
||||
stream.writeInt(this.blockOpacityStorage.get(new DhBlockPos(x, y, z).hashCode()));
|
||||
stream.writeInt(this.blockEmissionStorage.get(new DhBlockPos(x, y, z).hashCode()));
|
||||
}
|
||||
|
||||
stream.writeChar(';');
|
||||
|
||||
stream.writeInt(this.lightBlockingHeightMap[x][z]);
|
||||
stream.writeInt(this.solidHeightMap[x][z]);
|
||||
|
||||
stream.writeChar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
stream.flush();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOGGER.error("Unable to write to file: ["+e.getMessage()+"].", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be added into {@link com.seibel.distanthorizons.core.api.internal.SharedApi#applyChunkUpdate(IChunkWrapper, ILevelWrapper, boolean)}
|
||||
* to save chunks to file for future testing.
|
||||
*/
|
||||
public void tryConvertingAndSavingChunkWrapper(IChunkWrapper chunkWrapper)
|
||||
{
|
||||
try
|
||||
{
|
||||
File chunkFile = new File(LightingEngineTest.TEST_DATA_PATH + "/" + chunkWrapper.getChunkPos().toString());
|
||||
if (!chunkFile.exists())
|
||||
{
|
||||
LightingTestChunkWrapper testWrapper = new LightingTestChunkWrapper(chunkWrapper);
|
||||
testWrapper.writeToFile(chunkFile);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// chunk methods //
|
||||
//===============//
|
||||
|
||||
@Override
|
||||
public int getHeight() { return 255; }
|
||||
|
||||
@Override
|
||||
public int getMinBuildHeight() { return -64; }
|
||||
@Override
|
||||
public int getMaxBuildHeight() { return 255; }
|
||||
|
||||
@Override
|
||||
public int getMinNonEmptyHeight()
|
||||
{
|
||||
if (this.minNonEmptyHeight != Integer.MIN_VALUE)
|
||||
{
|
||||
return this.minNonEmptyHeight;
|
||||
}
|
||||
|
||||
|
||||
// default if every section is empty or missing
|
||||
this.minNonEmptyHeight = this.getMinBuildHeight();
|
||||
|
||||
// determine the lowest empty section (bottom up)
|
||||
int maxYHeight = this.getMaxBuildHeight();
|
||||
for (int y = this.getMinBuildHeight(); y < maxYHeight; y++)
|
||||
{
|
||||
if (this.blockOpacityStorage.get(new DhBlockPos(0, y, 0).hashCode()) != 0)
|
||||
{
|
||||
// -16 to simulate having to populate the full chunk section
|
||||
this.minNonEmptyHeight = Math.min(y - 16, maxYHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.minNonEmptyHeight;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getMaxNonEmptyHeight()
|
||||
{
|
||||
if (this.maxNonEmptyHeight != Integer.MAX_VALUE)
|
||||
{
|
||||
return this.maxNonEmptyHeight;
|
||||
}
|
||||
|
||||
|
||||
// default if every section is empty or missing
|
||||
this.maxNonEmptyHeight = this.getMaxBuildHeight();
|
||||
|
||||
// determine the highest empty section (top down)
|
||||
int minYHeight = this.getMinBuildHeight();
|
||||
for (int y = this.getMaxBuildHeight(); y >= minYHeight; y--)
|
||||
{
|
||||
if (this.blockOpacityStorage.get(new DhBlockPos(0, y, 0).hashCode()) != 0)
|
||||
{
|
||||
// -16 to simulate having to populate the full chunk section
|
||||
this.maxNonEmptyHeight = Math.max(y - 16, minYHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.maxNonEmptyHeight;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getSolidHeightMapValue(int xRel, int zRel) { return this.solidHeightMap[xRel][zRel]; }
|
||||
|
||||
@Override
|
||||
public int getLightBlockingHeightMapValue(int xRel, int zRel) { return this.lightBlockingHeightMap[xRel][zRel]; }
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public IBiomeWrapper getBiome(int relX, int relY, int relZ) { throw new UnsupportedOperationException("Not implemented"); }
|
||||
|
||||
@Override
|
||||
public DhChunkPos getChunkPos() { return this.chunkPos; }
|
||||
|
||||
@Override
|
||||
public int getMaxBlockX() { return 0; }
|
||||
@Override
|
||||
public int getMaxBlockZ() { return 0; }
|
||||
@Override
|
||||
public int getMinBlockX() { return LodUtil.CHUNK_WIDTH; }
|
||||
@Override
|
||||
public int getMinBlockZ() { return LodUtil.CHUNK_WIDTH; }
|
||||
|
||||
@Override
|
||||
public void setIsDhLightCorrect(boolean isDhLightCorrect) { }
|
||||
|
||||
@Override
|
||||
public void setUseDhLighting(boolean useDhLighting) { this.useDhLighting = useDhLighting; }
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isLightCorrect() { return false; }
|
||||
|
||||
|
||||
@Override
|
||||
public int getDhBlockLight(int relX, int y, int relZ)
|
||||
{
|
||||
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
|
||||
return this.getBlockLightStorage().get(relX, y, relZ);
|
||||
}
|
||||
@Override
|
||||
public void setDhBlockLight(int relX, int y, int relZ, int lightValue)
|
||||
{
|
||||
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
|
||||
this.getBlockLightStorage().set(relX, y, relZ, lightValue);
|
||||
}
|
||||
|
||||
private ChunkLightStorage getBlockLightStorage()
|
||||
{
|
||||
if (this.blockLightStorage == null)
|
||||
{
|
||||
this.blockLightStorage = new ChunkLightStorage(
|
||||
// +/- 16 is to fix an issue with the test chunk where the storage isn't big enough,
|
||||
// James probably just screwed up the min/max height slightly
|
||||
this.getMinBuildHeight() - 16, this.getMaxBuildHeight() + 16,
|
||||
// positions above and below the handled area should be unlit
|
||||
LodUtil.MIN_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
|
||||
}
|
||||
return this.blockLightStorage;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getDhSkyLight(int relX, int y, int relZ)
|
||||
{
|
||||
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
|
||||
return this.getSkyLightStorage().get(relX, y, relZ);
|
||||
}
|
||||
@Override
|
||||
public void setDhSkyLight(int relX, int y, int relZ, int lightValue)
|
||||
{
|
||||
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
|
||||
this.getSkyLightStorage().set(relX, y, relZ, lightValue);
|
||||
}
|
||||
|
||||
private ChunkLightStorage getSkyLightStorage()
|
||||
{
|
||||
if (this.skyLightStorage == null)
|
||||
{
|
||||
this.skyLightStorage = new ChunkLightStorage(
|
||||
// +/- 16 is to fix an issue with the test chunk where the storage isn't big enough,
|
||||
// James probably just screwed up the min/max height slightly
|
||||
this.getMinBuildHeight() - 16, this.getMaxBuildHeight() + 16,
|
||||
// positions above should be lit but positions below should be unlit
|
||||
LodUtil.MAX_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
|
||||
}
|
||||
return this.skyLightStorage;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getBlockLight(int relX, int y, int relZ)
|
||||
{
|
||||
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
|
||||
return this.getBlockLightStorage().get(relX, y, relZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSkyLight(int relX, int y, int relZ)
|
||||
{
|
||||
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
|
||||
return this.getSkyLightStorage().get(relX, y, relZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<DhBlockPos> getBlockLightPosList() { return this.blockLightPosList; }
|
||||
|
||||
@Override
|
||||
public boolean doNearbyChunksExist() { return false; }
|
||||
|
||||
@Override
|
||||
public String toString() { return this.chunkPos.toString(); }
|
||||
|
||||
@Override
|
||||
public IBlockStateWrapper getBlockState(int relX, int relY, int relZ)
|
||||
{
|
||||
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, relY, relZ);
|
||||
|
||||
int opacity = this.blockOpacityStorage.get(new DhBlockPos(relX, relY, relZ).hashCode());
|
||||
int lightEmission = this.blockEmissionStorage.get(new DhBlockPos(relX, relY, relZ).hashCode());
|
||||
return new LightingTestBlockStateWrapper(opacity, lightEmission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStillValid() { return true; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2023 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 tests;
|
||||
|
||||
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import testItems.lightingEngine.LightingTestChunkWrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Can be used to A/B Test lighting engine performance changes. <br><br>
|
||||
*
|
||||
* normal - chunks: [1595], total Time: [1490], avg time: [0.9341692789968652] <br>
|
||||
* only surface light prop - chunks: [1595], total Time: [984], avg time: [0.6169278996865204] <br>
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2024-6-11
|
||||
*/
|
||||
public class LightingEngineTest
|
||||
{
|
||||
/**
|
||||
* There should be test data in the following core repo folder: <br>
|
||||
* <code> Core\_Misc Files\test files\Lighting engine test chunk data.7z </code>
|
||||
*/
|
||||
public static final String TEST_DATA_PATH = "C:/Users/James_Seibel/Desktop/test chunk data";
|
||||
|
||||
|
||||
//@Test
|
||||
public void TestLightingEngine() throws DataCorruptedException
|
||||
{
|
||||
long totalNanoTime = 0;
|
||||
int chunkCount = 0;
|
||||
|
||||
File testFolder = new File(TEST_DATA_PATH);
|
||||
File[] chunkFiles = testFolder.listFiles();
|
||||
for (int i = 0; i < chunkFiles.length; i++)
|
||||
{
|
||||
// chunk file parsing //
|
||||
|
||||
File chunkFile = chunkFiles[i];
|
||||
|
||||
String fileName = chunkFile.getName(); // C[0,-3]
|
||||
fileName = fileName.replace("C", "").replace("[", "").replace("]", "");
|
||||
int xPos = Integer.parseInt(fileName.split(",")[0]);
|
||||
int zPos = Integer.parseInt(fileName.split(",")[1]);
|
||||
DhChunkPos pos = new DhChunkPos(xPos, zPos);
|
||||
|
||||
if (i % 100 == 0)
|
||||
{
|
||||
System.out.println(i + "/" + chunkFiles.length);
|
||||
}
|
||||
|
||||
LightingTestChunkWrapper chunk = new LightingTestChunkWrapper(pos, chunkFile);
|
||||
chunkCount++;
|
||||
|
||||
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<>(1);
|
||||
nearbyChunkList.add(chunk);
|
||||
|
||||
|
||||
|
||||
// lighting //
|
||||
|
||||
long startTime = System.nanoTime();
|
||||
DhLightingEngine.INSTANCE.lightChunk(chunk, nearbyChunkList, LodUtil.MAX_MC_LIGHT);
|
||||
long endTime = System.nanoTime();
|
||||
totalNanoTime += endTime - startTime;
|
||||
}
|
||||
long timeMs = totalNanoTime / 1_000_000;
|
||||
|
||||
|
||||
System.out.println("chunks: ["+chunkCount+"], total Time: ["+timeMs+"], avg time: ["+(timeMs/(double)chunkCount)+"]");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user