Fixed various bugs & Add some info to be logged to F3

This commit is contained in:
TomTheFurry
2022-09-08 21:18:22 +08:00
parent a53bdc6abf
commit 00a18352d8
17 changed files with 238 additions and 170 deletions
@@ -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);
}
}
@@ -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();