336 lines
16 KiB
Java
336 lines
16 KiB
Java
package com.hypixel.hytale.builtin.hytalegenerator.plugin;
|
|
|
|
import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.PropField;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.assets.AssetManager;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.assets.SettingsAsset;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.WorldStructureAsset;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.biomemap.BiomeMap;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.ChunkGenerator;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.ChunkRequest;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.FallbackGenerator;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.commands.ViewportCommand;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.NStagedChunkGenerator;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NCountedPixelBuffer;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NEntityBuffer;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NSimplePixelBuffer;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NBiomeDistanceStage;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NBiomeStage;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NEnvironmentStage;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NPropStage;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NStage;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NTerrainStage;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NTintStage;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox;
|
|
import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer;
|
|
import com.hypixel.hytale.codec.Codec;
|
|
import com.hypixel.hytale.codec.KeyedCodec;
|
|
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
|
import com.hypixel.hytale.math.vector.Transform;
|
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
|
import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk;
|
|
import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.LinkedBlockingQueue;
|
|
import java.util.concurrent.Semaphore;
|
|
import java.util.concurrent.ThreadPoolExecutor;
|
|
import java.util.concurrent.TimeUnit;
|
|
import javax.annotation.Nonnull;
|
|
|
|
public class HytaleGenerator extends JavaPlugin {
|
|
private AssetManager assetManager;
|
|
private Runnable assetReloadListener;
|
|
private final Map<ChunkRequest.GeneratorProfile, ChunkGenerator> generators = new HashMap<>();
|
|
private final Semaphore chunkGenerationSemaphore = new Semaphore(1);
|
|
private int concurrency;
|
|
private ExecutorService mainExecutor;
|
|
private ThreadPoolExecutor concurrentExecutor;
|
|
|
|
@Override
|
|
protected void start() {
|
|
super.start();
|
|
if (this.mainExecutor == null) {
|
|
this.loadExecutors(this.assetManager.getSettingsAsset());
|
|
}
|
|
|
|
if (this.assetReloadListener == null) {
|
|
this.assetReloadListener = () -> this.reloadGenerators();
|
|
this.assetManager.registerReloadListener(this.assetReloadListener);
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
public CompletableFuture<GeneratedChunk> submitChunkRequest(@Nonnull ChunkRequest request) {
|
|
return CompletableFuture.<GeneratedChunk>supplyAsync(() -> {
|
|
GeneratedChunk var3;
|
|
try {
|
|
this.chunkGenerationSemaphore.acquireUninterruptibly();
|
|
ChunkGenerator generator = this.getGenerator(request.generatorProfile());
|
|
var3 = generator.generate(request.arguments());
|
|
} finally {
|
|
this.chunkGenerationSemaphore.release();
|
|
}
|
|
|
|
return var3;
|
|
}, this.mainExecutor).handle((r, e) -> {
|
|
if (e == null) {
|
|
return (GeneratedChunk)r;
|
|
} else {
|
|
LoggerUtil.logException("generation of the chunk with request " + request, e, LoggerUtil.getLogger());
|
|
return FallbackGenerator.INSTANCE.generate(request.arguments());
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
protected void setup() {
|
|
this.assetManager = new AssetManager(this.getEventRegistry(), this.getLogger());
|
|
BuilderCodec<HandleProvider> generatorProvider = BuilderCodec.builder(HandleProvider.class, () -> new HandleProvider(this))
|
|
.documentation("The standard generator for Hytale.")
|
|
.append(new KeyedCodec<>("WorldStructure", Codec.STRING), HandleProvider::setWorldStructureName, HandleProvider::getWorldStructureName)
|
|
.documentation("The world structure to be used for this world.")
|
|
.add()
|
|
.append(new KeyedCodec<>("PlayerSpawn", Transform.CODEC), HandleProvider::setPlayerSpawn, HandleProvider::getPlayerSpawn)
|
|
.add()
|
|
.build();
|
|
IWorldGenProvider.CODEC.register("HytaleGenerator", HandleProvider.class, generatorProvider);
|
|
this.getCommandRegistry().registerCommand(new ViewportCommand(this.assetManager));
|
|
}
|
|
|
|
@Nonnull
|
|
public NStagedChunkGenerator createStagedChunkGenerator(
|
|
@Nonnull ChunkRequest.GeneratorProfile generatorProfile, @Nonnull WorldStructureAsset worldStructureAsset, @Nonnull SettingsAsset settingsAsset
|
|
) {
|
|
WorkerIndexer workerIndexer = new WorkerIndexer(this.concurrency);
|
|
SeedBox seed = new SeedBox(generatorProfile.seed());
|
|
MaterialCache materialCache = new MaterialCache();
|
|
BiomeMap<SolidMaterial> biomeMap = worldStructureAsset.buildBiomeMap(new WorldStructureAsset.Argument(materialCache, seed, workerIndexer));
|
|
worldStructureAsset.cleanUp();
|
|
NStagedChunkGenerator.Builder generatorBuilder = new NStagedChunkGenerator.Builder();
|
|
List<BiomeType> allBiomes = biomeMap.allPossibleValues();
|
|
List<Integer> allRuntimes = new ArrayList<>(getAllPossibleRuntimeIndices(allBiomes));
|
|
allRuntimes.sort(Comparator.naturalOrder());
|
|
int bufferTypeIndexCounter = 0;
|
|
NParametrizedBufferType biome_bufferType = new NParametrizedBufferType(
|
|
"Biome", bufferTypeIndexCounter++, NBiomeStage.bufferClass, NBiomeStage.biomeTypeClass, () -> new NCountedPixelBuffer<>(NBiomeStage.biomeTypeClass)
|
|
);
|
|
NStage biomeStage = new NBiomeStage("BiomeStage", biome_bufferType, biomeMap);
|
|
generatorBuilder.appendStage(biomeStage);
|
|
NParametrizedBufferType biomeDistance_bufferType = new NParametrizedBufferType(
|
|
"BiomeDistance",
|
|
bufferTypeIndexCounter++,
|
|
NBiomeDistanceStage.biomeDistanceBufferClass,
|
|
NBiomeDistanceStage.biomeDistanceClass,
|
|
() -> new NSimplePixelBuffer<>(NBiomeDistanceStage.biomeDistanceClass)
|
|
);
|
|
int MAX_BIOME_DISTANCE_RADIUS = 512;
|
|
int interpolationRadius = Math.clamp((long)(worldStructureAsset.getBiomeTransitionDistance() / 2), 0, 512);
|
|
int biomeEdgeRadius = Math.clamp((long)worldStructureAsset.getMaxBiomeEdgeDistance(), 0, 512);
|
|
int maxDistance = Math.max(interpolationRadius, biomeEdgeRadius);
|
|
NStage biomeDistanceStage = new NBiomeDistanceStage("BiomeDistanceStage", biome_bufferType, biomeDistance_bufferType, maxDistance);
|
|
generatorBuilder.appendStage(biomeDistanceStage);
|
|
int materialBufferIndexCounter = 0;
|
|
NParametrizedBufferType material0_bufferType = generatorBuilder.MATERIAL_OUTPUT_BUFFER_TYPE;
|
|
if (!allRuntimes.isEmpty()) {
|
|
material0_bufferType = new NParametrizedBufferType(
|
|
"Material" + materialBufferIndexCounter,
|
|
bufferTypeIndexCounter++,
|
|
NTerrainStage.materialBufferClass,
|
|
NTerrainStage.materialClass,
|
|
() -> new NVoxelBuffer<>(NTerrainStage.materialClass)
|
|
);
|
|
materialBufferIndexCounter++;
|
|
}
|
|
|
|
NStage terrainStage = new NTerrainStage(
|
|
"TerrainStage", biome_bufferType, biomeDistance_bufferType, material0_bufferType, interpolationRadius, materialCache, workerIndexer
|
|
);
|
|
generatorBuilder.appendStage(terrainStage);
|
|
NParametrizedBufferType materialInput_bufferType = material0_bufferType;
|
|
NBufferType entityInput_bufferType = null;
|
|
|
|
for (int i = 0; i < allRuntimes.size() - 1; i++) {
|
|
int runtime = allRuntimes.get(i);
|
|
String runtimeString = Integer.toString(runtime);
|
|
NParametrizedBufferType materialOutput_bufferType = new NParametrizedBufferType(
|
|
"Material" + materialBufferIndexCounter,
|
|
bufferTypeIndexCounter++,
|
|
NTerrainStage.materialBufferClass,
|
|
NTerrainStage.materialClass,
|
|
() -> new NVoxelBuffer<>(NTerrainStage.materialClass)
|
|
);
|
|
NBufferType entityOutput_bufferType = new NBufferType(
|
|
"Entity" + materialBufferIndexCounter, bufferTypeIndexCounter++, NEntityBuffer.class, NEntityBuffer::new
|
|
);
|
|
NStage propStage = new NPropStage(
|
|
"PropStage" + runtimeString,
|
|
biome_bufferType,
|
|
biomeDistance_bufferType,
|
|
materialInput_bufferType,
|
|
entityInput_bufferType,
|
|
materialOutput_bufferType,
|
|
entityOutput_bufferType,
|
|
materialCache,
|
|
allBiomes,
|
|
runtime
|
|
);
|
|
generatorBuilder.appendStage(propStage);
|
|
materialInput_bufferType = materialOutput_bufferType;
|
|
entityInput_bufferType = entityOutput_bufferType;
|
|
materialBufferIndexCounter++;
|
|
}
|
|
|
|
if (!allRuntimes.isEmpty()) {
|
|
int runtime = allRuntimes.getLast();
|
|
String runtimeString = Integer.toString(runtime);
|
|
NStage propStage = new NPropStage(
|
|
"PropStage" + runtimeString,
|
|
biome_bufferType,
|
|
biomeDistance_bufferType,
|
|
materialInput_bufferType,
|
|
entityInput_bufferType,
|
|
generatorBuilder.MATERIAL_OUTPUT_BUFFER_TYPE,
|
|
generatorBuilder.ENTITY_OUTPUT_BUFFER_TYPE,
|
|
materialCache,
|
|
allBiomes,
|
|
runtime
|
|
);
|
|
generatorBuilder.appendStage(propStage);
|
|
}
|
|
|
|
NStage tintStage = new NTintStage("TintStage", biome_bufferType, generatorBuilder.TINT_OUTPUT_BUFFER_TYPE);
|
|
generatorBuilder.appendStage(tintStage);
|
|
NStage environmentStage = new NEnvironmentStage("EnvironmentStage", biome_bufferType, generatorBuilder.ENVIRONMENT_OUTPUT_BUFFER_TYPE);
|
|
generatorBuilder.appendStage(environmentStage);
|
|
double bufferCapacityFactor = Math.max(0.0, settingsAsset.getBufferCapacityFactor());
|
|
double targetViewDistance = Math.max(0.0, settingsAsset.getTargetViewDistance());
|
|
double targetPlayerCount = Math.max(0.0, settingsAsset.getTargetPlayerCount());
|
|
Set<Integer> statsCheckpoints = new HashSet<>(settingsAsset.getStatsCheckpoints());
|
|
return generatorBuilder.withStats("WorldStructure Name: " + generatorProfile.worldStructureName(), statsCheckpoints)
|
|
.withMaterialCache(materialCache)
|
|
.withConcurrentExecutor(this.concurrentExecutor, workerIndexer)
|
|
.withBufferCapacity(bufferCapacityFactor, targetViewDistance, targetPlayerCount)
|
|
.build();
|
|
}
|
|
|
|
@Nonnull
|
|
private static Set<Integer> getAllPossibleRuntimeIndices(@Nonnull List<BiomeType> biomes) {
|
|
Set<Integer> allRuntimes = new HashSet<>();
|
|
|
|
for (BiomeType biome : biomes) {
|
|
for (PropField propField : biome.getPropFields()) {
|
|
allRuntimes.add(propField.getRuntime());
|
|
}
|
|
}
|
|
|
|
return allRuntimes;
|
|
}
|
|
|
|
@Nonnull
|
|
private ChunkGenerator getGenerator(@Nonnull ChunkRequest.GeneratorProfile profile) {
|
|
ChunkGenerator generator = this.generators.get(profile);
|
|
if (generator == null) {
|
|
if (profile.worldStructureName() == null) {
|
|
LoggerUtil.getLogger().warning("World Structure asset not loaded.");
|
|
return FallbackGenerator.INSTANCE;
|
|
}
|
|
|
|
WorldStructureAsset worldStructureAsset = this.assetManager.getWorldStructureAsset(profile.worldStructureName());
|
|
if (worldStructureAsset == null) {
|
|
LoggerUtil.getLogger().warning("World Structure asset not found: " + profile.worldStructureName());
|
|
return FallbackGenerator.INSTANCE;
|
|
}
|
|
|
|
SettingsAsset settingsAsset = this.assetManager.getSettingsAsset();
|
|
if (settingsAsset == null) {
|
|
LoggerUtil.getLogger().warning("Settings asset not found.");
|
|
return FallbackGenerator.INSTANCE;
|
|
}
|
|
|
|
generator = this.createStagedChunkGenerator(profile, worldStructureAsset, settingsAsset);
|
|
this.generators.put(profile, generator);
|
|
}
|
|
|
|
return generator;
|
|
}
|
|
|
|
private void loadExecutors(@Nonnull SettingsAsset settingsAsset) {
|
|
int newConcurrency = getConcurrency(settingsAsset);
|
|
if (newConcurrency != this.concurrency || this.mainExecutor == null || this.concurrentExecutor == null) {
|
|
this.concurrency = newConcurrency;
|
|
if (this.mainExecutor == null) {
|
|
this.mainExecutor = Executors.newSingleThreadExecutor();
|
|
}
|
|
|
|
if (this.concurrentExecutor != null && !this.concurrentExecutor.isShutdown()) {
|
|
try {
|
|
this.concurrentExecutor.shutdown();
|
|
if (!this.concurrentExecutor.awaitTermination(1L, TimeUnit.MINUTES)) {
|
|
}
|
|
} catch (InterruptedException var4) {
|
|
throw new RuntimeException(var4);
|
|
}
|
|
}
|
|
|
|
this.concurrentExecutor = new ThreadPoolExecutor(this.concurrency, this.concurrency, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), r -> {
|
|
Thread t = new Thread(r, "HytaleGenerator-Worker");
|
|
t.setPriority(1);
|
|
t.setDaemon(true);
|
|
return t;
|
|
});
|
|
if (this.mainExecutor == null || this.mainExecutor.isShutdown()) {
|
|
this.mainExecutor = Executors.newSingleThreadExecutor();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static int getConcurrency(@Nonnull SettingsAsset settingsAsset) {
|
|
int concurrencySetting = settingsAsset.getCustomConcurrency();
|
|
int availableProcessors = Runtime.getRuntime().availableProcessors();
|
|
int value = 1;
|
|
if (concurrencySetting < 1) {
|
|
value = Math.max(availableProcessors, 1);
|
|
} else {
|
|
if (concurrencySetting > availableProcessors) {
|
|
LoggerUtil.getLogger().warning("Concurrency setting " + concurrencySetting + " exceeds available processors " + availableProcessors);
|
|
}
|
|
|
|
value = concurrencySetting;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
private void reloadGenerators() {
|
|
try {
|
|
this.chunkGenerationSemaphore.acquireUninterruptibly();
|
|
this.loadExecutors(this.assetManager.getSettingsAsset());
|
|
this.generators.clear();
|
|
} finally {
|
|
this.chunkGenerationSemaphore.release();
|
|
}
|
|
|
|
LoggerUtil.getLogger().info("Reloaded HytaleGenerator.");
|
|
}
|
|
|
|
public HytaleGenerator(@Nonnull JavaPluginInit init) {
|
|
super(init);
|
|
}
|
|
}
|