World generation
This commit is contained in:
@@ -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; }
|
||||
|
||||
+8
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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));
|
||||
}
|
||||
|
||||
+6
-5
@@ -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
-79
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -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();
|
||||
}
|
||||
+2
-1
@@ -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;
|
||||
|
||||
|
||||
+158
@@ -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();
|
||||
}
|
||||
}
|
||||
-43
@@ -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)
|
||||
{
|
||||
|
||||
+11
-34
@@ -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) { }
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -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."); }
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
|
||||
+11
-15
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
|
||||
+5
-4
@@ -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()); }
|
||||
|
||||
}
|
||||
|
||||
+5
-4
@@ -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); }
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -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)
|
||||
|
||||
-8
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+4
-8
@@ -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);
|
||||
|
||||
+7
-7
@@ -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);
|
||||
|
||||
+21
@@ -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); }
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user