Merge branch 'main-upstream'
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* 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.api.enums.config;
|
||||
|
||||
/**
|
||||
* @since API 1.0.0
|
||||
*/
|
||||
public enum EGlProfileMode
|
||||
{
|
||||
CORE,
|
||||
COMPAT,
|
||||
ANY;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
||||
|
||||
/**
|
||||
* A singleton used to get variables from methods
|
||||
* where they are private or potentially absent.
|
||||
* Specifically the fog setting used by Optifine or the
|
||||
* presence/absence of other mods.
|
||||
* <p>
|
||||
* This interface doesn't necessarily have to exist, but
|
||||
* it makes using the singleton handler more uniform (always
|
||||
* passing in interfaces), and it may be needed in the future if
|
||||
* we find that reflection handlers need to be different for
|
||||
* different MC versions.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2022-11-24
|
||||
*/
|
||||
public interface IReflectionHandler extends IBindable
|
||||
{
|
||||
/** @return if Sodium (or a sodium like) mod is present. */
|
||||
boolean sodiumPresent();
|
||||
|
||||
boolean optifinePresent();
|
||||
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* A singleton used to determine if a class is present or
|
||||
* access variables from methods where they are private
|
||||
* or potentially absent. <br><br>
|
||||
*
|
||||
* For example: presence/absence of Optifine.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2022-11-24
|
||||
*/
|
||||
public class ReflectionHandler implements IReflectionHandler
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
|
||||
|
||||
public static final ReflectionHandler INSTANCE = new ReflectionHandler();
|
||||
|
||||
// populated when the methods are called the first time
|
||||
private Boolean sodiumPresent = null;
|
||||
private Boolean optifinePresent = false;
|
||||
|
||||
|
||||
|
||||
private ReflectionHandler() { }
|
||||
|
||||
|
||||
|
||||
//===================//
|
||||
// is [mod] present? //
|
||||
//===================//
|
||||
|
||||
@Override
|
||||
public boolean optifinePresent()
|
||||
{
|
||||
if (this.optifinePresent == null)
|
||||
{
|
||||
// call the base accessor so we don't have duplicate code
|
||||
this.optifinePresent = AbstractOptifineAccessor.isOptifinePresent();
|
||||
}
|
||||
return this.optifinePresent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sodiumPresent()
|
||||
{
|
||||
if (this.sodiumPresent == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class.forName("me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer");
|
||||
this.sodiumPresent = true;
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
this.sodiumPresent = false;
|
||||
}
|
||||
}
|
||||
|
||||
return this.sodiumPresent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -505,11 +505,6 @@ public class ClientApi
|
||||
IDhClientWorld dhClientWorld = SharedApi.getIDhClientWorld();
|
||||
IDhClientLevel level = dhClientWorld.getOrLoadClientLevel(levelWrapper);
|
||||
|
||||
if (prefLoggerEnabled)
|
||||
{
|
||||
level.dumpRamUsage();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1002,7 +1002,7 @@ public class Config
|
||||
+ "How long should a buffer wait per Megabyte of data uploaded? \n"
|
||||
+ "Helpful resource for frame times: https://fpstoms.com \n"
|
||||
+ "\n"
|
||||
+ "Longer times may reduce stuttering but will make fake chunks \n"
|
||||
+ "Longer times may reduce stuttering but will make LODs \n"
|
||||
+ "transition and load slower. Change this to [0] for no timeout. \n"
|
||||
+ "\n"
|
||||
+ "NOTE:\n"
|
||||
@@ -1010,6 +1010,16 @@ public class Config
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Boolean> gpuUploadAsync = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
+ "If true geometry data will be uploaded on a DH controlled thread, reducing FPS stuttering. \n"
|
||||
+ "If false uploading will be done on Minecraft's main rendering thread. \n"
|
||||
+ "\n"
|
||||
+ "Setting this to false may reduce crashes or corrupted geometry on systems with an AMD GPU when Sodium is installed.\n"
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
// deprecated and not implemented, can be made public if we ever re-implement it
|
||||
@Deprecated
|
||||
private static ConfigEntry<EBufferRebuildTimes> rebuildTimes = new ConfigEntry.Builder<EBufferRebuildTimes>()
|
||||
@@ -1211,16 +1221,6 @@ public class Config
|
||||
.comment("")
|
||||
.build();
|
||||
|
||||
// TODO temporary test, remove me
|
||||
public static ConfigEntry<EGlProfileMode> glProfileMode = new ConfigEntry.Builder<EGlProfileMode>()
|
||||
.set(EGlProfileMode.CORE)
|
||||
.comment("")
|
||||
.build();
|
||||
// TODO temporary test, remove me
|
||||
public static ConfigEntry<Boolean> glForwardCompatibilityMode = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment("")
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
|
||||
+54
-7
@@ -20,22 +20,22 @@
|
||||
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
||||
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.enums.EGLProxyContext;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.render.AbstractRenderBuffer;
|
||||
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
||||
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
|
||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.StatsMap;
|
||||
import com.seibel.distanthorizons.api.enums.config.EGpuUploadMethod;
|
||||
import com.seibel.distanthorizons.core.util.*;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.awt.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.*;
|
||||
@@ -48,6 +48,7 @@ import java.util.concurrent.*;
|
||||
public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
private static final IMinecraftClientWrapper minecraftClient = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||
|
||||
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
|
||||
|
||||
@@ -83,20 +84,66 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
|
||||
// buffer uploading //
|
||||
//==================//
|
||||
|
||||
public void uploadBuffer(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException
|
||||
/** Should be run on a DH thread. */
|
||||
public void uploadBuffer(LodQuadBuilder builder, EGpuUploadMethod gpuUploadMethod) throws InterruptedException
|
||||
{
|
||||
if (method.useEarlyMapping)
|
||||
LodUtil.assertTrue(Thread.currentThread().getName().startsWith(ThreadUtil.THREAD_NAME_PREFIX), "Buffer uploading needs to be done on a DH thread to prevent locking up any MC threads.");
|
||||
|
||||
|
||||
// the async is relative to MC's render thread
|
||||
boolean uploadAsync = Config.Client.Advanced.GpuBuffers.gpuUploadAsync.get();
|
||||
if (uploadAsync)
|
||||
{
|
||||
this.uploadBuffersMapped(builder, method);
|
||||
// upload here on a DH thread
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
EGLProxyContext oldContext = glProxy.getGlContext();
|
||||
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
|
||||
try
|
||||
{
|
||||
this.uploadBuffersUsingUploadMethod(builder, gpuUploadMethod);
|
||||
}
|
||||
finally
|
||||
{
|
||||
glProxy.setGlContext(oldContext);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.uploadBuffersDirect(builder, method);
|
||||
// upload on MC's render thread
|
||||
CompletableFuture<Void> uploadFuture = new CompletableFuture<>();
|
||||
minecraftClient.executeOnRenderThread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
this.uploadBuffersUsingUploadMethod(builder, gpuUploadMethod);
|
||||
uploadFuture.complete(null);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
});
|
||||
|
||||
// freeze this DH thread while we wait for MC to upload the buffer
|
||||
uploadFuture.join();
|
||||
}
|
||||
}
|
||||
private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EGpuUploadMethod gpuUploadMethod) throws InterruptedException
|
||||
{
|
||||
if (gpuUploadMethod.useEarlyMapping)
|
||||
{
|
||||
this.uploadBuffersMapped(builder, gpuUploadMethod);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.uploadBuffersDirect(builder, gpuUploadMethod);
|
||||
}
|
||||
|
||||
this.buffersUploaded = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void uploadBuffersMapped(LodQuadBuilder builder, EGpuUploadMethod method)
|
||||
{
|
||||
// opaque vbos //
|
||||
|
||||
+2
-10
@@ -117,12 +117,8 @@ public class ColumnRenderBufferBuilder
|
||||
try
|
||||
{
|
||||
EVENT_LOGGER.trace("RenderRegion start Upload @ " + renderSource.sectionPos);
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
|
||||
EGLProxyContext oldContext = glProxy.getGlContext();
|
||||
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
|
||||
ColumnRenderBuffer buffer = renderBufferRef.swap(null);
|
||||
|
||||
ColumnRenderBuffer buffer = renderBufferRef.swap(null);
|
||||
if (buffer == null)
|
||||
{
|
||||
buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.sectionPos.getMinCornerLodPos().getCornerBlockPos(), clientLevel.getMinY()), renderSource.sectionPos);
|
||||
@@ -130,7 +126,7 @@ public class ColumnRenderBufferBuilder
|
||||
|
||||
try
|
||||
{
|
||||
buffer.uploadBuffer(quadBuilder, method);
|
||||
buffer.uploadBuffer(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
|
||||
LodUtil.assertTrue(buffer.buffersUploaded);
|
||||
EVENT_LOGGER.trace("RenderRegion end Upload @ " + renderSource.sectionPos);
|
||||
return buffer;
|
||||
@@ -140,10 +136,6 @@ public class ColumnRenderBufferBuilder
|
||||
buffer.close();
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
glProxy.setGlContext(oldContext);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
|
||||
+21
-53
@@ -34,7 +34,6 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhLodPos;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||
import com.seibel.distanthorizons.core.util.MetaFileScanUtil;
|
||||
import com.seibel.distanthorizons.core.util.FileUtil;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||
@@ -56,16 +55,12 @@ import java.util.function.Consumer;
|
||||
|
||||
public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
{
|
||||
public static final boolean USE_LAZY_LOADING = true;
|
||||
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
protected static ExecutorService fileHandlerThreadPool;
|
||||
protected static ConfigChangeListener<Integer> configListener;
|
||||
|
||||
private final ConcurrentHashMap<DhSectionPos, File> unloadedFileBySectionPos = new ConcurrentHashMap<>();
|
||||
/** contains the loaded {@link FullDataMetaFile}'s */
|
||||
private final ConcurrentHashMap<DhSectionPos, FullDataMetaFile> metaFileBySectionPos = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<DhSectionPos, FullDataMetaFile> loadedMetaFileBySectionPos = new ConcurrentHashMap<>();
|
||||
|
||||
public Map<DhSectionPos, Integer> getLoadStates(Iterable<DhSectionPos> posList)
|
||||
{
|
||||
@@ -100,29 +95,6 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
{
|
||||
LOGGER.warn("Unable to create full data folder, file saving may fail.");
|
||||
}
|
||||
MetaFileScanUtil.scanFullDataFiles(saveStructure, level.getLevelWrapper(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScannedFiles(Collection<File> detectedFiles)
|
||||
{
|
||||
MetaFileScanUtil.ICreateMetadataFunc createMetadataFunc = (file) -> FullDataMetaFile.createFromExistingFile(this, this.level, file);
|
||||
|
||||
MetaFileScanUtil.IAddUnloadedFileFunc addUnloadedFileFunc = (pos, file) ->
|
||||
{
|
||||
this.unloadedFileBySectionPos.put(pos, file);
|
||||
this.topDetailLevelRef.updateAndGet(oldDetailLevel -> Math.max(oldDetailLevel, pos.getDetailLevel()));
|
||||
};
|
||||
MetaFileScanUtil.IAddLoadedMetaFileFunc addLoadedMetaFileFunc = (pos, loadedMetaFile) ->
|
||||
{
|
||||
this.topDetailLevelRef.updateAndGet(oldDetailLevel -> Math.max(oldDetailLevel, pos.getDetailLevel()));
|
||||
this.metaFileBySectionPos.put(pos, (FullDataMetaFile) loadedMetaFile);
|
||||
};
|
||||
|
||||
|
||||
MetaFileScanUtil.addScannedFiles(detectedFiles, USE_LAZY_LOADING, FullDataMetaFile.FILE_SUFFIX,
|
||||
createMetadataFunc,
|
||||
addUnloadedFileFunc, addLoadedMetaFileFunc);
|
||||
}
|
||||
|
||||
|
||||
@@ -171,23 +143,23 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
public FullDataMetaFile getFileIfExist(DhSectionPos pos) { return this.getLoadOrMakeFile(pos, false); }
|
||||
protected FullDataMetaFile getLoadOrMakeFile(DhSectionPos pos, boolean allowCreateFile)
|
||||
{
|
||||
FullDataMetaFile metaFile = this.metaFileBySectionPos.get(pos);
|
||||
FullDataMetaFile metaFile = this.loadedMetaFileBySectionPos.get(pos);
|
||||
if (metaFile != null)
|
||||
{
|
||||
return metaFile;
|
||||
}
|
||||
|
||||
|
||||
File fileToLoad = this.unloadedFileBySectionPos.get(pos);
|
||||
// File does exist, but not loaded yet.
|
||||
if (fileToLoad != null)
|
||||
// check if the file exists, but hasn't been loaded
|
||||
File fileToLoad = this.computeDataFilePath(pos);
|
||||
if (fileToLoad.exists())
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
// Double check locking for loading file, as loading file means also loading the metadata, which
|
||||
// while not... Very expensive, is still better to avoid multiple threads doing it, and dumping the
|
||||
// duplicated work to the trash. Therefore, eating the overhead of 'synchronized' is worth it.
|
||||
metaFile = this.metaFileBySectionPos.get(pos);
|
||||
metaFile = this.loadedMetaFileBySectionPos.get(pos);
|
||||
if (metaFile != null)
|
||||
{
|
||||
return metaFile; // someone else loaded it already.
|
||||
@@ -197,18 +169,14 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
{
|
||||
metaFile = FullDataMetaFile.createFromExistingFile(this, this.level, fileToLoad);
|
||||
this.topDetailLevelRef.updateAndGet(oldDetailLevel -> Math.max(oldDetailLevel, pos.getDetailLevel()));
|
||||
this.metaFileBySectionPos.put(pos, metaFile);
|
||||
this.loadedMetaFileBySectionPos.put(pos, metaFile);
|
||||
return metaFile;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOGGER.error("Failed to read data meta file at " + fileToLoad + ": ", e);
|
||||
LOGGER.error("Failed to read meta data file at " + fileToLoad + ": ", e);
|
||||
FileUtil.renameCorruptedFile(fileToLoad);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.unloadedFileBySectionPos.remove(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +201,8 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
|
||||
this.topDetailLevelRef.updateAndGet(oldDetailLevel -> Math.max(oldDetailLevel, pos.getDetailLevel()));
|
||||
|
||||
// This is a CAS with expected null value.
|
||||
FullDataMetaFile metaFileCas = this.metaFileBySectionPos.putIfAbsent(pos, metaFile);
|
||||
// This is a Compare And Swap with expected null value.
|
||||
FullDataMetaFile metaFileCas = this.loadedMetaFileBySectionPos.putIfAbsent(pos, metaFile);
|
||||
return metaFileCas == null ? metaFile : metaFileCas;
|
||||
}
|
||||
|
||||
@@ -274,8 +242,8 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if a file for this pos exists, either loaded and unloaded
|
||||
if (this.metaFileBySectionPos.containsKey(subPos) || this.unloadedFileBySectionPos.containsKey(subPos))
|
||||
// check if a file for this pos is loaded or exists
|
||||
if (this.loadedMetaFileBySectionPos.containsKey(subPos) || this.computeDataFilePath(subPos).exists())
|
||||
{
|
||||
allEmpty = false;
|
||||
break outerLoop;
|
||||
@@ -304,14 +272,14 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
DhSectionPos childPos = pos.getChildByIndex(childIndex);
|
||||
if (CompleteFullDataSource.firstDataPosCanAffectSecond(basePos, childPos))
|
||||
{
|
||||
// load the file if it isn't already
|
||||
if (this.unloadedFileBySectionPos.containsKey(childPos))
|
||||
// get or load the file if necessary
|
||||
if (!this.loadedMetaFileBySectionPos.containsKey(childPos))
|
||||
{
|
||||
this.getLoadOrMakeFile(childPos, true);
|
||||
this.getLoadOrMakeFile(childPos, false);
|
||||
}
|
||||
|
||||
|
||||
FullDataMetaFile metaFile = this.metaFileBySectionPos.get(childPos);
|
||||
FullDataMetaFile metaFile = this.loadedMetaFileBySectionPos.get(childPos);
|
||||
if (metaFile != null)
|
||||
{
|
||||
// we have reached a populated leaf node in the quad tree
|
||||
@@ -330,7 +298,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
}
|
||||
}
|
||||
|
||||
public void ForEachFile(Consumer<FullDataMetaFile> consumer) { this.metaFileBySectionPos.values().forEach(consumer); }
|
||||
public void ForEachFile(Consumer<FullDataMetaFile> consumer) { this.loadedMetaFileBySectionPos.values().forEach(consumer); }
|
||||
|
||||
|
||||
|
||||
@@ -351,7 +319,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
}
|
||||
private void writeChunkDataToMetaFile(DhSectionPos sectionPos, ChunkSizedFullDataAccessor chunkData)
|
||||
{
|
||||
FullDataMetaFile metaFile = this.metaFileBySectionPos.get(sectionPos);
|
||||
FullDataMetaFile metaFile = this.loadedMetaFileBySectionPos.get(sectionPos);
|
||||
if (metaFile != null)
|
||||
{
|
||||
// there is a file for this position
|
||||
@@ -370,7 +338,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
public CompletableFuture<Void> flushAndSave()
|
||||
{
|
||||
ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
for (FullDataMetaFile metaFile : this.metaFileBySectionPos.values())
|
||||
for (FullDataMetaFile metaFile : this.loadedMetaFileBySectionPos.values())
|
||||
{
|
||||
futures.add(metaFile.flushAndSaveAsync());
|
||||
}
|
||||
@@ -380,7 +348,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
@Override
|
||||
public CompletableFuture<Void> flushAndSave(DhSectionPos sectionPos)
|
||||
{
|
||||
FullDataMetaFile metaFile = this.metaFileBySectionPos.get(sectionPos);
|
||||
FullDataMetaFile metaFile = this.loadedMetaFileBySectionPos.get(sectionPos);
|
||||
if (metaFile == null)
|
||||
{
|
||||
return CompletableFuture.completedFuture(null);
|
||||
@@ -518,7 +486,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
|
||||
|
||||
FileUtil.renameCorruptedFile(metaFile.file);
|
||||
// remove the FullDataMetaFile since the old one was corrupted
|
||||
this.metaFileBySectionPos.remove(pos);
|
||||
this.loadedMetaFileBySectionPos.remove(pos);
|
||||
// create a new FullDataMetaFile to write new data to
|
||||
return this.getLoadOrMakeFile(pos, true);
|
||||
}
|
||||
|
||||
-69
@@ -633,75 +633,6 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
|
||||
return completionFuture;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
private void applyWriteQueueAndSave(IFullDataSource fullDataSourceToUpdate)
|
||||
{
|
||||
boolean dataChanged = this.applyWriteQueueToFullDataSource(fullDataSourceToUpdate);
|
||||
this.needsUpdate = false;
|
||||
|
||||
// attempt to promote the data source
|
||||
if (fullDataSourceToUpdate instanceof IIncompleteFullDataSource)
|
||||
{
|
||||
IFullDataSource newSource = ((IIncompleteFullDataSource) fullDataSourceToUpdate).tryPromotingToCompleteDataSource();
|
||||
dataChanged |= (newSource != fullDataSourceToUpdate);
|
||||
fullDataSourceToUpdate = newSource;
|
||||
}
|
||||
|
||||
|
||||
// the provider may need to modify other files based on this data source changing
|
||||
IFullDataSourceProvider.DataFileUpdateResult dataFileUpdateResult = this.fullDataSourceProvider.onDataFileUpdateAsync(fullDataSourceToUpdate, this, dataChanged);
|
||||
IFullDataSource fullDataSource = dataFileUpdateResult.fullDataSource;
|
||||
boolean dataSourceChanged = dataFileUpdateResult.dataSourceChanged;
|
||||
|
||||
|
||||
// only save to file if something was changed
|
||||
if (dataSourceChanged)
|
||||
{
|
||||
this.writeDataSource(fullDataSource);
|
||||
}
|
||||
|
||||
// keep track of non-null data sources
|
||||
if (fullDataSource != null)
|
||||
{
|
||||
new DataObjTracker(fullDataSource);
|
||||
new DataObjSoftTracker(this, fullDataSource);
|
||||
}
|
||||
|
||||
|
||||
boolean showFullDataFileStatus = Config.Client.Advanced.Debugging.DebugWireframe.showFullDataFileStatus.get();
|
||||
boolean showFullDataFileSampling = Config.Client.Advanced.Debugging.DebugWireframe.showFullDataFileSampling.get();
|
||||
if (showFullDataFileStatus || showFullDataFileSampling)
|
||||
{
|
||||
Color color;
|
||||
if (this.pos.getDetailLevel() == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
||||
{
|
||||
color = Color.GREEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.GREEN.darker().darker();
|
||||
}
|
||||
|
||||
DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(
|
||||
new DebugRenderer.Box(this.pos, 64f, 72f, 0.03f, color),
|
||||
0.2, 32f));
|
||||
}
|
||||
|
||||
|
||||
// save the updated data source
|
||||
this.cachedFullDataSourceRef = new DataSourceReferenceTracker.FullDataSourceSoftRef(this, fullDataSource);
|
||||
|
||||
|
||||
if (this.needsUpdate)
|
||||
{
|
||||
// another update was requested while this update was being processed
|
||||
this.getOrLoadCachedDataSourceAsync();
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/** @return true if the queue was not empty and chunk data was applied to this meta file's {@link IFullDataSource}. */
|
||||
|
||||
+1
-1
@@ -124,7 +124,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
|
||||
//========//
|
||||
|
||||
@Nullable
|
||||
private CompletableFuture<IFullDataSource> tryStartGenTask(FullDataMetaFile file, IIncompleteFullDataSource dataSource)
|
||||
private CompletableFuture<IFullDataSource> tryStartGenTask(FullDataMetaFile file, IIncompleteFullDataSource dataSource) // TODO after generation is finished, save and free any full datasources that aren't in use (IE high detail ones below the top)
|
||||
{
|
||||
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
|
||||
// breaks down the missing positions into the desired detail level that the gen queue could accept
|
||||
|
||||
-2
@@ -31,8 +31,6 @@ import java.util.concurrent.ExecutorService;
|
||||
|
||||
public interface IFullDataSourceProvider extends AutoCloseable
|
||||
{
|
||||
void addScannedFiles(Collection<File> detectedFiles);
|
||||
|
||||
CompletableFuture<IFullDataSource> readAsync(DhSectionPos pos);
|
||||
void writeChunkDataToFile(DhSectionPos sectionPos, ChunkSizedFullDataAccessor chunkData);
|
||||
CompletableFuture<Void> flushAndSave();
|
||||
|
||||
-2
@@ -35,8 +35,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
*/
|
||||
public interface ILodRenderSourceProvider extends AutoCloseable
|
||||
{
|
||||
void addScannedFiles(Collection<File> detectedFiles);
|
||||
|
||||
CompletableFuture<ColumnRenderSource> readAsync(DhSectionPos pos);
|
||||
|
||||
void writeChunkDataToFile(DhSectionPos sectionPos, ChunkSizedFullDataAccessor chunkData);
|
||||
|
||||
+1
-2
@@ -298,7 +298,6 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
|
||||
// cache handler //
|
||||
//===============//
|
||||
|
||||
// TODO
|
||||
public CompletableFuture<ColumnRenderSource> updateRenderCacheAsync(ColumnRenderSource renderSource)
|
||||
{
|
||||
DebugRenderer.BoxWithLife debugBox = new DebugRenderer.BoxWithLife(new DebugRenderer.Box(renderSource.sectionPos, 74f, 86f, 0.1f, Color.red), 1.0, 32f, Color.green.darker());
|
||||
@@ -326,7 +325,7 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
|
||||
|
||||
// get the metaFile's version
|
||||
FullDataMetaFile renderSourceMetaFile = this.fullDataSourceProvider.getFileIfExist(this.pos);
|
||||
if (renderSourceMetaFile != null)
|
||||
if (renderSourceMetaFile != null && renderSourceMetaFile.baseMetaData != null)
|
||||
{
|
||||
renderDataVersionRef.value = renderSourceMetaFile.baseMetaData.checksum;
|
||||
}
|
||||
|
||||
+32
-88
@@ -27,10 +27,8 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
|
||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||
import com.seibel.distanthorizons.core.util.MetaFileScanUtil;
|
||||
import com.seibel.distanthorizons.core.util.FileUtil;
|
||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
@@ -41,16 +39,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
{
|
||||
public static final boolean USE_LAZY_LOADING = true;
|
||||
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
private final ThreadPoolExecutor fileHandlerThreadPool;
|
||||
private final F3Screen.NestedMessage threadPoolMsg;
|
||||
|
||||
private final ConcurrentHashMap<DhSectionPos, File> unloadedFileBySectionPos = new ConcurrentHashMap<>();
|
||||
/** contains the loaded {@link RenderDataMetaFile}'s */
|
||||
private final ConcurrentHashMap<DhSectionPos, RenderDataMetaFile> metaFileBySectionPos = new ConcurrentHashMap<>();
|
||||
protected final ConcurrentHashMap<DhSectionPos, RenderDataMetaFile> loadedMetaFileBySectionPos = new ConcurrentHashMap<>();
|
||||
|
||||
private final IDhClientLevel clientLevel;
|
||||
private final File saveDir;
|
||||
@@ -79,36 +73,6 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
|
||||
|
||||
this.threadPoolMsg = new F3Screen.NestedMessage(this::f3Log);
|
||||
|
||||
MetaFileScanUtil.scanRenderFiles(saveStructure, clientLevel.getLevelWrapper(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller must ensure that this method is called only once,
|
||||
* and that the given files are not used before this method is called. <br><br>
|
||||
*
|
||||
* Used by {@link MetaFileScanUtil#scanRenderFiles(AbstractSaveStructure, ILevelWrapper, ILodRenderSourceProvider)}
|
||||
*/
|
||||
@Override
|
||||
public void addScannedFiles(Collection<File> detectedFiles)
|
||||
{
|
||||
MetaFileScanUtil.ICreateMetadataFunc createMetadataFunc = (file) -> RenderDataMetaFile.createFromExistingFile(this.fullDataSourceProvider, this.clientLevel, file);
|
||||
|
||||
MetaFileScanUtil.IAddUnloadedFileFunc addUnloadedFileFunc = (pos, file) ->
|
||||
{
|
||||
this.unloadedFileBySectionPos.put(pos, file);
|
||||
this.topDetailLevelRef.updateAndGet(oldDetailLevel -> Math.max(oldDetailLevel, pos.getDetailLevel()));
|
||||
};
|
||||
MetaFileScanUtil.IAddLoadedMetaFileFunc addLoadedMetaFileFunc = (pos, loadedMetaFile) ->
|
||||
{
|
||||
this.topDetailLevelRef.updateAndGet(oldDetailLevel -> Math.max(oldDetailLevel, pos.getDetailLevel()));
|
||||
this.metaFileBySectionPos.put(pos, (RenderDataMetaFile) loadedMetaFile);
|
||||
};
|
||||
|
||||
|
||||
MetaFileScanUtil.addScannedFiles(detectedFiles, USE_LAZY_LOADING, RenderDataMetaFile.FILE_SUFFIX,
|
||||
createMetadataFunc,
|
||||
addUnloadedFileFunc, addLoadedMetaFileFunc);
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +93,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
|
||||
|
||||
|
||||
this.topDetailLevelRef.updateAndGet(oldDetailLevel -> Math.max(oldDetailLevel, pos.getDetailLevel()));
|
||||
RenderDataMetaFile metaFile = this.getLoadOrMakeFile(pos);
|
||||
if (metaFile == null)
|
||||
{
|
||||
@@ -155,86 +120,68 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
/** @return null if there was an issue */
|
||||
private RenderDataMetaFile getLoadOrMakeFile(DhSectionPos pos)
|
||||
{
|
||||
RenderDataMetaFile metaFile = this.metaFileBySectionPos.get(pos);
|
||||
RenderDataMetaFile metaFile = this.loadedMetaFileBySectionPos.get(pos);
|
||||
if (metaFile != null)
|
||||
{
|
||||
// return the loaded file
|
||||
return metaFile;
|
||||
}
|
||||
|
||||
|
||||
// we don't have a loaded file, for that pos,
|
||||
// do we have an unloaded file for that pos?
|
||||
File fileToLoad = this.unloadedFileBySectionPos.get(pos);
|
||||
if (fileToLoad != null && !fileToLoad.exists())
|
||||
File fileToLoad = this.computeRenderFilePath(pos);
|
||||
if (fileToLoad.exists())
|
||||
{
|
||||
fileToLoad = null;
|
||||
this.unloadedFileBySectionPos.remove(pos);
|
||||
}
|
||||
|
||||
|
||||
if (fileToLoad != null)
|
||||
{
|
||||
// A file exists, but isn't loaded yet.
|
||||
|
||||
// Double check locking for loading file, as loading file means also loading the metadata, which
|
||||
// while not... Very expensive, is still better to avoid multiple threads doing it, and dumping the
|
||||
// duplicated work to the trash. Therefore, eating the overhead of 'synchronized' is worth it.
|
||||
synchronized (this)
|
||||
{
|
||||
// check if another thread already finished loading this file
|
||||
metaFile = this.metaFileBySectionPos.get(pos);
|
||||
// A file exists, but isn't loaded yet.
|
||||
|
||||
// Double check locking for loading file, as loading file means also loading the metadata, which
|
||||
// while not... Very expensive, is still better to avoid multiple threads doing it, and dumping the
|
||||
// duplicated work to the trash. Therefore, eating the overhead of 'synchronized' is worth it.
|
||||
metaFile = this.loadedMetaFileBySectionPos.get(pos);
|
||||
if (metaFile != null)
|
||||
{
|
||||
return metaFile;
|
||||
return metaFile; // someone else loaded it already.
|
||||
}
|
||||
|
||||
|
||||
// attempt to load the file
|
||||
try
|
||||
{
|
||||
metaFile = RenderDataMetaFile.createFromExistingFile(this.fullDataSourceProvider, this.clientLevel, fileToLoad);
|
||||
this.topDetailLevelRef.updateAndGet(currentTopDetailLevel -> Math.max(currentTopDetailLevel, pos.getDetailLevel()));
|
||||
this.metaFileBySectionPos.put(pos, metaFile);
|
||||
this.loadedMetaFileBySectionPos.put(pos, metaFile);
|
||||
return metaFile;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOGGER.error("Failed to read render meta file at " + fileToLoad + ": ", e);
|
||||
LOGGER.error("Failed to read meta data file at " + fileToLoad + ": ", e);
|
||||
FileUtil.renameCorruptedFile(fileToLoad);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.unloadedFileBySectionPos.remove(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Either no file exists for this position
|
||||
// or the existing file was corrupted.
|
||||
// Create a new file.
|
||||
// File does not exist, create it.
|
||||
// In this case, since 'creating' a file object doesn't actually do anything heavy on IO yet, we use CAS
|
||||
// to avoid overhead of 'synchronized', and eat the mini-overhead of possibly creating duplicate objects.
|
||||
try
|
||||
{
|
||||
// createFromExistingOrNewFile() is used instead of createFromExistingFile()
|
||||
// due to a rare issue where the file may already exist but isn't in the file list
|
||||
metaFile = RenderDataMetaFile.createFromExistingOrNewFile(this.clientLevel, this.fullDataSourceProvider, pos, this.computeRenderFilePath(pos));
|
||||
|
||||
this.topDetailLevelRef.updateAndGet(newDetailLevel -> Math.max(newDetailLevel, pos.getDetailLevel()));
|
||||
|
||||
// Compare And Swap to handle a concurrency issue where multiple threads created the same Meta File at the same time
|
||||
RenderDataMetaFile metaFileCas = this.metaFileBySectionPos.putIfAbsent(pos, metaFile);
|
||||
return (metaFileCas == null) ? metaFile : metaFileCas;
|
||||
metaFile = RenderDataMetaFile.createNewFileForPos(this.fullDataSourceProvider, this.clientLevel, pos, fileToLoad);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOGGER.error("IOException on creating new data file at "+pos, e);
|
||||
LOGGER.error("IOException on creating new render data file at "+pos, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.topDetailLevelRef.updateAndGet(oldDetailLevel -> Math.max(oldDetailLevel, pos.getDetailLevel()));
|
||||
|
||||
// This is a Compare And Swap with expected null value.
|
||||
RenderDataMetaFile metaFileCas = this.loadedMetaFileBySectionPos.putIfAbsent(pos, metaFile);
|
||||
return (metaFileCas == null) ? metaFile : metaFileCas;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// data saving //
|
||||
//=============//
|
||||
@@ -263,7 +210,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
for (int zOffset = 0; zOffset < width; zOffset++)
|
||||
{
|
||||
fileSectionPos.mutate(sectionDetailLevel, minSectionPos.getX() + xOffset, minSectionPos.getZ() + zOffset);
|
||||
RenderDataMetaFile metaFile = this.metaFileBySectionPos.get(fileSectionPos); // bypass the getLoadOrMakeFile() since we only want cached files.
|
||||
RenderDataMetaFile metaFile = this.loadedMetaFileBySectionPos.get(fileSectionPos); // bypass the getLoadOrMakeFile() since we only want cached files.
|
||||
if (metaFile != null)
|
||||
{
|
||||
metaFile.updateChunkIfSourceExistsAsync(chunk);
|
||||
@@ -282,16 +229,13 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
@Override
|
||||
public CompletableFuture<Void> flushAndSaveAsync()
|
||||
{
|
||||
LOGGER.info("Shutting down " + RenderSourceFileHandler.class.getSimpleName() + "...");
|
||||
|
||||
ArrayList<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
for (RenderDataMetaFile metaFile : this.metaFileBySectionPos.values())
|
||||
for (RenderDataMetaFile metaFile : this.loadedMetaFileBySectionPos.values())
|
||||
{
|
||||
futures.add(metaFile.flushAndSaveAsync());
|
||||
}
|
||||
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.whenComplete((voidObj, exception) -> LOGGER.info("Finished saving " + RenderSourceFileHandler.class.getSimpleName()));
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
|
||||
}
|
||||
|
||||
|
||||
@@ -305,7 +249,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
{
|
||||
ArrayList<String> lines = new ArrayList<>();
|
||||
lines.add("Render Source File Handler [" + this.clientLevel.getClientLevelWrapper().getDimensionType().getDimensionName() + "]");
|
||||
lines.add(" Loaded files: " + this.metaFileBySectionPos.size() + " / " + (this.unloadedFileBySectionPos.size() + this.metaFileBySectionPos.size()));
|
||||
lines.add(" Loaded files: " + this.loadedMetaFileBySectionPos.size());
|
||||
lines.add(" Thread pool tasks: " + this.fileHandlerThreadPool.getQueue().size() + " (completed: " + this.fileHandlerThreadPool.getCompletedTaskCount() + ")");
|
||||
|
||||
int totalFutures = this.taskTracker.size();
|
||||
@@ -349,7 +293,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
LOGGER.info("Closing " + this.getClass().getSimpleName() + " with [" + this.metaFileBySectionPos.size() + "] files...");
|
||||
LOGGER.info("Closing " + this.getClass().getSimpleName() + " with [" + this.loadedMetaFileBySectionPos.size() + "] files...");
|
||||
this.fileHandlerThreadPool.shutdown();
|
||||
this.threadPoolMsg.close();
|
||||
}
|
||||
@@ -370,7 +314,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
}
|
||||
|
||||
// clear the cached files
|
||||
this.metaFileBySectionPos.clear();
|
||||
this.loadedMetaFileBySectionPos.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-2
@@ -22,7 +22,6 @@ package com.seibel.distanthorizons.core.generation;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
||||
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.pos.DhLodPos;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
|
||||
import java.io.Closeable;
|
||||
@@ -37,7 +36,7 @@ public interface IWorldGenerationQueue extends Closeable
|
||||
void cancelGenTasks(Iterable<DhSectionPos> positions);
|
||||
|
||||
/** @param targetPos the position that world generation should be centered around, generally this will be the player's position. */
|
||||
void runCurrentGenTasksUntilBusy(DhBlockPos2D targetPos);
|
||||
void startGenerationQueueAndSetTargetPos(DhBlockPos2D targetPos);
|
||||
|
||||
int getWaitingTaskCount();
|
||||
int getInProgressTaskCount();
|
||||
|
||||
+31
-112
@@ -82,7 +82,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
|
||||
// 1. allow the generator to deal with larger sections (let the generator threads split up larger tasks into smaller one
|
||||
// 2. batch requests better. instead of sending 4 individual tasks of detail level N, send 1 task of detail level n+1
|
||||
private final ExecutorService queueingThread = ThreadUtil.makeSingleThreadPool("World Gen Queue");
|
||||
private boolean generationQueueStarted = false;
|
||||
private boolean generationQueueRunning = false;
|
||||
private DhBlockPos2D generationTargetPos = DhBlockPos2D.ZERO;
|
||||
/** can be used for debugging how many tasks are currently in the queue */
|
||||
private int numberOfTasksQueued = 0;
|
||||
@@ -179,39 +179,26 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
|
||||
// TODO Should we cancel generation of chunks that were loaded by the player?
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// running tasks //
|
||||
//===============//
|
||||
|
||||
public void runCurrentGenTasksUntilBusy(DhBlockPos2D targetPos)
|
||||
public void startGenerationQueueAndSetTargetPos(DhBlockPos2D targetPos)
|
||||
{
|
||||
generator.preGeneratorTaskStart();
|
||||
try
|
||||
// update the target pos
|
||||
this.generationTargetPos = targetPos;
|
||||
|
||||
// ensure the queuing thread is running
|
||||
if (!this.generationQueueRunning)
|
||||
{
|
||||
// the generator is shutting down, don't attempt to generate anything
|
||||
if (this.generatorClosingFuture != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// update the target pos
|
||||
this.generationTargetPos = targetPos;
|
||||
|
||||
// only start the queuing thread once
|
||||
if (!generationQueueStarted)
|
||||
{
|
||||
startWorldGenQueuingThread();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
this.startWorldGenQueuingThread();
|
||||
}
|
||||
}
|
||||
private void startWorldGenQueuingThread()
|
||||
{
|
||||
this.generationQueueStarted = true;
|
||||
this.generationQueueRunning = true;
|
||||
|
||||
// queue world generation tasks on its own thread since this process is very slow and would lag the server thread
|
||||
this.queueingThread.execute(() ->
|
||||
@@ -221,25 +208,18 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
|
||||
// loop until the generator is shutdown
|
||||
while (!Thread.interrupted())
|
||||
{
|
||||
// LOGGER.info("pre task count: " + this.numberOfTasksQueued);
|
||||
|
||||
// recenter the generator tasks, this is done to prevent generating chunks where the player isn't
|
||||
//this.waitingTaskQuadTree.setCenterBlockPos(this.generationTargetPos);
|
||||
this.generator.preGeneratorTaskStart();
|
||||
|
||||
// queue generation tasks until the generator is full, or there are no more tasks to generate
|
||||
boolean taskStarted = true;
|
||||
while (!this.generator.isBusy() && taskStarted)
|
||||
{
|
||||
//this.removeGarbageCollectedTasks(); // TODO this is extremely slow
|
||||
taskStarted = this.startNextWorldGenTask(this.generationTargetPos);
|
||||
if (!taskStarted)
|
||||
{
|
||||
int debugPointOne = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// LOGGER.info("after task count: " + this.numberOfTasksQueued);
|
||||
|
||||
// if there aren't any new tasks, wait a second before checking again // TODO replace with a listener instead
|
||||
Thread.sleep(1000);
|
||||
@@ -252,33 +232,11 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("queueing exception: " + e.getMessage(), e);
|
||||
this.generationQueueStarted = false;
|
||||
this.generationQueueRunning = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// /** Removes all {@link WorldGenTask}'s and {@link WorldGenTaskGroup}'s that have been garbage collected. */
|
||||
// private void removeGarbageCollectedTasks() // TODO remove, potential mystery errors caused by garbage collection isn't worth it (and may not be necessary any more now that we are using a quad tree to hold the tasks). // also this is very slow with the curent quad tree impelmentation
|
||||
// {
|
||||
// for (byte detailLevel = QuadTree.TREE_LOWEST_DETAIL_LEVEL; detailLevel < this.waitingTaskQuadTree.treeMaxDetailLevel; detailLevel++)
|
||||
// {
|
||||
// MovableGridRingList<WorldGenTask> gridRingList = this.waitingTaskQuadTree.getRingList(detailLevel);
|
||||
// Iterator<WorldGenTask> taskIterator = gridRingList.iterator();
|
||||
// while (taskIterator.hasNext())
|
||||
// {
|
||||
// // go through each WorldGenTask in the TaskGroup
|
||||
// WorldGenTask genTask = taskIterator.next();
|
||||
// if (genTask != null && !genTask.taskTracker.isMemoryAddressValid())
|
||||
// {
|
||||
// taskIterator.remove();
|
||||
// genTask.future.complete(WorldGenResult.CreateFail());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private final Set<WorldGenTask> CheckingTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
private static class Mapper
|
||||
{
|
||||
public final WorldGenTask task;
|
||||
@@ -297,63 +255,30 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
|
||||
*/
|
||||
private boolean startNextWorldGenTask(DhBlockPos2D targetPos)
|
||||
{
|
||||
long closestGenDist = Long.MAX_VALUE;
|
||||
|
||||
WorldGenTask closestTask = null;
|
||||
//CheckingTasks.clear();
|
||||
|
||||
/* // TODO improve, having to go over every node isn't super efficient, removing null nodes from the tree would help
|
||||
Iterator<QuadNode<WorldGenTask>> nodeIterator = this.waitingTaskQuadTree.nodeIterator();
|
||||
while (nodeIterator.hasNext())
|
||||
{
|
||||
QuadNode<WorldGenTask> taskNode = nodeIterator.next();
|
||||
WorldGenTask newGenTask = taskNode.value;
|
||||
DhSectionPos taskSectionPos = taskNode.sectionPos;
|
||||
|
||||
if (newGenTask != null) // TODO add an option to skip leaves with null values and potentially auto-prune them
|
||||
{
|
||||
CheckingTasks.add(newGenTask);
|
||||
if (!newGenTask.StillValid())
|
||||
{
|
||||
// skip and remove out-of-bound tasks or tasks that are no longer valid
|
||||
taskNode.value = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// use chebyShev distance in order to generate in rings around the target pos (also because it is a fast distance calculation)
|
||||
int chebDistToTargetPos = newGenTask.pos.getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D());
|
||||
if (chebDistToTargetPos < closestGenDist)
|
||||
{
|
||||
// this task is closer than the last one
|
||||
closestTask = newGenTask;
|
||||
closestGenDist = chebDistToTargetPos;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
waitingTasks.forEach((pos, task) -> {
|
||||
if (!task.StillValid())
|
||||
{
|
||||
waitingTasks.remove(pos);
|
||||
task.future.complete(WorldGenResult.CreateFail());
|
||||
}
|
||||
});
|
||||
|
||||
if (waitingTasks.size() == 0)
|
||||
if (this.waitingTasks.size() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Mapper closestTaskMap = waitingTasks.reduceEntries(1024,
|
||||
v -> new Mapper(v.getValue(), v.getValue().pos.getSectionBBoxPos().getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D())),
|
||||
(a, b) -> a.dist < b.dist ? a : b);
|
||||
this.waitingTasks.forEach((pos, task) ->
|
||||
{
|
||||
if (!task.StillValid())
|
||||
{
|
||||
this.waitingTasks.remove(pos);
|
||||
task.future.complete(WorldGenResult.CreateFail());
|
||||
}
|
||||
});
|
||||
|
||||
closestTask = closestTaskMap.task;
|
||||
|
||||
|
||||
Mapper closestTaskMap = this.waitingTasks.reduceEntries(1024,
|
||||
entry -> new Mapper(entry.getValue(), entry.getValue().pos.getSectionBBoxPos().getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D())),
|
||||
(aMapper, bMapper) -> aMapper.dist < bMapper.dist ? aMapper : bMapper);
|
||||
|
||||
WorldGenTask closestTask = closestTaskMap.task;
|
||||
|
||||
// remove the task we found, we are going to start it and don't want to run it multiple times
|
||||
//WorldGenTask removedWorldGenTask = this.waitingTaskQuadTree.setValue(new DhSectionPos(closestTask.pos.detailLevel, closestTask.pos.x, closestTask.pos.z), null);
|
||||
waitingTasks.remove(closestTask.pos, closestTask);
|
||||
this.waitingTasks.remove(closestTask.pos, closestTask);
|
||||
|
||||
// do we need to modify this task to generate it?
|
||||
if (this.canGeneratePos((byte) 0, closestTask.pos)) // TODO should detail level 0 be replaced?
|
||||
@@ -399,13 +324,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
|
||||
childFutures.add(newFuture);
|
||||
|
||||
WorldGenTask newGenTask = new WorldGenTask(childDhSectionPos, childDhSectionPos.getDetailLevel(), finalClosestTask.taskTracker, newFuture);
|
||||
waitingTasks.put(newGenTask.pos, newGenTask);
|
||||
//this.waitingTaskQuadTree.setValue(new DhSectionPos(childDhSectionPos.sectionDetailLevel, childDhSectionPos.sectionX, childDhSectionPos.sectionZ), newGenTask);
|
||||
|
||||
//boolean valueAdded = this.waitingTaskQuadTree.getValue(new DhSectionPos(childDhSectionPos.sectionDetailLevel, childDhSectionPos.sectionX, childDhSectionPos.sectionZ)) != null;
|
||||
//LodUtil.assertTrue(valueAdded); // failed to add world gen task to quad tree, this means the quad tree was the wrong size
|
||||
|
||||
// LOGGER.info("split feature "+sectionPos+" into "+childDhSectionPos+" "+(valueAdded ? "added" : "notAdded"));
|
||||
this.waitingTasks.put(newGenTask.pos, newGenTask);
|
||||
});
|
||||
|
||||
// send the child futures to the future recipient, to notify them of the new tasks
|
||||
|
||||
@@ -212,12 +212,6 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel
|
||||
// misc helper functions //
|
||||
//=======================//
|
||||
|
||||
@Override
|
||||
public void dumpRamUsage()
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public IFullDataSourceProvider getFileHandler()
|
||||
{
|
||||
|
||||
@@ -85,36 +85,34 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTick()
|
||||
{
|
||||
chunkToLodBuilder.tick();
|
||||
}
|
||||
public void serverTick() { this.chunkToLodBuilder.tick(); }
|
||||
|
||||
@Override
|
||||
public void doWorldGen()
|
||||
{
|
||||
serverside.worldGeneratorEnabledConfig.pollNewValue();
|
||||
boolean shouldDoWorldGen = serverside.worldGeneratorEnabledConfig.get() && clientside.isRendering();
|
||||
boolean isWorldGenRunning = serverside.worldGenModule.isWorldGenRunning();
|
||||
this.serverside.worldGeneratorEnabledConfig.pollNewValue();
|
||||
boolean shouldDoWorldGen = this.serverside.worldGeneratorEnabledConfig.get() && this.clientside.isRendering();
|
||||
boolean isWorldGenRunning = this.serverside.worldGenModule.isWorldGenRunning();
|
||||
if (shouldDoWorldGen && !isWorldGenRunning)
|
||||
{
|
||||
// start world gen
|
||||
serverside.worldGenModule.startWorldGen(serverside.dataFileHandler, new ServerLevelModule.WorldGenState(this));
|
||||
this.serverside.worldGenModule.startWorldGen(this.serverside.dataFileHandler, new ServerLevelModule.WorldGenState(this));
|
||||
}
|
||||
else if (!shouldDoWorldGen && isWorldGenRunning)
|
||||
{
|
||||
// stop world gen
|
||||
serverside.worldGenModule.stopWorldGen(serverside.dataFileHandler);
|
||||
this.serverside.worldGenModule.stopWorldGen(this.serverside.dataFileHandler);
|
||||
}
|
||||
|
||||
if (serverside.worldGenModule.isWorldGenRunning())
|
||||
if (this.serverside.worldGenModule.isWorldGenRunning())
|
||||
{
|
||||
ClientLevelModule.ClientRenderState renderState = clientside.ClientRenderStateRef.get();
|
||||
ClientLevelModule.ClientRenderState renderState = this.clientside.ClientRenderStateRef.get();
|
||||
if (renderState != null && renderState.quadtree != null)
|
||||
{
|
||||
serverside.dataFileHandler.removeGenRequestIf(p -> !renderState.quadtree.isSectionPosInBounds(p));
|
||||
this.serverside.dataFileHandler.removeGenRequestIf(pos -> !renderState.quadtree.isSectionPosInBounds(pos));
|
||||
}
|
||||
serverside.worldGenModule.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
|
||||
|
||||
this.serverside.worldGenModule.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,9 +193,9 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
clientside.close();
|
||||
this.clientside.close();
|
||||
super.close();
|
||||
serverside.close();
|
||||
this.serverside.close();
|
||||
LOGGER.info("Closed " + this.getClass().getSimpleName() + " for " + this.getServerLevelWrapper());
|
||||
}
|
||||
|
||||
@@ -214,10 +212,4 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS
|
||||
this.clientside.reloadPos(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpRamUsage()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -258,12 +258,6 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel
|
||||
@Override
|
||||
public int getMinY() { return getLevelWrapper().getMinHeight(); }
|
||||
|
||||
@Override
|
||||
public void dumpRamUsage()
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
|
||||
@@ -33,8 +33,6 @@ public interface IDhLevel extends AutoCloseable
|
||||
int getMinY();
|
||||
CompletableFuture<Void> saveAsync();
|
||||
|
||||
void dumpRamUsage();
|
||||
|
||||
/**
|
||||
* May return either a client or server level wrapper. <br>
|
||||
* Should not return null
|
||||
|
||||
@@ -111,8 +111,7 @@ public class WorldGenModule implements Closeable
|
||||
AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
|
||||
if (worldGenState != null)
|
||||
{
|
||||
// queue new world generation requests
|
||||
worldGenState.tick(targetPosForGeneration);
|
||||
worldGenState.startGenerationQueueAndSetTargetPos(targetPosForGeneration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +177,7 @@ public class WorldGenModule implements Closeable
|
||||
}
|
||||
|
||||
/** @param targetPosForGeneration the position that world generation should be centered around */
|
||||
public void tick(DhBlockPos2D targetPosForGeneration) { this.worldGenerationQueue.runCurrentGenTasksUntilBusy(targetPosForGeneration); }
|
||||
public void startGenerationQueueAndSetTargetPos(DhBlockPos2D targetPosForGeneration) { this.worldGenerationQueue.startGenerationQueueAndSetTargetPos(targetPosForGeneration); }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,13 +21,13 @@ package com.seibel.distanthorizons.core.render.glObject;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.seibel.distanthorizons.api.enums.config.EGLErrorHandlingMode;
|
||||
import com.seibel.distanthorizons.api.enums.config.EGlProfileMode;
|
||||
import com.seibel.distanthorizons.api.enums.config.EGpuUploadMethod;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.enums.EGLProxyContext;
|
||||
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.util.ReflectionUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.GLMessage;
|
||||
import com.seibel.distanthorizons.core.util.objects.GLMessageOutputStream;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
@@ -61,7 +61,6 @@ import java.util.concurrent.TimeUnit;
|
||||
* https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer <br><br>
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2022-10-1
|
||||
*/
|
||||
public class GLProxy
|
||||
{
|
||||
@@ -102,6 +101,10 @@ public class GLProxy
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
private GLProxy() throws IllegalStateException
|
||||
{
|
||||
// this must be created on minecraft's render context to work correctly
|
||||
@@ -123,13 +126,13 @@ public class GLProxy
|
||||
//============================//
|
||||
|
||||
// get Minecraft's context
|
||||
minecraftGlContext = GLFW.glfwGetCurrentContext();
|
||||
minecraftGlCapabilities = GL.getCapabilities();
|
||||
this.minecraftGlContext = GLFW.glfwGetCurrentContext();
|
||||
this.minecraftGlCapabilities = GL.getCapabilities();
|
||||
|
||||
// crash the game if the GPU doesn't support OpenGL 3.2
|
||||
if (!minecraftGlCapabilities.OpenGL32)
|
||||
if (!this.minecraftGlCapabilities.OpenGL32)
|
||||
{
|
||||
String supportedVersionInfo = getFailedVersionInfo(minecraftGlCapabilities);
|
||||
String supportedVersionInfo = this.getFailedVersionInfo(this.minecraftGlCapabilities);
|
||||
|
||||
// See full requirement at above.
|
||||
String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName()
|
||||
@@ -137,11 +140,11 @@ public class GLProxy
|
||||
"Additional info:\n" + supportedVersionInfo;
|
||||
MC.crashMinecraft(errorMessage, new UnsupportedOperationException("Distant Horizon OpenGL requirements not met"));
|
||||
}
|
||||
GL_LOGGER.info("minecraftGlCapabilities:\n" + getVersionInfo(minecraftGlCapabilities));
|
||||
GL_LOGGER.info("minecraftGlCapabilities:\n" + this.getVersionInfo(this.minecraftGlCapabilities));
|
||||
|
||||
if (Config.Client.Advanced.Debugging.overrideVanillaGLLogger.get())
|
||||
{
|
||||
GLUtil.setupDebugMessageCallback(new PrintStream(new GLMessageOutputStream(GLProxy::logMessage, vanillaDebugMessageBuilder), true));
|
||||
GLUtil.setupDebugMessageCallback(new PrintStream(new GLMessageOutputStream(GLProxy::logMessage, this.vanillaDebugMessageBuilder), true));
|
||||
}
|
||||
|
||||
|
||||
@@ -162,41 +165,25 @@ public class GLProxy
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_DEBUG_CONTEXT, GLFW.GLFW_TRUE);
|
||||
|
||||
// TODO remove me
|
||||
boolean useForwardCompatibility = Config.Client.Advanced.Debugging.glForwardCompatibilityMode.get();
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, useForwardCompatibility ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE);
|
||||
|
||||
// TODO remove me
|
||||
EGlProfileMode profileMode = Config.Client.Advanced.Debugging.glProfileMode.get();
|
||||
if (profileMode == EGlProfileMode.CORE)
|
||||
{
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE);
|
||||
}
|
||||
else if (profileMode == EGlProfileMode.ANY)
|
||||
{
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_ANY_PROFILE);
|
||||
}
|
||||
else
|
||||
{
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_COMPAT_PROFILE);
|
||||
}
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE);
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
// create the Lod Builder context
|
||||
lodBuilderGlContext = GLFW.glfwCreateWindow(64, 48, "LOD Builder Window", 0L, minecraftGlContext);
|
||||
if (lodBuilderGlContext == 0)
|
||||
this.lodBuilderGlContext = GLFW.glfwCreateWindow(64, 48, "LOD Builder Window", 0L, this.minecraftGlContext);
|
||||
if (this.lodBuilderGlContext == 0)
|
||||
{
|
||||
GL_LOGGER.error("ERROR: Failed to create GLFW context for OpenGL 3.2 with"
|
||||
+ " Forward Compat Core Profile! Your OS may have not been able to support it!");
|
||||
GL_LOGGER.error("ERROR: Failed to create LodBuilder GLFW context for OpenGL 3.2 with Forward compatible Core Profile! Your OS may have not been able to support it.");
|
||||
GL_LOGGER.error("Minecraft GL Capabilities:\n [\n"+ReflectionUtil.getAllFieldValuesAsString(this.minecraftGlCapabilities)+"\n]\n");
|
||||
|
||||
throw new UnsupportedOperationException("Forward Compat Core Profile 3.2 creation failure");
|
||||
}
|
||||
// create the window
|
||||
GLFW.glfwMakeContextCurrent(lodBuilderGlContext);
|
||||
GLFW.glfwMakeContextCurrent(this.lodBuilderGlContext);
|
||||
// set and log the capabilities
|
||||
lodBuilderGlCapabilities = GL.createCapabilities();
|
||||
GL_LOGGER.info("lodBuilderGlCapabilities:\n" + getVersionInfo(lodBuilderGlCapabilities));
|
||||
this.lodBuilderGlCapabilities = GL.createCapabilities();
|
||||
GL_LOGGER.info("lodBuilderGlCapabilities:\n" + this.getVersionInfo(this.lodBuilderGlCapabilities));
|
||||
// override the GL logger
|
||||
GLUtil.setupDebugMessageCallback(new PrintStream(new GLMessageOutputStream(GLProxy::logMessage, lodBuilderDebugMessageBuilder), true));
|
||||
GLUtil.setupDebugMessageCallback(new PrintStream(new GLMessageOutputStream(GLProxy::logMessage, this.lodBuilderDebugMessageBuilder), true));
|
||||
// clear the context for the next stage
|
||||
GLFW.glfwMakeContextCurrent(0L);
|
||||
|
||||
@@ -207,20 +194,21 @@ public class GLProxy
|
||||
//=================================//
|
||||
|
||||
// create the proxyWorker's context
|
||||
proxyWorkerGlContext = GLFW.glfwCreateWindow(64, 48, "LOD proxy worker Window", 0L, minecraftGlContext);
|
||||
if (proxyWorkerGlContext == 0)
|
||||
this.proxyWorkerGlContext = GLFW.glfwCreateWindow(64, 48, "LOD proxy worker Window", 0L, this.minecraftGlContext);
|
||||
if (this.proxyWorkerGlContext == 0)
|
||||
{
|
||||
GL_LOGGER.error("ERROR: Failed to create GLFW context for OpenGL 3.2 with"
|
||||
+ " Forward Compat Core Profile! Your OS may have not been able to support it!");
|
||||
GL_LOGGER.error("ERROR: Failed to create GLProxy Worker GLFW context for OpenGL 3.2 with Forward compatible Core Profile! Your OS may have not been able to support it.");
|
||||
GL_LOGGER.error("Minecraft GL Capabilities:\n [\n"+ReflectionUtil.getAllFieldValuesAsString(this.minecraftGlCapabilities)+"\n]\n");
|
||||
|
||||
throw new UnsupportedOperationException("Forward Compat Core Profile 3.2 creation failure");
|
||||
}
|
||||
// create the window
|
||||
GLFW.glfwMakeContextCurrent(proxyWorkerGlContext);
|
||||
GLFW.glfwMakeContextCurrent(this.proxyWorkerGlContext);
|
||||
// set and log the capabilities
|
||||
proxyWorkerGlCapabilities = GL.createCapabilities();
|
||||
GL_LOGGER.info("proxyWorkerGlCapabilities:\n" + getVersionInfo(lodBuilderGlCapabilities));
|
||||
this.proxyWorkerGlCapabilities = GL.createCapabilities();
|
||||
GL_LOGGER.info("proxyWorkerGlCapabilities:\n" + this.getVersionInfo(this.lodBuilderGlCapabilities));
|
||||
// override the GL logger
|
||||
GLUtil.setupDebugMessageCallback(new PrintStream(new GLMessageOutputStream(GLProxy::logMessage, proxyWorkerDebugMessageBuilder), true));
|
||||
GLUtil.setupDebugMessageCallback(new PrintStream(new GLMessageOutputStream(GLProxy::logMessage, this.proxyWorkerDebugMessageBuilder), true));
|
||||
// clear the context for the next stage
|
||||
GLFW.glfwMakeContextCurrent(0L);
|
||||
|
||||
@@ -231,19 +219,19 @@ public class GLProxy
|
||||
//======================//
|
||||
|
||||
// get capabilities from a context we use
|
||||
setGlContext(EGLProxyContext.LOD_BUILDER);
|
||||
this.setGlContext(EGLProxyContext.LOD_BUILDER);
|
||||
|
||||
// Check if we can use the make-over version of Vertex Attribute, which is available in GL4.3 or after
|
||||
VertexAttributeBufferBindingSupported = minecraftGlCapabilities.glBindVertexBuffer != 0L; // Nullptr
|
||||
this.VertexAttributeBufferBindingSupported = this.minecraftGlCapabilities.glBindVertexBuffer != 0L; // Nullptr
|
||||
|
||||
// UNUSED currently
|
||||
// Check if we can use the named version of all calls, which is available in GL4.5 or after
|
||||
namedObjectSupported = minecraftGlCapabilities.glNamedBufferData != 0L; //Nullptr
|
||||
this.namedObjectSupported = this.minecraftGlCapabilities.glNamedBufferData != 0L; //Nullptr
|
||||
|
||||
// get specific capabilities
|
||||
// Check if we can use the Buffer Storage, which is available in GL4.4 or after
|
||||
bufferStorageSupported = minecraftGlCapabilities.glBufferStorage != 0L && lodBuilderGlCapabilities.glBufferStorage != 0L; // Nullptr
|
||||
if (!bufferStorageSupported)
|
||||
this.bufferStorageSupported = this.minecraftGlCapabilities.glBufferStorage != 0L && this.lodBuilderGlCapabilities.glBufferStorage != 0L; // Nullptr
|
||||
if (!this.bufferStorageSupported)
|
||||
{
|
||||
GL_LOGGER.warn("This GPU doesn't support Buffer Storage (OpenGL 4.4), falling back to using other methods.");
|
||||
}
|
||||
@@ -253,14 +241,14 @@ public class GLProxy
|
||||
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
|
||||
{
|
||||
// NVIDIA card
|
||||
preferredUploadMethod = bufferStorageSupported ? EGpuUploadMethod.BUFFER_STORAGE : EGpuUploadMethod.SUB_DATA;
|
||||
this.preferredUploadMethod = this.bufferStorageSupported ? EGpuUploadMethod.BUFFER_STORAGE : EGpuUploadMethod.SUB_DATA;
|
||||
}
|
||||
else
|
||||
{
|
||||
// AMD or Intel card
|
||||
preferredUploadMethod = bufferStorageSupported ? EGpuUploadMethod.BUFFER_STORAGE : EGpuUploadMethod.DATA;
|
||||
this.preferredUploadMethod = this.bufferStorageSupported ? EGpuUploadMethod.BUFFER_STORAGE : EGpuUploadMethod.DATA;
|
||||
}
|
||||
GL_LOGGER.info("GPU Vendor [" + vendor + "], Preferred upload method is [" + preferredUploadMethod + "].");
|
||||
GL_LOGGER.info("GPU Vendor [" + vendor + "], Preferred upload method is [" + this.preferredUploadMethod + "].");
|
||||
|
||||
|
||||
|
||||
@@ -269,7 +257,7 @@ public class GLProxy
|
||||
//==========//
|
||||
|
||||
// Since this is created on the render thread, make sure the Minecraft context is used in the end
|
||||
setGlContext(EGLProxyContext.MINECRAFT);
|
||||
this.setGlContext(EGLProxyContext.MINECRAFT);
|
||||
|
||||
// GLProxy creation success
|
||||
GL_LOGGER.info(GLProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day.");
|
||||
@@ -281,7 +269,7 @@ public class GLProxy
|
||||
*/
|
||||
public void setGlContext(EGLProxyContext newContext)
|
||||
{
|
||||
EGLProxyContext currentContext = getGlContext();
|
||||
EGLProxyContext currentContext = this.getGlContext();
|
||||
|
||||
// we don't have to change the context, we are already there.
|
||||
if (currentContext == newContext)
|
||||
@@ -294,18 +282,18 @@ public class GLProxy
|
||||
switch (newContext)
|
||||
{
|
||||
case LOD_BUILDER:
|
||||
contextPointer = lodBuilderGlContext;
|
||||
newGlCapabilities = lodBuilderGlCapabilities;
|
||||
contextPointer = this.lodBuilderGlContext;
|
||||
newGlCapabilities = this.lodBuilderGlCapabilities;
|
||||
break;
|
||||
|
||||
case MINECRAFT:
|
||||
contextPointer = minecraftGlContext;
|
||||
newGlCapabilities = minecraftGlCapabilities;
|
||||
contextPointer = this.minecraftGlContext;
|
||||
newGlCapabilities = this.minecraftGlCapabilities;
|
||||
break;
|
||||
|
||||
case PROXY_WORKER:
|
||||
contextPointer = proxyWorkerGlContext;
|
||||
newGlCapabilities = proxyWorkerGlCapabilities;
|
||||
contextPointer = this.proxyWorkerGlContext;
|
||||
newGlCapabilities = this.proxyWorkerGlCapabilities;
|
||||
break;
|
||||
|
||||
default: // default should never happen, it is just here to make the compiler happy
|
||||
@@ -325,15 +313,15 @@ public class GLProxy
|
||||
long currentContext = GLFW.glfwGetCurrentContext();
|
||||
|
||||
|
||||
if (currentContext == lodBuilderGlContext)
|
||||
if (currentContext == this.lodBuilderGlContext)
|
||||
{
|
||||
return EGLProxyContext.LOD_BUILDER;
|
||||
}
|
||||
else if (currentContext == minecraftGlContext)
|
||||
else if (currentContext == this.minecraftGlContext)
|
||||
{
|
||||
return EGLProxyContext.MINECRAFT;
|
||||
}
|
||||
else if (currentContext == proxyWorkerGlContext)
|
||||
else if (currentContext == this.proxyWorkerGlContext)
|
||||
{
|
||||
return EGLProxyContext.PROXY_WORKER;
|
||||
}
|
||||
@@ -346,9 +334,9 @@ public class GLProxy
|
||||
// hopefully this shouldn't happen
|
||||
throw new IllegalStateException(Thread.currentThread().getName() +
|
||||
" has a unknown OpenGl context: [" + currentContext + "]. "
|
||||
+ "Minecraft context [" + minecraftGlContext + "], "
|
||||
+ "LodBuilder context [" + lodBuilderGlContext + "], "
|
||||
+ "ProxyWorker context [" + proxyWorkerGlContext + "], "
|
||||
+ "Minecraft context [" + this.minecraftGlContext + "], "
|
||||
+ "LodBuilder context [" + this.lodBuilderGlContext + "], "
|
||||
+ "ProxyWorker context [" + this.proxyWorkerGlContext + "], "
|
||||
+ "no context [0].");
|
||||
}
|
||||
}
|
||||
@@ -368,13 +356,13 @@ public class GLProxy
|
||||
public EGpuUploadMethod getGpuUploadMethod()
|
||||
{
|
||||
EGpuUploadMethod method = Config.Client.Advanced.GpuBuffers.gpuUploadMethod.get();
|
||||
if (!bufferStorageSupported && method == EGpuUploadMethod.BUFFER_STORAGE)
|
||||
if (!this.bufferStorageSupported && method == EGpuUploadMethod.BUFFER_STORAGE)
|
||||
{
|
||||
// if buffer storage isn't supported
|
||||
// default to DATA since that is the most compatible
|
||||
method = EGpuUploadMethod.DATA;
|
||||
}
|
||||
return method == EGpuUploadMethod.AUTO ? preferredUploadMethod : method;
|
||||
return method == EGpuUploadMethod.AUTO ? this.preferredUploadMethod : method;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,14 +374,14 @@ public class GLProxy
|
||||
public void recordOpenGlCall(Runnable renderCall)
|
||||
{
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
workerThread.execute(() -> runnableContainer(renderCall, stackTrace));
|
||||
this.workerThread.execute(() -> this.runnableContainer(renderCall, stackTrace));
|
||||
}
|
||||
private void runnableContainer(Runnable renderCall, StackTraceElement[] stackTrace)
|
||||
{
|
||||
try
|
||||
{
|
||||
// set up the context...
|
||||
setGlContext(EGLProxyContext.PROXY_WORKER);
|
||||
this.setGlContext(EGLProxyContext.PROXY_WORKER);
|
||||
// ...run the actual code...
|
||||
renderCall.run();
|
||||
}
|
||||
@@ -406,7 +394,7 @@ public class GLProxy
|
||||
finally
|
||||
{
|
||||
// ...and make sure the context is released when the thread finishes
|
||||
setGlContext(EGLProxyContext.NONE);
|
||||
this.setGlContext(EGLProxyContext.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,9 +448,9 @@ public class GLProxy
|
||||
return false;
|
||||
}
|
||||
|
||||
namedObjectSupported = c.glNamedBufferStorage != 0;
|
||||
bufferStorageSupported = c.glBufferStorage != 0;
|
||||
VertexAttributeBufferBindingSupported = c.glVertexAttribBinding != 0;
|
||||
this.namedObjectSupported = c.glNamedBufferStorage != 0;
|
||||
this.bufferStorageSupported = c.glBufferStorage != 0;
|
||||
this.VertexAttributeBufferBindingSupported = c.glVertexAttribBinding != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -145,7 +145,7 @@ public class GLBuffer implements AutoCloseable
|
||||
GL32.glBufferSubData(getBufferBindingTarget(), 0, bb);
|
||||
}
|
||||
|
||||
// Requires already binded
|
||||
/** Assumes the GL Context is already bound */
|
||||
public void uploadBuffer(ByteBuffer bb, EGpuUploadMethod uploadMethod, int maxExpansionSize, int bufferHint)
|
||||
{
|
||||
LodUtil.assertTrue(!uploadMethod.useEarlyMapping, "UploadMethod signal that this should use Mapping instead of uploadBuffer!");
|
||||
|
||||
+9
-5
@@ -61,16 +61,20 @@ public class GLVertexBuffer extends GLBuffer
|
||||
return GL32.GL_ARRAY_BUFFER;
|
||||
}
|
||||
|
||||
public void uploadBuffer(ByteBuffer bb, int vertCount, EGpuUploadMethod uploadMethod, int maxExpensionSize)
|
||||
public void uploadBuffer(ByteBuffer byteBuffer, int vertCount, EGpuUploadMethod uploadMethod, int maxExpensionSize)
|
||||
{
|
||||
if (vertCount < 0) throw new IllegalArgumentException("VertCount is negative!");
|
||||
if (vertCount < 0)
|
||||
{
|
||||
throw new IllegalArgumentException("VertCount is negative!");
|
||||
}
|
||||
|
||||
// If size is zero, just ignore it.
|
||||
if (bb.limit() - bb.position() != 0)
|
||||
if (byteBuffer.limit() - byteBuffer.position() != 0)
|
||||
{
|
||||
boolean useBuffStorage = uploadMethod.useBufferStorage;
|
||||
super.uploadBuffer(bb, uploadMethod, maxExpensionSize, useBuffStorage ? 0 : GL32.GL_STATIC_DRAW);
|
||||
super.uploadBuffer(byteBuffer, uploadMethod, maxExpensionSize, useBuffStorage ? 0 : GL32.GL_STATIC_DRAW);
|
||||
}
|
||||
vertexCount = vertCount;
|
||||
this.vertexCount = vertCount;
|
||||
}
|
||||
|
||||
public ByteBuffer mapBuffer(int targetSize, EGpuUploadMethod uploadMethod, int maxExpensionSize)
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataFileHandler;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
|
||||
import com.seibel.distanthorizons.core.file.metaData.AbstractMetaDataContainerFile;
|
||||
import com.seibel.distanthorizons.core.file.renderfile.ILodRenderSourceProvider;
|
||||
import com.seibel.distanthorizons.core.file.renderfile.RenderDataMetaFile;
|
||||
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/** Used to pull in the initial files used by both {@link IFullDataSourceProvider} and {@link ILodRenderSourceProvider}s. */
|
||||
public class MetaFileScanUtil
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
public static final int MAX_SCAN_DEPTH = 5;
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// file scanning //
|
||||
//===============//
|
||||
|
||||
// file scanning means to find all File's in a given directory
|
||||
|
||||
// TODO merge with the below method
|
||||
public static void scanFullDataFiles(AbstractSaveStructure saveStructure, ILevelWrapper levelWrapper, IFullDataSourceProvider dataSourceProvider)
|
||||
{
|
||||
try (Stream<Path> pathStream = Files.walk(saveStructure.getFullDataFolder(levelWrapper).toPath(), MAX_SCAN_DEPTH))
|
||||
{
|
||||
List<File> files = pathStream.filter(
|
||||
path -> path.toFile().getName().endsWith(FullDataMetaFile.FILE_SUFFIX) && path.toFile().isFile()
|
||||
).map(Path::toFile).collect(Collectors.toList());
|
||||
LOGGER.info("Found " + files.size() + " full data files for " + levelWrapper + " in " + saveStructure);
|
||||
dataSourceProvider.addScannedFiles(files);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Failed to scan and collect full data files for " + levelWrapper + " in " + saveStructure, e);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO merge with the above method
|
||||
public static void scanRenderFiles(AbstractSaveStructure saveStructure, ILevelWrapper levelWrapper, ILodRenderSourceProvider renderSourceProvider)
|
||||
{
|
||||
try (Stream<Path> pathStream = Files.walk(saveStructure.getRenderCacheFolder(levelWrapper).toPath(), MAX_SCAN_DEPTH))
|
||||
{
|
||||
List<File> files = pathStream.filter(
|
||||
path -> path.toFile().getName().endsWith(RenderDataMetaFile.FILE_SUFFIX) && path.toFile().isFile()
|
||||
).map(Path::toFile).collect(Collectors.toList());
|
||||
LOGGER.info("Found " + files.size() + " render cache files for " + levelWrapper + " in " + saveStructure);
|
||||
renderSourceProvider.addScannedFiles(files);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Failed to scan and collect cache files for " + levelWrapper + " in " + saveStructure, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//======================//
|
||||
// Adding scanned files //
|
||||
//======================//
|
||||
|
||||
/**
|
||||
* Caller must ensure that this method is called only once,
|
||||
* and that the {@link FullDataFileHandler} is not used before this method is called.
|
||||
*/
|
||||
public static void addScannedFiles(
|
||||
Collection<File> detectedFiles, boolean useLazyLoading, String fileSuffix,
|
||||
ICreateMetadataFunc createMetadataFunc,
|
||||
IAddUnloadedFileFunc addUnloadedFileFunc, IAddLoadedMetaFileFunc addLoadedMetaFileFunc)
|
||||
{
|
||||
if (useLazyLoading)
|
||||
{
|
||||
lazyAddScannedFile(detectedFiles, fileSuffix, addUnloadedFileFunc);
|
||||
}
|
||||
else
|
||||
{
|
||||
immediateAddScannedFile(detectedFiles, createMetadataFunc, addLoadedMetaFileFunc);
|
||||
}
|
||||
}
|
||||
private static void lazyAddScannedFile(Collection<File> detectedFiles, String fileSuffix, IAddUnloadedFileFunc addUnloadedFileFunc)
|
||||
{
|
||||
for (File file : detectedFiles)
|
||||
{
|
||||
if (file == null || !file.exists())
|
||||
{
|
||||
// can rarely happen if the user rapidly travels between dimensions
|
||||
LOGGER.warn("Null or non-existent file: " + ((file != null) ? file.getPath() : "NULL"));
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DhSectionPos pos = decodePositionFromFileName(file, fileSuffix);
|
||||
if (pos != null)
|
||||
{
|
||||
addUnloadedFileFunc.addFile(pos, file);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Failed to read data meta file at " + file + ": ", e);
|
||||
FileUtil.renameCorruptedFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
private static void immediateAddScannedFile(
|
||||
Collection<File> detectedFiles,
|
||||
ICreateMetadataFunc createMetadataFunc, IAddLoadedMetaFileFunc addLoadedMetaFileFunc)
|
||||
{
|
||||
HashMultimap<DhSectionPos, AbstractMetaDataContainerFile> filesByPos = HashMultimap.create();
|
||||
{ // Sort files by pos.
|
||||
for (File file : detectedFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbstractMetaDataContainerFile metaFile = createMetadataFunc.createFile(file);
|
||||
filesByPos.put(metaFile.pos, metaFile);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOGGER.error("Failed to read data meta file at " + file + ": ", e);
|
||||
FileUtil.renameCorruptedFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Warn for multiple files with the same pos, and then select the one with the latest timestamp.
|
||||
for (DhSectionPos pos : filesByPos.keySet())
|
||||
{
|
||||
Collection<AbstractMetaDataContainerFile> metaFiles = filesByPos.get(pos);
|
||||
AbstractMetaDataContainerFile metaFileToUse;
|
||||
if (metaFiles.size() > 1)
|
||||
{
|
||||
// sort by the file's last modified date
|
||||
metaFileToUse = Collections.max(metaFiles, Comparator.comparingLong(fullDataMetaFile -> fullDataMetaFile.file.lastModified()));
|
||||
|
||||
// log the duplicate files
|
||||
StringBuilder duplicateMessage = new StringBuilder();
|
||||
duplicateMessage.append("Multiple files with the same pos: ").append(pos).append("\n");
|
||||
for (AbstractMetaDataContainerFile metaFile : metaFiles)
|
||||
{
|
||||
duplicateMessage.append("\t").append(metaFile.file).append("\n");
|
||||
}
|
||||
duplicateMessage.append("\tUsing: ").append(metaFileToUse.file).append("\n");
|
||||
duplicateMessage.append("(Other files will be renamed by appending \".old\" to their name.)");
|
||||
LOGGER.warn(duplicateMessage.toString());
|
||||
|
||||
|
||||
|
||||
// Rename all other files with the same pos to .old
|
||||
for (AbstractMetaDataContainerFile metaFile : metaFiles)
|
||||
{
|
||||
if (metaFile == metaFileToUse)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
File oldFile = new File(metaFile.file + ".old");
|
||||
try
|
||||
{
|
||||
if (!metaFile.file.renameTo(oldFile))
|
||||
{
|
||||
throw new RuntimeException("Renaming failed");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Failed to rename file: " + metaFile.file + " to " + oldFile, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
metaFileToUse = metaFiles.iterator().next();
|
||||
}
|
||||
|
||||
// Add file to the list of files.
|
||||
addLoadedMetaFileFunc.addFile(pos, metaFileToUse);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
/** @return null if the file name can't be parsed into a {@link DhSectionPos} */
|
||||
@Nullable
|
||||
public static DhSectionPos decodePositionFromFileName(File file, String fileSuffix)
|
||||
{
|
||||
String fileName = file.getName();
|
||||
if (!fileName.endsWith(fileSuffix))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
fileName = fileName.substring(0, fileName.length() - 4);
|
||||
return DhSectionPos.deserialize(fileName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===================//
|
||||
// helper interfaces //
|
||||
//===================//
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ICreateMetadataFunc { AbstractMetaDataContainerFile createFile(File file) throws IOException; }
|
||||
|
||||
@FunctionalInterface
|
||||
public interface IAddUnloadedFileFunc { void addFile(DhSectionPos pos, File file); }
|
||||
@FunctionalInterface
|
||||
public interface IAddLoadedMetaFileFunc { void addFile(DhSectionPos pos, AbstractMetaDataContainerFile metaFile); }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ReflectionUtil
|
||||
{
|
||||
|
||||
public static String getAllFieldValuesAsString(Object obj)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
Field[] fields = obj.getClass().getDeclaredFields();
|
||||
for (Field field : fields)
|
||||
{
|
||||
String fieldName = field.getName();;
|
||||
String fieldStringValue;
|
||||
try
|
||||
{
|
||||
field.setAccessible(true);
|
||||
fieldStringValue = field.get(obj) + "";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
fieldStringValue = "ERROR:[" + e.getMessage() + "]";
|
||||
}
|
||||
|
||||
stringBuilder.append(fieldName+" - "+fieldStringValue+"\n");
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+2
-7
@@ -22,7 +22,6 @@ package com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor;
|
||||
import com.seibel.distanthorizons.api.enums.rendering.EFogDrawMode;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.core.ReflectionHandler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@@ -66,11 +65,7 @@ public abstract class AbstractOptifineAccessor implements IOptifineAccessor
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should not be called frequently since this uses reflection calls to determine if Optifine is present. <br>
|
||||
* Use {@link ReflectionHandler#optifinePresent()} instead.
|
||||
*/
|
||||
public static boolean isOptifinePresent() { return getOptifineFogField() != null; }
|
||||
public static boolean optifinePresent() { return getOptifineFogField() != null; }
|
||||
|
||||
|
||||
|
||||
@@ -115,7 +110,7 @@ public abstract class AbstractOptifineAccessor implements IOptifineAccessor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double getRenderResolutionMultiplier()
|
||||
{
|
||||
/*
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
"Distant Horizons",
|
||||
|
||||
|
||||
|
||||
|
||||
"distanthorizons.general.true":
|
||||
"True",
|
||||
"distanthorizons.general.false":
|
||||
"False",
|
||||
"distanthorizons.general.yes":
|
||||
"Yes",
|
||||
"distanthorizons.general.no":
|
||||
"No",
|
||||
"distanthorizons.general.back":
|
||||
"Back",
|
||||
"distanthorizons.general.next":
|
||||
@@ -12,10 +20,8 @@
|
||||
"Done",
|
||||
"distanthorizons.general.cancel":
|
||||
"Cancel",
|
||||
"distanthorizons.general.yes":
|
||||
"Yes",
|
||||
"distanthorizons.general.no":
|
||||
"No",
|
||||
"distanthorizons.general.reset":
|
||||
"Reset",
|
||||
|
||||
|
||||
|
||||
@@ -445,6 +451,10 @@
|
||||
"GPU upload speed (milliseconds)",
|
||||
"distanthorizons.config.client.advanced.buffers.gpuUploadPerMegabyteInMilliseconds.@tooltip":
|
||||
"How long should a buffer wait per Megabyte of data uploaded?\nMay be increased if there is frame stuttering.",
|
||||
"distanthorizons.config.client.advanced.buffers.gpuUploadAsync":
|
||||
"GPU upload Async",
|
||||
"distanthorizons.config.client.advanced.buffers.gpuUploadAsync.@tooltip":
|
||||
"If true geometry data will be uploaded on a DH controlled thread, reducing FPS stuttering. \nIf false uploading will be done on Minecraft's main rendering thread. \n\nSetting this to false may reduce crashes or corrupted geometry on systems with an AMD GPU when Sodium is installed.",
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
{
|
||||
"distanthorizons.title":
|
||||
"Distant Horizons",
|
||||
|
||||
|
||||
"distanthorizons.general.true":
|
||||
"Вкл.",
|
||||
"distanthorizons.general.false":
|
||||
"Выкл.",
|
||||
"distanthorizons.general.yes":
|
||||
"Да",
|
||||
"distanthorizons.general.no":
|
||||
"Нет",
|
||||
"distanthorizons.general.back":
|
||||
"Назад",
|
||||
"distanthorizons.general.next":
|
||||
"Следующий",
|
||||
"distanthorizons.general.done":
|
||||
"Готово",
|
||||
"distanthorizons.general.cancel":
|
||||
"Отмена",
|
||||
"distanthorizons.general.reset":
|
||||
"Сброc",
|
||||
|
||||
|
||||
|
||||
|
||||
"distanthorizons.updater.title":
|
||||
"Автоматическое обновление Distant Horizons",
|
||||
"distanthorizons.updater.text1":
|
||||
"§lДоступно новое обновление!",
|
||||
"distanthorizons.updater.text2":
|
||||
"§fВы хотите перейти с %s§f на %s§f?",
|
||||
"distanthorizons.updater.later":
|
||||
"Не сейчас",
|
||||
"distanthorizons.updater.never":
|
||||
"Не показывать снова",
|
||||
"distanthorizons.updater.update":
|
||||
"Обновить",
|
||||
"distanthorizons.updater.update.@tooltip":
|
||||
"Обновить мод 1 раз\n(обновляется при закрытии игры)",
|
||||
"distanthorizons.updater.silent":
|
||||
"Всегда тихое обновление",
|
||||
"distanthorizons.updater.silent.@tooltip":
|
||||
"Каждый раз, когда доступно обновление, оно будет обновляться\n(§6ПРЕДУПРЕЖДЕНИЕ§r: Оно не будет запрашивать у пользователя разрешение на обновление, но будет поддерживать мод в актуальном состоянии)",
|
||||
"distanthorizons.updater.waitingForClose":
|
||||
"Обновление Distant Horizons завершится после перезапуска игры",
|
||||
|
||||
|
||||
|
||||
|
||||
"distanthorizons.config.title":
|
||||
"Конфигурация Distant Horizons",
|
||||
"distanthorizons.config.client":
|
||||
"Клиент",
|
||||
|
||||
"distanthorizons.config.client.quickEnableRendering":
|
||||
"Включить рендеринг",
|
||||
"distanthorizons.config.client.quickEnableRendering.@tooltip":
|
||||
"Определяет, отображает ли Distant Horizons LODs.",
|
||||
|
||||
"distanthorizons.config.client.qualityPresetSetting":
|
||||
"Предустановленное качество",
|
||||
"distanthorizons.config.client.qualityPresetSetting.@tooltip":
|
||||
"Изменяет ряд графических настроек для быстрого изменения качества рендеринга Distant Horizons. \n\nУменьшите этот параметр, если ваш графический процессор максимально загружен или у вас возникли проблемы с частотой кадров.",
|
||||
"distanthorizons.config.client.threadPresetSetting":
|
||||
"Загруженность процессора",
|
||||
"distanthorizons.config.client.threadPresetSetting.@tooltip":
|
||||
"Изменяет количество потоков, которые будут использоваться в Distant Horizons. \n\nУвеличение этого параметра улучшит скорость удаленного генератора (Distant Generator) и скорость загрузки LOD, \nно также увеличит использование процессора/памяти и может привести к заиканию. \n\nПримечание: на процессорах с 4 ядрами или меньше эти настройки будут менее эффективными, \nи некоторые настройки дадут аналогичные результаты.",
|
||||
|
||||
"distanthorizons.config.client.optionsButton":
|
||||
"Отобразить кнопку Параметров",
|
||||
"distanthorizons.config.client.optionsButton.@tooltip":
|
||||
"Показать кнопку Параметров слева от кнопки FOV.",
|
||||
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced":
|
||||
"Расширенные параметры",
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics":
|
||||
"Графика",
|
||||
"distanthorizons.config.client.advanced.graphics.quality":
|
||||
"Качество рендеринга",
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.quality.maxHorizontalResolution": "Максимальное горизонтальное разрешение",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.maxHorizontalResolution.@tooltip": "Изображения с максимальной детализацией отображаются на.\n\n§6Самый быстрый:§r Чанк\n§6Самый красивый:§r Блок",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.lodChunkRenderDistance": "Расстояние рендеринга LOD",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.lodChunkRenderDistance.@tooltip": "Расстояние рендеринга Distant Horizons, измеряемое в чанках. \n\nПримечание: это максимально возможное число. \nДальность рендеринга может быть выше или ниже этого числа \nв зависимости от других графических настроек.",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.verticalQuality": "Вертикальное качество",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.verticalQuality.@tooltip": "На сколько хорошо LOD (уровень детализации) отображает выступы, пещеры, обрывы и т.д.\n\nБолее высокие параметры будут увеличивать использование памяти и GPU.",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.horizontalScale": "Горизонтальная шкала",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.horizontalScale.@tooltip": "На сколько быстро уровни детализации (LOD) снижают качество.\n\nБольшие значения улучшат вид дальней местности, \nно увеличат использование памяти и GPU.",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.horizontalQuality": "Горизонтальное качество",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.horizontalQuality.@tooltip": "На сколько далеко друг от друга находятся уровни качества.\n\nБолее высокие настройки увеличивают расстояние между уровнями,\nно также увеличивают использование памяти и GPU.",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.transparency": "Прозрачность",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.blocksToIgnore": "Игнорируемые блоки",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.blocksToIgnore.@tooltip": "Определяет типы блоков, которые следует игнорировать при генерации LODs.",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.tintWithAvoidedBlocks": "Окрашивать с игнорируемыми блоками",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.tintWithAvoidedBlocks.@tooltip": "§4Примечание: делает снег, ковры и люки очень плохо выглядящими.§r\nДолжны ли блоки под игнорируемыми блоками приобретать цвет игнорируемого блока?\n§6Вкл.:§r красный цвет цветка на траве окрасит траву под ним в красный цвет\n§6Выкл.:§r пропущенные блоки не изменят цвет поверхности под ними",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.lodBiomeBlending": "Смешивание биомов для LOD",
|
||||
"distanthorizons.config.client.advanced.graphics.quality.lodBiomeBlending.@tooltip": "Это то же самое, что и настройки смешивания биомов ванильного мира для области LOD. \n\nОбратите внимание, что значения, отличные от '0', существенно влияют на время создания LOD\nи увеличивают количество треугольников. Стоимость на скорость создания чанков также \nдовольно велика, если установлена слишком высокая плотность.\n\n'0' соответствует ванильному смешиванию биомов '1x1', \n'1' соответствует ванильному смешиванию биомов '3x3', \n'2' соответствует ванильному смешиванию биомов '5x5'... ",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.fog": "Туман",
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.fog.drawMode": "Режим отображения тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.drawMode.@tooltip": "Когда будет отрисовываться туман на LODs (уровнях детализации).",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.distance": "Дальность тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.distance.@tooltip": "Дальность(и), на которой туман будет отрисовываться на LODs.",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.colorMode": "Режим цвета тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.colorMode.@tooltip": "Цвет тумана на LODs.",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.disableVanillaFog": "Отключить ванильный туман",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.disableVanillaFog.@tooltip": "§6Вкл.:§r отключает туман Minecraft на ванильных чанках. \n§6Выкл.:§r Minecraft отрисовывает туман как обычно. \n\nМожет вызывать проблемы с другими модами, изменяющими туман. \nОтключите, если ванильные чанки полностью покрыты туманом.",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog": "Дополнительные настройки тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogStart": "Начало дальнего тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogStart.@tooltip": "С какого места должен начинаться дальний туман? \n\n'0.0': Туман начинается от позиции игрока. \n'1.0': Круг начала тумана точно вписывается в квадрат дальности отрисовки LOD.\n'1.414': Квадрат дальности отрисовки LOD точно вписывается в круг начала тумана.\n",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogEnd": "Конец дальнего тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogEnd.@tooltip": "С какого места должен заканчиваться дальний туман? \n\n'0.0': Туман заканчивается у позиции игрока. \n'1.0': Круг окончания тумана точно вписывается в квадрат дальности отрисовки LOD.\n'1.414': Квадрат дальности отрисовки LOD точно вписывается в круг окончания тумана.\n",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogMin": "Минимальная толщина дальнего тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogMin.@tooltip": "Какая должна быть минимальная толщина дальнего тумана? \n\n'0.0': Нет тумана. \n'1.0': Полностью цветной туман.\n",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogMax": "Максимальная толщина дальнего тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogMax.@tooltip": "Какая должна быть максимальная толщина дальнего тумана? \n\n'0.0': Нет тумана. \n'1.0': Полностью цветной туман.\n",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogFalloff": "Затухание дальнего тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogFalloff.@tooltip": "Как должна рассчитываться толщина дальнего тумана? \n\nЛИНЕЙНО: Линейно на основе расстояния (игнорирует 'плотность') \nЭКСПОНЕНЦИАЛЬНО: 1/(e^(расстояние*плотность)) \nКВАДРАТ ЭКСПОНЕНЦИАЛЬНО: 1/(e^((расстояние*плотность)^2) \n",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogDensity": "Плотность дальнего тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.farFogDensity.@tooltip": "Какая должна быть плотность дальнего тумана? ",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.ssao": "Окружающая окклюзия (Ambient Occlusion)",
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.enabled": "Включить окружающую окклюзию (Ambient Occlusion)",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.enabled.@tooltip": "Окружающая окклюзия добавляет глубину к освещению блоков.",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.sampleCount": "Количество образцов",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.sampleCount.@tooltip": "Определяет, сколько точек в пространстве отбирается для теста окклюзии. \nБольшее количество улучшит качество и уменьшит полосирование, но увеличит нагрузку на ГПУ.",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.radius": "Радиус",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.radius.@tooltip": "Определяет радиус применения эффекта окружающей окклюзии в блоках.",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.strength": "Сила",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.strength.@tooltip": "Определяет, насколько темным будет эффект окружающей окклюзии в пространстве экрана.",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.bias": "Смещение",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.bias.@tooltip": "Увеличение этого значения может уменьшить полосирование, но за счет уменьшения силы эффекта.",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.minLight": "Минимальная яркость",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.minLight.@tooltip": "Определяет, насколько темными могут быть тени окклюзии. \n0 = полностью черные в углах \n1 = без тени",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.blurRadius": "Радиус размытия",
|
||||
"distanthorizons.config.client.advanced.graphics.ssao.blurRadius.@tooltip": "Радиус, измеряемый в пикселях, для которого вычисляется размытие окружающей окклюзии. \nБольшие значения уменьшат полосирование, но увеличат нагрузку на ГПУ.",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog": "Параметры высотного тумана",
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogMixMode": "Режим смешивания высотного тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogMixMode.@tooltip": "Как высота должна влиять на толщину тумана, совмещаясь с обычной функцией? \n\nОСНОВНОЙ: Нет специального эффекта высотного тумана. Туман рассчитывается на основе расстояния от камеры. \nИГНОРИРОВАТЬ_ВЫСОТУ: Полное игнорирование высоты. Туман рассчитывается на основе горизонтального расстояния. \nСЛОЖЕНИЕ: высотный туман + обычный туман \nМАКСИМУМ: максимум из высотного тумана и обычного тумана \nУМНОЖЕНИЕ: высотный_туман * обычный_туман \nОБРАТНОЕ_УМНОЖЕНИЕ: 1 - (1-высотный_туман) * (1-обычный_туман) \nОГРАНИЧЕННОЕ_СЛОЖЕНИЕ: обычный_туман + максимум_из_обычного_и_высотного_тумана \nУМНОЖЕНИЕ_И_СЛОЖЕНИЕ: обычный_туман + обычный_туман * высотный_туман \nОБРАТНОЕ_УМНОЖЕНИЕ_И_СЛОЖЕНИЕ: обычный_туман + 1 - (1-высотный_туман) * (1-обычный_туман) \nСРЕДНЕЕ: обычный_туман * 0.5 + высотный_туман * 0.5 \n\nОбратите внимание, что для режимов 'ОСНОВНОЙ' и 'ИГНОРИРОВАТЬ_ВЫСОТУ' настройки тумана для высотного тумана не имеют эффекта.",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogMode": "Режим высотного тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogMode.@tooltip": "Где должен находиться высотный туман? \n\nНАД_КАМЕРОЙ: Высотный туман начинается от камеры до неба \nПОД_КАМЕРОЙ: Высотный туман начинается от камеры до пустоты \nНАД_И_ПОД_КАМЕРОЙ: Высотный туман начинается от камеры как до неба, так и до пустоты \nНАД_УСТАНОВЛЕННОЙ_ВЫСОТОЙ: Высотный туман начинается от установленной высоты до неба \nПОД_УСТАНОВЛЕННОЙ_ВЫСОТОЙ: Высотный туман начинается от установленной высоты до пустоты \nНАД_И_ПОД_УСТАНОВЛЕННОЙ_ВЫСОТОЙ: Высотный туман начинается от установленной высоты как до неба, так и до пустоты",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogBaseHeight": "Базовая высота высотного тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogBaseHeight.@tooltip": "Если высотный туман рассчитывается относительно установленной высоты, то какая это высота?",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogStart": "Начало высотного тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogStart.@tooltip": "Как далеко должно начинаться начало высотного тумана? \n\n'0.0': Начало тумана без смещения.\n'1.0': Начало тумана с смещением на всю высоту мира. (Включая глубину)",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogEnd": "Конец высотного тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogEnd.@tooltip": "Как далеко должен распространяться конец высотного тумана? \n\n'0.0': Конец тумана без смещения.\n'1.0': Конец тумана с смещением на всю высоту мира. (Включая глубину)",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogMin": "Минимальная толщина высотного тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogMin.@tooltip": "Какая минимальная толщина тумана должна быть? \n\n'0.0': Вообще нет тумана.\n'1.0': Полностью цвет тумана.",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogMax": "Максимальная толщина высотного тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogMax.@tooltip": "Какая максимальная толщина тумана должна быть? \n\n'0.0': Вообще нет тумана.\n'1.0': Полностью цвет тумана.",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogFalloff": "Затухание высотного тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogFalloff.@tooltip": "Как должна рассчитываться толщина высотного тумана? \n\nЛИНЕЙНОЕ: Линейно в зависимости от высоты (игнорирует 'плотность')\nЭКСПОНЕНЦИАЛЬНОЕ: 1/(e^(высота*плотность)) \nКВАДРАТ ЭКСПОНЕНЦИАЛЬНОЕ: 1/(e^((высота*плотность)^2) \n",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogDensity": "Плотность высотного тумана",
|
||||
"distanthorizons.config.client.advanced.graphics.fog.advancedFog.heightFog.heightFogDensity.@tooltip": "Какая плотность у высотного тумана?",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.noiseTextureSettings": "Текстура шума",
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.noiseTextureSettings.noiseEnabled": "Включить текстуру шума",
|
||||
"distanthorizons.config.client.advanced.graphics.noiseTextureSettings.noiseEnabled.@tooltip": "Если включено, текстура шума будет применяться к LOD для имитации текстур.",
|
||||
"distanthorizons.config.client.advanced.graphics.noiseTextureSettings.noiseSteps": "Шаги шума",
|
||||
"distanthorizons.config.client.advanced.graphics.noiseTextureSettings.noiseSteps.@tooltip": "Разрешение шума, применяемого к каждому блоку на LOD",
|
||||
"distanthorizons.config.client.advanced.graphics.noiseTextureSettings.noiseIntensity": "Интенсивность шума",
|
||||
"distanthorizons.config.client.advanced.graphics.noiseTextureSettings.noiseIntensity.@tooltip": "Определяет насколько интенсивной будет текстура шума.",
|
||||
"distanthorizons.config.client.advanced.graphics.noiseTextureSettings.noiseDropoff": "Затухание шума",
|
||||
"distanthorizons.config.client.advanced.graphics.noiseTextureSettings.noiseDropoff.@tooltip": "Определяет, на каком расстоянии текстура шума будет затухать (в блоках). \nУстановите значение 0, чтобы отключить затухание текстуры шума.",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics": "Расширенные параметры графики",
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.overdrawPrevention": "Предотвращение избыточной отрисовки",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.overdrawPrevention.@tooltip": "Включение этой опции предотвратит некоторые проблемы с избыточной отрисовкой, \nно может вызвать некорректную отрисовку близких LOD, \nособенно когда они находятся рядом с листвой или неполными блоками. \nМенее заметно при увеличении стандартной дистанции отрисовки Minecraft.",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.seamlessOverdraw": "(Экспериментально) Безшовная избыточная отрисовка",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.seamlessOverdraw.@tooltip": "Экспериментальная функция, которая попытается согласовать ближнюю и дальнюю плоскости отсечения \nDistant Horizons и Minecraft для уменьшения избыточной отрисовки. \n\nПримечание: работает только с Fabric.",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.brightnessMultiplier": "Множитель яркости",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.brightnessMultiplier.@tooltip": "Определяет, насколько яркими будут цвета LOD. \n\n0 = черный \n1 = нормальный \n2 = почти белый",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.saturationMultiplier": "Множитель насыщенности",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.saturationMultiplier.@tooltip": "Определяет, насколько насыщенными будут цвета LOD. \n\n0 = черно-белые \n1 = нормальные \n2 = насыщенные",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.enableCaveCulling": "Отсечение пещер",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.enableCaveCulling.@tooltip": "Если включено, пещеры будут отсекаться. \n\nДополнительная информация: В настоящее время это отсекает все грани с уровнем света 0 под уровнем отсечения пещер в измерениях, не имеющих потолка.",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.caveCullingHeight": "Уровень отсечения пещер",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.caveCullingHeight.@tooltip": "На какой высоте начинается отсечение пещер?",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.earthCurveRatio": "Коэффициент кривизны Земли §6(ЭКСПЕРИМЕНТАЛЬНО)§r",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.earthCurveRatio.@tooltip": "Значение 1 эквивалентно кривизне Земли в реальной жизни. \nМинимально допустимое значение - 50, максимальное - 5000. \nЗначения от 1 до 49 будут округлены до 50.",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias": "Смещение LOD §6(Влияет на ванильный мир)§r",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias.@tooltip": "Устанавливает значение смещения LOD ванильного мира. \nПожалуйста, нажмите F3+T, чтобы перезагрузить текстурные пакеты и применить это изменение.",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.lodShading": "Затенение LOD",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.lodShading.@tooltip": "Определяет, как следует затенять LOD. \nМожет использоваться для улучшения совместимости с шейдерами.",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.worldGenerator": "Генератор мира",
|
||||
|
||||
"distanthorizons.config.client.advanced.worldGenerator.enableDistantGeneration": "Включить удаленную генерацию",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.enableDistantGeneration.@tooltip": "§6Вкл.:§r в одиночной игре LOD будут генерироваться за пределами стандартной дистанции отрисовки Minecraft.\nПримечание: это может потреблять большое количество ресурсов CPU.\n\n§6Выкл.:§r LOD будут генерироваться только в пределах стандартной дистанции отрисовки Minecraft.",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.distantGeneratorMode": "Режим удаленной генерации",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.distantGeneratorMode.@tooltip": "Настройка сложности генерации при создании LOD за пределами стандартной дистанции отрисовки Minecraft. \n\n§6§6Самый быстрый:§r только биомы \n§6Наилучшее качество:§r объекты (рекомендуется)",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.worldGenLightingEngine": "Световой движок",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.worldGenLightingEngine.@tooltip": "§6Minecraft:§r использует световой движок Minecraft, обеспечивает точное освещение.\n§6Distant Horizons:§r оценивает освещение, тени не будут такими плавными, но более стабильны.\n\nЕсли LOD отображаются черными, установите это значение на §6Distant Horizons§r.",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.worldGenerationTimeoutLengthInSeconds": "Длительность ожидания генерации мира (в секундах)",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.worldGenerationTimeoutLengthInSeconds.@tooltip": "Как долго поток генератора мира должен работать перед завершением по тайм-ауту? \nПримечание: если у вас возникают ошибки по тайм-ауту, лучше сначала уменьшить использование CPU через настройки потоков, а затем уже менять это значение.",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.generationPriority": "Приоритет генерации",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.generationPriority.@tooltip": "Приоритет генерации чанков вокруг игрока.",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.multiplayer": "Многопользовательская игра",
|
||||
|
||||
"distanthorizons.config.client.advanced.multiplayer.serverFolderNameMode": "Режим имени папки сервера",
|
||||
"distanthorizons.config.client.advanced.multiplayer.serverFolderNameMode.@tooltip": "Определяет формат имени папки для локальных данных многопользовательских игр.\n\n§6Только имя:§r\nИспользует имя сервера в браузере серверов. Например, \"Сервер Minecraft\"\n§6Имя и IP:§r\n\"Сервер Minecraft, IP 192.168.1.40\"\n§6Имя, IP и порт:§r\n\"Сервер Minecraft, IP 192.168.1.40:25565\"\n§6Имя, IP, порт и версия MC:§r\n\"Сервер Minecraft, IP 192.168.1.40:25565, Версия игры 1.18.1\"\n\n§c§lПредупреждение:§r изменение во время подключения к многопользовательскому серверу может вызвать глюки.",
|
||||
"distanthorizons.config.client.advanced.multiplayer.multiverseSimilarityRequiredPercent": "Требуемое сходство для мультивселенной, %",
|
||||
"distanthorizons.config.client.advanced.multiplayer.multiverseSimilarityRequiredPercent.@tooltip": "При сопоставлении миров одного типа измерения проверяемые чанки должны быть по крайней мере на этот процент одинаковыми, чтобы считаться одним и тем же миром.\n\nПримечание: если вы используете порталы для входа в измерение в двух разных местах, эта система может считать, что это два разных мира.\n\n§61.0:§r чанки должны быть идентичными.\n§60.5:§r чанки должны быть наполовину одинаковыми.\n§60.0:§r отключает поддержку мультивселенных\n будет использоваться только один мир на измерение.",
|
||||
"distanthorizons.config.client.advanced.multiplayer.enableMultiverseNetworking": "Поддержка сети для мультивселенных",
|
||||
"distanthorizons.config.client.advanced.multiplayer.enableMultiverseNetworking.@tooltip": "Если установлено в true, Distant Horizons будет пытаться взаимодействовать с подключенным сервером для улучшения поддержки мультивселенных.",
|
||||
"distanthorizons.config.client.advanced.multiplayer.enableServerNetworking": "§4Нереализовано, только для разработчиков§r - Поддержка сервера",
|
||||
"distanthorizons.config.client.advanced.multiplayer.enableServerNetworking.@tooltip": "§6Внимание:§r это только для разработчиков и пока не реализовано.\n\nЕсли установлено в \"Вкл\", Distant Horizons будет пытаться взаимодействовать с подключенным сервером для загрузки LOD за пределами вашей обычной дистанции отрисовки Minecraft.\n\nПримечание: для работы этой функции требуется установить DH на сервере.",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.multiThreading": "Многопоточность",
|
||||
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfWorldGenerationThreads": "Количество потоков генерации мира",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfWorldGenerationThreads.@tooltip": "Сколько потоков следует использовать при генерации LODs вне обычной зоны отрисовки? \n\nЕсли это число меньше 1, оно будет рассматриваться как процент времени, в течение которого один поток может работать, прежде чем перейти в режим ожидания. \n\nЕсли у вас есть проблемы с прерываниями при генерации дальних LODs, уменьшите это число. Если вы хотите увеличить скорость генерации LOD, увеличьте это число. \n\nКоличество потоков для генерации LOD и количество потоков для построения буферов независимы друг от друга. Если их сумма больше количества ядер вашего процессора, это нормально.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForWorldGenerationThreads": "Процент времени выполнения для потоков генерации мира",
|
||||
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfBufferBuilderThreads": "Количество потоков построения буферов",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfBufferBuilderThreads.@tooltip": "Количество потоков, используемых при построении данных геометрии. \nМожет быть только от 1 до количества процессорных ядер вашего CPU.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForBufferBuilderThreads": "Процент времени выполнения для потоков построения буферов",
|
||||
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfFileHandlerThreads": "Количество потоков обработки файлов",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfFileHandlerThreads.@tooltip": "Количество потоков, используемых при построении буферов вершин (вещей, отправляемых на ваш GPU для отрисовки LODs). \nМожет быть только от 1 до количества процессорных ядер вашего CPU.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForFileHandlerThreads": "Процент времени выполнения для потоков обработки файлов",
|
||||
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfDataTransformerThreads": "Количество потоков преобразования данных",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfDataTransformerThreads.@tooltip": "Количество потоков, используемых при преобразовании данных ID в данные, подходящие для отрисовки. \n(Это, как правило, происходит при генерации нового ландшафта или изменении настроек графики). \nМожет быть только от 1 до количества процессорных ядер вашего CPU.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForDataTransformerThreads": "Процент времени выполнения для потоков преобразования данных",
|
||||
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfChunkLodConverterThreads": "Количество потоков преобразования LOD чанков",
|
||||
"distanthorizons.config.client.advanced.multiThreading.numberOfChunkLodConverterThreads.@tooltip": "Сколько потоков следует использовать для преобразования чанков Minecraft в данные LOD? \nЭти потоки работают как при генерации мира, так и при загрузке, выгрузке и модификации чанков.",
|
||||
"distanthorizons.config.client.advanced.multiThreading.runTimeRatioForChunkLodConverterThreads": "Процент времени выполнения для потоков преобразования LOD чанков",
|
||||
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.debugging": "Отладка",
|
||||
"distanthorizons.config.client.advanced.debugging.rendererMode": "Режим рендера",
|
||||
"distanthorizons.config.client.advanced.debugging.debugRendering": "Отладка рендера",
|
||||
"distanthorizons.config.client.advanced.debugging.debugMode.@tooltip": "Активный режим отладки.",
|
||||
"distanthorizons.config.client.advanced.debugging.renderWireframe": "Отображение проволочной модели",
|
||||
"distanthorizons.config.client.advanced.debugging.renderWireframe.@tooltip": "Если включено, уровни детализации будут отображаться как проволочные модели.",
|
||||
"distanthorizons.config.client.advanced.debugging.enableDebugKeybindings": "Включить клавиши отладки",
|
||||
"distanthorizons.config.client.advanced.debugging.enableDebugKeybindings.@tooltip": "§6Вкл.:§r клавиши отладки могут быть использованы для изменения режима отладки в игре.",
|
||||
"distanthorizons.config.client.advanced.debugging.lodOnlyMode": "Рендерить только уровни детализации (LOD)",
|
||||
"distanthorizons.config.client.advanced.debugging.lodOnlyMode.@tooltip": "Если включено, это отключит (большую часть) стандартного рендера Minecraft.\n\nПРИМЕЧАНИЕ: Не сообщайте о проблемах, когда этот режим включен!\nЭта настройка предназначена только для развлечения и отладки.\nСовместимость с модами не гарантируется.",
|
||||
"distanthorizons.config.client.advanced.debugging.enableWhiteWorld": "Включить белый мир",
|
||||
"distanthorizons.config.client.advanced.debugging.enableWhiteWorld.@tooltip": "Если включено, цвет вершин не будет передаваться.\nПолезно для отладки шейдеров.",
|
||||
"distanthorizons.config.client.advanced.debugging.allowUnsafeValues": "Разрешить небезопасные значения в пользовательском интерфейсе",
|
||||
"distanthorizons.config.client.advanced.debugging.allowUnsafeValues.@tooltip": "Если включено, будет выполняться очень ограниченная проверка входных данных конфигурации.\n\nПредупреждение: включение этой опции может вызвать нестабильность или сбои, используйте на свой страх и риск.\nПримечание: эта опция не сохраняется между сеансами.",
|
||||
"distanthorizons.config.client.advanced.debugging.overrideVanillaGLLogger": "Переопределить встроенный регистратор ошибок OpenGL",
|
||||
"distanthorizons.config.client.advanced.debugging.overrideVanillaGLLogger.@tooltip": "Требуется перезагрузка Minecraft для применения изменений.",
|
||||
"distanthorizons.config.client.advanced.debugging.glErrorHandlingMode": "Режим обработки ошибок OpenGL",
|
||||
"distanthorizons.config.client.advanced.debugging.glErrorHandlingMode.@tooltip": "Определяет, как обрабатываются ошибки OpenGL.\nМожет неправильно обнаруживать ошибки OpenGL, вызванные другими модами.",
|
||||
"distanthorizons.config.client.advanced.debugging.glProfileMode": "Режим профиля OpenGL",
|
||||
"distanthorizons.config.client.advanced.debugging.glProfileMode.@tooltip": "Требуется перезагрузка Minecraft для применения изменений.",
|
||||
"distanthorizons.config.client.advanced.debugging.glForwardCompatibilityMode": "Режим совместимости впереди OpenGL",
|
||||
"distanthorizons.config.client.advanced.debugging.glForwardCompatibilityMode.@tooltip": "Требуется перезагрузка Minecraft для применения изменений.",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.buffers": "Буферы",
|
||||
"distanthorizons.config.client.advanced.buffers.gpuUploadMethod": "Метод загрузки на GPU",
|
||||
"distanthorizons.config.client.advanced.buffers.gpuUploadMethod.@tooltip": "Метод загрузки геометрии на GPU.\n\nЕсли у вас возникают тормоза при низкой загрузке CPU и GPU, попробуйте изменить эту настройку.\nПримечание: если вы находитесь в мире, вам может потребоваться выйти и зайти заново, чтобы увидеть полный эффект.",
|
||||
"distanthorizons.config.client.advanced.buffers.gpuUploadPerMegabyteInMilliseconds": "Скорость загрузки на GPU (миллисекунды)",
|
||||
"distanthorizons.config.client.advanced.buffers.gpuUploadPerMegabyteInMilliseconds.@tooltip": "Сколько времени должен ждать буфер на каждый мегабайт загруженных данных?\nМожет быть увеличено, если наблюдается прерывание кадров.",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.autoUpdater": "Автообновление",
|
||||
"distanthorizons.config.client.advanced.autoUpdater.enableAutoUpdater": "Включить автоматические обновления",
|
||||
"distanthorizons.config.client.advanced.autoUpdater.enableSilentUpdates": "Включить бесшумные обновления",
|
||||
"distanthorizons.config.client.advanced.autoUpdater.enableSilentUpdates.@tooltip": "Автоматически обновляет мод, когда доступно обновление",
|
||||
|
||||
"distanthorizons.config.client.advanced.logging": "Логирование",
|
||||
"distanthorizons.config.client.advanced.logging.@tooltip": "Управляет тем, как должно обрабатываться логирование.",
|
||||
|
||||
"distanthorizons.config.client.advanced.logging.logWorldGenEvent": "События генерации мира",
|
||||
"distanthorizons.config.client.advanced.logging.logWorldGenPerformance": "Производительность генерации мира",
|
||||
"distanthorizons.config.client.advanced.logging.logWorldGenLoadEvent": "События загрузки генерации мира",
|
||||
"distanthorizons.config.client.advanced.logging.logLodBuilderEvent": "События построения LOD",
|
||||
"distanthorizons.config.client.advanced.logging.logRendererBufferEvent": "События буфера отрисовки",
|
||||
"distanthorizons.config.client.advanced.logging.logRendererGLEvent": "События OpenGL",
|
||||
"distanthorizons.config.client.advanced.logging.logFileReadWriteEvent": "События чтения/записи файла",
|
||||
"distanthorizons.config.client.advanced.logging.logFileSubDimEvent": "События подразделения файла",
|
||||
"distanthorizons.config.client.advanced.logging.logNetworkEvent": "События сети",
|
||||
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen": "Отладка настройки экрана",
|
||||
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.debugConfigScreenNote": "Этот экран предназначен для отладки функций экрана настройки",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.boolTest": "Тест логического типа (bool)",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.byteTest": "Тест байтов (byte)",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.intTest": "Тест целых чисел (int)",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.doubleTest": "Тест чисел с плавающей точкой (double)",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.shortTest": "Тест коротких чисел (short)",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.longTest": "Тест длинных чисел (long)",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.floatTest": "Тест чисел с плавающей точкой (float)",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.stringTest": "Тест строк (string)",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.uiButtonTest": "Тест кнопки интерфейса",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.listTest": "Тест списка (ArrayList)",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.mapTest": "Тест карты (HashMap)",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.categoryTest": "Тест категории",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.linkableTest": "Тест ссылок",
|
||||
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.linkableTest.@tooltip": "Значение этой строки должно совпадать с значением в категории теста",
|
||||
|
||||
"distanthorizons.config.client.advanced.debugging.debugWireframe": "Отладочная сетка",
|
||||
|
||||
"distanthorizons.config.client.advanced.debugging.debugWireframe.enableRendering": "Включить отладку отрисовки сетки",
|
||||
"distanthorizons.config.client.advanced.debugging.debugWireframe.enableRendering.@tooltip": "При включении будут отрисованы различные сетки для отладки внутренних функций.",
|
||||
"distanthorizons.config.client.advanced.debugging.debugWireframe.showWorldGenQueue": "Показать очередь генерации мира",
|
||||
"distanthorizons.config.client.advanced.debugging.debugWireframe.showRenderSectionStatus": "Показать статус раздела отрисовки",
|
||||
"distanthorizons.config.client.advanced.debugging.debugWireframe.showFullDataFileStatus": "Показать статус полного файла данных",
|
||||
"distanthorizons.config.client.advanced.debugging.debugWireframe.showFullDataFileSampling": "Показать выборку полного файла данных",
|
||||
"distanthorizons.config.client.advanced.debugging.debugWireframe.showRenderDataFileStatus": "Показать статус файла данных отрисовки",
|
||||
|
||||
"distanthorizons.config.client.resetSettingsConfirmation": "Сбросить все настройки?",
|
||||
|
||||
"distanthorizons.config.client.resetSettingsConfirmation.resetConfirmationNote": "Вы уверены? Это действие нельзя отменить!",
|
||||
"distanthorizons.config.client.resetSettingsConfirmation.resetAllSettings": "Сбросить все настройки",
|
||||
|
||||
|
||||
|
||||
"distanthorizons.config.enum.EQualityPreset.CUSTOM": "Пользовательское",
|
||||
"distanthorizons.config.enum.EQualityPreset.MINIMUM": "Минимальный",
|
||||
"distanthorizons.config.enum.EQualityPreset.LOW": "Низкий",
|
||||
"distanthorizons.config.enum.EQualityPreset.MEDIUM": "Средний",
|
||||
"distanthorizons.config.enum.EQualityPreset.HIGH": "Высокий",
|
||||
"distanthorizons.config.enum.EQualityPreset.EXTREME": "Экстремальный",
|
||||
|
||||
"distanthorizons.config.enum.EThreadPreset.CUSTOM": "Пользовательское",
|
||||
"distanthorizons.config.enum.EThreadPreset.MINIMAL_IMPACT": "Минимальное влияние",
|
||||
"distanthorizons.config.enum.EThreadPreset.LOW_IMPACT": "Низкое влияние",
|
||||
"distanthorizons.config.enum.EThreadPreset.BALANCED": "Среднее влияние",
|
||||
"distanthorizons.config.enum.EThreadPreset.AGGRESSIVE": "Высокое влияние",
|
||||
"distanthorizons.config.enum.EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU": "Я Заплатил За Весь ПРОЦЕССОР",
|
||||
|
||||
"distanthorizons.config.enum.EMaxHorizontalResolution.BLOCK": "Блок",
|
||||
"distanthorizons.config.enum.EMaxHorizontalResolution.TWO_BLOCKS": "2 блока",
|
||||
"distanthorizons.config.enum.EMaxHorizontalResolution.FOUR_BLOCKS": "4 блока",
|
||||
"distanthorizons.config.enum.EMaxHorizontalResolution.HALF_CHUNK": "Половина чанка",
|
||||
"distanthorizons.config.enum.EMaxHorizontalResolution.CHUNK": "Чанк",
|
||||
|
||||
"distanthorizons.config.enum.EVerticalQuality.HEIGHT_MAP": "Карта высот",
|
||||
"distanthorizons.config.enum.EVerticalQuality.LOW": "Низкое",
|
||||
"distanthorizons.config.enum.EVerticalQuality.MEDIUM": "Среднее",
|
||||
"distanthorizons.config.enum.EVerticalQuality.HIGH": "Высокое",
|
||||
"distanthorizons.config.enum.EVerticalQuality.EXTREME": "Экстремальное",
|
||||
"distanthorizons.config.enum.EHorizontalQuality.LOWEST": "Самое низкое",
|
||||
"distanthorizons.config.enum.EHorizontalQuality.LOW": "Низкое",
|
||||
"distanthorizons.config.enum.EHorizontalQuality.MEDIUM": "Среднее",
|
||||
"distanthorizons.config.enum.EHorizontalQuality.HIGH": "Высокое",
|
||||
"distanthorizons.config.enum.EHorizontalQuality.EXTREME": "Экстремальное",
|
||||
"distanthorizons.config.enum.EHorizontalQuality.UNLIMITED": "Неограниченное",
|
||||
|
||||
"distanthorizons.config.enum.ETransparency.DISABLED": "Отключено",
|
||||
"distanthorizons.config.enum.ETransparency.FAKE": "Фейковое",
|
||||
"distanthorizons.config.enum.ETransparency.COMPLETE": "Полное",
|
||||
|
||||
"distanthorizons.config.enum.EFogDistance.NEAR": "Близко",
|
||||
"distanthorizons.config.enum.EFogDistance.FAR": "Далеко",
|
||||
"distanthorizons.config.enum.EFogDistance.NEAR_AND_FAR": "Близко и далеко",
|
||||
|
||||
"distanthorizons.config.enum.EFogDrawMode.USE_OPTIFINE_SETTING": "Использовать настройки мода",
|
||||
"distanthorizons.config.enum.EFogDrawMode.FOG_ENABLED": "Включено",
|
||||
"distanthorizons.config.enum.EFogDrawMode.FOG_DISABLED": "Выключено",
|
||||
"distanthorizons.config.enum.EFogColorMode.USE_WORLD_FOG_COLOR": "Использовать цвет мира",
|
||||
"distanthorizons.config.enum.EFogColorMode.USE_SKY_COLOR": "Использовать цвет неба",
|
||||
|
||||
"distanthorizons.config.enum.EFogFalloff.LINEAR": "Линейное",
|
||||
"distanthorizons.config.enum.EFogFalloff.EXPONENTIAL": "Экспоненциальное",
|
||||
"distanthorizons.config.enum.EFogFalloff.EXPONENTIAL_SQUARED": "Экспоненциальное в квадрате",
|
||||
|
||||
"distanthorizons.config.enum.EHeightFogMixMode.BASIC": "Базовое",
|
||||
"distanthorizons.config.enum.EHeightFogMixMode.IGNORE_HEIGHT": "Игнорировать высоту",
|
||||
"distanthorizons.config.enum.EHeightFogMixMode.ADDITION": "Сложение",
|
||||
"distanthorizons.config.enum.EHeightFogMixMode.MAX": "Максимум",
|
||||
"distanthorizons.config.enum.EHeightFogMixMode.MULTIPLY": "Умножение",
|
||||
"distanthorizons.config.enum.EHeightFogMixMode.INVERSE_MULTIPLY": "Обратное умножение",
|
||||
"distanthorizons.config.enum.EHeightFogMixMode.LIMITED_ADDITION": "Ограниченное сложение",
|
||||
"distanthorizons.config.enum.EHeightFogMixMode.MULTIPLY_ADDITION": "Умножение и сложение",
|
||||
"distanthorizons.config.enum.EHeightFogMixMode.INVERSE_MULTIPLY_ADDITION": "Обратное умножение и сложение",
|
||||
"distanthorizons.config.enum.EHeightFogMixMode.AVERAGE": "Среднее",
|
||||
|
||||
"distanthorizons.config.enum.EHeightFogMode.ABOVE_CAMERA": "Выше камеры",
|
||||
"distanthorizons.config.enum.EHeightFogMode.BELOW_CAMERA": "Ниже камеры",
|
||||
"distanthorizons.config.enum.EHeightFogMode.ABOVE_AND_BELOW_CAMERA": "Выше и ниже камеры",
|
||||
"distanthorizons.config.enum.EHeightFogMode.ABOVE_SET_HEIGHT": "Выше заданной высоты",
|
||||
"distanthorizons.config.enum.EHeightFogMode.BELOW_SET_HEIGHT": "Ниже заданной высоты",
|
||||
"distanthorizons.config.enum.EHeightFogMode.ABOVE_AND_BELOW_SET_HEIGHT": "Выше и ниже заданной высоты",
|
||||
|
||||
"distanthorizons.config.enum.EVanillaOverdraw.NEVER": "Никогда",
|
||||
"distanthorizons.config.enum.EVanillaOverdraw.DYNAMIC": "Динамический",
|
||||
"distanthorizons.config.enum.EVanillaOverdraw.ALWAYS": "Всегда",
|
||||
|
||||
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.NONE": "Только существующие",
|
||||
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.PRE_EXISTING_ONLY": "Только существующие части",
|
||||
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.BIOME_ONLY": "Только биомы",
|
||||
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.BIOME_ONLY_SIMULATE_HEIGHT": "Только биомы, моделировать высоту",
|
||||
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.SURFACE": "Поверхность",
|
||||
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.FEATURES": "Особенности",
|
||||
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.FULL": "Полный",
|
||||
|
||||
"distanthorizons.config.enum.ELightGenerationMode.DISTANT_HORIZONS": "Далекие горизонты",
|
||||
"distanthorizons.config.enum.ELightGenerationMode.MINECRAFT": "Майнкрафт",
|
||||
|
||||
"distanthorizons.config.enum.EGenerationPriority.AUTO": "Авто",
|
||||
"distanthorizons.config.enum.EGenerationPriority.NEAR_FIRST": "Ближние впереди",
|
||||
"distanthorizons.config.enum.EGenerationPriority.BALANCED": "Сбалансировано",
|
||||
"distanthorizons.config.enum.EGenerationPriority.FAR_FIRST": "Дальние впереди",
|
||||
|
||||
"distanthorizons.config.enum.EBlocksToAvoid.NONE": "Нет",
|
||||
"distanthorizons.config.enum.EBlocksToAvoid.NON_COLLIDING": "Без коллизий",
|
||||
|
||||
"distanthorizons.config.enum.EOverdrawPrevention.NONE": "Нет",
|
||||
"distanthorizons.config.enum.EOverdrawPrevention.LIGHT": "Легкая",
|
||||
"distanthorizons.config.enum.EOverdrawPrevention.MEDIUM": "Средняя",
|
||||
"distanthorizons.config.enum.EOverdrawPrevention.HEAVY": "Высокая",
|
||||
|
||||
"distanthorizons.config.enum.EServerFolderNameMode.NAME_ONLY": "Только имя",
|
||||
"distanthorizons.config.enum.EServerFolderNameMode.NAME_IP": "Имя и IP",
|
||||
"distanthorizons.config.enum.EServerFolderNameMode.NAME_IP_PORT": "Имя, IP и порт",
|
||||
"distanthorizons.config.enum.EServerFolderNameMode.NAME_IP_PORT_MC_VERSION": "Имя, IP, порт и версия MC",
|
||||
|
||||
"distanthorizons.config.enum.ERendererMode.DEFAULT": "По умолчанию",
|
||||
"distanthorizons.config.enum.ERendererMode.DEBUG": "Отладка",
|
||||
"distanthorizons.config.enum.ERendererMode.DISABLED": "Отключено",
|
||||
|
||||
"distanthorizons.config.enum.EDebugRendering.OFF": "Выкл.",
|
||||
"distanthorizons.config.enum.EDebugRendering.SHOW_DETAIL": "Показать детали",
|
||||
"distanthorizons.config.enum.EDebugRendering.SHOW_GENMODE": "Показать режим генерации",
|
||||
"distanthorizons.config.enum.EDebugRendering.SHOW_OVERLAPPING_QUADS": "Показать перекрывающиеся квадраты",
|
||||
"distanthorizons.config.enum.EDebugRendering.SHOW_RENDER_SOURCE_FLAG": "Показать флаг источника рендера",
|
||||
|
||||
"distanthorizons.config.enum.EGLErrorHandlingMode.IGNORE": "Игнорировать",
|
||||
"distanthorizons.config.enum.EGLErrorHandlingMode.LOG": "Логировать",
|
||||
"distanthorizons.config.enum.EGLErrorHandlingMode.LOG_THROW": "Логировать и выбросить (THROW)",
|
||||
|
||||
"distanthorizons.config.enum.EGlProfileMode.CORE": "Основной",
|
||||
"distanthorizons.config.enum.EGlProfileMode.COMPAT": "Совместимый",
|
||||
"distanthorizons.config.enum.EGlProfileMode.ANY": "Любой",
|
||||
|
||||
"distanthorizons.config.enum.ELoggerMode.DISABLED": "Отключено",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_ALL_TO_FILE": "Файл: Все, Чат: Выкл.",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_ERROR_TO_CHAT": "Файл: Все, Чат: Ошибки",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_WARNING_TO_CHAT": "Файл: Все, Чат: Предупреждения",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_INFO_TO_CHAT": "Файл: Все, Чат: Информация",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_DEBUG_TO_CHAT": "Файл: Все, Чат: Отладка",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_ALL_TO_CHAT": "Файл: Все, Чат: Все",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_ERROR_TO_CHAT_AND_FILE": "Файл: Ошибки, Чат: Ошибки",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_WARNING_TO_CHAT_AND_FILE": "Файл: Предупреждения, Чат: Предупреждения",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_INFO_TO_CHAT_AND_FILE": "Файл: Информация, Чат: Информация",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_DEBUG_TO_CHAT_AND_FILE": "Файл: Отладка, Чат: Отладка",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE": "Файл: Информация, Чат: Предупреждения",
|
||||
"distanthorizons.config.enum.ELoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE": "Файл: Информация, Чат: Ошибки",
|
||||
|
||||
"distanthorizons.config.enum.EGpuUploadMethod.AUTO":
|
||||
"Авто",
|
||||
"distanthorizons.config.enum.EGpuUploadMethod.BUFFER_STORAGE":
|
||||
"Хранение буфера",
|
||||
"distanthorizons.config.enum.EGpuUploadMethod.SUB_DATA":
|
||||
"Данные SUB",
|
||||
"distanthorizons.config.enum.EGpuUploadMethod.BUFFER_MAPPING":
|
||||
"Отображение буфера",
|
||||
"distanthorizons.config.enum.EGpuUploadMethod.DATA":
|
||||
"Данные",
|
||||
|
||||
"distanthorizons.config.enum.EBufferRebuildTimes.CONSTANT":
|
||||
"Постоянно",
|
||||
"distanthorizons.config.enum.EBufferRebuildTimes.FREQUENT":
|
||||
"Часто",
|
||||
"distanthorizons.config.enum.EBufferRebuildTimes.NORMAL":
|
||||
"Обычно",
|
||||
"distanthorizons.config.enum.EBufferRebuildTimes.RARE":
|
||||
"Редко",
|
||||
|
||||
"distanthorizons.config.enum.ELodShading.MINECRAFT":
|
||||
"Майнкрафт-овское",
|
||||
"distanthorizons.config.enum.ELodShading.OLD_LIGHTING":
|
||||
"Старое освещение",
|
||||
"distanthorizons.config.enum.ELodShading.NONE":
|
||||
"Ничего"
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user