Fixed various bugs & Add some info to be logged to F3
This commit is contained in:
@@ -2,6 +2,7 @@ package com.seibel.lod.core.a7.datatype;
|
||||
|
||||
import com.seibel.lod.core.a7.datatype.full.ChunkSizedData;
|
||||
import com.seibel.lod.core.a7.level.IClientLevel;
|
||||
import com.seibel.lod.core.a7.level.ILevel;
|
||||
import com.seibel.lod.core.a7.pos.DhSectionPos;
|
||||
import com.seibel.lod.core.a7.render.LodQuadTree;
|
||||
import com.seibel.lod.core.a7.render.RenderBuffer;
|
||||
@@ -32,11 +33,6 @@ public interface LodRenderSource {
|
||||
|
||||
void saveRender(IClientLevel level, RenderMetaFile file, OutputStream dataStream) throws IOException;
|
||||
|
||||
@Deprecated
|
||||
void write(ChunkSizedData chunkData);
|
||||
@Deprecated
|
||||
void flushWrites(IClientLevel level);
|
||||
|
||||
byte getRenderVersion();
|
||||
|
||||
/**
|
||||
@@ -44,6 +40,7 @@ public interface LodRenderSource {
|
||||
*/
|
||||
boolean isValid();
|
||||
|
||||
void fastWrite(ChunkSizedData chunkData, IClientLevel level);
|
||||
// Only override the data that has not been written directly using write(), and skip those that are empty
|
||||
void weakWrite(LodRenderSource source);
|
||||
}
|
||||
|
||||
@@ -49,11 +49,6 @@ public class PlaceHolderRenderSource implements LodRenderSource {
|
||||
public void saveRender(IClientLevel level, RenderMetaFile file, OutputStream dataStream) throws IOException {
|
||||
throw new UnsupportedOperationException("EmptyRenderSource should NEVER be saved!");
|
||||
}
|
||||
@Override
|
||||
public void write(ChunkSizedData chunkData) {}
|
||||
|
||||
@Override
|
||||
public void flushWrites(IClientLevel level) {}
|
||||
|
||||
@Override
|
||||
public byte getRenderVersion() {
|
||||
@@ -69,6 +64,9 @@ public class PlaceHolderRenderSource implements LodRenderSource {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fastWrite(ChunkSizedData chunkData, IClientLevel level) {}
|
||||
|
||||
@Override
|
||||
public void weakWrite(LodRenderSource source) {
|
||||
|
||||
|
||||
@@ -369,32 +369,11 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
|
||||
@Override
|
||||
public void saveRender(IClientLevel level, RenderMetaFile file, OutputStream dataStream) throws IOException {
|
||||
flushWrites(level);
|
||||
try (DataOutputStream dos = new DataOutputStream(dataStream)) {
|
||||
writeData(dos);
|
||||
}
|
||||
}
|
||||
|
||||
private final ConcurrentLinkedQueue<ChunkSizedData> writeRequest = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@Override
|
||||
public void write(ChunkSizedData chunkData) {
|
||||
writeRequest.add(chunkData);
|
||||
}
|
||||
@Override
|
||||
public void flushWrites(IClientLevel level) {
|
||||
boolean didSomething = false;
|
||||
while (!writeRequest.isEmpty()) {
|
||||
isEmpty = false;
|
||||
ChunkSizedData chunkData = writeRequest.poll();
|
||||
FullToColumnTransformer.writeFullDataChunkToColumnData(this, level, chunkData);
|
||||
didSomething = true;
|
||||
}
|
||||
if (didSomething) {
|
||||
lastNs = -1; // Reset the timeout to allow rebuilding the buffer again
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getRenderVersion() {
|
||||
return LATEST_VERSION;
|
||||
@@ -426,4 +405,9 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fastWrite(ChunkSizedData chunkData, IClientLevel level) {
|
||||
FullToColumnTransformer.writeFullDataChunkToColumnData(this, level, chunkData);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import java.util.concurrent.ExecutorService;
|
||||
//TODO: Merge this with FullToColumnTransformer
|
||||
public class DataRenderTransformer {
|
||||
public static final ExecutorService TRANSFORMER_THREADS
|
||||
= LodUtil.makeSingleThreadPool("Data/Render Transformer");
|
||||
= LodUtil.makeThreadPool(2, "Data/Render Transformer");
|
||||
|
||||
public static CompletableFuture<LodRenderSource> transformDataSource(LodDataSource data, IClientLevel level) {
|
||||
return CompletableFuture.supplyAsync(() -> transform(data, level), TRANSFORMER_THREADS);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.seibel.lod.core.a7.generation;
|
||||
import com.seibel.lod.core.a7.datatype.full.ChunkSizedData;
|
||||
import com.seibel.lod.core.a7.pos.DhBlockPos2D;
|
||||
import com.seibel.lod.core.a7.pos.DhLodPos;
|
||||
import com.seibel.lod.core.a7.util.UncheckedInterruptedException;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.objects.DHChunkPos;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
@@ -360,7 +361,8 @@ public class GenerationQueue implements Closeable {
|
||||
chunkPosMin, granularity, dataDetail, task.group::accept);
|
||||
task.genFuture.whenComplete((v, ex) -> {
|
||||
if (ex != null) {
|
||||
logger.error("Error generating data for section {}", pos, ex);
|
||||
if (!UncheckedInterruptedException.isThrowableInterruption(ex))
|
||||
logger.error("Error generating data for section {}", pos, ex);
|
||||
task.group.members.forEach(m -> m.future.complete(false));
|
||||
} else {
|
||||
logger.info("Section generation at {} complated", pos);
|
||||
@@ -375,7 +377,13 @@ public class GenerationQueue implements Closeable {
|
||||
taskGroups.values().forEach(g -> g.members.forEach(t -> t.future.complete(false)));
|
||||
taskGroups.clear();
|
||||
ArrayList<CompletableFuture<Void>> array = new ArrayList<>(inProgress.size());
|
||||
inProgress.values().forEach(runningTask -> array.add(runningTask.genFuture));
|
||||
inProgress.values().forEach(runningTask -> array.add(
|
||||
runningTask.genFuture.exceptionally((ex) -> {
|
||||
if (ex instanceof CompletionException) ex = ex.getCause();
|
||||
if (!UncheckedInterruptedException.isThrowableInterruption(ex))
|
||||
logger.error("Error when terminating data generation for section {}", runningTask.group.pos, ex);
|
||||
return null;
|
||||
})));
|
||||
closer = CompletableFuture.allOf(array.toArray(CompletableFuture[]::new));
|
||||
if (cancelCurrentGeneration) {
|
||||
array.forEach(f -> f.cancel(alsoInterruptRunning));
|
||||
|
||||
@@ -57,6 +57,7 @@ public class DhClientLevel implements IClientLevel {
|
||||
public void clientTick() {
|
||||
tree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
|
||||
renderBufferHandler.update();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.seibel.lod.core.a7.generation.GenerationQueue;
|
||||
import com.seibel.lod.core.a7.render.LodQuadTree;
|
||||
import com.seibel.lod.core.a7.save.io.file.GeneratedDataFileHandler;
|
||||
import com.seibel.lod.core.a7.util.FileScanner;
|
||||
import com.seibel.lod.core.a7.save.io.file.DataFileHandler;
|
||||
import com.seibel.lod.core.a7.save.io.render.RenderFileHandler;
|
||||
import com.seibel.lod.core.a7.pos.DhBlockPos2D;
|
||||
import com.seibel.lod.core.a7.render.RenderBufferHandler;
|
||||
@@ -13,9 +12,11 @@ import com.seibel.lod.core.builders.worldGeneration.BatchGenerator;
|
||||
import com.seibel.lod.core.config.Config;
|
||||
import com.seibel.lod.core.handlers.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.logging.f3.F3Screen;
|
||||
import com.seibel.lod.core.objects.DHBlockPos;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
import com.seibel.lod.core.a7.render.a7LodRenderer;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
@@ -23,10 +24,10 @@ import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
import net.minecraft.world.entity.ambient.Bat;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class DhClientServerLevel implements IClientLevel, IServerLevel {
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
@@ -41,6 +42,9 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel {
|
||||
public a7LodRenderer renderer = null;
|
||||
public LodQuadTree tree = null;
|
||||
public volatile BatchGenerator worldGenerator = null;
|
||||
private final ReentrantLock renderStateLifecycleLock = new ReentrantLock();
|
||||
|
||||
public F3Screen.NestedMessage f3Msg;
|
||||
|
||||
public DhClientServerLevel(LocalSaveStructure save, IServerLevelWrapper level) {
|
||||
this.serverLevel = level;
|
||||
@@ -50,13 +54,36 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel {
|
||||
dataFileHandler = new GeneratedDataFileHandler(this, save.getDataFolder(level));
|
||||
FileScanner.scanFile(save, serverLevel, dataFileHandler, null);
|
||||
LOGGER.info("Started DHLevel for {} with saves at {}", level, save);
|
||||
f3Msg = new F3Screen.NestedMessage(this::f3Log);
|
||||
}
|
||||
|
||||
private String[] f3Log() {
|
||||
if (clientLevel == null) {
|
||||
return new String[]{LodUtil.formatLog("level @ {}: Inactive", serverLevel.getDimensionType().getDimensionName())};
|
||||
} else {
|
||||
return new String[]{
|
||||
LodUtil.formatLog("level @ {}: Active", serverLevel.getDimensionType().getDimensionName())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientTick() {
|
||||
//LOGGER.info("Client tick for {}", level);
|
||||
if (tree != null) tree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
|
||||
if (renderBufferHandler != null) renderBufferHandler.update();
|
||||
if (clientLevel == null) return;
|
||||
if (tree.viewDistance != Config.Client.Graphics.Quality.lodChunkRenderDistance.get()*16) {
|
||||
IClientLevelWrapper temp = clientLevel;
|
||||
renderStateLifecycleLock.lock();
|
||||
try {
|
||||
stopRenderer();
|
||||
startRenderer(temp);
|
||||
} finally {
|
||||
renderStateLifecycleLock.unlock();
|
||||
}
|
||||
return;
|
||||
}
|
||||
tree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
|
||||
renderBufferHandler.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,61 +93,77 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel {
|
||||
|
||||
public void startRenderer(IClientLevelWrapper clientLevel) {
|
||||
LOGGER.info("Starting renderer for {}", this);
|
||||
if (renderBufferHandler != null || this.clientLevel != null) {
|
||||
LOGGER.warn("Tried to call startRenderer() on {} when renderer is already setup!", this);
|
||||
return;
|
||||
renderStateLifecycleLock.lock();
|
||||
try {
|
||||
if (renderBufferHandler != null || this.clientLevel != null) {
|
||||
LOGGER.warn("Tried to call startRenderer() on {} when renderer is already setup!", this);
|
||||
return;
|
||||
}
|
||||
this.clientLevel = clientLevel;
|
||||
// TODO: Make a registry for generators for modding support.
|
||||
worldGenerator = new BatchGenerator(this);
|
||||
generationQueue = new GenerationQueue(worldGenerator);
|
||||
dataFileHandler.setGenerationQueue(generationQueue);
|
||||
renderFileHandler = new RenderFileHandler(dataFileHandler, this, save.getRenderCacheFolder(serverLevel));
|
||||
tree = new LodQuadTree(this, Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * 16,
|
||||
MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, renderFileHandler);
|
||||
renderBufferHandler = new RenderBufferHandler(tree);
|
||||
FileScanner.scanFile(save, serverLevel, null, renderFileHandler);
|
||||
} finally {
|
||||
renderStateLifecycleLock.unlock();
|
||||
}
|
||||
this.clientLevel = clientLevel;
|
||||
// TODO: Make a registry for generators for modding support.
|
||||
worldGenerator = new BatchGenerator(this);
|
||||
generationQueue = new GenerationQueue(worldGenerator);
|
||||
dataFileHandler.setGenerationQueue(generationQueue);
|
||||
renderFileHandler = new RenderFileHandler(dataFileHandler, this, save.getRenderCacheFolder(serverLevel));
|
||||
tree = new LodQuadTree(this, Config.Client.Graphics.Quality.lodChunkRenderDistance.get()*16,
|
||||
MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, renderFileHandler);
|
||||
renderBufferHandler = new RenderBufferHandler(tree);
|
||||
FileScanner.scanFile(save, serverLevel, null, renderFileHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) {
|
||||
if (renderBufferHandler == null) {
|
||||
LOGGER.error("Tried to call render() on {} when renderer has not been started!", this);
|
||||
return;
|
||||
if (!renderStateLifecycleLock.tryLock()) return;
|
||||
try {
|
||||
if (renderBufferHandler == null) {
|
||||
LOGGER.error("Tried to call render() on {} when renderer has not been started!", this);
|
||||
return;
|
||||
}
|
||||
if (renderer == null) {
|
||||
renderer = new a7LodRenderer(this);
|
||||
}
|
||||
renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler);
|
||||
} finally {
|
||||
renderStateLifecycleLock.unlock();
|
||||
}
|
||||
if (renderer == null) {
|
||||
renderer = new a7LodRenderer(this);
|
||||
}
|
||||
renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler);
|
||||
}
|
||||
|
||||
public void stopRenderer() {
|
||||
LOGGER.info("Stopping renderer for {}", this);
|
||||
if (renderBufferHandler == null) {
|
||||
LOGGER.warn("Tried to call stopRenderer() on {} when renderer is already closed!", this);
|
||||
return;
|
||||
renderStateLifecycleLock.lock();
|
||||
try {
|
||||
if (renderBufferHandler == null) {
|
||||
LOGGER.warn("Tried to call stopRenderer() on {} when renderer is already closed!", this);
|
||||
return;
|
||||
}
|
||||
tree.close();
|
||||
tree = null;
|
||||
dataFileHandler.popGenerationQueue();
|
||||
final BatchGenerator f_worldGen = worldGenerator;
|
||||
CompletableFuture<Void> closer = generationQueue.startClosing(true, true)
|
||||
.exceptionally(ex -> {
|
||||
LOGGER.error("Error closing generation queue", ex);
|
||||
return null;
|
||||
}).thenRun(f_worldGen::close)
|
||||
.exceptionally(ex -> {
|
||||
LOGGER.error("Error closing world gen", ex);
|
||||
return null;
|
||||
});
|
||||
generationQueue = null;
|
||||
worldGenerator = null;
|
||||
renderBufferHandler.close();
|
||||
renderBufferHandler = null;
|
||||
renderFileHandler.flushAndSave(); //Ignore the completion feature so that this action is async
|
||||
renderFileHandler.close();
|
||||
renderFileHandler = null;
|
||||
closer.join(); // TODO: Could this cause deadlocks? we are blocking in main thread.
|
||||
clientLevel = null;
|
||||
} finally {
|
||||
renderStateLifecycleLock.unlock();
|
||||
}
|
||||
tree.close();
|
||||
tree = null;
|
||||
dataFileHandler.popGenerationQueue();
|
||||
final BatchGenerator f_worldGen = worldGenerator;
|
||||
CompletableFuture<Void> closer = generationQueue.startClosing(true, true)
|
||||
.exceptionally(ex -> {
|
||||
LOGGER.error("Error closing geberation queue", ex);
|
||||
return null;
|
||||
}).thenRun(f_worldGen::close)
|
||||
.exceptionally(ex -> {
|
||||
LOGGER.error("Error closing geberation queue", ex);
|
||||
return null;
|
||||
});
|
||||
generationQueue = null;
|
||||
worldGenerator = null;
|
||||
renderBufferHandler.close();
|
||||
renderBufferHandler = null;
|
||||
renderFileHandler.flushAndSave(); //Ignore the completion feature so that this action is async
|
||||
renderFileHandler.close();
|
||||
renderFileHandler = null;
|
||||
closer.join(); // TODO: Could this cause deadlocks? we are blocking in main thread.
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,13 +208,18 @@ public class DhClientServerLevel implements IClientLevel, IServerLevel {
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
if (generationQueue != null) generationQueue.close();
|
||||
if (worldGenerator != null) worldGenerator.close();
|
||||
if (renderer != null) renderer.close();
|
||||
if (tree != null) tree.close();
|
||||
if (renderBufferHandler != null) renderBufferHandler.close();
|
||||
if (renderFileHandler != null) renderFileHandler.close();
|
||||
dataFileHandler.close();
|
||||
renderStateLifecycleLock.lock();
|
||||
try {
|
||||
if (generationQueue != null) generationQueue.close();
|
||||
if (worldGenerator != null) worldGenerator.close();
|
||||
if (renderer != null) renderer.close();
|
||||
if (tree != null) tree.close();
|
||||
if (renderBufferHandler != null) renderBufferHandler.close();
|
||||
if (renderFileHandler != null) renderFileHandler.close();
|
||||
dataFileHandler.close();
|
||||
} finally {
|
||||
renderStateLifecycleLock.unlock();
|
||||
}
|
||||
LOGGER.info("Closed {}", this);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
|
||||
public interface IClientLevel extends ILevel {
|
||||
/**
|
||||
* Return whether the level needs to be reloaded
|
||||
*/
|
||||
void clientTick();
|
||||
|
||||
void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler);
|
||||
|
||||
@@ -70,7 +70,6 @@ public class LodRenderSection {
|
||||
}
|
||||
if (lodRenderSource != null) {
|
||||
provider.refreshRenderSource(lodRenderSource);
|
||||
lodRenderSource.flushWrites(level);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ public class RenderFileHandler implements IRenderSourceProvider {
|
||||
}
|
||||
RenderMetaFile metaFile = files.get(sectPos);
|
||||
if (metaFile != null) { // Fast path: if there is a file for this section, just write to it.
|
||||
metaFile.updateChunkIfNeeded(chunkData);
|
||||
metaFile.updateChunkIfNeeded(chunkData, level);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,13 +41,13 @@ public class RenderMetaFile extends MetaFile
|
||||
|
||||
//FIXME: This can cause concurrent modification of LodRenderSource.
|
||||
// Not sure if it will cause issues or not.
|
||||
public void updateChunkIfNeeded(ChunkSizedData chunkData) {
|
||||
public void updateChunkIfNeeded(ChunkSizedData chunkData, IClientLevel level) {
|
||||
DhLodPos chunkPos = new DhLodPos((byte) (chunkData.dataDetail + 4), chunkData.x, chunkData.z);
|
||||
LodUtil.assertTrue(pos.getSectionBBoxPos().overlaps(chunkPos), "Chunk pos {} doesn't overlap with section {}", chunkPos, pos);
|
||||
|
||||
CompletableFuture<LodRenderSource> source = _readCached(data.get());
|
||||
if (source == null) return;
|
||||
if (source.isDone()) source.join().write(chunkData);
|
||||
if (source.isDone()) source.join().fastWrite(chunkData, level);
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> flushAndSave(ExecutorService renderCacheThread) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.seibel.lod.core.a7.level.DhClientServerLevel;
|
||||
import com.seibel.lod.core.a7.level.ILevel;
|
||||
import com.seibel.lod.core.a7.save.structure.LocalSaveStructure;
|
||||
import com.seibel.lod.core.config.Config;
|
||||
import com.seibel.lod.core.logging.f3.F3Screen;
|
||||
import com.seibel.lod.core.util.EventLoop;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
@@ -12,39 +13,47 @@ import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class DhClientServerWorld extends DhWorld implements IClientWorld, IServerWorld
|
||||
{
|
||||
private final HashMap<ILevelWrapper, DhClientServerLevel> levels;
|
||||
private final HashMap<ILevelWrapper, DhClientServerLevel> levelObjMap;
|
||||
private final HashSet<DhClientServerLevel> dhLevels;
|
||||
public final LocalSaveStructure saveStructure;
|
||||
public ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHTickerThread", 2);
|
||||
public EventLoop eventLoop = new EventLoop(dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop
|
||||
public F3Screen.DynamicMessage f3Msg;
|
||||
|
||||
public DhClientServerWorld() {
|
||||
super(WorldEnvironment.Client_Server);
|
||||
saveStructure = new LocalSaveStructure();
|
||||
levels = new HashMap<>();
|
||||
levelObjMap = new HashMap<>();
|
||||
dhLevels = new HashSet<>();
|
||||
LOGGER.info("Started DhWorld of type {}", environment);
|
||||
f3Msg = new F3Screen.DynamicMessage(() ->
|
||||
LodUtil.formatLog("{} World with {} levels", environment, dhLevels.size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DhClientServerLevel getOrLoadLevel(ILevelWrapper wrapper) {
|
||||
if (wrapper instanceof IServerLevelWrapper) {
|
||||
return levels.computeIfAbsent(wrapper, (w) -> {
|
||||
return levelObjMap.computeIfAbsent(wrapper, (w) -> {
|
||||
File levelFile = saveStructure.tryGetLevelFolder(w);
|
||||
LodUtil.assertTrue(levelFile != null);
|
||||
return new DhClientServerLevel(saveStructure, (IServerLevelWrapper) w);
|
||||
DhClientServerLevel level = new DhClientServerLevel(saveStructure, (IServerLevelWrapper) w);
|
||||
dhLevels.add(level);
|
||||
return level;
|
||||
});
|
||||
} else {
|
||||
return levels.computeIfAbsent(wrapper, (w) -> {
|
||||
return levelObjMap.computeIfAbsent(wrapper, (w) -> {
|
||||
IClientLevelWrapper clientSide = (IClientLevelWrapper) w;
|
||||
IServerLevelWrapper serverSide = clientSide.tryGetServerSideWrapper();
|
||||
LodUtil.assertTrue(serverSide != null);
|
||||
DhClientServerLevel level = levels.get(serverSide);
|
||||
DhClientServerLevel level = levelObjMap.get(serverSide);
|
||||
if (level==null) return null;
|
||||
level.startRenderer(clientSide);
|
||||
return level;
|
||||
@@ -54,77 +63,61 @@ public class DhClientServerWorld extends DhWorld implements IClientWorld, IServe
|
||||
|
||||
@Override
|
||||
public DhClientServerLevel getLevel(ILevelWrapper wrapper) {
|
||||
return levels.get(wrapper);
|
||||
return levelObjMap.get(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILevel[] getAllLoadedLevels()
|
||||
public Iterable<? extends ILevel> getAllLoadedLevels()
|
||||
{
|
||||
ILevel[] array = new ILevel[this.levels.size()];
|
||||
|
||||
int i = 0;
|
||||
for (ILevel level : this.levels.values())
|
||||
{
|
||||
array[i] = level;
|
||||
i++;
|
||||
}
|
||||
|
||||
return array;
|
||||
return dhLevels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unloadLevel(ILevelWrapper wrapper) {
|
||||
if (levels.containsKey(wrapper)) {
|
||||
if (levelObjMap.containsKey(wrapper)) {
|
||||
if (wrapper instanceof IServerLevelWrapper) {
|
||||
LOGGER.info("Unloading level {} ", levels.get(wrapper));
|
||||
levels.remove(wrapper).close();
|
||||
LOGGER.info("Unloading level {} ", levelObjMap.get(wrapper));
|
||||
DhClientServerLevel level = levelObjMap.remove(wrapper);
|
||||
dhLevels.remove(level);
|
||||
level.close();
|
||||
} else {
|
||||
levels.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere.
|
||||
levelObjMap.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void _clientTick() {
|
||||
//LOGGER.info("Client world tick with {} levels", levels.size());
|
||||
int newViewDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * 16;
|
||||
Iterator<DhClientServerLevel> iterator = levels.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
DhClientServerLevel level = iterator.next();
|
||||
if (level.tree != null && level.tree.viewDistance != newViewDistance) {
|
||||
level.close(); //FIXME: Is this fine for current logic?
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
//DetailDistanceUtil.updateSettings();
|
||||
levels.values().forEach(DhClientServerLevel::clientTick);
|
||||
dhLevels.forEach(DhClientServerLevel::clientTick);
|
||||
}
|
||||
|
||||
public void clientTick() {
|
||||
//LOGGER.info("Client world tick");
|
||||
eventLoop.tick();
|
||||
}
|
||||
|
||||
public void serverTick() {
|
||||
levels.values().forEach(DhClientServerLevel::serverTick);
|
||||
dhLevels.forEach(DhClientServerLevel::serverTick);
|
||||
}
|
||||
|
||||
public void doWorldGen() {
|
||||
levels.values().forEach(DhClientServerLevel::doWorldGen);
|
||||
dhLevels.forEach(DhClientServerLevel::doWorldGen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> saveAndFlush() {
|
||||
return CompletableFuture.allOf(levels.values().stream().map(DhClientServerLevel::save).toArray(CompletableFuture[]::new));
|
||||
return CompletableFuture.allOf(dhLevels.stream().map(DhClientServerLevel::save).toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
saveAndFlush().join();
|
||||
for (DhClientServerLevel level : levels.values()) {
|
||||
for (DhClientServerLevel level : dhLevels) {
|
||||
LOGGER.info("Unloading level " + level.serverLevel.getDimensionType().getDimensionName());
|
||||
level.close();
|
||||
}
|
||||
levels.clear();
|
||||
levelObjMap.clear();
|
||||
eventLoop.close();
|
||||
LOGGER.info("Closed DhWorld of type {}", environment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.seibel.lod.core.a7.world;
|
||||
|
||||
import com.seibel.lod.core.a7.level.DhClientLevel;
|
||||
import com.seibel.lod.core.a7.level.DhClientServerLevel;
|
||||
import com.seibel.lod.core.a7.level.ILevel;
|
||||
import com.seibel.lod.core.a7.save.structure.ClientOnlySaveStructure;
|
||||
import com.seibel.lod.core.config.Config;
|
||||
@@ -49,18 +48,9 @@ public class DhClientWorld extends DhWorld implements IClientWorld
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILevel[] getAllLoadedLevels()
|
||||
public Iterable<? extends ILevel> getAllLoadedLevels()
|
||||
{
|
||||
ILevel[] array = new ILevel[this.levels.size()];
|
||||
|
||||
int i = 0;
|
||||
for (ILevel level : this.levels.values())
|
||||
{
|
||||
array[i] = level;
|
||||
i++;
|
||||
}
|
||||
|
||||
return array;
|
||||
return levels.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,6 +93,7 @@ public class DhClientWorld extends DhWorld implements IClientWorld
|
||||
level.close();
|
||||
}
|
||||
levels.clear();
|
||||
eventLoop.close();
|
||||
LOGGER.info("Closed DhWorld of type {}", environment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,18 +40,9 @@ public class DhServerWorld extends DhWorld implements IServerWorld
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILevel[] getAllLoadedLevels()
|
||||
public Iterable<? extends ILevel> getAllLoadedLevels()
|
||||
{
|
||||
ILevel[] array = new ILevel[this.levels.size()];
|
||||
|
||||
int i = 0;
|
||||
for (ILevel level : this.levels.values())
|
||||
{
|
||||
array[i] = level;
|
||||
i++;
|
||||
}
|
||||
|
||||
return array;
|
||||
return levels.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,7 +20,7 @@ public abstract class DhWorld implements Closeable
|
||||
public abstract ILevel getOrLoadLevel(ILevelWrapper wrapper);
|
||||
|
||||
public abstract ILevel getLevel(ILevelWrapper wrapper);
|
||||
public abstract ILevel[] getAllLoadedLevels();
|
||||
public abstract Iterable<? extends ILevel> getAllLoadedLevels();
|
||||
|
||||
public abstract void unloadLevel(ILevelWrapper wrapper);
|
||||
public abstract CompletableFuture<Void> saveAndFlush();
|
||||
|
||||
@@ -17,29 +17,83 @@ public class F3Screen {
|
||||
"",
|
||||
ModInfo.READABLE_NAME + " version: " + ModInfo.VERSION
|
||||
};
|
||||
private static final LinkedList<WeakReference<SelfUpdateMessage>> selfUpdateMessages = new LinkedList<>();
|
||||
private static final LinkedList<WeakReference<Message>> selfUpdateMessages = new LinkedList<>();
|
||||
public static void addStringToDisplay(List<String> list) {
|
||||
list.addAll(Arrays.asList(DEFAULT_STR));
|
||||
Iterator<WeakReference<SelfUpdateMessage>> it = selfUpdateMessages.iterator();
|
||||
Iterator<WeakReference<Message>> it = selfUpdateMessages.iterator();
|
||||
while (it.hasNext()) {
|
||||
WeakReference<SelfUpdateMessage> ref = it.next();
|
||||
SelfUpdateMessage msg = ref.get();
|
||||
WeakReference<Message> ref = it.next();
|
||||
Message msg = ref.get();
|
||||
if (msg == null) {
|
||||
it.remove();
|
||||
} else {
|
||||
msg.print(list);
|
||||
msg.printTo(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SelfUpdateMessage {
|
||||
private final Supplier<String> supplier;
|
||||
public SelfUpdateMessage(Supplier<String> message) {
|
||||
@SuppressWarnings("unused")
|
||||
public static abstract class Message {
|
||||
protected Message() {
|
||||
selfUpdateMessages.add(new WeakReference<>(this));
|
||||
this.supplier = message;
|
||||
}
|
||||
public void print(List<String> list) {
|
||||
list.add(supplier.get());
|
||||
|
||||
public abstract void printTo(List<String> output);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class StaticMessage extends Message {
|
||||
private final String[] lines;
|
||||
public StaticMessage(String... lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
@Override
|
||||
public void printTo(List<String> output) {
|
||||
output.addAll(Arrays.asList(lines));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class DynamicMessage extends Message {
|
||||
private final Supplier<String> supplier;
|
||||
public DynamicMessage(Supplier<String> message) {
|
||||
this.supplier = message;
|
||||
}
|
||||
public void printTo(List<String> list) {
|
||||
String msg = supplier.get();
|
||||
if (msg != null) {
|
||||
list.add(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unused")
|
||||
public static class MultiDynamicMessage extends Message {
|
||||
private final Supplier<String>[] supplier;
|
||||
@SafeVarargs
|
||||
public MultiDynamicMessage(Supplier<String>... messages) {
|
||||
this.supplier = messages;
|
||||
}
|
||||
public void printTo(List<String> list) {
|
||||
for (Supplier<String> s : supplier) {
|
||||
String msg = s.get();
|
||||
if (msg != null) {
|
||||
list.add(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class NestedMessage extends Message {
|
||||
private final Supplier<String[]> supplier;
|
||||
public NestedMessage(Supplier<String[]> message) {
|
||||
this.supplier = message;
|
||||
}
|
||||
public void printTo(List<String> list) {
|
||||
String[] msg = supplier.get();
|
||||
if (msg != null) {
|
||||
list.addAll(Arrays.asList(msg));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class EventLoop { //FIXME This should have close. We are leaking stuff.
|
||||
public class EventLoop implements AutoCloseable { //FIXME This should have close. We are leaking stuff.
|
||||
private final boolean PAUSE_ON_ERROR = ModInfo.IS_DEV_BUILD;
|
||||
private final Logger logger = DhLoggerBuilder.getLogger();
|
||||
private final ExecutorService executorService;
|
||||
@@ -35,10 +35,11 @@ public class EventLoop { //FIXME This should have close. We are leaking stuff.
|
||||
future = CompletableFuture.runAsync(runnable, executorService);
|
||||
}
|
||||
}
|
||||
public void halt() {
|
||||
public void close() {
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
future = null;
|
||||
}
|
||||
public boolean isRunning() {
|
||||
return future != null && !future.isDone();
|
||||
|
||||
Reference in New Issue
Block a user