mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
implement headless for 1.21.4
This commit is contained in:
13 files changed
+994
-22
No files matched your search
@@ -140,12 +140,16 @@ public class CommandDeveloper implements DecreeExecutor {
|
|||||||
public void packBenchmark(
|
public void packBenchmark(
|
||||||
@Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld")
|
@Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld")
|
||||||
IrisDimension dimension,
|
IrisDimension dimension,
|
||||||
@Param(description = "Radius in regions", defaultValue = "5")
|
@Param(description = "Diameter in regions", defaultValue = "5")
|
||||||
int radius,
|
int diameter,
|
||||||
|
@Param(description = "Headless", defaultValue = "true")
|
||||||
|
boolean headless,
|
||||||
@Param(description = "Open GUI while benchmarking", defaultValue = "false")
|
@Param(description = "Open GUI while benchmarking", defaultValue = "false")
|
||||||
boolean gui
|
boolean gui
|
||||||
) {
|
) {
|
||||||
new IrisPackBenchmarking(dimension, radius, gui);
|
int rb = diameter << 9;
|
||||||
|
Iris.info("Benchmarking pack " + dimension.getName() + " with diameter: " + rb + "(" + diameter + ")");
|
||||||
|
new IrisPackBenchmarking(dimension, diameter, headless, gui);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Decree(description = "Upgrade to another Minecraft version")
|
@Decree(description = "Upgrade to another Minecraft version")
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.volmit.iris.core.nms;
|
||||||
|
|
||||||
|
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||||
|
import com.volmit.iris.util.documentation.ChunkCoordinates;
|
||||||
|
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||||
|
import com.volmit.iris.util.parallel.MultiBurst;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public interface IHeadless extends Closeable {
|
||||||
|
int getLoadedChunks();
|
||||||
|
|
||||||
|
@ChunkCoordinates
|
||||||
|
boolean exists(int x, int z);
|
||||||
|
|
||||||
|
@RegionCoordinates
|
||||||
|
CompletableFuture<Void> generateRegion(MultiBurst burst, int x, int z, int maxConcurrent, PregenListener listener);
|
||||||
|
|
||||||
|
@ChunkCoordinates
|
||||||
|
void generateChunk(int x, int z);
|
||||||
|
}
|
||||||
@@ -30,15 +30,12 @@ import com.volmit.iris.util.nbt.mca.palette.MCAPaletteAccess;
|
|||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.entity.Dolphin;
|
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.EntityType;
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
import org.bukkit.generator.structure.Structure;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
|
||||||
public interface INMSBinding {
|
public interface INMSBinding {
|
||||||
@@ -124,5 +121,9 @@ public interface INMSBinding {
|
|||||||
return 441;
|
return 441;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default IHeadless createHeadless(Engine engine) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
KList<String> getStructureKeys();
|
KList<String> getStructureKeys();
|
||||||
}
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||||
|
* Copyright (c) 2024 Arcane Arts (Volmit Software)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.volmit.iris.core.pregenerator.methods;
|
||||||
|
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.IrisSettings;
|
||||||
|
import com.volmit.iris.core.nms.IHeadless;
|
||||||
|
import com.volmit.iris.core.nms.INMS;
|
||||||
|
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||||
|
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
|
||||||
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
|
import com.volmit.iris.util.mantle.Mantle;
|
||||||
|
import com.volmit.iris.util.parallel.MultiBurst;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
public class HeadlessPregenMethod implements PregeneratorMethod {
|
||||||
|
private final Engine engine;
|
||||||
|
private final IHeadless headless;
|
||||||
|
private final Semaphore semaphore;
|
||||||
|
private final int max;
|
||||||
|
private final MultiBurst burst;
|
||||||
|
|
||||||
|
public HeadlessPregenMethod(Engine engine) {
|
||||||
|
this.max = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism());
|
||||||
|
this.engine = engine;
|
||||||
|
this.headless = INMS.get().createHeadless(engine);
|
||||||
|
burst = new MultiBurst("HeadlessPregen", 8);
|
||||||
|
this.semaphore = new Semaphore(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
semaphore.acquire(max);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
headless.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to close headless");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
burst.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsRegions(int x, int z, PregenListener listener) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMethod(int x, int z) {
|
||||||
|
return "Headless";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void generateRegion(int x, int z, PregenListener listener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void generateChunk(int x, int z, PregenListener listener) {
|
||||||
|
try {
|
||||||
|
semaphore.acquire();
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
burst.complete(() -> {
|
||||||
|
try {
|
||||||
|
listener.onChunkGenerating(x, z);
|
||||||
|
headless.generateChunk(x, z);
|
||||||
|
listener.onChunkGenerated(x, z);
|
||||||
|
} finally {
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mantle getMantle() {
|
||||||
|
return engine.getMantle().getMantle();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,17 @@ package com.volmit.iris.core.tools;
|
|||||||
|
|
||||||
|
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.IrisSettings;
|
||||||
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||||
|
import com.volmit.iris.core.pregenerator.methods.HeadlessPregenMethod;
|
||||||
|
import com.volmit.iris.core.pregenerator.methods.HybridPregenMethod;
|
||||||
|
import com.volmit.iris.core.service.StudioSVC;
|
||||||
|
import com.volmit.iris.engine.IrisEngine;
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
|
import com.volmit.iris.engine.framework.EngineTarget;
|
||||||
import com.volmit.iris.engine.object.IrisDimension;
|
import com.volmit.iris.engine.object.IrisDimension;
|
||||||
|
import com.volmit.iris.engine.object.IrisWorld;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.collection.KMap;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.exceptions.IrisException;
|
import com.volmit.iris.util.exceptions.IrisException;
|
||||||
@@ -33,13 +41,16 @@ public class IrisPackBenchmarking {
|
|||||||
public static boolean benchmarkInProgress = false;
|
public static boolean benchmarkInProgress = false;
|
||||||
private final PrecisionStopwatch stopwatch = new PrecisionStopwatch();
|
private final PrecisionStopwatch stopwatch = new PrecisionStopwatch();
|
||||||
private final IrisDimension dimension;
|
private final IrisDimension dimension;
|
||||||
private final int radius;
|
private final int diameter;
|
||||||
private final boolean gui;
|
private final boolean gui;
|
||||||
|
private final boolean headless;
|
||||||
|
private transient Engine engine;
|
||||||
|
|
||||||
public IrisPackBenchmarking(IrisDimension dimension, int radius, boolean gui) {
|
public IrisPackBenchmarking(IrisDimension dimension, int diameter, boolean headless, boolean gui) {
|
||||||
instance = this;
|
instance = this;
|
||||||
this.dimension = dimension;
|
this.dimension = dimension;
|
||||||
this.radius = radius;
|
this.diameter = diameter;
|
||||||
|
this.headless = headless;
|
||||||
this.gui = gui;
|
this.gui = gui;
|
||||||
runBenchmark();
|
runBenchmark();
|
||||||
}
|
}
|
||||||
@@ -55,7 +66,7 @@ public class IrisPackBenchmarking {
|
|||||||
deleteDirectory(file.toPath());
|
deleteDirectory(file.toPath());
|
||||||
}
|
}
|
||||||
createBenchmark();
|
createBenchmark();
|
||||||
while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
while (!headless && !IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
||||||
J.sleep(1000);
|
J.sleep(1000);
|
||||||
Iris.debug("Iris PackBenchmark: Waiting...");
|
Iris.debug("Iris PackBenchmark: Waiting...");
|
||||||
}
|
}
|
||||||
@@ -73,7 +84,6 @@ public class IrisPackBenchmarking {
|
|||||||
public void finishedBenchmark(KList<Integer> cps) {
|
public void finishedBenchmark(KList<Integer> cps) {
|
||||||
try {
|
try {
|
||||||
String time = Form.duration(stopwatch.getMillis());
|
String time = Form.duration(stopwatch.getMillis());
|
||||||
Engine engine = IrisToolbelt.access(Bukkit.getWorld("benchmark")).getEngine();
|
|
||||||
Iris.info("-----------------");
|
Iris.info("-----------------");
|
||||||
Iris.info("Results:");
|
Iris.info("Results:");
|
||||||
Iris.info("- Total time: " + time);
|
Iris.info("- Total time: " + time);
|
||||||
@@ -114,12 +124,16 @@ public class IrisPackBenchmarking {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
J.s(() -> {
|
if (headless) {
|
||||||
var world = Bukkit.getWorld("benchmark");
|
engine.close();
|
||||||
if (world == null) return;
|
} else {
|
||||||
IrisToolbelt.evacuate(world);
|
J.s(() -> {
|
||||||
Bukkit.unloadWorld(world, true);
|
var world = Bukkit.getWorld("benchmark");
|
||||||
});
|
if (world == null) return;
|
||||||
|
IrisToolbelt.evacuate(world);
|
||||||
|
Bukkit.unloadWorld(world, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
stopwatch.end();
|
stopwatch.end();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -130,13 +144,34 @@ public class IrisPackBenchmarking {
|
|||||||
|
|
||||||
private void createBenchmark() {
|
private void createBenchmark() {
|
||||||
try {
|
try {
|
||||||
IrisToolbelt.createWorld()
|
if (headless) {
|
||||||
|
Iris.info("Using headless benchmark!");
|
||||||
|
IrisWorld world = IrisWorld.builder()
|
||||||
|
.name("benchmark")
|
||||||
|
.minHeight(dimension.getMinHeight())
|
||||||
|
.maxHeight(dimension.getMaxHeight())
|
||||||
|
.seed(1337)
|
||||||
|
.worldFolder(new File(Bukkit.getWorldContainer(), "benchmark"))
|
||||||
|
.environment(dimension.getEnvironment())
|
||||||
|
.build();
|
||||||
|
Iris.service(StudioSVC.class).installIntoWorld(
|
||||||
|
Iris.getSender(),
|
||||||
|
dimension.getLoadKey(),
|
||||||
|
world.worldFolder());
|
||||||
|
var data = IrisData.get(new File(world.worldFolder(), "iris/pack"));
|
||||||
|
var dim = data.getDimensionLoader().load(dimension.getLoadKey());
|
||||||
|
engine = new IrisEngine(new EngineTarget(world, dim, data), false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
engine = IrisToolbelt.access(IrisToolbelt.createWorld()
|
||||||
.dimension(dimension.getLoadKey())
|
.dimension(dimension.getLoadKey())
|
||||||
.name("benchmark")
|
.name("benchmark")
|
||||||
.seed(1337)
|
.seed(1337)
|
||||||
.studio(false)
|
.studio(false)
|
||||||
.benchmark(true)
|
.benchmark(true)
|
||||||
.create();
|
.create())
|
||||||
|
.getEngine();
|
||||||
} catch (IrisException e) {
|
} catch (IrisException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -146,9 +181,15 @@ public class IrisPackBenchmarking {
|
|||||||
IrisToolbelt.pregenerate(PregenTask
|
IrisToolbelt.pregenerate(PregenTask
|
||||||
.builder()
|
.builder()
|
||||||
.gui(gui)
|
.gui(gui)
|
||||||
.width(radius)
|
.width(diameter)
|
||||||
.height(radius)
|
.height(diameter)
|
||||||
.build(), Bukkit.getWorld("benchmark")
|
.build(), headless ?
|
||||||
|
new HeadlessPregenMethod(engine) :
|
||||||
|
new HybridPregenMethod(
|
||||||
|
engine.getWorld().realWorld(),
|
||||||
|
IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())
|
||||||
|
),
|
||||||
|
engine
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -304,6 +304,11 @@ public class IrisEngine implements Engine {
|
|||||||
return generated.get();
|
return generated.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addGenerated(int x, int z) {
|
||||||
|
generated.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getGeneratedPerSecond() {
|
public double getGeneratedPerSecond() {
|
||||||
if (perSecondLatch.flip()) {
|
if (perSecondLatch.flip()) {
|
||||||
|
|||||||
@@ -612,6 +612,9 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
|||||||
|
|
||||||
int getGenerated();
|
int getGenerated();
|
||||||
|
|
||||||
|
@ChunkCoordinates
|
||||||
|
void addGenerated(int x, int z);
|
||||||
|
|
||||||
CompletableFuture<Long> getHash32();
|
CompletableFuture<Long> getHash32();
|
||||||
|
|
||||||
default <T> IrisPosition lookForStreamResult(T find, ProceduralStream<T> stream, Function2<T, T, Boolean> matcher, long timeout) {
|
default <T> IrisPosition lookForStreamResult(T find, ProceduralStream<T> stream, Function2<T, T, Boolean> matcher, long timeout) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package com.volmit.iris.engine.object;
|
package com.volmit.iris.engine.object;
|
||||||
|
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IHeadless;
|
||||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
@@ -48,6 +49,7 @@ public class IrisWorld {
|
|||||||
private long seed;
|
private long seed;
|
||||||
private World.Environment environment;
|
private World.Environment environment;
|
||||||
private World realWorld;
|
private World realWorld;
|
||||||
|
private IHeadless headless;
|
||||||
private int minHeight;
|
private int minHeight;
|
||||||
private int maxHeight;
|
private int maxHeight;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.volmit.iris.util.hunk.view;
|
||||||
|
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.util.hunk.storage.ArrayHunk;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
|
||||||
|
public class SyncChunkDataHunkHolder extends ArrayHunk<BlockData> {
|
||||||
|
private static final BlockData AIR = Material.AIR.createBlockData();
|
||||||
|
private final ChunkGenerator.ChunkData chunk;
|
||||||
|
private final Thread mainThread = Thread.currentThread();
|
||||||
|
|
||||||
|
public SyncChunkDataHunkHolder(ChunkGenerator.ChunkData chunk) {
|
||||||
|
super(16, chunk.getMaxHeight() - chunk.getMinHeight(), 16);
|
||||||
|
this.chunk = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRaw(int x, int y, int z, BlockData data) {
|
||||||
|
if (Thread.currentThread() != mainThread)
|
||||||
|
Iris.warn("SyncChunkDataHunkHolder is not on the main thread");
|
||||||
|
super.setRaw(x, y, z, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockData getRaw(int x, int y, int z) {
|
||||||
|
if (Thread.currentThread() != mainThread)
|
||||||
|
Iris.warn("SyncChunkDataHunkHolder is not on the main thread");
|
||||||
|
BlockData b = super.getRaw(x, y, z);
|
||||||
|
|
||||||
|
return b != null ? b : AIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply() {
|
||||||
|
for (int i = getHeight()-1; i >= 0; i--) {
|
||||||
|
for (int j = 0; j < getWidth(); j++) {
|
||||||
|
for (int k = 0; k < getDepth(); k++) {
|
||||||
|
BlockData b = super.getRaw(j, i, k);
|
||||||
|
|
||||||
|
if (b != null) {
|
||||||
|
chunk.setBlock(j, i + chunk.getMinHeight(), k, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,11 @@ package com.volmit.iris.core.nms.v1_21_R3;
|
|||||||
|
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IHeadless;
|
||||||
import com.volmit.iris.core.nms.INMSBinding;
|
import com.volmit.iris.core.nms.INMSBinding;
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||||
|
import com.volmit.iris.core.nms.v1_21_R3.headless.Headless;
|
||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
@@ -643,4 +645,9 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IHeadless createHeadless(Engine engine) {
|
||||||
|
return new Headless(engine);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+194
@@ -0,0 +1,194 @@
|
|||||||
|
package com.volmit.iris.core.nms.v1_21_R3.headless;
|
||||||
|
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||||
|
import com.volmit.iris.engine.data.chunk.TerrainChunk;
|
||||||
|
import com.volmit.iris.util.data.IrisBlockData;
|
||||||
|
import lombok.Data;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.Biome;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R3.block.CraftBiome;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R3.block.CraftBlockType;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R3.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R3.util.CraftMagicNumbers;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.bukkit.material.MaterialData;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public final class DirectTerrainChunk implements TerrainChunk {
|
||||||
|
private final ChunkAccess access;
|
||||||
|
private final int minHeight, maxHeight;
|
||||||
|
|
||||||
|
public DirectTerrainChunk(ChunkAccess access) {
|
||||||
|
this.access = access;
|
||||||
|
this.minHeight = access.getMinY();
|
||||||
|
this.maxHeight = access.getMaxY();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiomeBaseInjector getBiomeBaseInjector() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int z) {
|
||||||
|
return getBiome(x, 0, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int y, int z) {
|
||||||
|
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
|
||||||
|
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBiome(int x, int z, Biome bio) {
|
||||||
|
for (int y = minHeight; y < maxHeight; y += 4) {
|
||||||
|
setBiome(x, y, z, bio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBiome(int x, int y, int z, Biome bio) {
|
||||||
|
if (y < minHeight || y > maxHeight) return;
|
||||||
|
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlock(int x, int y, int z, Material material) {
|
||||||
|
this.setBlock(x, y, z, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlock(int x, int y, int z, MaterialData material) {
|
||||||
|
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, BlockData blockData) {
|
||||||
|
if (blockData == null) {
|
||||||
|
Iris.error("NULL BD");
|
||||||
|
}
|
||||||
|
if (blockData instanceof IrisBlockData data)
|
||||||
|
blockData = data.getBase();
|
||||||
|
if (!(blockData instanceof CraftBlockData craftBlockData))
|
||||||
|
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
|
||||||
|
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Material getType(int x, int y, int z) {
|
||||||
|
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockData getBlockData(int x, int y, int z) {
|
||||||
|
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChunkGenerator.ChunkData getRaw() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRaw(ChunkGenerator.ChunkData data) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inject(ChunkGenerator.BiomeGrid biome) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||||
|
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (xMin < 0) {
|
||||||
|
xMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMin < this.minHeight) {
|
||||||
|
yMin = this.minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMin < 0) {
|
||||||
|
zMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMax > 16) {
|
||||||
|
xMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMax > this.maxHeight) {
|
||||||
|
yMax = this.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMax > 16) {
|
||||||
|
zMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int y = yMin; y < yMax; ++y) {
|
||||||
|
for (int x = xMin; x < xMax; ++x) {
|
||||||
|
for (int z = zMin; z < zMax; ++z) {
|
||||||
|
this.setBlock(x, y, z, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockState getTypeId(int x, int y, int z) {
|
||||||
|
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||||
|
return Blocks.AIR.defaultBlockState();
|
||||||
|
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBlock(int x, int y, int z, BlockState type) {
|
||||||
|
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
|
||||||
|
return;
|
||||||
|
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||||
|
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||||
|
if (type.hasBlockEntity()) {
|
||||||
|
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
|
||||||
|
if (tileEntity == null) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
} else {
|
||||||
|
access.setBlockEntity(tileEntity);
|
||||||
|
}
|
||||||
|
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,453 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||||
|
* Copyright (c) 2024 Arcane Arts (Volmit Software)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.volmit.iris.core.nms.v1_21_R3.headless;
|
||||||
|
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IHeadless;
|
||||||
|
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||||
|
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||||
|
import com.volmit.iris.engine.data.cache.Cache;
|
||||||
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
|
import com.volmit.iris.engine.framework.EngineStage;
|
||||||
|
import com.volmit.iris.engine.framework.WrongEngineBroException;
|
||||||
|
import com.volmit.iris.engine.object.IrisBiome;
|
||||||
|
import com.volmit.iris.util.collection.KList;
|
||||||
|
import com.volmit.iris.util.collection.KMap;
|
||||||
|
import com.volmit.iris.util.context.ChunkContext;
|
||||||
|
import com.volmit.iris.util.context.IrisContext;
|
||||||
|
import com.volmit.iris.util.documentation.BlockCoordinates;
|
||||||
|
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||||
|
import com.volmit.iris.util.hunk.Hunk;
|
||||||
|
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
|
||||||
|
import com.volmit.iris.util.hunk.view.SyncChunkDataHunkHolder;
|
||||||
|
import com.volmit.iris.util.mantle.MantleFlag;
|
||||||
|
import com.volmit.iris.util.math.M;
|
||||||
|
import com.volmit.iris.util.math.RNG;
|
||||||
|
import com.volmit.iris.util.parallel.MultiBurst;
|
||||||
|
import com.volmit.iris.util.scheduling.J;
|
||||||
|
import com.volmit.iris.util.scheduling.Looper;
|
||||||
|
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||||
|
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||||
|
import it.unimi.dsi.fastutil.shorts.ShortList;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.minecraft.Optionull;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.core.RegistryAccess;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.NbtIo;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.LevelHeightAccessor;
|
||||||
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import net.minecraft.world.level.chunk.*;
|
||||||
|
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||||
|
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||||
|
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
|
||||||
|
import net.minecraft.world.level.levelgen.Heightmap;
|
||||||
|
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R3.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R3.block.CraftBiome;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class Headless implements IHeadless, LevelHeightAccessor {
|
||||||
|
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
|
||||||
|
private final long KEEP_ALIVE = TimeUnit.SECONDS.toMillis(10L);
|
||||||
|
private final Engine engine;
|
||||||
|
private final RegionFileStorage storage;
|
||||||
|
private final ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
private final KMap<Long, Region> regions = new KMap<>();
|
||||||
|
private final AtomicInteger loadedChunks = new AtomicInteger();
|
||||||
|
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
|
||||||
|
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
|
||||||
|
private final RNG biomeRng;
|
||||||
|
private final @Getter int minY;
|
||||||
|
private final @Getter int height;
|
||||||
|
private transient CompletingThread regionThread;
|
||||||
|
private transient boolean closed = false;
|
||||||
|
|
||||||
|
public Headless(Engine engine) {
|
||||||
|
this.engine = engine;
|
||||||
|
this.storage = new RegionFileStorage(engine.getWorld().worldFolder());
|
||||||
|
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
|
||||||
|
this.minY = engine.getDimension().getMinHeight();
|
||||||
|
this.height = engine.getDimension().getMaxHeight() - minY;
|
||||||
|
engine.getWorld().headless(this);
|
||||||
|
|
||||||
|
AtomicInteger failed = new AtomicInteger();
|
||||||
|
var dimKey = engine.getDimension().getLoadKey();
|
||||||
|
for (var biome : engine.getAllBiomes()) {
|
||||||
|
if (!biome.isCustom()) continue;
|
||||||
|
for (var custom : biome.getCustomDerivitives()) {
|
||||||
|
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
|
||||||
|
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
|
||||||
|
failed.incrementAndGet();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (failed.get() > 0) {
|
||||||
|
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
|
||||||
|
}
|
||||||
|
|
||||||
|
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
|
||||||
|
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
|
||||||
|
minecraftBiomes.values().removeAll(customBiomes.values());
|
||||||
|
startRegionCleaner();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startRegionCleaner() {
|
||||||
|
var cleaner = new Looper() {
|
||||||
|
@Override
|
||||||
|
protected long loop() {
|
||||||
|
if (closed) return -1;
|
||||||
|
long time = M.ms() - KEEP_ALIVE;
|
||||||
|
regions.values()
|
||||||
|
.stream()
|
||||||
|
.filter(r -> r.lastEntry < time)
|
||||||
|
.forEach(Region::submit);
|
||||||
|
return closed ? -1 : 1000;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cleaner.setName("Iris Region Cleaner - " + engine.getWorld().name());
|
||||||
|
cleaner.setPriority(Thread.MIN_PRIORITY);
|
||||||
|
cleaner.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLoadedChunks() {
|
||||||
|
return loadedChunks.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the mca plate is fully generated or not.
|
||||||
|
*
|
||||||
|
* @param x coord of the chunk
|
||||||
|
* @param z coord of the chunk
|
||||||
|
* @return true if the chunk exists in .mca
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean exists(int x, int z) {
|
||||||
|
if (closed) return false;
|
||||||
|
if (engine.getWorld().hasRealWorld() && engine.getWorld().realWorld().isChunkLoaded(x, z))
|
||||||
|
return true;
|
||||||
|
try {
|
||||||
|
CompoundTag tag = storage.read(new ChunkPos(x, z));
|
||||||
|
return tag != null && !"empty".equals(tag.getString("Status"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized CompletableFuture<Void> generateRegion(MultiBurst burst, int x, int z, int maxConcurrent, PregenListener listener) {
|
||||||
|
if (closed) return CompletableFuture.completedFuture(null);
|
||||||
|
if (regionThread != null && !regionThread.future.isDone())
|
||||||
|
throw new IllegalStateException("Region generation already in progress");
|
||||||
|
|
||||||
|
regionThread = new CompletingThread(() -> {
|
||||||
|
boolean listening = listener != null;
|
||||||
|
Semaphore semaphore = new Semaphore(maxConcurrent);
|
||||||
|
CountDownLatch latch = new CountDownLatch(1024);
|
||||||
|
|
||||||
|
iterateRegion(x, z, pos -> {
|
||||||
|
try {
|
||||||
|
semaphore.acquire();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
semaphore.release();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
burst.complete(() -> {
|
||||||
|
try {
|
||||||
|
if (listening) listener.onChunkGenerating(pos.x, pos.z);
|
||||||
|
generateChunk(pos.x, pos.z);
|
||||||
|
if (listening) listener.onChunkGenerated(pos.x, pos.z);
|
||||||
|
} finally {
|
||||||
|
semaphore.release();
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
if (listening) listener.onRegionGenerated(x, z);
|
||||||
|
}, "Region Generator - " + x + "," + z, Thread.MAX_PRIORITY);
|
||||||
|
|
||||||
|
return regionThread.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RegionCoordinates
|
||||||
|
private static void iterateRegion(int x, int z, Consumer<ChunkPos> chunkPos) {
|
||||||
|
int cX = x << 5;
|
||||||
|
int cZ = z << 5;
|
||||||
|
for (int xx = 0; xx < 32; xx++) {
|
||||||
|
for (int zz = 0; zz < 32; zz++) {
|
||||||
|
chunkPos.accept(new ChunkPos(cX + xx, cZ + zz));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void generateChunk(int x, int z) {
|
||||||
|
if (closed || exists(x, z)) return;
|
||||||
|
try {
|
||||||
|
var pos = new ChunkPos(x, z);
|
||||||
|
ProtoChunk chunk = newProtoChunk(pos);
|
||||||
|
var tc = new DirectTerrainChunk(chunk);
|
||||||
|
loadedChunks.incrementAndGet();
|
||||||
|
|
||||||
|
SyncChunkDataHunkHolder blocks = new SyncChunkDataHunkHolder(tc);
|
||||||
|
BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight());
|
||||||
|
ChunkContext ctx = generate(engine, pos.x << 4, pos.z << 4, blocks, biomes);
|
||||||
|
blocks.apply();
|
||||||
|
biomes.apply();
|
||||||
|
|
||||||
|
chunk.fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
|
||||||
|
chunk.setPersistedStatus(ChunkStatus.FULL);
|
||||||
|
|
||||||
|
long key = Cache.key(pos.getRegionX(), pos.getRegionZ());
|
||||||
|
regions.computeIfAbsent(key, Region::new)
|
||||||
|
.add(chunk);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
loadedChunks.decrementAndGet();
|
||||||
|
Iris.error("Failed to generate " + x + ", " + z);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BlockCoordinates
|
||||||
|
private ChunkContext generate(Engine engine, int x, int z, Hunk<BlockData> vblocks, Hunk<org.bukkit.block.Biome> vbiomes) throws WrongEngineBroException {
|
||||||
|
if (engine.isClosed()) {
|
||||||
|
throw new WrongEngineBroException();
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.getContext().touch();
|
||||||
|
engine.getEngineData().getStatistics().generatedChunk();
|
||||||
|
ChunkContext ctx = null;
|
||||||
|
try {
|
||||||
|
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||||
|
Hunk<BlockData> blocks = vblocks.listen((xx, y, zz, t) -> engine.catchBlockUpdates(x + xx, y + engine.getMinHeight(), z + zz, t));
|
||||||
|
|
||||||
|
var dimension = engine.getDimension();
|
||||||
|
if (dimension.isDebugChunkCrossSections() && ((x >> 4) % dimension.getDebugCrossSectionsMod() == 0 || (z >> 4) % dimension.getDebugCrossSectionsMod() == 0)) {
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
for (int j = 0; j < 16; j++) {
|
||||||
|
blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx = new ChunkContext(x, z, engine.getComplex());
|
||||||
|
IrisContext.getOr(engine).setChunkContext(ctx);
|
||||||
|
|
||||||
|
for (EngineStage i : engine.getMode().getStages()) {
|
||||||
|
i.generate(x, z, blocks, vbiomes, false, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true);
|
||||||
|
engine.getMetrics().getTotal().put(p.getMilliseconds());
|
||||||
|
engine.addGenerated(x,z);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
engine.fail("Failed to generate " + x + ", " + z, e);
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
|
||||||
|
int m = y - engine.getMinHeight();
|
||||||
|
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
|
||||||
|
if (ib.isCustom()) {
|
||||||
|
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
|
||||||
|
} else {
|
||||||
|
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (closed) return;
|
||||||
|
try {
|
||||||
|
if (regionThread != null) {
|
||||||
|
regionThread.future.join();
|
||||||
|
regionThread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
regions.values().forEach(Region::submit);
|
||||||
|
Iris.info("Waiting for " + loadedChunks.get() + " chunks to unload...");
|
||||||
|
while (loadedChunks.get() > 0 || !regions.isEmpty())
|
||||||
|
J.sleep(1);
|
||||||
|
Iris.info("All chunks unloaded");
|
||||||
|
executor.shutdown();
|
||||||
|
storage.close();
|
||||||
|
engine.getWorld().headless(null);
|
||||||
|
} finally {
|
||||||
|
closed = true;
|
||||||
|
customBiomes.clear();
|
||||||
|
minecraftBiomes.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Region implements Runnable {
|
||||||
|
private final int x, z;
|
||||||
|
private final long key;
|
||||||
|
private final KList<ProtoChunk> chunks = new KList<>(1024);
|
||||||
|
private final AtomicBoolean full = new AtomicBoolean();
|
||||||
|
private long lastEntry = M.ms();
|
||||||
|
|
||||||
|
public Region(long key) {
|
||||||
|
this.x = Cache.keyX(key);
|
||||||
|
this.z = Cache.keyZ(key);
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
RegionFile regionFile;
|
||||||
|
try {
|
||||||
|
regionFile = storage.getRegionFile(new ChunkPos(x, z), false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Iris.error("Failed to load region file " + x + ", " + z);
|
||||||
|
Iris.reportError(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (regionFile == null) {
|
||||||
|
Iris.error("Failed to load region file " + x + ", " + z);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var chunk : chunks) {
|
||||||
|
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos())) {
|
||||||
|
NbtIo.write(write(chunk), dos);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.error("Failed to save chunk " + chunk.getPos().x + ", " + chunk.getPos().z);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
loadedChunks.decrementAndGet();
|
||||||
|
}
|
||||||
|
regions.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void add(ProtoChunk chunk) {
|
||||||
|
chunks.add(chunk);
|
||||||
|
lastEntry = M.ms();
|
||||||
|
if (chunks.size() < 1024)
|
||||||
|
return;
|
||||||
|
submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submit() {
|
||||||
|
if (full.getAndSet(true)) return;
|
||||||
|
executor.submit(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompoundTag write(ProtoChunk chunk) {
|
||||||
|
RegistryAccess access = registryAccess();
|
||||||
|
List<SerializableChunkData.SectionData> list = new ArrayList<>();
|
||||||
|
LevelChunkSection[] sections = chunk.getSections();
|
||||||
|
|
||||||
|
int minLightSection = getMinSectionY() - 1;
|
||||||
|
int maxLightSection = minLightSection + getSectionsCount() + 2;
|
||||||
|
for(int y = minLightSection; y < maxLightSection; ++y) {
|
||||||
|
int index = chunk.getSectionIndexFromSectionY(y);
|
||||||
|
if (index < 0 || index >= sections.length) continue;
|
||||||
|
LevelChunkSection section = sections[index].copy();
|
||||||
|
list.add(new SerializableChunkData.SectionData(y, section, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CompoundTag> blockEntities = new ArrayList<>(chunk.getBlockEntitiesPos().size());
|
||||||
|
|
||||||
|
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
|
||||||
|
CompoundTag nbt = chunk.getBlockEntityNbtForSaving(blockPos, access);
|
||||||
|
if (nbt != null) {
|
||||||
|
blockEntities.add(nbt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<Heightmap.Types, long[]> heightMap = new EnumMap<>(Heightmap.Types.class);
|
||||||
|
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
|
||||||
|
if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) {
|
||||||
|
heightMap.put(entry.getKey(), entry.getValue().getRawData().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkAccess.PackedTicks packedTicks = chunk.getTicksForSerialization(0);
|
||||||
|
ShortList[] postProcessing = Arrays.stream(chunk.getPostProcessing()).map((shortlist) -> shortlist != null ? new ShortArrayList(shortlist) : null).toArray(ShortList[]::new);
|
||||||
|
CompoundTag structureData = new CompoundTag();
|
||||||
|
structureData.put("starts", new CompoundTag());
|
||||||
|
structureData.put("References", new CompoundTag());
|
||||||
|
|
||||||
|
CompoundTag persistentDataContainer = null;
|
||||||
|
if (!chunk.persistentDataContainer.isEmpty()) {
|
||||||
|
persistentDataContainer = chunk.persistentDataContainer.toTagCompound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SerializableChunkData(access.lookupOrThrow(Registries.BIOME), chunk.getPos(),
|
||||||
|
chunk.getMinSectionY(), 0, chunk.getInhabitedTime(), chunk.getPersistedStatus(),
|
||||||
|
Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(),
|
||||||
|
chunk.getUpgradeData().copy(), null, heightMap, packedTicks, postProcessing,
|
||||||
|
chunk.isLightCorrect(), list, new ArrayList<>(), blockEntities, structureData, persistentDataContainer)
|
||||||
|
.write();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProtoChunk newProtoChunk(ChunkPos pos) {
|
||||||
|
return new ProtoChunk(pos, UpgradeData.EMPTY, this, registryAccess().lookupOrThrow(Registries.BIOME), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CompletingThread extends Thread {
|
||||||
|
private final CompletableFuture<Void> future = new CompletableFuture<>();
|
||||||
|
|
||||||
|
private CompletingThread(Runnable task, String name, int priority) {
|
||||||
|
super(task, name);
|
||||||
|
setPriority(priority);
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
super.run();
|
||||||
|
} finally {
|
||||||
|
future.complete(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RegistryAccess registryAccess() {
|
||||||
|
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
|
||||||
|
return registryAccess().lookupOrThrow(Registries.BIOME).get(ResourceLocation.fromNamespaceAndPath(namespace, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
package com.volmit.iris.core.nms.v1_21_R3.headless;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||||
|
import net.minecraft.FileUtil;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.NbtIo;
|
||||||
|
import net.minecraft.util.ExceptionCollector;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||||
|
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class RegionFileStorage implements AutoCloseable {
|
||||||
|
private static final RegionStorageInfo info = new RegionStorageInfo("headless", Level.OVERWORLD, "headless");
|
||||||
|
private final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap<>();
|
||||||
|
private final Path folder;
|
||||||
|
|
||||||
|
public RegionFileStorage(File folder) {
|
||||||
|
this.folder = new File(folder, "region").toPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException {
|
||||||
|
long id = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ());
|
||||||
|
RegionFile regionFile;
|
||||||
|
synchronized (this.regionCache) {
|
||||||
|
regionFile = this.regionCache.getAndMoveToFirst(id);
|
||||||
|
}
|
||||||
|
if (regionFile != null) {
|
||||||
|
return regionFile;
|
||||||
|
} else {
|
||||||
|
if (this.regionCache.size() >= 256) {
|
||||||
|
synchronized (this.regionCache) {
|
||||||
|
this.regionCache.removeLast().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUtil.createDirectoriesSafe(this.folder);
|
||||||
|
Path path = folder.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca");
|
||||||
|
if (existingOnly && !Files.exists(path)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
regionFile = new RegionFile(info, path, this.folder, true);
|
||||||
|
synchronized (this.regionCache) {
|
||||||
|
this.regionCache.putAndMoveToFirst(id, regionFile);
|
||||||
|
}
|
||||||
|
return regionFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public CompoundTag read(ChunkPos chunkPos) throws IOException {
|
||||||
|
RegionFile regionFile = this.getRegionFile(chunkPos, true);
|
||||||
|
if (regionFile == null) return null;
|
||||||
|
|
||||||
|
try (DataInputStream din = regionFile.getChunkDataInputStream(chunkPos)) {
|
||||||
|
if (din == null) return null;
|
||||||
|
return NbtIo.read(din);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
ExceptionCollector<IOException> collector = new ExceptionCollector<>();
|
||||||
|
|
||||||
|
for (RegionFile regionFile : this.regionCache.values()) {
|
||||||
|
try {
|
||||||
|
regionFile.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
collector.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collector.throwIfPresent();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user