Prevent creating LODs for already processed chunks

This commit is contained in:
James Seibel
2024-06-15 09:42:43 -05:00
parent 385e3dc964
commit 81d1ed419e
14 changed files with 311 additions and 31 deletions
@@ -41,10 +41,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
@@ -302,6 +299,17 @@ public class SharedApi
try
{
// check if this chunk has been converted into an LOD already
int oldChunkHash = dhLevel.getChunkHash(chunkWrapper.getChunkPos()); // shouldn't happen on the render thread since it may take a few moments to run
if (oldChunkHash == chunkWrapper.getBlockBiomeHashCode())
{
// if the chunk hashes are the same then we don't need to bother with lighting the chunk
// or creating/updating the LODs
return;
}
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
@@ -336,6 +344,7 @@ public class SharedApi
}
dhLevel.updateChunkAsync(chunkWrapper);
dhLevel.setChunkHash(chunkWrapper.getChunkPos(), chunkWrapper.getBlockBiomeHashCode());
}
catch (Exception e)
{
@@ -45,8 +45,6 @@ public abstract class AbstractSaveStructure implements AutoCloseable
*/
public abstract File getLevelFolder(ILevelWrapper wrapper);
/** Will return null if no parent folder exists for the given {@link ILevelWrapper}. */
public abstract File getRenderCacheFolder(ILevelWrapper world);
/** Will return null if no parent folder exists for the given {@link ILevelWrapper}. */
public abstract File getFullDataFolder(ILevelWrapper world);
@@ -168,18 +168,6 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
}
@Override
public File getRenderCacheFolder(ILevelWrapper level)
{
File levelFolder = this.levelWrapperToFileMap.get(level);
if (levelFolder == null)
{
return null;
}
return levelFolder;
}
@Override
public File getFullDataFolder(ILevelWrapper level)
{
@@ -51,14 +51,6 @@ public class LocalSaveStructure extends AbstractSaveStructure
return serverSide.getSaveFolder();
}
@Override
public File getRenderCacheFolder(ILevelWrapper level)
{
IServerLevelWrapper serverSide = (IServerLevelWrapper) level;
this.debugPath = serverSide.getSaveFolder();
return serverSide.getSaveFolder();
}
@Override
public File getFullDataFolder(ILevelWrapper level)
{
@@ -23,18 +23,30 @@ import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkMo
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.transformers.ChunkToLodBuilder;
import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
public abstract class AbstractDhLevel implements IDhLevel
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final ChunkToLodBuilder chunkToLodBuilder;
/** if this is null then the other handler is probably null too, but just in case */
@Nullable
public ChunkHashRepo chunkHashRepo;
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 2_000);
/** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */
protected final ConcurrentHashMap<Long, HashSet<DhChunkPos>> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>();
@@ -47,6 +59,20 @@ public abstract class AbstractDhLevel implements IDhLevel
protected AbstractDhLevel() { this.chunkToLodBuilder = new ChunkToLodBuilder(); }
protected void createAndSetChunkHashRepo(String databaseFilePath)
{
ChunkHashRepo newChunkHashRepo = null;
try
{
newChunkHashRepo = new ChunkHashRepo("jdbc:sqlite", databaseFilePath);
}
catch (SQLException e)
{
LOGGER.error("Unable to create [ChunkHashRepo], error: ["+e.getMessage()+"].", e);
}
this.chunkHashRepo = newChunkHashRepo;
}
//=================//
@@ -99,6 +125,32 @@ public abstract class AbstractDhLevel implements IDhLevel
}
@Override
public int getChunkHash(DhChunkPos pos)
{
if (this.chunkHashRepo == null)
{
return 0;
}
ChunkHashDTO dto = this.chunkHashRepo.getByKey(pos);
return (dto != null) ? dto.chunkHash : 0;
}
@Override
public void setChunkHash(DhChunkPos pos, int chunkHash)
{
if (this.chunkHashRepo != null)
{
this.chunkHashRepo.save(new ChunkHashDTO(pos, chunkHash));
}
}
//================//
// base overrides //
//================//
@Override
public void close() { this.chunkToLodBuilder.close(); }
@@ -61,6 +61,8 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
this.dataFileHandler = new RemoteFullDataSourceProvider(this, saveStructure, fullDataSaveDirOverride);
this.clientside = new ClientLevelModule(this);
this.createAndSetChunkHashRepo(this.dataFileHandler.repo.databaseLocation);
if (enableRendering)
{
this.clientside.startRenderer(clientLevelWrapper);
@@ -53,6 +53,10 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
//=============//
// constructor //
//=============//
public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper)
{
if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
@@ -62,6 +66,8 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
this.serverLevelWrapper = serverLevelWrapper;
this.serverside = new ServerLevelModule(this, saveStructure);
this.clientside = new ClientLevelModule(this);
this.createAndSetChunkHashRepo(this.serverside.fullDataFileHandler.repo.databaseLocation);
LOGGER.info("Started " + DhClientServerLevel.class.getSimpleName() + " for " + serverLevelWrapper + " with saves at " + saveStructure);
}
@@ -72,10 +78,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
//==============//
@Override
public void clientTick()
{
clientside.clientTick();
}
public void clientTick() { this.clientside.clientTick(); }
@Override
public void render(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
@@ -124,6 +127,8 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
}
}
//========//
// render //
//========//
@@ -132,6 +137,8 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
public void stopRenderer() { this.clientside.stopRenderer(); }
//================//
// level handling //
//================//
@@ -180,7 +187,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.clientside.updateDataSourcesAsync(data); }
@Override
public int getMinY() { return getLevelWrapper().getMinHeight(); }
public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
@@ -36,6 +36,8 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
public final ServerLevelModule serverside;
private final IServerLevelWrapper serverLevelWrapper;
public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper)
{
if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
@@ -44,9 +46,13 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
}
this.serverLevelWrapper = serverLevelWrapper;
this.serverside = new ServerLevelModule(this, saveStructure);
this.createAndSetChunkHashRepo(this.serverside.fullDataFileHandler.repo.databaseLocation);
LOGGER.info("Started DHLevel for {} with saves at {}", serverLevelWrapper, saveStructure);
}
public void serverTick() { this.chunkToLodBuilder.tick(); }
@Override
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
@@ -37,6 +38,9 @@ public interface IDhLevel extends AutoCloseable
*/
ILevelWrapper getLevelWrapper();
/** @return 0 if no hash is known */
int getChunkHash(DhChunkPos pos);
void setChunkHash(DhChunkPos pos, int chunkHash);
void updateChunkAsync(IChunkWrapper chunk);
FullDataSourceProviderV2 getFullDataProvider();
@@ -43,6 +43,10 @@ public class ServerLevelModule implements AutoCloseable
//=============//
// constructor //
//=============//
public ServerLevelModule(IDhServerLevel parentServerLevel, AbstractSaveStructure saveStructure)
{
this.parentServerLevel = parentServerLevel;
@@ -54,6 +58,10 @@ public class ServerLevelModule implements AutoCloseable
//================//
// base overrides //
//================//
@Override
public void close()
{
@@ -0,0 +1,72 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.sql.dto;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
/** handles storing {@link FullDataSourceV2}'s in the database. */
public class ChunkHashDTO implements IBaseDTO<DhChunkPos>
{
public DhChunkPos pos;
public int chunkHash;
//=============//
// constructor //
//=============//
public ChunkHashDTO(DhChunkPos pos, int chunkHash)
{
this.pos = pos;
this.chunkHash = chunkHash;
}
//===========//
// overrides //
//===========//
@Override
public DhChunkPos getKey() { return this.pos; }
}
@@ -0,0 +1,128 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.sql.repo;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.Logger;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Map;
public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
//=============//
// constructor //
//=============//
public ChunkHashRepo(String databaseType, String databaseLocation) throws SQLException
{
super(databaseType, databaseLocation, ChunkHashDTO.class);
}
//===========//
// overrides //
//===========//
@Override
public String getTableName() { return "ChunkHash"; }
@Override
public String createWhereStatement(DhChunkPos pos) { return "ChunkPosX = '"+pos.x+"' AND ChunkPosZ = '"+pos.z+"'"; }
//=======================//
// repo required methods //
//=======================//
@Override
public ChunkHashDTO convertDictionaryToDto(Map<String, Object> objectMap) throws ClassCastException
{
int posX = (Integer) objectMap.get("ChunkPosX");
int posZ = (Integer) objectMap.get("ChunkPosZ");
int chunkHash = (Integer) objectMap.get("ChunkHash");
ChunkHashDTO dto = new ChunkHashDTO(new DhChunkPos(posX, posZ), chunkHash);
return dto;
}
@Override
public PreparedStatement createInsertStatement(ChunkHashDTO dto) throws SQLException
{
String sql =
"INSERT INTO "+this.getTableName() + " (\n" +
" ChunkPosX, ChunkPosZ, \n" +
" ChunkHash, \n" +
" LastModifiedUnixDateTime, CreatedUnixDateTime) \n" +
"VALUES( \n" +
" ?, ?, \n" +
" ?, \n" +
" ?, ? \n" +
");";
PreparedStatement statement = this.createPreparedStatement(sql);
int i = 1;
statement.setObject(i++, dto.pos.x);
statement.setObject(i++, dto.pos.z);
statement.setObject(i++, dto.chunkHash);
statement.setObject(i++, System.currentTimeMillis()); // last modified unix time
statement.setObject(i++, System.currentTimeMillis()); // created unix time
return statement;
}
@Override
public PreparedStatement createUpdateStatement(ChunkHashDTO dto) throws SQLException
{
String sql =
"UPDATE "+this.getTableName()+" \n" +
"SET \n" +
" ChunkHash = ? \n" +
" ,LastModifiedUnixDateTime = ? \n" +
"WHERE ChunkPosX = ? AND ChunkPosZ = ?";
PreparedStatement statement = this.createPreparedStatement(sql);
int i = 1;
statement.setObject(i++, dto.chunkHash);
statement.setObject(i++, dto.pos.x);
statement.setObject(i++, dto.pos.z);
return statement;
}
}
@@ -0,0 +1,13 @@
CREATE TABLE ChunkHash(
-- compound primary key
ChunkPosX INT NOT NULL
,ChunkPosZ INT NOT NULL
,ChunkHash INT NOT NULL
,LastModifiedUnixDateTime BIGINT NOT NULL -- in GMT 0
,CreatedUnixDateTime BIGINT NOT NULL -- in GMT 0
,PRIMARY KEY (ChunkPosX, ChunkPosZ)
);
@@ -5,3 +5,4 @@
0031-sqlite-useSqliteWalJournaling.sql
0040-sqlite-removeRenderCache.sql
0050-sqlite-addApplyToParentIndex.sql
0060-sqlite-createChunkHashTable.sql