diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index b39287576..a5e1279b9 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -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) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/AbstractSaveStructure.java b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/AbstractSaveStructure.java index 69eb6c228..4163e94ee 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/AbstractSaveStructure.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/AbstractSaveStructure.java @@ -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); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java index 6e9809934..dc8cad673 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java @@ -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) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/LocalSaveStructure.java b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/LocalSaveStructure.java index 8bd8d07de..6dc2ede8e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/LocalSaveStructure.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/LocalSaveStructure.java @@ -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) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java index 080130820..5a7e3f5b3 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java @@ -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> 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(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index 184bf20af..f39d12ec8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -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); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java index eadcc1ece..066acea0d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java @@ -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 updateDataSourcesAsync(FullDataSourceV2 data) { return this.clientside.updateDataSourcesAsync(data); } @Override - public int getMinY() { return getLevelWrapper().getMinHeight(); } + public int getMinY() { return this.getLevelWrapper().getMinHeight(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index addb9416e..7f9f6e682 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -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 diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java index 0d717bf08..41f560e1a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java @@ -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(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java index 57a7fedf4..7ca1f3e09 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java @@ -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() { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/ChunkHashDTO.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/ChunkHashDTO.java new file mode 100644 index 000000000..8e532057e --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/ChunkHashDTO.java @@ -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 . + */ + +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 +{ + 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; } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/ChunkHashRepo.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/ChunkHashRepo.java new file mode 100644 index 000000000..4a1a5ada5 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/ChunkHashRepo.java @@ -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 . + */ + +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 +{ + 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 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; + } + + + +} diff --git a/core/src/main/resources/sqlScripts/0060-sqlite-createChunkHashTable.sql b/core/src/main/resources/sqlScripts/0060-sqlite-createChunkHashTable.sql new file mode 100644 index 000000000..7bd6f54d7 --- /dev/null +++ b/core/src/main/resources/sqlScripts/0060-sqlite-createChunkHashTable.sql @@ -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) +); diff --git a/core/src/main/resources/sqlScripts/scriptList.txt b/core/src/main/resources/sqlScripts/scriptList.txt index d2104f42c..52027a061 100644 --- a/core/src/main/resources/sqlScripts/scriptList.txt +++ b/core/src/main/resources/sqlScripts/scriptList.txt @@ -5,3 +5,4 @@ 0031-sqlite-useSqliteWalJournaling.sql 0040-sqlite-removeRenderCache.sql 0050-sqlite-addApplyToParentIndex.sql +0060-sqlite-createChunkHashTable.sql