World generation

This commit is contained in:
s809
2023-07-31 15:23:56 +05:00
parent d2f2a3b8aa
commit 95d721e1a3
40 changed files with 639 additions and 432 deletions
@@ -271,6 +271,7 @@ public class ClientApi
if (clientWorld != null)
{
clientWorld.clientTick();
SharedApi.worldGenTick(clientWorld::doWorldGen);
}
profiler.pop();
}
@@ -64,12 +64,7 @@ public class ServerApi
if (serverWorld != null)
{
serverWorld.serverTick();
this.lastWorldGenTickDelta--;
if (this.lastWorldGenTickDelta <= 0)
{
serverWorld.doWorldGen();
this.lastWorldGenTickDelta = 20;
}
SharedApi.worldGenTick(serverWorld::doWorldGen);
}
}
catch (Exception e)
@@ -12,6 +12,7 @@ import com.seibel.distanthorizons.core.world.*;
public class SharedApi
{
private static AbstractDhWorld currentWorld;
private static int lastWorldGenTickDelta = 0;
@@ -49,6 +50,16 @@ public class SharedApi
}
}
public static void worldGenTick(Runnable worldGenRunnable)
{
lastWorldGenTickDelta--;
if (lastWorldGenTickDelta <= 0)
{
worldGenRunnable.run();
lastWorldGenTickDelta = 20;
}
}
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */
public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld != null && DhClientServerWorld.class.isInstance(currentWorld)) ? (DhClientServerWorld) currentWorld : null; }
@@ -3,6 +3,7 @@ package com.seibel.distanthorizons.core.dataObjects.fullData.loader;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
@@ -23,4 +24,11 @@ public class CompleteFullDataSourceLoader extends AbstractFullDataSourceLoader
return dataSource;
}
/** Uses a given stream to create a temporary {@link CompleteFullDataSource}, which is not saved. */
public CompleteFullDataSource loadData(DhSectionPos pos, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
{
CompleteFullDataSource dataSource = CompleteFullDataSource.createEmpty(pos);
dataSource.populateFromStream(null, inputStream, level);
return dataSource;
}
}
@@ -21,6 +21,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import javax.annotation.CheckForNull;
import java.io.*;
/**
@@ -85,10 +86,10 @@ public class CompleteFullDataSource extends FullDataArrayAccessor implements IFu
}
@Override
public FullDataSourceSummaryData readSourceSummaryInfo(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException
public FullDataSourceSummaryData readSourceSummaryInfo(@CheckForNull FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException
{
int dataDetail = inputStream.readInt();
if (dataFile.baseMetaData != null && dataDetail != dataFile.baseMetaData.dataLevel)
if (dataFile != null && dataFile.baseMetaData != null && dataDetail != dataFile.baseMetaData.dataLevel)
{
throw new IOException(LodUtil.formatLog("Data level mismatch: " + dataDetail + " != " + dataFile.baseMetaData.dataLevel));
}
@@ -5,6 +5,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFull
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.generation.IWorldGenerationQueue;
import com.seibel.distanthorizons.core.generation.WorldGenerationQueue;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
@@ -29,7 +30,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final AtomicReference<WorldGenerationQueue> worldGenQueueRef = new AtomicReference<>(null);
private final AtomicReference<IWorldGenerationQueue> worldGenQueueRef = new AtomicReference<>(null);
private final ArrayList<IOnWorldGenCompleteListener> onWorldGenTaskCompleteListeners = new ArrayList<>();
@@ -57,7 +58,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
//==================//
/** Assumes there isn't a pre-existing queue. */
public void setGenerationQueue(WorldGenerationQueue newWorldGenQueue)
public void setGenerationQueue(IWorldGenerationQueue newWorldGenQueue)
{
boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue);
LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!");
@@ -109,12 +110,12 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
@Nullable
private CompletableFuture<IFullDataSource> tryStartGenTask(FullDataMetaFile file, IIncompleteFullDataSource dataSource) {
WorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
// breaks down the missing positions into the desired detail level that the gen queue could accept
if (worldGenQueue != null && !file.genQueueChecked) {
DhSectionPos pos = file.pos;
file.genQueueChecked = true;
byte maxSectDataDetailLevel = worldGenQueue.largestDataDetail;
byte maxSectDataDetailLevel = worldGenQueue.largestDataDetail();
byte targetDataDetailLevel = dataSource.getDataDetailLevel();
if (targetDataDetailLevel > maxSectDataDetailLevel) {
@@ -210,7 +211,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
if (source instanceof IIncompleteFullDataSource && !file.genQueueChecked)
{
WorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue != null)
{
CompletableFuture<IFullDataSource> future = this.updateFromExistingDataSources(file, (IIncompleteFullDataSource) source);
@@ -1,89 +1,11 @@
package com.seibel.distanthorizons.core.file.fullDatafile;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.network.NetworkClient;
import com.seibel.distanthorizons.core.network.messages.FullDataSourceRequestMessage;
import com.seibel.distanthorizons.core.network.messages.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
public class RemoteFullDataFileHandler extends GeneratedFullDataFileHandler
{
protected static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final NetworkClient networkClient;
private final F3Screen.NestedMessage f3Message;
private int finishedRequests = 0;
private int totalRequests = 0;
private int failedRequests = 0;
public RemoteFullDataFileHandler(IDhLevel level, AbstractSaveStructure saveStructure, NetworkClient networkClient) {
public RemoteFullDataFileHandler(IDhLevel level, AbstractSaveStructure saveStructure) {
super(level, saveStructure);
this.networkClient = networkClient;
this.f3Message = new F3Screen.NestedMessage(this::f3Log);
}
@Override
public CompletableFuture<IFullDataSource> read(DhSectionPos pos) {
FullDataMetaFile metaFile = this.getLoadOrMakeFile(pos, false);
if (metaFile != null)
{
return super.read(pos).thenCompose(fullDataSource -> requestFromServer(pos, fullDataSource));
}
else
{
return requestFromServer(pos, null).thenCompose(fullDataSource -> fullDataSource != null
? CompletableFuture.completedFuture(fullDataSource)
: super.read(pos));
}
}
@NotNull
private CompletableFuture<IFullDataSource> requestFromServer(DhSectionPos pos, IFullDataSource fullDataSource)
{
totalRequests++;
return networkClient.<FullDataSourceResponseMessage>sendRequest(new FullDataSourceRequestMessage(pos))
.handle((response, throwable) -> {
try
{
finishedRequests++;
if (throwable != null)
throw throwable;
LOGGER.info("FullDataSourceResponseMessage " + pos);
FullDataMetaFile metaFile = this.getLoadOrMakeFile(pos, true);
return response.getFullDataSource(metaFile, level);
}
catch (Throwable e)
{
failedRequests++;
LOGGER.error("Error while fetching full data source", e);
return fullDataSource;
}
});
}
private String[] f3Log()
{
ArrayList<String> lines = new ArrayList<>();
lines.add("Remote Full Data File Handler ["+this.level.getLevelWrapper().getDimensionType().getDimensionName()+"]");
lines.add(" Requests: "+this.finishedRequests +" / "+this.totalRequests +" (failed: "+ this.failedRequests+")");
return lines.toArray(new String[0]);
}
@Override
public void close() {
f3Message.close();
super.close();
}
}
@@ -0,0 +1,22 @@
package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
public interface IWorldGenerationQueue extends Closeable
{
byte largestDataDetail();
CompletableFuture<WorldGenResult> submitGenTask(DhLodPos pos, byte requiredDataDetail, IWorldGenTaskTracker tracker);
void runCurrentGenTasksUntilBusy(DhBlockPos2D targetPos);
CompletableFuture<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning);
void close();
}
@@ -29,7 +29,7 @@ import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
public class WorldGenerationQueue implements Closeable, IDebugRenderable
public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRenderable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -47,6 +47,7 @@ public class WorldGenerationQueue implements Closeable, IDebugRenderable
/** largest numerical detail level allowed */
public final byte largestDataDetail;
@Override public byte largestDataDetail() { return this.largestDataDetail; }
/** lowest numerical detail level allowed */
public final byte smallestDataDetail;
@@ -0,0 +1,158 @@
package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.network.NetworkClient;
import com.seibel.distanthorizons.core.network.messages.FullDataSourceRequestMessage;
import com.seibel.distanthorizons.core.network.messages.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import javax.annotation.CheckForNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public class WorldRemoteGenerationQueue implements IWorldGenerationQueue
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final int MAX_CONCURRENT_REQUESTS = 5;
private final NetworkClient networkClient;
private final IDhClientLevel level;
private final ConcurrentMap<DhSectionPos, WorldGenQueueEntry> waitingTasks = new ConcurrentHashMap<>();
private final AtomicInteger pendingTasks = new AtomicInteger();
private final F3Screen.NestedMessage f3Message = new F3Screen.NestedMessage(this::f3Log);
private final AtomicInteger finishedRequests = new AtomicInteger();
private final AtomicInteger totalRequests = new AtomicInteger();
private final AtomicInteger failedRequests = new AtomicInteger();
public WorldRemoteGenerationQueue(NetworkClient networkClient, IDhClientLevel level)
{
this.networkClient = networkClient;
this.level = level;
}
@Override public byte largestDataDetail()
{
return LodUtil.BLOCK_DETAIL_LEVEL;
}
@Override public CompletableFuture<WorldGenResult> submitGenTask(DhLodPos lodPos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
{
LodUtil.assertTrue(lodPos.detailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL, "Only highest-detail sections are allowed.");
DhSectionPos sectionPos = new DhSectionPos(lodPos.detailLevel, lodPos);
totalRequests.incrementAndGet();
WorldGenQueueEntry entry = new WorldGenQueueEntry(new CompletableFuture<>(), tracker);
waitingTasks.put(sectionPos, entry);
return entry.future;
}
@Override public void runCurrentGenTasksUntilBusy(DhBlockPos2D targetPos)
{
if (pendingTasks.get() > MAX_CONCURRENT_REQUESTS || waitingTasks.isEmpty())
return;
DhSectionPos sectionPos = waitingTasks.keySet().stream().reduce(null, (a, b)
-> a != null
&& a.getCenter().getCenterBlockPos().distSquared(targetPos)
< b.getCenter().getCenterBlockPos().distSquared(targetPos)
? a : b);
WorldGenQueueEntry entry = waitingTasks.remove(sectionPos);
pendingTasks.incrementAndGet();
networkClient.<FullDataSourceResponseMessage>sendRequest(new FullDataSourceRequestMessage(sectionPos))
.handle((response, throwable) -> {
pendingTasks.decrementAndGet();
finishedRequests.incrementAndGet();
try
{
if (throwable != null)
throw throwable;
LOGGER.info("FullDataSourceResponseMessage " + sectionPos);
CompleteFullDataSource fullDataSource = response.getFullDataSource(sectionPos, level);
Consumer<ChunkSizedFullDataAccessor> chunkDataConsumer = entry.tracker.getChunkDataConsumer();
sectionPos.forEachChildAtLevel(LodUtil.CHUNK_DETAIL_LEVEL, childPos -> {
ChunkSizedFullDataAccessor accessor = new ChunkSizedFullDataAccessor(new DhChunkPos(childPos.sectionX, childPos.sectionZ));
int detailLevelDifference = sectionPos.sectionDetailLevel - childPos.sectionDetailLevel;
int childRelativeX = childPos.sectionX - sectionPos.sectionX * BitShiftUtil.powerOfTwo(detailLevelDifference);
int childRelativeZ = childPos.sectionZ - sectionPos.sectionZ * BitShiftUtil.powerOfTwo(detailLevelDifference);
fullDataSource.subView(
LodUtil.CHUNK_WIDTH,
childRelativeX * LodUtil.CHUNK_WIDTH,
childRelativeZ * LodUtil.CHUNK_WIDTH
).shadowCopyTo(accessor);
chunkDataConsumer.accept(accessor);
});
return entry.future.complete(WorldGenResult.CreateSuccess(sectionPos));
}
catch (Throwable e)
{
failedRequests.incrementAndGet();
LOGGER.error("Error while fetching full data source", e);
return entry.future.complete(WorldGenResult.CreateFail());
}
});
}
private String[] f3Log()
{
ArrayList<String> lines = new ArrayList<>();
lines.add("World Remote Generation Queue ["+level.getClientLevelWrapper().getDimensionType().getDimensionName()+"]");
lines.add(" Requests: "+this.finishedRequests +" / "+this.totalRequests +" (failed: "+ this.failedRequests+")");
return lines.toArray(new String[0]);
}
@Override public CompletableFuture<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
{
// TODO Cancel generation requests?
return CompletableFuture.completedFuture(null);
}
@Override public void close()
{
f3Message.close();
}
private static class WorldGenQueueEntry
{
public CompletableFuture<WorldGenResult> future;
public IWorldGenTaskTracker tracker;
public WorldGenQueueEntry(CompletableFuture<WorldGenResult> future, IWorldGenTaskTracker tracker)
{
this.future = future;
this.tracker = tracker;
}
}
}
@@ -1,14 +1,17 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering;
import com.seibel.distanthorizons.core.config.AppliedConfigState;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.renderfile.RenderSourceFileHandler;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.generation.WorldRemoteGenerationQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.network.NetworkClient;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
@@ -19,20 +22,21 @@ import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import org.apache.logging.log4j.Logger;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
public class ClientLevelModule implements Closeable {
public class ClientLevelModule {
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private final IDhClientLevel parent;
public final AtomicReference<ClientRenderState> ClientRenderStateRef = new AtomicReference<>();
public final F3Screen.NestedMessage f3Message;
public ClientLevelModule(IDhClientLevel parent)
{
this.parent = parent;
@@ -171,7 +175,7 @@ public class ClientLevelModule implements Closeable {
return CompletableFuture.completedFuture(null);
}
}
public void close()
{
// shutdown the renderer
@@ -1,14 +1,21 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.config.AppliedConfigState;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataFileHandler;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.generation.WorldRemoteGenerationQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.ChildNetworkEventSource;
import com.seibel.distanthorizons.core.network.NetworkClient;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -16,17 +23,34 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.util.concurrent.CompletableFuture;
/** The level used when connected to a server */
public class DhClientLevel extends DhLevel implements IDhClientLevel
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static class WorldGenState extends WorldGenModule.WorldGenState
{
WorldGenState(IDhClientLevel level, NetworkClient client)
{
this.worldGenerationQueue = new WorldRemoteGenerationQueue(client, level);
}
}
public final ClientLevelModule clientside;
public final IClientLevelWrapper levelWrapper;
public final AbstractSaveStructure saveStructure;
public final RemoteFullDataFileHandler dataFileHandler;
private final NetworkClient networkClient;
public final WorldGenModule worldGenModule;
// TODO maybe use some other value?
public final AppliedConfigState<Boolean> worldGeneratorEnabledConfig;
//=============//
// constructor //
@@ -36,7 +60,12 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel
{
this.levelWrapper = clientLevelWrapper;
this.saveStructure = saveStructure;
dataFileHandler = new RemoteFullDataFileHandler(this, saveStructure, networkClient);
this.dataFileHandler = new RemoteFullDataFileHandler(this, saveStructure);
this.networkClient = networkClient;
this.worldGenModule = new WorldGenModule(dataFileHandler, this);
this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration);
clientside = new ClientLevelModule(this);
clientside.startRenderer();
LOGGER.info("Started DHLevel for "+this.levelWrapper+" with saves at "+this.saveStructure);
@@ -52,6 +81,33 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel
chunkToLodBuilder.tick();
clientside.clientTick();
}
public void doWorldGen()
{
worldGeneratorEnabledConfig.pollNewValue();
boolean shouldDoWorldGen = worldGeneratorEnabledConfig.get() && clientside.isRendering();
boolean isWorldGenRunning = worldGenModule.isWorldGenRunning();
if (shouldDoWorldGen && !isWorldGenRunning)
{
// start world gen
worldGenModule.startWorldGen(this.dataFileHandler, new WorldGenState(this, this.networkClient));
}
else if (!shouldDoWorldGen && isWorldGenRunning)
{
// stop world gen
worldGenModule.stopWorldGen(this.dataFileHandler);
}
if (worldGenModule.isWorldGenRunning())
{
ClientLevelModule.ClientRenderState renderState = clientside.ClientRenderStateRef.get();
if (renderState != null && renderState.quadtree != null)
{
dataFileHandler.removeGenRequestIf(p -> !renderState.quadtree.isSectionPosInBounds(p));
}
worldGenModule.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
}
}
@Override
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) {
@@ -92,6 +148,8 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel
@Override
public void close()
{
if (worldGenModule != null)
worldGenModule.close();
clientside.close();
super.close();
dataFileHandler.close();
@@ -117,5 +175,17 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel
public AbstractSaveStructure getSaveStructure() {
return saveStructure;
}
@Override
public void onWorldGenTaskComplete(DhSectionPos pos)
{
//if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(pos, 128f, 156f, 0.09f, Color.red.darker()),
0.2, 32f
)
);
clientside.reloadPos(pos);
}
}
@@ -72,26 +72,26 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS
{
serverside.worldGeneratorEnabledConfig.pollNewValue();
boolean shouldDoWorldGen = serverside.worldGeneratorEnabledConfig.get() && clientside.isRendering();
boolean isWorldGenRunning = serverside.isWorldGenRunning();
boolean isWorldGenRunning = serverside.worldGenModule.isWorldGenRunning();
if (shouldDoWorldGen && !isWorldGenRunning)
{
// start world gen
serverside.startWorldGen();
serverside.worldGenModule.startWorldGen(serverside.dataFileHandler, new ServerLevelModule.WorldGenState(this));
}
else if (!shouldDoWorldGen && isWorldGenRunning)
{
// stop world gen
serverside.stopWorldGen();
serverside.worldGenModule.stopWorldGen(serverside.dataFileHandler);
}
if (serverside.isWorldGenRunning())
if (serverside.worldGenModule.isWorldGenRunning())
{
ClientLevelModule.ClientRenderState renderState = clientside.ClientRenderStateRef.get();
if (renderState != null && renderState.quadtree != null)
{
serverside.dataFileHandler.removeGenRequestIf(p -> !renderState.quadtree.isSectionPosInBounds(p));
}
serverside.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
serverside.worldGenModule.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
}
}
@@ -4,9 +4,6 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedF
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.network.messages.FullDataSourceRequestMessage;
import com.seibel.distanthorizons.core.network.messages.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
@@ -69,25 +66,25 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel
public void doWorldGen()
{
boolean shouldDoWorldGen = true; //todo;
boolean isWorldGenRunning = serverside.isWorldGenRunning();
boolean isWorldGenRunning = serverside.worldGenModule.isWorldGenRunning();
if (shouldDoWorldGen && !isWorldGenRunning)
{
// start world gen
serverside.startWorldGen();
serverside.worldGenModule.startWorldGen(serverside.dataFileHandler, new ServerLevelModule.WorldGenState(this));
}
else if (!shouldDoWorldGen && isWorldGenRunning)
{
// stop world gen
serverside.stopWorldGen();
serverside.worldGenModule.stopWorldGen(serverside.dataFileHandler);
}
}
public void doWorldGen(DhBlockPos2D pos) {
this.doWorldGen();
if (serverside.isWorldGenRunning())
if (serverside.worldGenModule.isWorldGenRunning())
{
serverside.worldGenTick(pos);
serverside.worldGenModule.worldGenTick(pos);
}
}
@@ -7,7 +7,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrap
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
public interface IDhClientLevel extends IDhLevel
public interface IDhClientLevel extends IDhWorldGenLevel
{
void clientTick();
@@ -3,11 +3,11 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
public interface IDhServerLevel extends IDhLevel, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener
public interface IDhServerLevel extends IDhWorldGenLevel, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener
{
void serverTick();
void doWorldGen();
IServerLevelWrapper getServerLevelWrapper();
}
@@ -0,0 +1,9 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler;
public interface IDhWorldGenLevel extends IDhLevel, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener
{
void doWorldGen();
}
@@ -17,9 +17,8 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
public class ServerLevelModule {
private static class WorldGenState
public static class WorldGenState extends WorldGenModule.WorldGenState
{
public final WorldGenerationQueue worldGenerationQueue;
WorldGenState(IDhServerLevel level)
{
IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper());
@@ -33,26 +32,6 @@ public class ServerLevelModule {
}
this.worldGenerationQueue = new WorldGenerationQueue(worldGenerator);
}
CompletableFuture<Void> closeAsync(boolean doInterrupt)
{
return this.worldGenerationQueue.startClosing(true, doInterrupt)
.exceptionally(ex ->
{
LOGGER.error("Error closing generation queue", ex);
return null;
}
).thenRun(this.worldGenerationQueue::close)
.exceptionally(ex ->
{
LOGGER.error("Error closing world gen", ex);
return null;
});
}
public void tick(DhBlockPos2D targetPosForGeneration) {
worldGenerationQueue.runCurrentGenTasksUntilBusy(targetPosForGeneration);
}
}
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -61,7 +40,7 @@ public class ServerLevelModule {
public final AbstractSaveStructure saveStructure;
public final GeneratedFullDataFileHandler dataFileHandler;
public final AppliedConfigState<Boolean> worldGeneratorEnabledConfig;
private final AtomicReference<WorldGenState> worldGenStateRef = new AtomicReference<>();
public final WorldGenModule worldGenModule;
public ServerLevelModule(IDhServerLevel parent, IServerLevelWrapper levelWrapper, AbstractSaveStructure saveStructure)
{
@@ -70,62 +49,7 @@ public class ServerLevelModule {
this.saveStructure = saveStructure;
this.dataFileHandler = new GeneratedFullDataFileHandler(parent, saveStructure);
this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration);
}
//==============//
// tick methods //
//==============//
public void startWorldGen()
{
// create the new world generator
WorldGenState newWgs = new WorldGenState(parent);
if (!this.worldGenStateRef.compareAndSet(null, newWgs))
{
LOGGER.warn("Failed to start world gen due to concurrency");
newWgs.closeAsync(false);
}
dataFileHandler.addWorldGenCompleteListener(parent);
dataFileHandler.setGenerationQueue(newWgs.worldGenerationQueue);
}
public void stopWorldGen()
{
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
LOGGER.warn("Attempted to stop world gen when it was not running");
return;
}
// shut down the world generator
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
return;
}
}
dataFileHandler.clearGenerationQueue();
worldGenState.closeAsync(true).join(); //TODO: Make it async.
dataFileHandler.removeWorldGenCompleteListener(parent);
}
public boolean isWorldGenRunning()
{
return this.worldGenStateRef.get() != null;
}
public void worldGenTick(DhBlockPos2D targetPosForGeneration)
{
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
// queue new world generation requests
worldGenState.tick(targetPosForGeneration);
}
this.worldGenModule = new WorldGenModule(this.dataFileHandler, this.parent);
}
//===============//
@@ -134,23 +58,7 @@ public class ServerLevelModule {
public void close()
{
// shutdown the world-gen
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
break;
}
}
if (worldGenState != null)
{
worldGenState.closeAsync(true).join(); //TODO: Make it async.
}
}
this.worldGenModule.close();
dataFileHandler.close();
}
@@ -0,0 +1,125 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler;
import com.seibel.distanthorizons.core.generation.IWorldGenerationQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import org.apache.logging.log4j.Logger;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
public class WorldGenModule implements Closeable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final GeneratedFullDataFileHandler dataFileHandler;
private final GeneratedFullDataFileHandler.IOnWorldGenCompleteListener onWorldGenCompleteListener;
private final AtomicReference<WorldGenState> worldGenStateRef = new AtomicReference<>();
public static abstract class WorldGenState
{
public IWorldGenerationQueue worldGenerationQueue;
CompletableFuture<Void> closeAsync(boolean doInterrupt)
{
return this.worldGenerationQueue.startClosing(true, doInterrupt)
.exceptionally(ex ->
{
LOGGER.error("Error closing generation queue", ex);
return null;
}
).thenRun(this.worldGenerationQueue::close)
.exceptionally(ex ->
{
LOGGER.error("Error closing world gen", ex);
return null;
});
}
public void tick(DhBlockPos2D targetPosForGeneration)
{
worldGenerationQueue.runCurrentGenTasksUntilBusy(targetPosForGeneration);
}
}
public WorldGenModule(GeneratedFullDataFileHandler dataFileHandler, GeneratedFullDataFileHandler.IOnWorldGenCompleteListener onWorldGenCompleteListener)
{
this.dataFileHandler = dataFileHandler;
this.onWorldGenCompleteListener = onWorldGenCompleteListener;
}
public void startWorldGen(GeneratedFullDataFileHandler dataFileHandler, WorldGenState newWgs)
{
// create the new world generator
if (!this.worldGenStateRef.compareAndSet(null, newWgs))
{
LOGGER.warn("Failed to start world gen due to concurrency");
newWgs.closeAsync(false);
}
dataFileHandler.addWorldGenCompleteListener(onWorldGenCompleteListener);
dataFileHandler.setGenerationQueue(newWgs.worldGenerationQueue);
}
public void stopWorldGen(GeneratedFullDataFileHandler dataFileHandler)
{
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
LOGGER.warn("Attempted to stop world gen when it was not running");
return;
}
// shut down the world generator
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
return;
}
}
dataFileHandler.clearGenerationQueue();
worldGenState.closeAsync(true).join(); //TODO: Make it async.
dataFileHandler.removeWorldGenCompleteListener(onWorldGenCompleteListener);
}
public boolean isWorldGenRunning()
{
return this.worldGenStateRef.get() != null;
}
public void worldGenTick(DhBlockPos2D targetPosForGeneration)
{
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
// queue new world generation requests
worldGenState.tick(targetPosForGeneration);
}
}
public void close()
{
// shutdown the world-gen
WorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
{
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
break;
}
}
if (worldGenState != null)
{
worldGenState.closeAsync(true).join(); //TODO: Make it async.
}
}
dataFileHandler.close();
}
}
@@ -1,43 +0,0 @@
package com.seibel.distanthorizons.core.network;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import io.netty.channel.ChannelHandlerContext;
import java.util.function.BiConsumer;
/** Provides a way to register network message handlers which are expected to be removed later. */
public final class ChildNetworkEventSource<TParent extends NetworkEventSource> extends NetworkEventSource
{
private final TParent parent;
private boolean isClosed = false;
public ChildNetworkEventSource(TParent parent)
{
this.parent = parent;
}
public ChildNetworkEventSource(ChildNetworkEventSource<TParent> child)
{
this(child.parent);
}
@Override public <T extends INetworkMessage> void registerHandler(Class<T> handlerClass, BiConsumer<T, ChannelHandlerContext> handlerImplementation)
{
if (isClosed) return;
if (!this.hasHandler(handlerClass))
{
parent.registerHandler(handlerClass, this::handleMessage);
}
super.registerHandler(handlerClass, handlerImplementation);
}
@Override public void close()
{
isClosed = true;
for (Class<? extends INetworkMessage> handlerClass : this.handlers.keySet())
{
parent.removeHandler(handlerClass, this::handleMessage);
}
}
}
@@ -1,12 +1,14 @@
package com.seibel.distanthorizons.core.network;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.AckMessage;
import com.seibel.distanthorizons.core.network.messages.CloseMessage;
import com.seibel.distanthorizons.core.network.messages.CloseReasonMessage;
import com.seibel.distanthorizons.core.network.messages.HelloMessage;
import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.MessageHandler;
import com.seibel.distanthorizons.core.network.protocol.NetworkChannelInitializer;
import com.seibel.distanthorizons.core.network.protocol.NetworkMessage;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
@@ -59,20 +61,15 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable
private void registerHandlers()
{
this.registerAckHandler(HelloMessage.class, channelContext ->
{
LOGGER.info("Connected to server: "+channelContext.channel().remoteAddress());
});
this.registerHandler(CloseReasonMessage.class, (closeReasonMessage, channelContext) ->
this.registerHandler(CloseReasonMessage.class, closeReasonMessage ->
{
LOGGER.info(closeReasonMessage.reason);
this.connectionState = EConnectionState.CLOSE_WAIT;
});
this.registerHandler(CloseMessage.class, (closeMessage, channelContext) ->
this.registerHandler(CloseMessage.class, closeMessage ->
{
LOGGER.info("Disconnected from server: "+channelContext.channel().remoteAddress());
LOGGER.info("Disconnected from server: "+closeMessage.getChannelContext().channel().remoteAddress());
if (this.connectionState == EConnectionState.CLOSE_WAIT)
{
this.close();
@@ -90,13 +87,13 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable
ChannelFuture connectFuture = this.clientBootstrap.connect(this.address);
connectFuture.addListener((ChannelFuture channelFuture) ->
{
if (!channelFuture.isSuccess())
if (!channelFuture.isSuccess())
{
LOGGER.warn("Connection failed: "+channelFuture.cause());
return;
}
LOGGER.warn("Connection failed: "+channelFuture.cause());
return;
}
this.channel.writeAndFlush(new HelloMessage());
channel.writeAndFlush(new HelloMessage());
});
this.channel = connectFuture.channel();
@@ -129,13 +126,6 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable
}
});
}
/** Kills the current connection, triggering auto-reconnection immediately. */
public void reconnect()
{
this.connectionState = EConnectionState.RECONNECT_FORCE;
this.channel.disconnect();
}
public final <TResponse extends FutureTrackableNetworkMessage> CompletableFuture<TResponse> sendRequest(FutureTrackableNetworkMessage msg)
{
@@ -5,38 +5,32 @@ import com.google.common.collect.Table;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.AckMessage;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import io.netty.channel.Channel;
import com.seibel.distanthorizons.core.network.protocol.NetworkMessage;
import io.netty.channel.ChannelHandlerContext;
import org.apache.logging.log4j.Logger;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public abstract class NetworkEventSource
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
protected final Map<Class<? extends INetworkMessage>, Set<BiConsumer<INetworkMessage, ChannelHandlerContext>>> handlers = new HashMap<>();
protected final Map<Class<? extends NetworkMessage>, Set<Consumer<NetworkMessage>>> handlers = new HashMap<>();
private final Table<ChannelHandlerContext, Integer, CompletableFuture<FutureTrackableNetworkMessage>> pendingFutures = HashBasedTable.create();
protected boolean hasHandler(Class<? extends INetworkMessage> handlerClass)
{
return this.handlers.containsKey(handlerClass);
}
protected void handleMessage(INetworkMessage message, ChannelHandlerContext channelContext)
protected final void handleMessage(NetworkMessage message, ChannelHandlerContext channelContext)
{
message.setChannelContext(channelContext);
boolean handled = false;
Set<BiConsumer<INetworkMessage, ChannelHandlerContext>> handlerList = this.handlers.get(message.getClass());
Set<Consumer<NetworkMessage>> handlerList = this.handlers.get(message.getClass());
if (handlerList != null)
{
for (BiConsumer<INetworkMessage, ChannelHandlerContext> handler : handlerList)
for (Consumer<NetworkMessage> handler : handlerList)
{
handled = true;
handler.accept(message, channelContext);
handler.accept(message);
}
}
@@ -57,27 +51,10 @@ public abstract class NetworkEventSource
}
}
public <T extends INetworkMessage> void registerHandler(Class<T> handlerClass, BiConsumer<T, ChannelHandlerContext> handlerImplementation)
public <T extends NetworkMessage> void registerHandler(Class<T> handlerClass, Consumer<T> handlerImplementation)
{
this.handlers.computeIfAbsent(handlerClass, missingHandlerClass -> new HashSet<>())
.add((BiConsumer<INetworkMessage, ChannelHandlerContext>) handlerImplementation);
}
public <T extends INetworkMessage> void registerAckHandler(Class<T> clazz, Consumer<ChannelHandlerContext> handler)
{
this.registerHandler(AckMessage.class, (ackMessage, channelContext) ->
{
if (ackMessage.messageType == clazz)
{
handler.accept(channelContext);
}
});
}
protected <T extends INetworkMessage> void removeHandler(Class<T> handlerClass, BiConsumer<T, ChannelHandlerContext> handlerImplementation)
{
this.handlers.computeIfAbsent(handlerClass, missingHandlerClass -> new HashSet<>())
.remove(handlerImplementation);
.add((Consumer<NetworkMessage>) handlerImplementation);
}
protected <TResponse extends FutureTrackableNetworkMessage> CompletableFuture<TResponse> sendRequest(ChannelHandlerContext ctx, FutureTrackableNetworkMessage msg)
@@ -93,13 +70,13 @@ public abstract class NetworkEventSource
return responseFuture;
}
protected void completeAllFuturesExceptionally(ChannelHandlerContext ctx, Throwable cause) {
protected final void completeAllFuturesExceptionally(ChannelHandlerContext ctx, Throwable cause) {
for (CompletableFuture<FutureTrackableNetworkMessage> futureData : pendingFutures.row(ctx).values())
futureData.completeExceptionally(cause);
pendingFutures.row(ctx).clear();
}
protected void completeAllFuturesExceptionally(Throwable cause) {
protected final void completeAllFuturesExceptionally(Throwable cause) {
for (ChannelHandlerContext ctx : pendingFutures.rowKeySet())
this.completeAllFuturesExceptionally(ctx, cause);
}
@@ -44,8 +44,9 @@ public class NetworkServer extends NetworkEventSource implements AutoCloseable
private void registerHandlers()
{
this.registerHandler(HelloMessage.class, (helloMessage, channelContext) ->
this.registerHandler(HelloMessage.class, helloMessage ->
{
ChannelHandlerContext channelContext = helloMessage.getChannelContext();
LOGGER.info("Client connected: "+channelContext.channel().remoteAddress());
if (helloMessage.version != ModInfo.PROTOCOL_VERSION)
@@ -63,13 +64,15 @@ public class NetworkServer extends NetworkEventSource implements AutoCloseable
return;
}
channelContext.writeAndFlush(new AckMessage(HelloMessage.class));
channelContext.writeAndFlush(new HelloMessage());
});
this.registerHandler(CloseMessage.class, (closeMessage, channelContext) ->
this.registerHandler(CloseMessage.class, closeMessage ->
{
LOGGER.info("Client disconnected: "+channelContext.channel().remoteAddress());
this.completeAllFuturesExceptionally(channelContext, channelContext.channel().closeFuture().cause());
Channel channel = closeMessage.getChannelContext().channel();
LOGGER.info("Client disconnected: "+channel.remoteAddress());
this.completeAllFuturesExceptionally(closeMessage.getChannelContext(), channel.closeFuture().cause());
});
}
@@ -1,6 +1,7 @@
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.NetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.MessageRegistry;
import io.netty.buffer.ByteBuf;
@@ -8,19 +9,14 @@ import io.netty.buffer.ByteBuf;
* Simple empty response message.
* This message is not sent automatically.
*/
public class AckMessage implements INetworkMessage
public class AckMessage extends FutureTrackableNetworkMessage
{
public Class<? extends INetworkMessage> messageType;
public AckMessage() { }
public AckMessage(Class<? extends INetworkMessage> messageType) { this.messageType = messageType; }
@Override
public void encode(ByteBuf out) { out.writeInt(MessageRegistry.INSTANCE.getMessageId(this.messageType)); }
public void encode0(ByteBuf out) { }
@Override
public void decode(ByteBuf in) { this.messageType = MessageRegistry.INSTANCE.getMessageClassById(in.readInt()); }
public void decode0(ByteBuf in) { }
}
@@ -1,13 +1,13 @@
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.NetworkMessage;
import io.netty.buffer.ByteBuf;
/**
* This is not a "real" message, and only used to indicate a disconnection.
* To send a "disconnect reason" message, use {@link CloseReasonMessage}.
*/
public class CloseMessage implements INetworkMessage
public class CloseMessage extends NetworkMessage
{
@Override
public void encode(ByteBuf out) { throw new UnsupportedOperationException("CloseMessage is not a real message, and must not be sent."); }
@@ -1,10 +1,10 @@
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.NetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.INetworkObject;
import io.netty.buffer.ByteBuf;
public class CloseReasonMessage implements INetworkMessage
public class CloseReasonMessage extends NetworkMessage
{
public String reason;
@@ -1,11 +1,12 @@
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.dataObjects.fullData.loader.AbstractFullDataSourceLoader;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
import com.seibel.distanthorizons.core.dataObjects.fullData.loader.CompleteFullDataSourceLoader;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.level.DhServerLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import io.netty.buffer.ByteBuf;
@@ -16,14 +17,14 @@ import java.io.IOException;
public class FullDataSourceResponseMessage extends FutureTrackableNetworkMessage
{
private IFullDataSource fullDataSource;
private CompleteFullDataSource fullDataSource;
private DhServerLevel level;
private AbstractFullDataSourceLoader fullDataSourceLoader;
private CompleteFullDataSourceLoader fullDataSourceLoader;
private ByteBuf dataBuffer;
public FullDataSourceResponseMessage() {}
public FullDataSourceResponseMessage(IFullDataSource fullDataSource, DhServerLevel level)
public FullDataSourceResponseMessage(CompleteFullDataSource fullDataSource, DhServerLevel level)
{
this.fullDataSource = fullDataSource;
this.level = level;
@@ -38,7 +39,6 @@ public class FullDataSourceResponseMessage extends FutureTrackableNetworkMessage
fullDataSource.writeToStream(dhOutputStream, level);
dhOutputStream.flush();
out.writeLong(fullDataSource.getTypeId());
out.writeByte(fullDataSource.getBinaryDataFormatVersion());
out.writeInt(outputStream.size());
out.writeBytes(outputStream.toByteArray());
@@ -46,25 +46,21 @@ public class FullDataSourceResponseMessage extends FutureTrackableNetworkMessage
}
@Override
public void decode0(ByteBuf in) throws IOException
public void decode0(ByteBuf in)
{
long typeId = in.readLong();
byte dataVersion = in.readByte();
this.fullDataSourceLoader = AbstractFullDataSourceLoader.getLoader(typeId, dataVersion);
if (this.fullDataSourceLoader == null)
{
throw new IOException("Invalid file: Data type loader not found: "+typeId+"(v"+dataVersion +")");
}
this.fullDataSourceLoader = (CompleteFullDataSourceLoader) AbstractFullDataSourceLoader.getLoader(CompleteFullDataSource.TYPE_ID, dataVersion);
assert this.fullDataSourceLoader != null;
this.dataBuffer = in.readBytes(in.readInt());
}
public IFullDataSource getFullDataSource(FullDataMetaFile metaFile, IDhLevel level) throws IOException, InterruptedException
public CompleteFullDataSource getFullDataSource(DhSectionPos pos, IDhLevel level) throws IOException, InterruptedException
{
try (ByteBufInputStream inputStream = new ByteBufInputStream(dataBuffer))
{
return fullDataSourceLoader.loadData(metaFile, new DhDataInputStream(inputStream), level);
return fullDataSourceLoader.loadData(pos, new DhDataInputStream(inputStream), level);
}
}
}
@@ -1,10 +1,10 @@
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.NetworkMessage;
import com.seibel.distanthorizons.coreapi.ModInfo;
import io.netty.buffer.ByteBuf;
public class HelloMessage implements INetworkMessage
public class HelloMessage extends NetworkMessage
{
public int version = ModInfo.PROTOCOL_VERSION;
@@ -1,11 +1,12 @@
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.NetworkMessage;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
public class PlayerUUIDMessage implements INetworkMessage
public class PlayerUUIDMessage extends FutureTrackableNetworkMessage
{
public UUID playerUUID;
@@ -15,13 +16,13 @@ public class PlayerUUIDMessage implements INetworkMessage
public PlayerUUIDMessage(UUID playerUUID) { this.playerUUID = playerUUID; }
@Override
public void encode(ByteBuf out)
public void encode0(ByteBuf out)
{
out.writeLong(this.playerUUID.getMostSignificantBits());
out.writeLong(this.playerUUID.getLeastSignificantBits());
}
@Override
public void decode(ByteBuf in) { this.playerUUID = new UUID(in.readLong(), in.readLong()); }
public void decode0(ByteBuf in) { this.playerUUID = new UUID(in.readLong(), in.readLong()); }
}
@@ -1,11 +1,12 @@
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.INetworkObject;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.NetworkMessage;
import com.seibel.distanthorizons.core.network.objects.RemotePlayer;
import io.netty.buffer.ByteBuf;
public class RemotePlayerConfigMessage implements INetworkMessage
public class RemotePlayerConfigMessage extends FutureTrackableNetworkMessage
{
public RemotePlayer.Payload payload;
@@ -15,9 +16,9 @@ public class RemotePlayerConfigMessage implements INetworkMessage
public RemotePlayerConfigMessage(RemotePlayer.Payload payload) { this.payload = payload; }
@Override
public void encode(ByteBuf out) { this.payload.encode(out); }
public void encode0(ByteBuf out) { this.payload.encode(out); }
@Override
public void decode(ByteBuf in) { this.payload = INetworkObject.decode(new RemotePlayer.Payload(), in); }
public void decode0(ByteBuf in) { this.payload = INetworkObject.decode(new RemotePlayer.Payload(), in); }
}
@@ -3,15 +3,15 @@ package com.seibel.distanthorizons.core.network.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
public abstract class FutureTrackableNetworkMessage implements INetworkMessage
public abstract class FutureTrackableNetworkMessage extends NetworkMessage
{
private static int lastId = 0;
public int futureId = lastId++;
public static void sendResponse(ChannelHandlerContext ctx, FutureTrackableNetworkMessage requestMessage, FutureTrackableNetworkMessage responseMessage)
public void sendResponse(FutureTrackableNetworkMessage responseMessage)
{
responseMessage.futureId = requestMessage.futureId;
ctx.writeAndFlush(responseMessage);
responseMessage.futureId = futureId;
getChannelContext().writeAndFlush(responseMessage);
}
@Override public final void encode(ByteBuf out)
@@ -1,8 +0,0 @@
package com.seibel.distanthorizons.core.network.protocol;
/** For now this is only used for constraining listeners */
public interface INetworkMessage extends INetworkObject
{
}
@@ -11,7 +11,7 @@ public class MessageDecoder extends ByteToMessageDecoder
@Override
protected void decode(ChannelHandlerContext channelContext, ByteBuf inputByteBuf, List<Object> outputDecodedObjectList)
{
INetworkMessage message = MessageRegistry.INSTANCE.createMessage(inputByteBuf.readShort());
NetworkMessage message = MessageRegistry.INSTANCE.createMessage(inputByteBuf.readShort());
outputDecodedObjectList.add(INetworkObject.decode(message, inputByteBuf));
}
@@ -4,10 +4,10 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MessageEncoder extends MessageToByteEncoder<INetworkMessage>
public class MessageEncoder extends MessageToByteEncoder<NetworkMessage>
{
@Override
protected void encode(ChannelHandlerContext channelContext, INetworkMessage message, ByteBuf outputByteBuf) throws IllegalArgumentException
protected void encode(ChannelHandlerContext channelContext, NetworkMessage message, ByteBuf outputByteBuf) throws IllegalArgumentException
{
outputByteBuf.writeShort(MessageRegistry.INSTANCE.getMessageId(message));
message.encode(outputByteBuf);
@@ -8,26 +8,22 @@ import io.netty.channel.SimpleChannelInboundHandler;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
@ChannelHandler.Sharable
public class MessageHandler extends SimpleChannelInboundHandler<INetworkMessage>
public class MessageHandler extends SimpleChannelInboundHandler<NetworkMessage>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final BiConsumer<INetworkMessage, ChannelHandlerContext> messageConsumer;
private final BiConsumer<NetworkMessage, ChannelHandlerContext> messageConsumer;
public MessageHandler(BiConsumer<INetworkMessage, ChannelHandlerContext> messageConsumer)
public MessageHandler(BiConsumer<NetworkMessage, ChannelHandlerContext> messageConsumer)
{
this.messageConsumer = messageConsumer;
}
@Override
protected void channelRead0(ChannelHandlerContext channelContext, INetworkMessage message)
protected void channelRead0(ChannelHandlerContext channelContext, NetworkMessage message)
{
LOGGER.trace("Received message: " + message.getClass().getSimpleName());
this.messageConsumer.accept(message, channelContext);
@@ -12,8 +12,8 @@ public class MessageRegistry
{
public static final MessageRegistry INSTANCE = new MessageRegistry();
private final Map<Integer, Supplier<? extends INetworkMessage>> idToSupplier = new HashMap<>();
private final BiMap<Class<? extends INetworkMessage>, Integer> classToId = HashBiMap.create();
private final Map<Integer, Supplier<? extends NetworkMessage>> idToSupplier = new HashMap<>();
private final BiMap<Class<? extends NetworkMessage>, Integer> classToId = HashBiMap.create();
@@ -35,16 +35,16 @@ public class MessageRegistry
public <T extends INetworkMessage> void registerMessage(Class<T> clazz, Supplier<T> supplier)
public <T extends NetworkMessage> void registerMessage(Class<T> clazz, Supplier<T> supplier)
{
int id = this.idToSupplier.size() + 1;
this.idToSupplier.put(id, supplier);
this.classToId.put(clazz, id);
}
public Class<? extends INetworkMessage> getMessageClassById(int messageId) { return this.classToId.inverse().get(messageId); }
public Class<? extends NetworkMessage> getMessageClassById(int messageId) { return this.classToId.inverse().get(messageId); }
public INetworkMessage createMessage(int messageId) throws IllegalArgumentException
public NetworkMessage createMessage(int messageId) throws IllegalArgumentException
{
try
{
@@ -56,9 +56,9 @@ public class MessageRegistry
}
}
public int getMessageId(INetworkMessage message) { return this.getMessageId(message.getClass()); }
public int getMessageId(NetworkMessage message) { return this.getMessageId(message.getClass()); }
public int getMessageId(Class<? extends INetworkMessage> messageClass) {
public int getMessageId(Class<? extends NetworkMessage> messageClass) {
try
{
return this.classToId.get(messageClass);
@@ -0,0 +1,21 @@
package com.seibel.distanthorizons.core.network.protocol;
import io.netty.channel.ChannelHandlerContext;
public abstract class NetworkMessage implements INetworkObject
{
private ChannelHandlerContext channelContext = null;
public ChannelHandlerContext getChannelContext()
{
return channelContext;
}
public void setChannelContext(ChannelHandlerContext channelContext)
{
if (this.channelContext != null)
throw new IllegalStateException("Channel context cannot be changed after initial setting.");
this.channelContext = channelContext;
}
}
@@ -4,8 +4,10 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.network.NetworkClient;
import com.seibel.distanthorizons.core.network.messages.AckMessage;
import com.seibel.distanthorizons.core.network.messages.HelloMessage;
import com.seibel.distanthorizons.core.network.messages.PlayerUUIDMessage;
import com.seibel.distanthorizons.core.network.messages.RemotePlayerConfigMessage;
@@ -62,26 +64,20 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
}
private void registerNetworkHandlers() {
this.networkClient.registerAckHandler(HelloMessage.class, ctx ->
{
ctx.writeAndFlush(new PlayerUUIDMessage(MC_CLIENT.getPlayerUUID()));
});
// TODO Proper payload handling
this.networkClient.registerAckHandler(PlayerUUIDMessage.class, ctx ->
{
ctx.writeAndFlush(new RemotePlayerConfigMessage(new RemotePlayer.Payload()));
});
this.networkClient.registerHandler(RemotePlayerConfigMessage.class, (msg, ctx) ->
{
});
this.networkClient.registerAckHandler(RemotePlayerConfigMessage.class, ctx ->
{
// TODO Actually request chunks
// ctx.writeAndFlush(new ChunkRequestMessage(new DhSectionPos(new DhBlockPos2D(0, 0))));
});
this.networkClient.registerHandler(HelloMessage.class, helloMessage ->
{
LOGGER.info("Connected to server: "+helloMessage.getChannelContext().channel().remoteAddress());
this.networkClient.sendRequest(new PlayerUUIDMessage(MC_CLIENT.getPlayerUUID()))
.thenCompose(ack -> this.networkClient.<RemotePlayerConfigMessage>sendRequest(new RemotePlayerConfigMessage(new RemotePlayer.Payload())))
.thenAccept(responseConfigMsg -> {
// TODO do something with received config
})
.exceptionally(throwable -> {
LOGGER.error("Error while fetching server's config", throwable);
return null;
});
});
}
@@ -146,6 +142,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
}
public void clientTick() { this.eventLoop.tick(); }
public void doWorldGen() { this.levels.values().forEach(DhClientLevel::doWorldGen); }
@Override
public CompletableFuture<Void> saveAndFlush()
@@ -2,7 +2,10 @@ package com.seibel.distanthorizons.core.world;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler;
import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
@@ -20,11 +23,10 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import io.netty.channel.ChannelHandlerContext;
import javax.annotation.CheckForNull;
import java.io.File;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.*;
import java.util.concurrent.*;
public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
{
@@ -34,7 +36,9 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
private final NetworkServer networkServer;
private final HashMap<UUID, RemotePlayer> playersByUUID;
private final BiMap<ChannelHandlerContext, RemotePlayer> playersByConnection;
private final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenLoopingQueue = new ConcurrentLinkedQueue<>();
private ConcurrentMap<DhSectionPos, IncompleteDataSourceEntry> incompleteDataSources = new ConcurrentHashMap<>();
@@ -56,17 +60,18 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
private void registerNetworkHandlers()
{
this.networkServer.registerHandler(CloseMessage.class, (closeMessage, channelContext) ->
this.networkServer.registerHandler(CloseMessage.class, closeMessage ->
{
RemotePlayer dhPlayer = this.playersByConnection.remove(channelContext);
RemotePlayer dhPlayer = this.playersByConnection.remove(closeMessage.getChannelContext());
if (dhPlayer != null)
{
dhPlayer.channelContext = null;
}
});
this.networkServer.registerHandler(PlayerUUIDMessage.class, (playerUUIDMessage, channelContext) ->
this.networkServer.registerHandler(PlayerUUIDMessage.class, playerUUIDMessage ->
{
ChannelHandlerContext channelContext = playerUUIDMessage.getChannelContext();
RemotePlayer dhPlayer = this.playersByUUID.get(playerUUIDMessage.playerUUID);
if (dhPlayer == null)
@@ -84,27 +89,30 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
dhPlayer.channelContext = channelContext;
this.playersByConnection.put(channelContext, dhPlayer);
channelContext.writeAndFlush(new AckMessage(PlayerUUIDMessage.class));
playerUUIDMessage.sendResponse(new AckMessage());
});
this.networkServer.registerHandler(RemotePlayerConfigMessage.class, (dhRemotePlayerConfigMessage, channelContext) ->
this.networkServer.registerHandler(RemotePlayerConfigMessage.class, remotePlayerConfigMessage ->
{
// TODO Take notice of received payload and possibly echo back a constrained version
channelContext.writeAndFlush(new AckMessage(RemotePlayerConfigMessage.class));
// TODO Take notice of and/or constrain received payload
remotePlayerConfigMessage.sendResponse(remotePlayerConfigMessage);
});
// This should be at DhServerLevel I guess
this.networkServer.registerHandler(FullDataSourceRequestMessage.class, (msg, ctx) ->
this.networkServer.registerHandler(FullDataSourceRequestMessage.class, msg ->
{
LOGGER.info("FullDataSourceRequestMessage received at pos ({}, {}) with detail level {}", msg.dhSectionPos.sectionX, msg.dhSectionPos.sectionZ, msg.dhSectionPos.sectionDetailLevel);
DhServerLevel level = this.getLevel(playersByConnection.get(ctx).serverPlayer.getLevel());
DhServerLevel level = this.getLevel(playersByConnection.get(msg.getChannelContext()).serverPlayer.getLevel());
GeneratedFullDataFileHandler handler = level.serverside.dataFileHandler;
handler.read(msg.dhSectionPos).thenAccept(fullDataSource -> {
// Send chunk response message back
FutureTrackableNetworkMessage.sendResponse(ctx, msg, new FullDataSourceResponseMessage(fullDataSource, level));
});
incompleteDataSources.computeIfAbsent(msg.dhSectionPos, pos -> {
IncompleteDataSourceEntry entry = new IncompleteDataSourceEntry();
handler.read(msg.dhSectionPos).thenAccept(fullDataSource -> {
entry.fullDataSource = fullDataSource;
});
return entry;
}).requestMessages.add(msg);
});
}
@@ -169,21 +177,50 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
}
}
public void serverTick() { this.levels.values().forEach(DhServerLevel::serverTick); }
public void doWorldGen() { this.levels.values().forEach(level -> {
// TODO Deal with dimensions and dimension switches
public void serverTick() {
this.levels.values().forEach(DhServerLevel::serverTick);
IServerPlayerWrapper firstPlayer = this.worldGenLoopingQueue.poll();
if (firstPlayer == null) {
level.doWorldGen();
return;
for (Iterator<IncompleteDataSourceEntry> it = incompleteDataSources.values().iterator(); it.hasNext(); )
{
IncompleteDataSourceEntry entry = it.next();
if (entry.fullDataSource instanceof IIncompleteFullDataSource)
{
IIncompleteFullDataSource incompleteSource = (IIncompleteFullDataSource) entry.fullDataSource;
if (!incompleteSource.hasBeenPromoted()) continue;
entry.fullDataSource = incompleteSource.tryPromotingToCompleteDataSource();
}
if (!(entry.fullDataSource instanceof CompleteFullDataSource))
LodUtil.assertNotReach("Invalid full data source");
it.remove();
CompleteFullDataSource completeSource = (CompleteFullDataSource) entry.fullDataSource;
for (FullDataSourceRequestMessage msg : entry.requestMessages)
{
RemotePlayer remotePlayer = playersByConnection.get(msg.getChannelContext());
if (remotePlayer == null) continue;
DhServerLevel level = this.getLevel(remotePlayer.serverPlayer.getLevel());
msg.sendResponse(new FullDataSourceResponseMessage(completeSource, level));
}
}
this.worldGenLoopingQueue.add(firstPlayer);
com.seibel.distanthorizons.coreapi.util.math.Vec3d position = firstPlayer.getPosition();
level.doWorldGen(new DhBlockPos2D((int) position.x, (int) position.z));
}); }
}
public void doWorldGen() {
this.levels.values().forEach(level -> {
// TODO Deal with dimensions and dimension switches
IServerPlayerWrapper firstPlayer = this.worldGenLoopingQueue.poll();
if (firstPlayer == null) {
level.doWorldGen();
return;
}
this.worldGenLoopingQueue.add(firstPlayer);
com.seibel.distanthorizons.coreapi.util.math.Vec3d position = firstPlayer.getPosition();
level.doWorldGen(new DhBlockPos2D((int) position.x, (int) position.z));
});
}
@Override
public CompletableFuture<Void> saveAndFlush()
@@ -206,4 +243,11 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
LOGGER.info("Closed DhWorld of type "+this.environment);
}
private static class IncompleteDataSourceEntry
{
@CheckForNull
public IFullDataSource fullDataSource;
public Set<FullDataSourceRequestMessage> requestMessages = ConcurrentHashMap.newKeySet();
}
}
@@ -7,6 +7,8 @@ public interface IDhClientWorld extends IDhWorld
{
void clientTick();
void doWorldGen();
default IDhClientLevel getOrLoadClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getOrLoadLevel(levelWrapper); }
}