From 9144606688ad58e826eccbb4d92a46eec55f9d9b Mon Sep 17 00:00:00 2001 From: repixelatedmc Date: Mon, 19 Aug 2024 19:36:35 +0200 Subject: [PATCH 01/20] Biggest 1 line fix ever --- core/src/main/java/com/volmit/iris/core/ServerConfigurator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java index 1a760d9d7..a1e273d65 100644 --- a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java +++ b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java @@ -58,7 +58,7 @@ public class ServerConfigurator { } private static void increaseKeepAliveSpigot() throws IOException, InvalidConfigurationException { - File spigotConfig = new File("config/spigot.yml"); + File spigotConfig = new File("spigot.yml"); FileConfiguration f = new YamlConfiguration(); f.load(spigotConfig); long tt = f.getLong("settings.timeout-time"); From 38ad345f851b9d17001aad982606c3439c1e469e Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 25 Aug 2024 19:35:49 +0200 Subject: [PATCH 02/20] potential fix for pregen deadlock --- .../volmit/iris/core/gui/PregeneratorJob.java | 7 ++++- .../methods/AsyncPregenMethod.java | 26 +++++-------------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java b/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java index 6d3534971..35569f6f6 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java +++ b/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java @@ -93,7 +93,12 @@ public class PregeneratorJob implements PregenListener { open(); } - J.a(this.pregenerator::start, 20); + var t = new Thread(() -> { + J.sleep(1000); + this.pregenerator.start(); + }, "Iris Pregenerator"); + t.setPriority(Thread.MIN_PRIORITY); + t.start(); } public static boolean shutdownInstance() { diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java index 80b0a3dfd..5d9048fd3 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java @@ -29,15 +29,11 @@ import com.volmit.iris.util.math.M; import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.scheduling.J; import io.papermc.lib.PaperLib; -import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.World; import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; public class AsyncPregenMethod implements PregeneratorMethod { @@ -52,8 +48,8 @@ public class AsyncPregenMethod implements PregeneratorMethod { } this.world = world; - burst = MultiBurst.burst; - future = new KList<>(1024); + burst = new MultiBurst("Iris Async Pregen", Thread.NORM_PRIORITY); + future = new KList<>(256); this.lastUse = new KMap<>(); } @@ -81,24 +77,17 @@ public class AsyncPregenMethod implements PregeneratorMethod { private void completeChunk(int x, int z, PregenListener listener) { try { - future.add(PaperLib.getChunkAtAsync(world, x, z, true).thenApply((i) -> { - if (i == null) { - - } - Chunk c = Bukkit.getWorld(world.getUID()).getChunkAt(x, z); - lastUse.put(c, M.ms()); + PaperLib.getChunkAtAsync(world, x, z, true).thenAccept((i) -> { + lastUse.put(i, M.ms()); listener.onChunkGenerated(x, z); listener.onChunkCleaned(x, z); - return 0; - })); + }).join(); } catch (Throwable e) { e.printStackTrace(); } } private void waitForChunksPartial(int maxWaiting) { - future.removeWhere(Objects::isNull); - while (future.size() > maxWaiting) { try { Future i = future.remove(0); @@ -127,8 +116,6 @@ public class AsyncPregenMethod implements PregeneratorMethod { e.printStackTrace(); } } - - future.removeWhere(Objects::isNull); } @Override @@ -145,6 +132,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { public void close() { waitForChunks(); unloadAndSaveAllChunks(); + burst.close(); } @Override @@ -169,7 +157,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { if (future.size() > 256) { waitForChunksPartial(256); } - future.add(burst.complete(() -> completeChunk(x, z, listener))); + burst.complete(() -> completeChunk(x, z, listener)); } @Override From be3e8ebd516f1f948f3ded865e9f8221a35cafb2 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 25 Aug 2024 19:41:35 +0200 Subject: [PATCH 03/20] fix MMOItems support --- .../volmit/iris/core/link/MMOItemsDataProvider.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java index b402f32ab..f5a940590 100644 --- a/core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java @@ -5,6 +5,7 @@ import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.scheduling.J; import net.Indyuce.mmoitems.MMOItems; +import net.Indyuce.mmoitems.api.ItemTier; import net.Indyuce.mmoitems.api.Type; import net.Indyuce.mmoitems.api.block.CustomBlock; import org.bukkit.Bukkit; @@ -46,8 +47,13 @@ public class MMOItemsDataProvider extends ExternalDataProvider { Runnable run = () -> { try { var type = api().getTypes().get(parts[1]); - int level = customNbt.containsKey("level") ? (int) customNbt.get("level") : -1; - var tier = api().getTiers().get(String.valueOf(customNbt.get("tier"))); + int level = -1; + ItemTier tier = null; + + if (customNbt != null) { + level = (int) customNbt.getOrDefault("level", -1); + tier = api().getTiers().get(String.valueOf(customNbt.get("tier"))); + } ItemStack itemStack; if (type == null) { From 29007fdbfab7a75aadf0429689e0ccab8c30a721 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 25 Aug 2024 21:48:10 +0200 Subject: [PATCH 04/20] fix walls not being rotated --- .../engine/object/IrisObjectRotation.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java index 6e8eb2a11..9ae2c3c32 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java @@ -22,6 +22,7 @@ import com.volmit.iris.Iris; import com.volmit.iris.engine.object.annotations.Desc; import com.volmit.iris.engine.object.annotations.Snippet; import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -30,9 +31,12 @@ import org.bukkit.Axis; import org.bukkit.Material; import org.bukkit.block.BlockFace; import org.bukkit.block.data.*; +import org.bukkit.block.data.type.Wall; +import org.bukkit.block.structure.StructureRotation; import org.bukkit.util.BlockVector; import java.util.List; +import java.util.Map; @Snippet("object-rotator") @Accessors(chain = true) @@ -41,6 +45,8 @@ import java.util.List; @Desc("Configures rotation for iris") @Data public class IrisObjectRotation { + private static final List WALL_FACES = List.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST); + @Desc("If this rotator is enabled or not") private boolean enabled = true; @@ -282,6 +288,22 @@ public class IrisObjectRotation { for (BlockFace i : faces) { g.setFace(i, true); } + } else if (d instanceof Wall wall) { + KMap faces = new KMap<>(); + + for (BlockFace i : WALL_FACES) { + Wall.Height h = wall.getHeight(i); + BlockVector bv = new BlockVector(i.getModX(), i.getModY(), i.getModZ()); + bv = rotate(bv.clone(), spinx, spiny, spinz); + BlockFace r = getFace(bv); + if (WALL_FACES.contains(r)) { + faces.put(r, h); + } + } + + for (BlockFace i : WALL_FACES) { + wall.setHeight(i, faces.getOrDefault(i, Wall.Height.NONE)); + } } else if (d.getMaterial().equals(Material.NETHER_PORTAL) && d instanceof Orientable g) { //TODO: Fucks up logs BlockFace f = faceForAxis(g.getAxis()); From 66a17396662b44ba72cf7b4963ef36a2bfb85616 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 25 Aug 2024 22:16:40 +0200 Subject: [PATCH 05/20] v+ --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 62e4c9025..711dcff67 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ plugins { id "de.undercouch.download" version "5.0.1" } -version '3.4.1-1.19.2-1.21.1' +version '3.4.3-1.19.2-1.21.1' // ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED // ======================== WINDOWS ============================= From 3f24d5c8e1c40988015c39a5cae1b73e37f732f4 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Tue, 27 Aug 2024 15:55:02 +0200 Subject: [PATCH 06/20] fix seed for structure placement --- .../iris/engine/mantle/components/MantleJigsawComponent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java index 4f48245bf..17e192c19 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java @@ -57,7 +57,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { @ChunkCoordinates private void generateJigsaw(MantleWriter writer, int x, int z, IrisBiome biome, IrisRegion region) { - long seed = cng.fit(Integer.MIN_VALUE, Integer.MIN_VALUE, x, z); + long seed = cng.fit(Integer.MIN_VALUE, Integer.MAX_VALUE, x, z); if (getDimension().getStronghold() != null) { List poss = getDimension().getStrongholds(seed()); @@ -130,7 +130,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { public IrisJigsawStructure guess(int x, int z) { // todo The guess doesnt bring into account that the placer may return -1 // todo doesnt bring skipped placements into account - long seed = cng.fit(Integer.MIN_VALUE, Integer.MIN_VALUE, x, z); + long seed = cng.fit(Integer.MIN_VALUE, Integer.MAX_VALUE, x, z); IrisBiome biome = getEngineMantle().getEngine().getSurfaceBiome((x << 4) + 8, (z << 4) + 8); IrisRegion region = getEngineMantle().getEngine().getRegion((x << 4) + 8, (z << 4) + 8); From 79341bf56287e3253a4bdcb947c90aa9bdfab452 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 28 Aug 2024 14:17:54 +0200 Subject: [PATCH 07/20] update adventure api --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 711dcff67..81d90ce2e 100644 --- a/build.gradle +++ b/build.gradle @@ -130,9 +130,9 @@ allprojects { // Shaded implementation 'com.dfsek:Paralithic:0.4.0' implementation 'io.papermc:paperlib:1.0.5' - implementation "net.kyori:adventure-text-minimessage:4.13.1" - implementation 'net.kyori:adventure-platform-bukkit:4.3.2' - implementation 'net.kyori:adventure-api:4.13.1' + implementation "net.kyori:adventure-text-minimessage:4.17.0" + implementation 'net.kyori:adventure-platform-bukkit:4.3.4' + implementation 'net.kyori:adventure-api:4.17.0' //implementation 'org.bytedeco:javacpp:1.5.10' //implementation 'org.bytedeco:cuda-platform:12.3-8.9-1.5.10' compileOnly 'io.lumine:Mythic-Dist:5.2.1' From ec8af56f0d83b774ac4596b379d34c4c6994448b Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 28 Aug 2024 20:30:43 +0200 Subject: [PATCH 08/20] woops --- .../iris/core/pregenerator/methods/AsyncPregenMethod.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java index 5d9048fd3..8bb12df0b 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java @@ -48,7 +48,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { } this.world = world; - burst = new MultiBurst("Iris Async Pregen", Thread.NORM_PRIORITY); + burst = new MultiBurst("Iris Async Pregen", Thread.MIN_PRIORITY); future = new KList<>(256); this.lastUse = new KMap<>(); } @@ -81,7 +81,8 @@ public class AsyncPregenMethod implements PregeneratorMethod { lastUse.put(i, M.ms()); listener.onChunkGenerated(x, z); listener.onChunkCleaned(x, z); - }).join(); + }).get(); + } catch (InterruptedException ignored) { } catch (Throwable e) { e.printStackTrace(); } @@ -157,7 +158,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { if (future.size() > 256) { waitForChunksPartial(256); } - burst.complete(() -> completeChunk(x, z, listener)); + future.add(burst.complete(() -> completeChunk(x, z, listener))); } @Override From 0c92c20c658fba6dee4751f4f8634b7dad201651 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 31 Aug 2024 14:13:50 +0200 Subject: [PATCH 09/20] ehm --- .../main/java/com/volmit/iris/engine/framework/Engine.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java index a7083fff7..7547126e4 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java @@ -53,6 +53,7 @@ import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.matter.MatterCavern; import com.volmit.iris.util.matter.MatterUpdate; +import com.volmit.iris.util.matter.TileWrapper; import com.volmit.iris.util.matter.slices.container.JigsawPieceContainer; import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.MultiBurst; @@ -282,10 +283,10 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.TILE, () -> J.s(() -> { - getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), TileData.class, (x, y, z, tile) -> { + getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> { int betterY = y + getWorld().minHeight(); - if (!TileData.setTileState(c.getBlock(x, betterY, z), tile)) - Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", x, betterY, z, c.getBlock(x, betterY, z).getBlockData().getMaterial().getKey(), tile.getMaterial().name()); + if (!TileData.setTileState(c.getBlock(x, betterY, z), v.getData())) + Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", x, betterY, z, c.getBlock(x, betterY, z).getBlockData().getMaterial().getKey(), v.getData().getMaterial().name()); }); })); getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.CUSTOM, () -> J.s(() -> { From cc584ba377965067ea12f19b0d1f57e6622f4904 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 31 Aug 2024 16:01:25 +0200 Subject: [PATCH 10/20] add progressbar to object saving --- .../com/volmit/iris/core/service/WandSVC.java | 76 ++++++++++++++----- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java index d5a57d77d..8cf63af47 100644 --- a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java @@ -36,6 +36,7 @@ import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.S; import com.volmit.iris.util.scheduling.SR; +import com.volmit.iris.util.scheduling.jobs.Job; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.enchantments.Enchantment; @@ -55,11 +56,12 @@ import java.awt.Color; import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; public class WandSVC implements IrisService { private static final Particle CRIT_MAGIC = E.getOrDefault(Particle.class, "CRIT_MAGIC", "CRIT"); private static final Particle REDSTONE = E.getOrDefault(Particle.class, "REDSTONE", "DUST"); - private static final int BLOCKS_PER_TICK = Integer.parseInt(System.getProperty("iris.blocks_per_tick", "1000")); + private static final int MS_PER_TICK = Integer.parseInt(System.getProperty("iris.ms_per_tick", "30")); private static ItemStack dust; private static ItemStack wand; @@ -85,27 +87,63 @@ public class WandSVC implements IrisService { IrisObject s = new IrisObject(c.getSizeX(), c.getSizeY(), c.getSizeZ()); var it = c.iterator(); + + int total = c.getSizeX() * c.getSizeY() * c.getSizeZ(); + AtomicInteger i = new AtomicInteger(0); var latch = new CountDownLatch(1); - new SR() { + new Job() { @Override - public void run() { - for (int i = 0; i < BLOCKS_PER_TICK; i++) { - if (!it.hasNext()) { - cancel(); - latch.countDown(); - return; - } - - var b = it.next(); - if (b.getType().equals(Material.AIR)) - continue; - - BlockVector bv = b.getLocation().subtract(c.getLowerNE().toVector()).toVector().toBlockVector(); - s.setUnsigned(bv.getBlockX(), bv.getBlockY(), bv.getBlockZ(), b); - } + public String getName() { + return "Scanning Selection"; } - }; - latch.await(); + + @Override + public void execute() { + new SR() { + @Override + public void run() { + var time = M.ms() + MS_PER_TICK; + while (time > M.ms()) { + if (!it.hasNext()) { + cancel(); + latch.countDown(); + return; + } + + try { + var b = it.next(); + if (b.getType().equals(Material.AIR)) + continue; + + BlockVector bv = b.getLocation().subtract(c.getLowerNE().toVector()).toVector().toBlockVector(); + s.setUnsigned(bv.getBlockX(), bv.getBlockY(), bv.getBlockZ(), b); + } finally { + i.incrementAndGet(); + } + } + } + }; + try { + latch.await(); + } catch (InterruptedException ignored) {} + } + + @Override + public void completeWork() {} + + @Override + public int getTotalWork() { + return total; + } + + @Override + public int getWorkCompleted() { + return i.get(); + } + }.execute(new VolmitSender(p), true, () -> {}); + try { + latch.await(); + } catch (InterruptedException ignored) {} return s; } catch (Throwable e) { From 18e57e40970fadb3273823dd08c3e9f87e3a119c Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 31 Aug 2024 16:28:52 +0200 Subject: [PATCH 11/20] add progressbar to object writing --- .../iris/core/commands/CommandObject.java | 2 +- .../volmit/iris/engine/object/IrisObject.java | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java index c5cdd74b6..fd2d8f214 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java @@ -381,7 +381,7 @@ public class CommandObject implements DecreeExecutor { return; } try { - o.write(file); + o.write(file, sender()); } catch (IOException e) { sender().sendMessage(C.RED + "Failed to save object because of an IOException: " + e.getMessage()); Iris.reportError(e); diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java index 0866ee63f..472dad80c 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java @@ -43,6 +43,7 @@ import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.IrisLock; import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import com.volmit.iris.util.scheduling.jobs.Job; import com.volmit.iris.util.stream.ProceduralStream; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -63,7 +64,9 @@ import org.bukkit.util.Vector; import java.io.*; import java.util.*; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; @Accessors(chain = true) @@ -384,6 +387,88 @@ public class IrisObject extends IrisRegistrant { } } + public void write(OutputStream o, VolmitSender sender) throws IOException { + AtomicReference ref = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + new Job() { + private int total = getBlocks().size() * 3 + getStates().size(); + private int c = 0; + + @Override + public String getName() { + return "Saving Object"; + } + + @Override + public void execute() { + try { + DataOutputStream dos = new DataOutputStream(o); + dos.writeInt(w); + dos.writeInt(h); + dos.writeInt(d); + dos.writeUTF("Iris V2 IOB;"); + + KList palette = new KList<>(); + + for (BlockData i : getBlocks().values()) { + palette.addIfMissing(i.getAsString()); + ++c; + } + total -= getBlocks().size() - palette.size(); + + dos.writeShort(palette.size()); + + for (String i : palette) { + dos.writeUTF(i); + ++c; + } + + dos.writeInt(getBlocks().size()); + + for (BlockVector i : getBlocks().keySet()) { + dos.writeShort(i.getBlockX()); + dos.writeShort(i.getBlockY()); + dos.writeShort(i.getBlockZ()); + dos.writeShort(palette.indexOf(getBlocks().get(i).getAsString())); + ++c; + } + + dos.writeInt(getStates().size()); + for (BlockVector i : getStates().keySet()) { + dos.writeShort(i.getBlockX()); + dos.writeShort(i.getBlockY()); + dos.writeShort(i.getBlockZ()); + getStates().get(i).toBinary(dos); + ++c; + } + } catch (IOException e) { + ref.set(e); + } finally { + latch.countDown(); + } + } + + @Override + public void completeWork() {} + + @Override + public int getTotalWork() { + return total; + } + + @Override + public int getWorkCompleted() { + return c; + } + }.execute(sender, true, () -> {}); + + try { + latch.await(); + } catch (InterruptedException ignored) {} + if (ref.get() != null) + throw ref.get(); + } + public void read(File file) throws IOException { var fin = new BufferedInputStream(new FileInputStream(file)); try { @@ -408,6 +493,16 @@ public class IrisObject extends IrisRegistrant { out.close(); } + public void write(File file, VolmitSender sender) throws IOException { + if (file == null) { + return; + } + + FileOutputStream out = new FileOutputStream(file); + write(out, sender); + out.close(); + } + public void shrinkwrap() { BlockVector min = new BlockVector(); BlockVector max = new BlockVector(); From 8041db4f40fc91410a89efb876a0b5072c93bbee Mon Sep 17 00:00:00 2001 From: RePixelatedMC <107539181+RePixelatedMC@users.noreply.github.com> Date: Sat, 31 Aug 2024 17:17:20 +0200 Subject: [PATCH 12/20] - New draw system - Crazy performance compared to the old system --- .../volmit/iris/core/wand/WandSelection.java | 119 ++++++++---------- 1 file changed, 53 insertions(+), 66 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java b/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java index e2b99633b..30ecd3771 100644 --- a/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java +++ b/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java @@ -30,9 +30,11 @@ import org.bukkit.util.Vector; import java.awt.*; public class WandSelection { - private static final Particle REDSTONE = E.getOrDefault(Particle.class, "REDSTONE", "DUST"); + private static final Particle REDSTONE = E.getOrDefault(Particle.class, "REDSTONE", "DUST"); private final Cuboid c; private final Player p; + private static final double STEP = 0.25; + private static final int MAX_PARTICLES = 10000; public WandSelection(Cuboid c, Player p) { this.c = c; @@ -40,77 +42,62 @@ public class WandSelection { } public void draw() { - double accuracy; - double dist; + Location playerLoc = p.getLocation(); + double maxDistanceSquared = 256 * 256; + int particleCount = 0; - for (double i = c.getLowerX() - 1; i < c.getUpperX() + 1; i += 0.25) { - for (double j = c.getLowerY() - 1; j < c.getUpperY() + 1; j += 0.25) { - for (double k = c.getLowerZ() - 1; k < c.getUpperZ() + 1; k += 0.25) { - boolean ii = i == c.getLowerX() || i == c.getUpperX(); - boolean jj = j == c.getLowerY() || j == c.getUpperY(); - boolean kk = k == c.getLowerZ() || k == c.getUpperZ(); + // cube! + Location[][] edges = { + {c.getLowerNE(), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ())}, + {c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ())}, + {c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())}, + {new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())}, + {new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)} + }; - if ((ii && jj) || (ii && kk) || (kk && jj)) { - Vector push = new Vector(0, 0, 0); + for (Location[] edge : edges) { + Vector direction = edge[1].toVector().subtract(edge[0].toVector()); + double length = direction.length(); + direction.normalize(); - if (i == c.getLowerX()) { - push.add(new Vector(-0.55, 0, 0)); - } + for (double d = 0; d <= length; d += STEP) { + Location particleLoc = edge[0].clone().add(direction.clone().multiply(d)); - if (j == c.getLowerY()) { - push.add(new Vector(0, -0.55, 0)); - } - - if (k == c.getLowerZ()) { - push.add(new Vector(0, 0, -0.55)); - } - - if (i == c.getUpperX()) { - push.add(new Vector(0.55, 0, 0)); - } - - if (j == c.getUpperY()) { - push.add(new Vector(0, 0.55, 0)); - } - - if (k == c.getUpperZ()) { - push.add(new Vector(0, 0, 0.55)); - } - - Location a = new Location(c.getWorld(), i, j, k).add(0.5, 0.5, 0.5).add(push); - accuracy = M.lerpInverse(0, 64 * 64, p.getLocation().distanceSquared(a)); - dist = M.lerp(0.125, 3.5, accuracy); - - if (M.r(M.min(dist * 5, 0.9D) * 0.995)) { - continue; - } - - if (ii && jj) { - a.add(0, 0, RNG.r.d(-0.3, 0.3)); - } - - if (kk && jj) { - a.add(RNG.r.d(-0.3, 0.3), 0, 0); - } - - if (ii && kk) { - a.add(0, RNG.r.d(-0.3, 0.3), 0); - } - - if (p.getLocation().distanceSquared(a) < 256 * 256) { - Color color = Color.getHSBColor((float) (0.5f + (Math.sin((i + j + k + (p.getTicksLived() / 2f)) / (20f)) / 2)), 1, 1); - int r = color.getRed(); - int g = color.getGreen(); - int b = color.getBlue(); - - p.spawnParticle(REDSTONE, a.getX(), a.getY(), a.getZ(), - 1, 0, 0, 0, 0, - new Particle.DustOptions(org.bukkit.Color.fromRGB(r, g, b), - (float) dist * 3f)); - } - } + if (playerLoc.distanceSquared(particleLoc) > maxDistanceSquared) { + continue; } + + if (particleCount >= MAX_PARTICLES) { + return; + } + + spawnParticle(particleLoc, playerLoc); + particleCount++; } } } + + private void spawnParticle(Location particleLoc, Location playerLoc) { + double accuracy = M.lerpInverse(0, 64 * 64, playerLoc.distanceSquared(particleLoc)); + double dist = M.lerp(0.125, 3.5, accuracy); + + if (M.r(Math.min(dist * 5, 0.9D) * 0.995)) { + return; + } + + float hue = (float) (0.5f + (Math.sin((particleLoc.getX() + particleLoc.getY() + particleLoc.getZ() + (p.getTicksLived() / 2f)) / 20f) / 2)); + Color color = Color.getHSBColor(hue, 1, 1); + + p.spawnParticle(REDSTONE, particleLoc, + 0, 0, 0, 0, 1, + new Particle.DustOptions(org.bukkit.Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue()), + (float) dist * 3f)); + } } From 08fa436885a7237bf517578251ed88603551d94f Mon Sep 17 00:00:00 2001 From: RePixelatedMC <107539181+RePixelatedMC@users.noreply.github.com> Date: Sat, 31 Aug 2024 17:45:09 +0200 Subject: [PATCH 13/20] better --- .../main/java/com/volmit/iris/core/wand/WandSelection.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java b/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java index 30ecd3771..d6f3d4aac 100644 --- a/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java +++ b/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java @@ -33,8 +33,7 @@ public class WandSelection { private static final Particle REDSTONE = E.getOrDefault(Particle.class, "REDSTONE", "DUST"); private final Cuboid c; private final Player p; - private static final double STEP = 0.25; - private static final int MAX_PARTICLES = 10000; + private static final double STEP = 0.10; public WandSelection(Cuboid c, Player p) { this.c = c; @@ -74,10 +73,6 @@ public class WandSelection { continue; } - if (particleCount >= MAX_PARTICLES) { - return; - } - spawnParticle(particleLoc, playerLoc); particleCount++; } From 67638440309a69df06977ca3ae0e093a9f373903 Mon Sep 17 00:00:00 2001 From: RePixelatedMC <107539181+RePixelatedMC@users.noreply.github.com> Date: Sat, 31 Aug 2024 18:29:43 +0200 Subject: [PATCH 14/20] back to debug --- .../src/main/java/com/volmit/iris/engine/object/IrisObject.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java index 472dad80c..aabae1998 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java @@ -573,7 +573,7 @@ public class IrisObject extends IrisRegistrant { getBlocks().put(v, data); TileData state = TileData.getTileState(block); if (state != null) { - Iris.info("Saved State " + v); + Iris.debug("Saved State " + v); getStates().put(v, state); } } From 3c9bcc9bb07f55aaa2570437deb0c8148995f6e9 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 1 Sep 2024 15:24:49 +0200 Subject: [PATCH 15/20] add chunked cuboid iterator --- .../com/volmit/iris/core/service/WandSVC.java | 24 +++++- .../com/volmit/iris/util/data/Cuboid.java | 83 +++++++++++++++++++ 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java index 8cf63af47..1899941bc 100644 --- a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java @@ -86,12 +86,14 @@ public class WandSVC implements IrisService { Cuboid c = new Cuboid(f[0], f[1]); IrisObject s = new IrisObject(c.getSizeX(), c.getSizeY(), c.getSizeZ()); - var it = c.iterator(); + var it = c.chunkedIterator(); int total = c.getSizeX() * c.getSizeY() * c.getSizeZ(); - AtomicInteger i = new AtomicInteger(0); var latch = new CountDownLatch(1); new Job() { + private int i; + private Chunk chunk; + @Override public String getName() { return "Scanning Selection"; @@ -105,6 +107,11 @@ public class WandSVC implements IrisService { var time = M.ms() + MS_PER_TICK; while (time > M.ms()) { if (!it.hasNext()) { + if (chunk != null) { + chunk.removePluginChunkTicket(Iris.instance); + chunk = null; + } + cancel(); latch.countDown(); return; @@ -112,13 +119,22 @@ public class WandSVC implements IrisService { try { var b = it.next(); + var bChunk = b.getChunk(); + if (chunk == null) { + chunk = bChunk; + chunk.addPluginChunkTicket(Iris.instance); + } else if (chunk != bChunk) { + chunk.removePluginChunkTicket(Iris.instance); + chunk = bChunk; + } + if (b.getType().equals(Material.AIR)) continue; BlockVector bv = b.getLocation().subtract(c.getLowerNE().toVector()).toVector().toBlockVector(); s.setUnsigned(bv.getBlockX(), bv.getBlockY(), bv.getBlockZ(), b); } finally { - i.incrementAndGet(); + i++; } } } @@ -138,7 +154,7 @@ public class WandSVC implements IrisService { @Override public int getWorkCompleted() { - return i.get(); + return i; } }.execute(new VolmitSender(p), true, () -> {}); try { diff --git a/core/src/main/java/com/volmit/iris/util/data/Cuboid.java b/core/src/main/java/com/volmit/iris/util/data/Cuboid.java index 437f65298..c2ea5b51a 100644 --- a/core/src/main/java/com/volmit/iris/util/data/Cuboid.java +++ b/core/src/main/java/com/volmit/iris/util/data/Cuboid.java @@ -20,6 +20,7 @@ package com.volmit.iris.util.data; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.math.Direction; +import com.volmit.iris.util.math.Position2; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.configuration.serialization.ConfigurationSerializable; @@ -651,6 +652,10 @@ public class Cuboid implements Iterable, Cloneable, ConfigurationSerializ return new CuboidIterator(getWorld(), x1, y1, z1, x2, y2, z2); } + public Iterator chunkedIterator() { + return new ChunkedCuboidIterator(getWorld(), x1, y1, z1, x2, y2, z2); + } + /* * (non-Javadoc) * @@ -746,4 +751,82 @@ public class Cuboid implements Iterable, Cloneable, ConfigurationSerializ } } + public static class ChunkedCuboidIterator implements Iterator { + private final World w; + private final int minRX, minY, minRZ, maxRX, maxY, maxRZ; + private final int minCX, minCZ, maxCX, maxCZ; + private int mX, mZ, bX, rX, rZ, y; + + private Position2 chunk; + private int cX, cZ; + + public ChunkedCuboidIterator(World w, int x1, int y1, int z1, int x2, int y2, int z2) { + this.w = w; + minY = Math.min(y1, y2); + maxY = Math.max(y1, y2); + int minX = Math.min(x1, x2); + int minZ = Math.min(z1, z2); + int maxX = Math.max(x1, x2); + int maxZ = Math.max(z1, z2); + minRX = minX & 15; + minRZ = minZ & 15; + maxRX = maxX & 15; + maxRZ = maxZ & 15; + + minCX = minX >> 4; + minCZ = minZ >> 4; + maxCX = maxX >> 4; + maxCZ = maxZ >> 4; + cX = minCX; + cZ = minCZ; + + rX = minX & 15; + rZ = minZ & 15; + y = minY; + } + + @Override + public boolean hasNext() { + return chunk != null || hasNextChunk(); + } + + public boolean hasNextChunk() { + return cX <= maxCX && cZ <= maxCZ; + } + + @Override + public Block next() { + if (chunk == null) { + chunk = new Position2(cX, cZ); + if (++cX > maxCX) { + cX = minCX; + cZ++; + } + + mX = chunk.getX() == maxCX ? maxRX : 15; + mZ = chunk.getZ() == maxCZ ? maxRZ : 15; + rX = bX = chunk.getX() == minCX ? minRX : 0; + rZ = chunk.getZ() == minCZ ? minRZ : 0; + } + + var b = w.getBlockAt((chunk.getX() << 4) + rX, y, (chunk.getZ() << 4) + rZ); + if (++y >= maxY) { + y = minY; + if (++rX > mX) { + if (++rZ > mZ) { + chunk = null; + return b; + } + rX = bX; + } + } + + return b; + } + + @Override + public void remove() { + // nop + } + } } \ No newline at end of file From e101155a4c34a3cb4d224d7558686615d8a33c4d Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 1 Sep 2024 18:51:23 +0200 Subject: [PATCH 16/20] fix datapack particle rarity --- .../java/com/volmit/iris/engine/object/IrisBiomeCustom.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustom.java b/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustom.java index dd3e16af6..aa3859c83 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustom.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustom.java @@ -104,7 +104,7 @@ public class IrisBiomeCustom { JSONObject po = new JSONObject(); po.put("type", ambientParticle.getParticle().name().toLowerCase()); particle.put("options", po); - particle.put("probability", ambientParticle.getRarity()); + particle.put("probability", 1f/ambientParticle.getRarity()); effects.put("particle", particle); } From b0eedee519a5bd71587e9bba913f1bf60391b366 Mon Sep 17 00:00:00 2001 From: RePixelatedMC <107539181+RePixelatedMC@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:07:30 +0200 Subject: [PATCH 17/20] - Progress bar for /iris std dist - Noted out that its in chunks for radius in /iris std dist --- .../iris/core/commands/CommandStudio.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java index 774e760b8..7227b431d 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java @@ -55,6 +55,7 @@ import com.volmit.iris.util.noise.CNG; import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.O; import com.volmit.iris.util.scheduling.PrecisionStopwatch; @@ -63,6 +64,7 @@ import io.papermc.lib.PaperLib; import org.bukkit.*; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; +import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; @@ -77,6 +79,7 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Date; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; @@ -335,29 +338,38 @@ public class CommandStudio implements DecreeExecutor { @Decree(description = "Get all structures in a radius of chunks", aliases = "dist", origin = DecreeOrigin.PLAYER) - public void distances(@Param(description = "The radius") int radius) { + public void distances(@Param(description = "The radius in chunks") int radius) { var engine = engine(); if (engine == null) { sender().sendMessage(C.RED + "Only works in an Iris world!"); return; } var sender = sender(); - int d = radius*2; + int d = radius * 2; KMap> data = new KMap<>(); var multiBurst = new MultiBurst("Distance Sampler", Thread.MIN_PRIORITY); var executor = multiBurst.burst(radius * radius); sender.sendMessage(C.GRAY + "Generating data..."); var loc = player().getLocation(); + int totalTasks = d * d; + AtomicInteger completedTasks = new AtomicInteger(0); + int c = J.ar(() -> { + sender.sendProgress((double) completedTasks.get() / totalTasks, "Finding structures"); + }, 0); + new Spiraler(d, d, (x, z) -> executor.queue(() -> { var struct = engine.getStructureAt(x, z); if (struct != null) { data.computeIfAbsent(struct.getLoadKey(), (k) -> new KList<>()).add(new Position2(x, z)); } + completedTasks.incrementAndGet(); })).setOffset(loc.getBlockX(), loc.getBlockZ()).drain(); executor.complete(); multiBurst.close(); + J.car(c); + for (var key : data.keySet()) { var list = data.get(key); KList distances = new KList<>(list.size() - 1); @@ -390,6 +402,7 @@ public class CommandStudio implements DecreeExecutor { } } + @Decree(description = "Render a world map (External GUI)", aliases = "render") public void map( @Param(name = "world", description = "The world to open the generator for", contextual = true) From e21fdf46e0dede099a58398483ab78caee7c5964 Mon Sep 17 00:00:00 2001 From: RePixelatedMC <107539181+RePixelatedMC@users.noreply.github.com> Date: Thu, 5 Sep 2024 21:57:40 +0200 Subject: [PATCH 18/20] - New Noise color < need further testing > - Iris schem converter --- .../com/volmit/iris/core/IrisSettings.java | 1 + .../iris/core/commands/CommandDeveloper.java | 1 + .../iris/core/commands/CommandObject.java | 11 + .../iris/core/gui/NoiseExplorerGUI.java | 8 +- .../volmit/iris/core/tools/IrisConverter.java | 287 +----------------- 5 files changed, 34 insertions(+), 274 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index 9eccc80e1..19c16e6ba 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -171,6 +171,7 @@ public class IrisSettings { public static class IrisSettingsGUI { public boolean useServerLaunchedGuis = true; public boolean maximumPregenGuiFPS = false; + public boolean colorMode = true; } @Data diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index 81473b45f..cb43a29d2 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -26,6 +26,7 @@ import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.core.nms.v1X.NMSBinding1X; import com.volmit.iris.core.pregenerator.ChunkUpdater; import com.volmit.iris.core.service.IrisEngineSVC; +import com.volmit.iris.core.tools.IrisConverter; import com.volmit.iris.core.tools.IrisPackBenchmarking; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java index fd2d8f214..6c7270db8 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java @@ -24,6 +24,7 @@ import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.service.ObjectSVC; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.service.WandSVC; +import com.volmit.iris.core.tools.IrisConverter; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.*; import com.volmit.iris.util.data.Cuboid; @@ -210,6 +211,16 @@ public class CommandObject implements DecreeExecutor { } } + @Decree(description = "Convert .schem files in the 'convert' folder to .iob files.") + public void convert () { + try { + IrisConverter.convertSchematics(sender()); + } catch (Exception e) { + e.printStackTrace(); + } + + } + @Decree(description = "Get a powder that reveals objects", studio = true, aliases = "d") public void dust() { player().getInventory().addItem(WandSVC.createDust()); diff --git a/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java b/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java index 6f816f6f3..e4be6dea9 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java +++ b/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java @@ -19,6 +19,7 @@ package com.volmit.iris.core.gui; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.events.IrisEngineHotloadEvent; import com.volmit.iris.engine.object.NoiseStyle; import com.volmit.iris.util.collection.KList; @@ -61,7 +62,7 @@ public class NoiseExplorerGUI extends JPanel implements MouseWheelListener, List @SuppressWarnings("CanBeFinal") RollingSequence r = new RollingSequence(20); @SuppressWarnings("CanBeFinal") - boolean colorMode = true; + boolean colorMode = IrisSettings.get().getGui().colorMode; double scale = 1; CNG cng = NoiseStyle.STATIC.create(new RNG(RNG.r.nextLong())); @SuppressWarnings("CanBeFinal") @@ -274,7 +275,10 @@ public class NoiseExplorerGUI extends JPanel implements MouseWheelListener, List n = n > 1 ? 1 : n < 0 ? 0 : n; try { - Color color = colorMode ? Color.getHSBColor((float) (n), 1f - (float) (n * n * n * n * n * n), 1f - (float) n) : Color.getHSBColor(0f, 0f, (float) n); + //Color color = colorMode ? Color.getHSBColor((float) (n), 1f - (float) (n * n * n * n * n * n), 1f - (float) n) : Color.getHSBColor(0f, 0f, (float) n); + //Color color = colorMode ? Color.getHSBColor((float) (n), (float) (n * n * n * n * n * n), (float) n) : Color.getHSBColor(0f, 0f, (float) n); + Color color = colorMode ? Color.getHSBColor((float) n, (float) (n * n * n * n * n * n), (float) n) : Color.getHSBColor(0f, 0f, (float) n); + int rgb = color.getRGB(); img.setRGB(xx, z, rgb); } catch (Throwable ignored) { diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisConverter.java b/core/src/main/java/com/volmit/iris/core/tools/IrisConverter.java index fb4d344eb..f58cc5c69 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisConverter.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisConverter.java @@ -12,9 +12,11 @@ import com.volmit.iris.util.reflect.V; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import org.apache.commons.io.FileUtils; import org.bukkit.Bukkit; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; +import org.bukkit.util.FileUtil; import org.bukkit.util.Vector; import java.io.File; @@ -33,11 +35,15 @@ public class IrisConverter { FilenameFilter filter = (dir, name) -> name.endsWith(".schem"); File[] fileList = folder.listFiles(filter); + if (fileList == null) { + sender.sendMessage("No schematic files to convert found in " + folder.getAbsolutePath()); + return; + } ExecutorService executorService = Executors.newFixedThreadPool(1); executorService.submit(() -> { for (File schem : fileList) { try { - PrecisionStopwatch p = new PrecisionStopwatch(); + PrecisionStopwatch p = PrecisionStopwatch.start(); boolean largeObject = false; NamedTag tag = null; try { @@ -52,21 +58,17 @@ public class IrisConverter { int objW = ((ShortTag) compound.get("Width")).getValue(); int objH = ((ShortTag) compound.get("Height")).getValue(); int objD = ((ShortTag) compound.get("Length")).getValue(); + int i = -1; int mv = objW * objH * objD; AtomicInteger v = new AtomicInteger(0); - AtomicInteger fv = new AtomicInteger(0); if (mv > 500_000) { largeObject = true; Iris.info(C.GRAY + "Converting.. "+ schem.getName() + " -> " + schem.getName().replace(".schem", ".iob")); Iris.info(C.GRAY + "- It may take a while"); if (sender.isPlayer()) { - J.a(() -> { -// while (v.get() != mv) { -// double pr = ((double) v.get() / (double ) mv); -// sender.sendProgress(pr, "Converting"); -// J.sleep(16); -// } - }); + i = J.ar(() -> { + sender.sendProgress((double) v.get() / mv, "Converting"); + }, 0); } } @@ -82,165 +84,8 @@ public class IrisConverter { ByteArrayTag byteArray = (ByteArrayTag) compound.get("BlockData"); byte[] originalBlockArray = byteArray.getValue(); - int b = 0; - int a = 0; - Map y = new HashMap<>(); - Map x = new HashMap<>(); - Map z = new HashMap<>(); - // Height adjustments - for (int h = 0; h < objH; h++) { - if (b == 0) { - y.put(h, (byte) 0); - } - if (b > 0) { - y.put(h, (byte) 1); - } - a = 0; - b = 0; - for (int d = 0; d < objD; d++) { - for (int w = 0; w < objW; w++) { - BlockData db = blockmap.get((int) originalBlockArray[fv.get()]); - if(db.getAsString().contains("minecraft:air")) { - a++; - } else { - b++; - } - fv.getAndAdd(1); - } - } - } - fv.set(0); - - // Width adjustments - for (int w = 0; w < objW; w++) { - if (b == 0) { - x.put(w, (byte) 0); - } - if (b > 0) { - x.put(w, (byte) 1); - } - a = 0; - b = 0; - for (int h = 0; h < objH; h++) { - for (int d = 0; d < objD; d++) { - BlockData db = blockmap.get((int) originalBlockArray[fv.get()]); - if(db.getAsString().contains("minecraft:air")) { - a++; - } else { - b++; - } - fv.getAndAdd(1); - } - } - } - fv.set(0); - - // Depth adjustments - for (int d = 0; d < objD; d++) { - if (b == 0) { - z.put(d, (byte) 0); - } - if (b > 0) { - z.put(d, (byte) 1); - } - a = 0; - b = 0; - for (int h = 0; h < objH; h++) { - for (int w = 0; w < objW; w++) { - BlockData db = blockmap.get((int) originalBlockArray[fv.get()]); - if(db.getAsString().contains("minecraft:air")) { - a++; - } else { - b++; - } - fv.getAndAdd(1); - } - } - } - fv.set(0); - int CorrectObjH = getCorrectY(y, objH); - int CorrectObjW = getCorrectX(x, objW); - int CorrectObjD = getCorrectZ(z, objD); - - //IrisObject object = new IrisObject(CorrectObjW, CorrectObjH, CorrectObjH); IrisObject object = new IrisObject(objW, objH, objD); - Vector originalVector = new Vector(objW,objH,objD); - - - int[] yc = null; - int[] xc = null; - int[] zc = null; - - - int fo = 0; - int so = 0; - int o = 0; - int c = 0; - for (Integer i : y.keySet()) { - if (y.get(i) == 0) { - o++; - } - if (y.get(i) == 1) { - c++; - if (c == 1) { - fo = o; - } - o = 0; - } - } - so = o; - yc = new int[]{fo, so}; - - fo = 0; - so = 0; - o = 0; - c = 0; - for (Integer i : x.keySet()) { - if (x.get(i) == 0) { - o++; - } - if (x.get(i) == 1) { - c++; - if (c == 1) { - fo = o; - } - o = 0; - } - } - so = o; - xc = new int[]{fo, so}; - - fo = 0; - so = 0; - o = 0; - c = 0; - for (Integer i : z.keySet()) { - if (z.get(i) == 0) { - o++; - } - if (z.get(i) == 1) { - c++; - if (c == 1) { - fo = o; - } - o = 0; - } - } - so = o; - zc = new int[]{fo, so}; - - int h1, h2, w1, w2, v1 = 0, volume = objW * objH * objD; - Map blockLocationMap = new LinkedHashMap<>(); - boolean hasAir = false; - int pos = 0; - for (int i : originalBlockArray) { - blockLocationMap.put(pos, i); - pos++; - } - - - for (int h = 0; h < objH; h++) { for (int d = 0; d < objD; d++) { for (int w = 0; w < objW; w++) { @@ -252,9 +97,9 @@ public class IrisConverter { } } } - - + if (i != -1) J.car(i); try { + object.shrinkwrap(); object.write(new File(folder, schem.getName().replace(".schem", ".iob"))); } catch (IOException e) { Iris.info(C.RED + "Failed to save: " + schem.getName()); @@ -272,7 +117,7 @@ public class IrisConverter { } else { Iris.info(C.GRAY + "Converted " + schem.getName() + " -> " + schem.getName().replace(".schem", ".iob")); } - // schem.delete(); + FileUtils.delete(schem); } } catch (Exception e) { Iris.info(C.RED + "Failed to convert: " + schem.getName()); @@ -283,112 +128,10 @@ public class IrisConverter { Iris.reportError(e); } } + sender.sendMessage(C.GRAY + "converted: " + fileList.length); }); } - public static boolean isNewPointFurther(int[] originalPoint, int[] oldPoint, int[] newPoint) { - int oX = oldPoint[1]; - int oY = oldPoint[2]; - int oZ = oldPoint[3]; - - int nX = newPoint[1]; - int nY = newPoint[2]; - int nZ = newPoint[3]; - - int orX = originalPoint[1]; - int orY = originalPoint[2]; - int orZ = originalPoint[3]; - - double oldDistance = Math.sqrt(Math.pow(oX - orX, 2) + Math.pow(oY - orY, 2) + Math.pow(oZ - orZ, 2)); - double newDistance = Math.sqrt(Math.pow(nX - orX, 2) + Math.pow(nY - orY, 2) + Math.pow(nZ - orZ, 2)); - - if (newDistance > oldDistance) { - return true; - } - return false; - } - - public static int[] getCoordinates(int pos, int obX, int obY, int obZ) { - int z = 0; - int[] coords = new int[4]; - for (int h = 0; h < obY; h++) { - for (int d = 0; d < obZ; d++) { - for (int w = 0; w < obX; w++) { - if (z == pos) { - coords[1] = w; - coords[2] = h; - coords[3] = d; - return coords; - } - z++; - } - } - } - return null; - } - - public static int getCorrectY(Map y, int H) { - int fo = 0; - int so = 0; - int o = 0; - int c = 0; - for (Integer i : y.keySet()) { - if (y.get(i) == 0) { - o++; - } - if (y.get(i) == 1) { - c++; - if(c == 1){ - fo = o; - } - o = 0; - } - } - so = o; - return H = H - (fo + so); - } - - public static int getCorrectX(Map x, int W) { - int fo = 0; - int so = 0; - int o = 0; - int c = 0; - for (Integer i : x.keySet()) { - if (x.get(i) == 0) { - o++; - } - if (x.get(i) == 1) { - c++; - if(c == 1){ - fo = o; - } - o = 0; - } - } - so = o; - return W = W - (fo + so); - } - - public static int getCorrectZ(Map z, int D) { - int fo = 0; - int so = 0; - int o = 0; - int c = 0; - for (Integer i : z.keySet()) { - if (z.get(i) == 0) { - o++; - } - if (z.get(i) == 1) { - c++; - if(c == 1){ - fo = o; - } - o = 0; - } - } - so = o; - return D = D - (fo + so); - } } From 0a256eaa4cdef5fd23d6c4c618bca6ecdc1a64f1 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Fri, 6 Sep 2024 23:21:08 +0200 Subject: [PATCH 19/20] remote/cloud pregen! --- build.gradle | 6 + core/src/main/java/com/volmit/iris/Iris.java | 31 +- .../com/volmit/iris/core/IrisSettings.java | 12 + .../iris/core/commands/CommandDeveloper.java | 6 +- .../com/volmit/iris/core/nms/IHeadless.java | 10 +- .../iris/core/pregenerator/PregenTask.java | 3 + .../methods/HeadlessPregenMethod.java | 1 + .../iris/core/tools/IrisPackBenchmarking.java | 27 +- .../com/volmit/iris/server/EntryPoint.java | 116 ++++++++ .../volmit/iris/server/IrisConnection.java | 201 +++++++++++++ .../server/execption/RejectedException.java | 8 + .../iris/server/master/IrisMasterClient.java | 35 +++ .../iris/server/master/IrisMasterServer.java | 107 +++++++ .../iris/server/master/IrisMasterSession.java | 98 +++++++ .../volmit/iris/server/node/IrisServer.java | 68 +++++ .../volmit/iris/server/node/IrisSession.java | 110 ++++++++ .../com/volmit/iris/server/packet/Packet.java | 24 ++ .../volmit/iris/server/packet/Packets.java | 91 ++++++ .../iris/server/packet/handle/Decoder.java | 17 ++ .../iris/server/packet/handle/Encoder.java | 14 + .../iris/server/packet/handle/Prepender.java | 14 + .../iris/server/packet/handle/Splitter.java | 33 +++ .../iris/server/packet/init/EnginePacket.java | 60 ++++ .../iris/server/packet/init/FilePacket.java | 56 ++++ .../iris/server/packet/init/InfoPacket.java | 33 +++ .../iris/server/packet/work/ChunkPacket.java | 37 +++ .../iris/server/packet/work/DonePacket.java | 28 ++ .../server/packet/work/MantleChunkPacket.java | 103 +++++++ .../iris/server/packet/work/PregenPacket.java | 35 +++ .../iris/server/pregen/CloudMethod.java | 264 ++++++++++++++++++ .../volmit/iris/server/pregen/CloudTask.java | 70 +++++ .../volmit/iris/server/util/ByteBufUtil.java | 27 ++ .../volmit/iris/server/util/CPSLooper.java | 67 +++++ .../iris/server/util/ConnectionHolder.java | 8 + .../volmit/iris/server/util/ErrorPacket.java | 54 ++++ .../iris/server/util/LimitedSemaphore.java | 41 +++ .../iris/server/util/PacketListener.java | 14 + .../iris/server/util/PacketSendListener.java | 26 ++ .../volmit/iris/server/util/PregenHolder.java | 62 ++++ .../com/volmit/iris/util/mantle/Mantle.java | 5 + .../volmit/iris/util/mantle/MantleChunk.java | 4 + .../iris/util/mantle/TectonicPlate.java | 14 + core/src/main/resources/plugin.yml | 1 + .../iris/core/nms/v1_20_R3/Headless.java | 131 +++++++-- 44 files changed, 2147 insertions(+), 25 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/server/EntryPoint.java create mode 100644 core/src/main/java/com/volmit/iris/server/IrisConnection.java create mode 100644 core/src/main/java/com/volmit/iris/server/execption/RejectedException.java create mode 100644 core/src/main/java/com/volmit/iris/server/master/IrisMasterClient.java create mode 100644 core/src/main/java/com/volmit/iris/server/master/IrisMasterServer.java create mode 100644 core/src/main/java/com/volmit/iris/server/master/IrisMasterSession.java create mode 100644 core/src/main/java/com/volmit/iris/server/node/IrisServer.java create mode 100644 core/src/main/java/com/volmit/iris/server/node/IrisSession.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/Packet.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/Packets.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/handle/Decoder.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/handle/Encoder.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/handle/Prepender.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/handle/Splitter.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/init/EnginePacket.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/init/FilePacket.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/init/InfoPacket.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/work/ChunkPacket.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/work/DonePacket.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/work/MantleChunkPacket.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/work/PregenPacket.java create mode 100644 core/src/main/java/com/volmit/iris/server/pregen/CloudMethod.java create mode 100644 core/src/main/java/com/volmit/iris/server/pregen/CloudTask.java create mode 100644 core/src/main/java/com/volmit/iris/server/util/ByteBufUtil.java create mode 100644 core/src/main/java/com/volmit/iris/server/util/CPSLooper.java create mode 100644 core/src/main/java/com/volmit/iris/server/util/ConnectionHolder.java create mode 100644 core/src/main/java/com/volmit/iris/server/util/ErrorPacket.java create mode 100644 core/src/main/java/com/volmit/iris/server/util/LimitedSemaphore.java create mode 100644 core/src/main/java/com/volmit/iris/server/util/PacketListener.java create mode 100644 core/src/main/java/com/volmit/iris/server/util/PacketSendListener.java create mode 100644 core/src/main/java/com/volmit/iris/server/util/PregenHolder.java diff --git a/build.gradle b/build.gradle index c49d5bb4d..2f0920924 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,7 @@ def JVM_VERSION = Map.of( "v1_21_R1", 21, "v1_20_R4", 21, ) +def entryPoint = 'com.volmit.iris.server.EntryPoint' NMS_BINDINGS.each { nms -> project(":nms:${nms.key}") { apply plugin: 'java' @@ -95,6 +96,10 @@ shadowJar { relocate 'io.papermc.lib', 'com.volmit.iris.util.paper' relocate 'net.kyori', 'com.volmit.iris.util.kyori' archiveFileName.set("Iris-${project.version}.jar") + + manifest { + attributes 'Main-Class': entryPoint + } } dependencies { @@ -156,6 +161,7 @@ allprojects { compileOnly 'net.bytebuddy:byte-buddy-agent:1.12.8' compileOnly 'org.bytedeco:javacpp:1.5.10' compileOnly 'org.bytedeco:cuda-platform:12.3-8.9-1.5.10' + compileOnly 'io.netty:netty-all:4.1.112.Final' } /** diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index ce32385aa..a4215738a 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -41,6 +41,8 @@ import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.engine.object.IrisWorld; import com.volmit.iris.engine.platform.BukkitChunkGenerator; import com.volmit.iris.engine.platform.DummyChunkGenerator; +import com.volmit.iris.server.master.IrisMasterServer; +import com.volmit.iris.server.node.IrisServer; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.exceptions.IrisException; @@ -91,10 +93,7 @@ import org.jetbrains.annotations.Nullable; import java.io.*; import java.lang.annotation.Annotation; import java.net.URL; -import java.util.Collection; -import java.util.Date; -import java.util.Map; -import java.util.Properties; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -112,6 +111,7 @@ public class Iris extends VolmitPlugin implements Listener { public static MythicMobsLink linkMythicMobs; public static IrisCompat compat; public static FileWatcher configWatcher; + private static IrisServer server; private static VolmitSender sender; static { @@ -475,6 +475,29 @@ public class Iris extends VolmitPlugin implements Listener { services.values().forEach(this::registerListener); ServerConfigurator.setupDataPack(); installMainDimension(); + try { + info("Starting server..."); + try { + int port = Integer.parseInt(System.getProperty("com.volmit.iris.server.port")); + String[] remote = Optional.ofNullable(System.getProperty("com.volmit.iris.server.remote")) + .map(String::trim) + .map(s -> s.isBlank() ? null : s.split(",")) + .orElse(new String[0]); + server = remote.length > 0 ? new IrisMasterServer(port, remote) : new IrisServer(port); + } catch (NullPointerException | NumberFormatException ignored) { + var serverSettings = IrisSettings.get().getServer(); + if (serverSettings.isActive()) { + server = serverSettings.isRemote() ? + new IrisMasterServer(serverSettings.getPort(), serverSettings.remote) : + new IrisServer(serverSettings.getPort()); + } + } + } catch (InterruptedException ignored) { + } catch (Throwable e) { + error("Failed to start server: " + e.getClass().getSimpleName()); + e.printStackTrace(); + } + if (!IrisSafeguard.instance.acceptUnstable && IrisSafeguard.instance.unstablemode) { Iris.info(C.RED + "World loading has been disabled until the incompatibility is resolved."); Iris.info(C.DARK_RED + "Alternatively, go to plugins/iris/settings.json and set ignoreBootMode to true."); diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index c17c8f0a2..f3cd720dd 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -44,6 +44,7 @@ public class IrisSettings { private IrisSettingsPerformance performance = new IrisSettingsPerformance(); private IrisWorldDump worldDump = new IrisWorldDump(); private IrisWorldSettings irisWorldSettings = new IrisWorldSettings(); + private IrisServerSettings server = new IrisServerSettings(); public static int getThreadCount(int c) { return switch (c) { @@ -202,6 +203,17 @@ public class IrisSettings { public int mcaCacheSize = 3; } + @Data + public static class IrisServerSettings { + public boolean active = false; + public int port = 1337; + public String[] remote = new String[0]; + + public boolean isRemote() { + return remote.length != 0; + } + } + // todo: Goal:Have these as the default world settings and when put in bukkit.yml it will again overwrite that world from these. @Data public static class IrisWorldSettings { diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index 20296b8b3..7207af562 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -102,8 +102,10 @@ public class CommandDeveloper implements DecreeExecutor { @Decree(description = "Test") public void packBenchmark( - @Param(description = "The pack to bench", aliases = {"pack"}) + @Param(description = "The pack to bench", defaultValue = "overworld", aliases = {"pack"}) IrisDimension dimension, + @Param(description = "The address to use", defaultValue = "-") + String address, @Param(description = "Headless", defaultValue = "true") boolean headless, @Param(description = "GUI", defaultValue = "false") @@ -113,7 +115,7 @@ public class CommandDeveloper implements DecreeExecutor { ) { int rb = diameter << 9; Iris.info("Benchmarking pack " + dimension.getName() + " with diameter: " + rb + "(" + diameter + ")"); - IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, diameter, headless, gui); + IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, address.replace("-", "").trim(), diameter, headless, gui); benchmark.runBenchmark(); } diff --git a/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java b/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java index 90d56c0de..1c27b5a36 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java +++ b/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java @@ -19,22 +19,30 @@ package com.volmit.iris.core.nms; import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.server.node.IrisSession; +import com.volmit.iris.server.packet.work.ChunkPacket; 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 { + void setSession(IrisSession session); + int getLoadedChunks(); @ChunkCoordinates boolean exists(int x, int z); @RegionCoordinates - void generateRegion(MultiBurst burst, int x, int z, PregenListener listener); + CompletableFuture generateRegion(MultiBurst burst, int x, int z, int maxConcurrent, PregenListener listener); @ChunkCoordinates void generateChunk(int x, int z); + + @ChunkCoordinates + void addChunk(ChunkPacket packet); } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java b/core/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java index 08307b28c..eea5ed08b 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java @@ -23,6 +23,8 @@ import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.Spiraled; import com.volmit.iris.util.math.Spiraler; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -30,6 +32,7 @@ import java.util.Comparator; @Builder @Data +@AllArgsConstructor(access = AccessLevel.PROTECTED) public class PregenTask { private static final Position2 ZERO = new Position2(0, 0); private static final KList ORDER_CENTER = computeChunkOrder(); diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java index d4ef57f3f..7e42de852 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java @@ -65,6 +65,7 @@ public class HeadlessPregenMethod implements PregeneratorMethod { Iris.error("Failed to close headless"); e.printStackTrace(); } + burst.close(); } @Override diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java index c8495a338..3cc9a2944 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java @@ -31,6 +31,8 @@ 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.IrisWorld; +import com.volmit.iris.server.pregen.CloudMethod; +import com.volmit.iris.server.pregen.CloudTask; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.exceptions.IrisException; @@ -65,10 +67,12 @@ public class IrisPackBenchmarking { private int radius; private boolean finished = false; private Engine engine; + private String address; - public IrisPackBenchmarking(IrisDimension dimension, int r, boolean headless, boolean gui) { + public IrisPackBenchmarking(IrisDimension dimension, String address, int r, boolean headless, boolean gui) { instance = this; this.IrisDimension = dimension; + this.address = address; this.radius = r; this.headless = headless; this.gui = gui; @@ -90,7 +94,13 @@ public class IrisPackBenchmarking { } Iris.info("Starting Benchmark!"); stopwatch.begin(); - startBenchmark(); + try { + if (address != null && !address.isBlank()) + startCloudBenchmark(); + else startBenchmark(); + } catch (Throwable e) { + e.printStackTrace(); + } }, "PackBenchmarking").start(); } @@ -197,6 +207,19 @@ public class IrisPackBenchmarking { IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), engine); } + private void startCloudBenchmark() throws InterruptedException { + int x = 0; + int z = 0; + IrisToolbelt.pregenerate(CloudTask + .couldBuilder() + .gui(gui) + .center(new Position2(x, z)) + .width(radius) + .height(radius) + .distance(engine.getMantle().getRadius() * 2) + .build(), new CloudMethod(address, engine), engine); + } + private double calculateAverage(KList list) { double sum = 0; for (int num : list) { diff --git a/core/src/main/java/com/volmit/iris/server/EntryPoint.java b/core/src/main/java/com/volmit/iris/server/EntryPoint.java new file mode 100644 index 000000000..13ab1c90f --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/EntryPoint.java @@ -0,0 +1,116 @@ +/* + * 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 . + */ + +package com.volmit.iris.server; + +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +@Log(topic = "Iris-Server") +public class EntryPoint { + + public static void main(String[] args) throws Throwable { + if (args.length < 4) { + log.info("Usage: java -jar Iris.jar [nodes]"); + System.exit(-1); + return; + } + + String[] nodes = new String[args.length - 4]; + System.arraycopy(args, 4, nodes, 0, nodes.length); + try { + runServer(args[0], Integer.parseInt(args[1]), Integer.parseInt(args[2]), Integer.parseInt(args[3]), nodes); + } catch (Throwable e) { + log.log(Level.SEVERE, "Failed to start server", e); + System.exit(-1); + } + } + + private static void runServer(String version, int minMemory, int maxMemory, int serverPort, String[] nodes) throws IOException { + File serverJar = new File("cache", "spigot-"+version+".jar"); + if (!serverJar.getParentFile().exists() && !serverJar.getParentFile().mkdirs()) { + log.severe("Failed to create cache directory"); + System.exit(-1); + return; + } + + + if (!serverJar.exists()) { + try (var in = new URL("https://download.getbukkit.org/spigot/spigot-"+ version+".jar").openStream()) { + Files.copy(in, serverJar.toPath()); + } + log.info("Downloaded spigot-"+version+".jar to "+serverJar.getAbsolutePath()); + } + + File pluginFile = new File("plugins/Iris.jar"); + if (pluginFile.exists()) pluginFile.delete(); + if (!pluginFile.getParentFile().exists() && !pluginFile.getParentFile().mkdirs()) { + log.severe("Failed to create plugins directory"); + System.exit(-1); + return; + } + + boolean windows = System.getProperty("os.name").toLowerCase().contains("win"); + String path = System.getProperty("java.home") + File.separator + "bin" + File.separator + (windows ? "java.exe" : "java"); + + try { + File irisFile = new File(EntryPoint.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + if (!irisFile.isFile()) { + log.severe("Failed to locate the Iris plugin jar"); + System.exit(-1); + return; + } + Files.createSymbolicLink(pluginFile.toPath(), irisFile.toPath()); + } catch (URISyntaxException ignored) {} + + List cmd = new ArrayList<>(List.of( + path, + "-Xms" + minMemory + "M", + "-Xmx" + maxMemory + "M", + "-XX:+AlwaysPreTouch", + "-XX:+HeapDumpOnOutOfMemoryError", + "-Ddisable.watchdog=true", + "-Dcom.mojang.eula.agree=true", + "-Dcom.volmit.iris.server.port="+serverPort + )); + if (nodes.length > 0) + cmd.add("-Dcom.volmit.iris.server.remote=" + String.join(",", nodes)); + cmd.addAll(List.of("-jar", serverJar.getAbsolutePath(), "nogui")); + + var process = new ProcessBuilder(cmd) + .inheritIO() + .start(); + Runtime.getRuntime().addShutdownHook(new Thread(process::destroy)); + + + while (true) { + try { + process.waitFor(); + break; + } catch (InterruptedException ignored) {} + } + } +} diff --git a/core/src/main/java/com/volmit/iris/server/IrisConnection.java b/core/src/main/java/com/volmit/iris/server/IrisConnection.java new file mode 100644 index 000000000..68189936b --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/IrisConnection.java @@ -0,0 +1,201 @@ +package com.volmit.iris.server; + +import com.volmit.iris.server.execption.RejectedException; +import com.volmit.iris.server.packet.Packet; +import com.volmit.iris.server.packet.handle.Decoder; +import com.volmit.iris.server.packet.handle.Encoder; +import com.volmit.iris.server.packet.handle.Prepender; +import com.volmit.iris.server.packet.handle.Splitter; +import com.volmit.iris.server.util.ErrorPacket; +import com.volmit.iris.server.util.ConnectionHolder; +import com.volmit.iris.server.util.PacketListener; +import com.volmit.iris.server.util.PacketSendListener; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.TimeoutException; +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; + +import javax.annotation.Nullable; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; +import java.util.logging.Level; + +@RequiredArgsConstructor +@Log(topic = "IrisConnection") +public class IrisConnection extends SimpleChannelInboundHandler { + private static EventLoopGroup WORKER; + + private Channel channel; + private SocketAddress address; + private final PacketListener listener; + private final Queue queue = new ConcurrentLinkedQueue<>(); + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Packet packet) throws Exception { + if (!channel.isOpen() || listener == null || !listener.isAccepting()) return; + + try { + listener.onPacket(packet); + } catch (RejectedException e) { + send(new ErrorPacket("Rejected: " + e.getMessage())); + } + } + + public void send(Packet packet) { + this.send(packet, null); + } + + public void send(Packet packet, @Nullable PacketSendListener listener) { + if (!isConnected()) { + queue.add(new PacketHolder(packet, listener)); + return; + } + + flushQueue(); + sendPacket(packet, listener); + } + + public boolean isConnected() { + return channel != null && channel.isOpen(); + } + + public void disconnect() { + try { + if (channel != null && channel.isOpen()) { + log.info("Closed on " + address); + channel.close(); + } + if (listener != null) + listener.onDisconnect(); + } catch (Throwable e) { + log.log(Level.SEVERE, "Failed to close on " + address, e); + } + } + + public void execute(Runnable runnable) { + if (channel == null || !channel.isOpen()) return; + channel.eventLoop().execute(runnable); + } + + private void flushQueue() { + if (this.channel != null && this.channel.isOpen()) { + synchronized(this.queue) { + PacketHolder packetHolder; + while((packetHolder = this.queue.poll()) != null) { + sendPacket(packetHolder.packet, packetHolder.listener); + } + } + } + } + + private void sendPacket(Packet packet, @Nullable PacketSendListener listener) { + if (!channel.eventLoop().inEventLoop()) { + channel.eventLoop().execute(() -> sendPacket(packet, listener)); + return; + } + + ChannelFuture channelFuture = channel.writeAndFlush(packet); + + if (listener != null) { + channelFuture.addListener(future -> { + if (future.isSuccess()) { + listener.onSuccess(); + } else { + Packet fallback = listener.onFailure(); + if (fallback == null) return; + channel.writeAndFlush(fallback) + .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + }); + } + channelFuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + + channel = ctx.channel(); + address = channel.remoteAddress(); + log.info("Opened on " + channel.remoteAddress()); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + disconnect(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + if (!channel.isOpen()) return; + ErrorPacket error; + if (cause instanceof TimeoutException) { + error = new ErrorPacket("Timed out"); + } else { + error = new ErrorPacket("Internal Exception: " + cause.getMessage()); + log.log(Level.SEVERE, "Failed to send packet", cause); + } + + sendPacket(error, PacketSendListener.thenRun(this::disconnect)); + channel.config().setAutoRead(false); + } + + @Override + public String toString() { + return "IrisConnection{address=%s}".formatted(address); + } + + public static void configureSerialization(Channel channel, ConnectionHolder holder) { + channel.pipeline() + .addLast("timeout", new ReadTimeoutHandler(30)) + .addLast("splitter", new Splitter()) + .addLast("decoder", new Decoder()) + .addLast("prepender", new Prepender()) + .addLast("encoder", new Encoder()) + .addLast("packet_handler", holder.getConnection()); + } + + public static T connect(InetSocketAddress address, Supplier factory) throws InterruptedException { + var holder = factory.get(); + new Bootstrap() + .group(getWorker()) + .handler(new ChannelInitializer<>() { + @Override + protected void initChannel(Channel channel) { + channel.config().setOption(ChannelOption.TCP_NODELAY, true); + IrisConnection.configureSerialization(channel, holder); + } + }) + .channel(NioSocketChannel.class) + .connect(address) + .sync(); + return holder; + } + + private static class PacketHolder { + private final Packet packet; + @Nullable + private final PacketSendListener listener; + + public PacketHolder(Packet packet, @Nullable PacketSendListener listener) { + this.packet = packet; + this.listener = listener; + } + } + + private static EventLoopGroup getWorker() { + if (WORKER == null) { + WORKER = new NioEventLoopGroup(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> WORKER.shutdownGracefully())); + } + return WORKER; + } +} diff --git a/core/src/main/java/com/volmit/iris/server/execption/RejectedException.java b/core/src/main/java/com/volmit/iris/server/execption/RejectedException.java new file mode 100644 index 000000000..e4f1579bf --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/execption/RejectedException.java @@ -0,0 +1,8 @@ +package com.volmit.iris.server.execption; + +public class RejectedException extends Exception { + + public RejectedException(String message) { + super(message); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/master/IrisMasterClient.java b/core/src/main/java/com/volmit/iris/server/master/IrisMasterClient.java new file mode 100644 index 000000000..da65a1aec --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/master/IrisMasterClient.java @@ -0,0 +1,35 @@ +package com.volmit.iris.server.master; + +import com.volmit.iris.server.IrisConnection; +import com.volmit.iris.server.packet.Packet; +import com.volmit.iris.server.util.ConnectionHolder; +import com.volmit.iris.server.util.PacketListener; +import com.volmit.iris.server.util.PacketSendListener; +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +public class IrisMasterClient implements ConnectionHolder, PacketListener { + private final @Getter IrisConnection connection = new IrisConnection(this); + private final IrisMasterSession session; + + IrisMasterClient(IrisMasterSession session) { + this.session = session; + } + + protected void send(Packet packet) { + connection.send(packet); + } + + protected void send(Packet packet, @Nullable PacketSendListener listener) { + connection.send(packet, listener); + } + + @Override + public void onPacket(Packet packet) throws Exception { + session.onClientPacket(packet); + } + + public void disconnect() { + connection.disconnect(); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/master/IrisMasterServer.java b/core/src/main/java/com/volmit/iris/server/master/IrisMasterServer.java new file mode 100644 index 000000000..bd1b6b130 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/master/IrisMasterServer.java @@ -0,0 +1,107 @@ +package com.volmit.iris.server.master; + +import com.volmit.iris.server.IrisConnection; +import com.volmit.iris.server.node.IrisServer; +import com.volmit.iris.server.util.PregenHolder; +import com.volmit.iris.util.collection.KMap; +import lombok.extern.java.Log; + +import java.net.InetSocketAddress; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +@Log(topic = "Iris-MasterServer") +public class IrisMasterServer extends IrisServer { + private static final Object VALUE = new Object(); + private static IrisMasterServer instance; + private final KMap>> sessions = new KMap<>(); + private final KMap nodes = new KMap<>(); + + public IrisMasterServer(int port, String[] remote) throws InterruptedException { + super("Iris-MasterServer", port, IrisMasterSession::new); + if (instance != null && !instance.isRunning()) + close("Server already running"); + instance = this; + + for (var address : remote) { + try { + var split = address.split(":"); + if (split.length != 2 || !split[1].matches("\\d+")) { + log.warning("Invalid remote server address: " + address); + continue; + } + + addNode(new InetSocketAddress(split[0], Integer.parseInt(split[1]))); + } catch (Throwable e) { + log.log(Level.WARNING, "Failed to parse address: " + address, e); + } + } + } + + public void addNode(InetSocketAddress address) { + nodes.put(address, VALUE); + } + + public void removeNode(InetSocketAddress address) { + nodes.remove(address); + } + + public static void close(UUID session) { + var map = get().sessions.remove(session); + if (map == null) return; + map.keySet().forEach(IrisMasterClient::disconnect); + map.clear(); + } + + public static KMap> getNodes(IrisMasterSession session) { + var master = get(); + var uuid = session.getUuid(); + close(uuid); + + master.getLogger().info("Requesting nodes for session " + uuid); + var map = new KMap>(); + for (var address : master.nodes.keySet()) { + try { + map.put(IrisConnection.connect(address, () -> new IrisMasterClient(session)), new KMap<>()); + } catch (Exception e) { + master.getLogger().log(Level.WARNING, "Failed to connect to server " + address, e); + master.removeNode(address); + } + } + + master.sessions.put(uuid, map); + return map; + } + + @Override + public void close() throws Exception { + log.info("Closing!"); + super.close(); + sessions.values() + .stream() + .map(KMap::keySet) + .flatMap(Set::stream) + .forEach(IrisMasterClient::disconnect); + sessions.clear(); + } + + @Override + protected Logger getLogger() { + return log; + } + + private void close(String message) throws IllegalStateException { + try { + close(); + } catch (Exception ignored) {} + throw new IllegalStateException(message); + } + + public static IrisMasterServer get() { + if (instance == null) + throw new IllegalStateException("IrisMasterServer not running"); + return instance; + } +} diff --git a/core/src/main/java/com/volmit/iris/server/master/IrisMasterSession.java b/core/src/main/java/com/volmit/iris/server/master/IrisMasterSession.java new file mode 100644 index 000000000..feb81106d --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/master/IrisMasterSession.java @@ -0,0 +1,98 @@ +package com.volmit.iris.server.master; + +import com.volmit.iris.server.IrisConnection; +import com.volmit.iris.server.execption.RejectedException; +import com.volmit.iris.server.packet.Packet; +import com.volmit.iris.server.packet.Packets; +import com.volmit.iris.server.packet.init.EnginePacket; +import com.volmit.iris.server.packet.init.FilePacket; +import com.volmit.iris.server.packet.init.InfoPacket; +import com.volmit.iris.server.packet.work.ChunkPacket; +import com.volmit.iris.server.packet.work.MantleChunkPacket; +import com.volmit.iris.server.packet.work.PregenPacket; +import com.volmit.iris.server.util.*; +import com.volmit.iris.util.collection.KMap; +import lombok.Getter; +import lombok.extern.java.Log; + +import java.util.Comparator; +import java.util.UUID; +import java.util.logging.Level; + +//TODO handle done packet +@Log(topic = "IrisMasterSession") +public class IrisMasterSession implements ConnectionHolder, PacketListener { + private final @Getter IrisConnection connection = new IrisConnection(this); + private final @Getter UUID uuid = UUID.randomUUID(); + private final KMap map = new KMap<>(); + private final CPSLooper cpsLooper = new CPSLooper("IrisMasterSession-" + uuid, connection); + private KMap> clients; + private int radius = -1; + + @Override + public void onPacket(Packet raw) throws Exception { + if (clients == null) { + clients = IrisMasterServer.getNodes(this); + cpsLooper.setNodeCount(clients.size()); + + var info = Packets.INFO.newPacket(); + info.setNodeCount(clients.size()); + connection.send(info); + } + + if (raw instanceof FilePacket packet) { + if (radius != -1) + throw new RejectedException("Engine already setup"); + clients.keySet().forEach(client -> client.send(packet)); + } else if (raw instanceof EnginePacket packet) { + if (radius != -1) + throw new RejectedException("Engine already setup"); + radius = packet.getRadius(); + clients.keySet().forEach(client -> client.send(packet)); + } else if (raw instanceof PregenPacket packet) { + if (radius == -1) + throw new RejectedException("Engine not setup"); + var client = pick(); + map.put(packet.getId(), client); + new PregenHolder(packet, radius, true, null) + .put(clients.get(client)); + + client.send(packet); + } else if (raw instanceof MantleChunkPacket packet) { + var client = map.get(packet.getPregenId()); + client.send(packet); + } + } + + protected void onClientPacket(Packet raw) { + if (raw instanceof ErrorPacket packet) { + packet.log(log, Level.SEVERE); + } else if (raw instanceof ChunkPacket) { + connection.send(raw); + } else if (raw instanceof MantleChunkPacket packet) { + var map = clients.get(this.map.get(packet.getPregenId())); + if (map.get(packet.getPregenId()) + .remove(packet.getX(), packet.getZ())) + map.get(packet.getPregenId()); + + connection.send(packet); + } else if (raw instanceof MantleChunkPacket.Request packet) { + connection.send(packet); + } else if (raw instanceof InfoPacket packet) { + int i = packet.getGenerated(); + if (i != -1) cpsLooper.addChunks(i); + } + } + + @Override + public void onDisconnect() { + IrisMasterServer.close(uuid); + } + + private IrisMasterClient pick() throws RejectedException { + return clients.keySet() + .stream() + .min(Comparator.comparingInt(c -> clients.get(c).size())) + .orElseThrow(() -> new RejectedException("No clients available")); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/node/IrisServer.java b/core/src/main/java/com/volmit/iris/server/node/IrisServer.java new file mode 100644 index 000000000..fd4f647ec --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/node/IrisServer.java @@ -0,0 +1,68 @@ +package com.volmit.iris.server.node; + +import com.volmit.iris.server.IrisConnection; +import com.volmit.iris.server.util.ConnectionHolder; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; + +import java.util.function.Supplier; +import java.util.logging.Logger; + +@Log(topic = "Iris-Server") +public class IrisServer implements AutoCloseable { + private final NioEventLoopGroup bossGroup, workerGroup; + private final Channel channel; + private @Getter boolean running = true; + + public IrisServer(int port) throws InterruptedException { + this("Iris-Server", port, IrisSession::new); + } + + protected IrisServer(String name, int port, Supplier factory) throws InterruptedException { + bossGroup = new NioEventLoopGroup(1); + workerGroup = new NioEventLoopGroup(); + + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(name, LogLevel.DEBUG)) + .childHandler(new Initializer(factory)); + + channel = bootstrap.bind(port).sync().channel(); + + getLogger().info("Started on port " + port); + } + + @Override + public void close() throws Exception { + if (!running) return; + running = false; + channel.close().sync(); + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + + getLogger().info("Stopped"); + } + + protected Logger getLogger() { + return log; + } + + @RequiredArgsConstructor + private static class Initializer extends ChannelInitializer { + private final Supplier factory; + + @Override + protected void initChannel(Channel ch) { + IrisConnection.configureSerialization(ch, factory.get()); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/server/node/IrisSession.java b/core/src/main/java/com/volmit/iris/server/node/IrisSession.java new file mode 100644 index 000000000..960326945 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/node/IrisSession.java @@ -0,0 +1,110 @@ +package com.volmit.iris.server.node; + +import com.volmit.iris.core.nms.IHeadless; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.server.IrisConnection; +import com.volmit.iris.server.execption.RejectedException; +import com.volmit.iris.server.packet.Packet; +import com.volmit.iris.server.packet.Packets; +import com.volmit.iris.server.packet.init.EnginePacket; +import com.volmit.iris.server.packet.init.FilePacket; +import com.volmit.iris.server.util.CPSLooper; +import com.volmit.iris.server.util.ConnectionHolder; +import com.volmit.iris.server.util.PacketListener; +import com.volmit.iris.server.packet.work.ChunkPacket; +import com.volmit.iris.server.packet.work.MantleChunkPacket; +import com.volmit.iris.server.packet.work.PregenPacket; +import com.volmit.iris.server.util.PregenHolder; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.parallel.MultiBurst; +import lombok.Getter; + +import java.io.File; +import java.util.UUID; + +public class IrisSession implements ConnectionHolder, PacketListener { + private static final MultiBurst burst = new MultiBurst("IrisHeadless", 8); + private final @Getter IrisConnection connection = new IrisConnection(this); + private final File base = new File("cache/" + UUID.randomUUID()); + private final KMap chunks = new KMap<>(); + private final KMap pregens = new KMap<>(); + private final CPSLooper cpsLooper = new CPSLooper("IrisSession-"+base.getName(), connection); + + private Engine engine; + private IHeadless headless; + + @Override + public void onPacket(Packet raw) throws Exception { + cpsLooper.setNodeCount(1); + if (raw instanceof FilePacket packet) { + if (engine != null) throw new RejectedException("Engine already setup"); + + packet.write(base); + Packets.DONE.newPacket() + .setId(packet.getId()) + .send(connection); + } else if (raw instanceof EnginePacket packet) { + if (engine != null) throw new RejectedException("Engine already setup"); + engine = packet.getEngine(base); + headless = INMS.get().createHeadless(engine); + headless.setSession(this); + + Packets.DONE.newPacket() + .setId(packet.getId()) + .send(connection); + } else if (raw instanceof MantleChunkPacket packet) { + if (engine == null) throw new RejectedException("Engine not setup"); + packet.set(engine.getMantle().getMantle()); + + var holder = chunks.get(packet.getPregenId()); + if (holder.remove(packet.getX(), packet.getZ())) { + headless.generateRegion(burst, holder.getX(), holder.getZ(), 20, null) + .thenRun(() -> { + holder.iterate(chunkPos -> { + var resp = Packets.MANTLE_CHUNK.newPacket(); + resp.setPregenId(holder.getId()); + resp.read(chunkPos, engine.getMantle().getMantle()); + connection.send(resp); + }); + chunks.remove(holder.getId()); + }); + } + } else if (raw instanceof PregenPacket packet) { + if (engine == null) throw new RejectedException("Engine not setup"); + var radius = engine.getMantle().getRadius(); + + var holder = new PregenHolder(packet, radius, true, null); + var request = Packets.MANTLE_CHUNK_REQUEST.newPacket() + .setPregenId(packet.getId()); + holder.iterate(request::add); + var pregen = new PregenHolder(packet, 0, true, null); + pregens.put(Cache.key(pregen.getX(), pregen.getZ()), pregen); + + chunks.put(packet.getId(), holder); + connection.send(request); + } else throw new RejectedException("Unhandled packet: " + raw.getClass().getSimpleName()); + } + + public void completeChunk(int x, int z, byte[] data) { + cpsLooper.addChunks(1); + long id = Cache.key(x >> 5, z >> 5); + var pregen = pregens.get(id); + if (pregen.remove(x, z)) + pregens.remove(id); + connection.send(new ChunkPacket(pregen.getId(), x, z, data)); + } + + @Override + public void onDisconnect() { + if (engine != null) { + engine.close(); + engine = null; + headless = null; + } + cpsLooper.exit(); + IO.delete(base); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/Packet.java b/core/src/main/java/com/volmit/iris/server/packet/Packet.java new file mode 100644 index 000000000..103420093 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/Packet.java @@ -0,0 +1,24 @@ +package com.volmit.iris.server.packet; + +import com.volmit.iris.server.IrisConnection; +import com.volmit.iris.server.util.PacketSendListener; +import io.netty.buffer.ByteBuf; + +import java.io.IOException; + +public interface Packet { + void read(ByteBuf byteBuf) throws IOException; + void write(ByteBuf byteBuf) throws IOException; + + default Packets getType() { + return Packets.get(getClass()); + } + + default void send(IrisConnection connection) { + send(connection, null); + } + + default void send(IrisConnection connection, PacketSendListener listener) { + connection.send(this, listener); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/Packets.java b/core/src/main/java/com/volmit/iris/server/packet/Packets.java new file mode 100644 index 000000000..ed1baa858 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/Packets.java @@ -0,0 +1,91 @@ +package com.volmit.iris.server.packet; + +import com.volmit.iris.server.packet.init.EnginePacket; +import com.volmit.iris.server.packet.init.FilePacket; +import com.volmit.iris.server.packet.init.InfoPacket; +import com.volmit.iris.server.packet.work.DonePacket; +import com.volmit.iris.server.util.ErrorPacket; +import com.volmit.iris.server.packet.work.ChunkPacket; +import com.volmit.iris.server.packet.work.MantleChunkPacket; +import com.volmit.iris.server.packet.work.PregenPacket; +import lombok.AccessLevel; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.Supplier; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class Packets { + private static final List> REGISTRY; + private static final Map, Packets> MAP; + + public static final Packets ERROR; + public static final Packets INFO; + public static final Packets FILE; + public static final Packets ENGINE; + + public static final Packets DONE; + public static final Packets PREGEN; + public static final Packets CHUNK; + public static final Packets MANTLE_CHUNK; + public static final Packets MANTLE_CHUNK_REQUEST; + + private final Class type; + private final Supplier factory; + private int id = -1; + + public int getId() { + if (id == -1) throw new IllegalStateException("Unknown packet type: " + this); + return id; + } + + public T newPacket() { + return factory.get(); + } + + @NotNull + public static Packets get(int id) { + return REGISTRY.get(id); + } + + @NotNull + public static Packet newPacket(int id) { + return get(id).newPacket(); + } + + @NotNull + public static Packets get(Class type) { + var t = MAP.get(type); + if (t == null) throw new IllegalArgumentException("Unknown packet type: " + type); + return (Packets) t; + } + + public static int getId(Class type) { + return get(type).getId(); + } + + static { + ERROR = new Packets<>(ErrorPacket.class, ErrorPacket::new); + INFO = new Packets<>(InfoPacket.class, InfoPacket::new); + FILE = new Packets<>(FilePacket.class, FilePacket::new); + ENGINE = new Packets<>(EnginePacket.class, EnginePacket::new); + + DONE = new Packets<>(DonePacket.class, DonePacket::new); + PREGEN = new Packets<>(PregenPacket.class, PregenPacket::new); + CHUNK = new Packets<>(ChunkPacket.class, ChunkPacket::new); + MANTLE_CHUNK = new Packets<>(MantleChunkPacket.class, MantleChunkPacket::new); + MANTLE_CHUNK_REQUEST = new Packets<>(MantleChunkPacket.Request.class, MantleChunkPacket.Request::new); + + REGISTRY = List.of(ERROR, INFO, FILE, ENGINE, DONE, PREGEN, CHUNK, MANTLE_CHUNK, MANTLE_CHUNK_REQUEST); + + var map = new HashMap, Packets>(); + for (int i = 0; i < REGISTRY.size(); i++) { + var entry = REGISTRY.get(i); + entry.id = i; + map.put(entry.type, entry); + } + MAP = Collections.unmodifiableMap(map); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/handle/Decoder.java b/core/src/main/java/com/volmit/iris/server/packet/handle/Decoder.java new file mode 100644 index 000000000..a3cf54c85 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/handle/Decoder.java @@ -0,0 +1,17 @@ +package com.volmit.iris.server.packet.handle; + +import com.volmit.iris.server.packet.Packets; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + +import java.util.List; + +public class Decoder extends ByteToMessageDecoder { + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List list) throws Exception { + var packet = Packets.newPacket(byteBuf.readByte()); + packet.read(byteBuf); + list.add(packet); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/handle/Encoder.java b/core/src/main/java/com/volmit/iris/server/packet/handle/Encoder.java new file mode 100644 index 000000000..0ca3b11bf --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/handle/Encoder.java @@ -0,0 +1,14 @@ +package com.volmit.iris.server.packet.handle; + +import com.volmit.iris.server.packet.Packet; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +public class Encoder extends MessageToByteEncoder { + @Override + protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf byteBuf) throws Exception { + byteBuf.writeByte(packet.getType().getId()); + packet.write(byteBuf); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/handle/Prepender.java b/core/src/main/java/com/volmit/iris/server/packet/handle/Prepender.java new file mode 100644 index 000000000..3c7af8bb1 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/handle/Prepender.java @@ -0,0 +1,14 @@ +package com.volmit.iris.server.packet.handle; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +public class Prepender extends MessageToByteEncoder { + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception { + int i = in.readableBytes(); + out.writeInt(i); + out.writeBytes(in, in.readerIndex(), i); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/handle/Splitter.java b/core/src/main/java/com/volmit/iris/server/packet/handle/Splitter.java new file mode 100644 index 000000000..cdfd75582 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/handle/Splitter.java @@ -0,0 +1,33 @@ +package com.volmit.iris.server.packet.handle; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + +import java.util.List; + +public class Splitter extends ByteToMessageDecoder { + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List list) throws Exception { + if (!byteBuf.isReadable(4)) + return; + byteBuf.markReaderIndex(); + + byte[] bytes = new byte[4]; + byteBuf.readBytes(bytes); + var buffer = Unpooled.wrappedBuffer(bytes); + try { + int j = buffer.readInt(); + if (byteBuf.readableBytes() >= j) { + list.add(byteBuf.readBytes(j)); + return; + } + + byteBuf.resetReaderIndex(); + } finally { + buffer.release(); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/init/EnginePacket.java b/core/src/main/java/com/volmit/iris/server/packet/init/EnginePacket.java new file mode 100644 index 000000000..8a6ba80f9 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/init/EnginePacket.java @@ -0,0 +1,60 @@ +package com.volmit.iris.server.packet.init; + +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.engine.IrisEngine; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.framework.EngineTarget; +import com.volmit.iris.engine.object.IrisWorld; +import com.volmit.iris.server.util.ByteBufUtil; +import com.volmit.iris.server.packet.Packet; +import io.netty.buffer.ByteBuf; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +@Data +@Accessors(chain = true) +@NoArgsConstructor +public class EnginePacket implements Packet { + private UUID id = UUID.randomUUID(); + private String dimension; + private long seed; + private int radius; + + @Override + public void read(ByteBuf byteBuf) throws IOException { + id = new UUID(byteBuf.readLong(), byteBuf.readLong()); + dimension = ByteBufUtil.readString(byteBuf); + seed = byteBuf.readLong(); + radius = byteBuf.readInt(); + } + + @Override + public void write(ByteBuf byteBuf) throws IOException { + byteBuf.writeLong(id.getMostSignificantBits()); + byteBuf.writeLong(id.getLeastSignificantBits()); + ByteBufUtil.writeString(byteBuf, dimension); + byteBuf.writeLong(seed); + byteBuf.writeInt(radius); + } + + public Engine getEngine(File base) { + var data = IrisData.get(new File(base, "iris/pack")); + var type = data.getDimensionLoader().load(dimension); + var world = IrisWorld.builder() + .name(base.getName()) + .seed(seed) + .worldFolder(base) + .minHeight(type.getMinHeight()) + .maxHeight(type.getMaxHeight()) + .environment(type.getEnvironment()) + .build(); + + return new IrisEngine(new EngineTarget(world, type, data), false); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/init/FilePacket.java b/core/src/main/java/com/volmit/iris/server/packet/init/FilePacket.java new file mode 100644 index 000000000..e43f5949a --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/init/FilePacket.java @@ -0,0 +1,56 @@ +package com.volmit.iris.server.packet.init; + +import com.volmit.iris.server.util.ByteBufUtil; +import com.volmit.iris.server.packet.Packet; +import io.netty.buffer.ByteBuf; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.*; +import java.util.UUID; + +@Data +@Accessors(chain = true) +@NoArgsConstructor +public class FilePacket implements Packet { + private UUID id = UUID.randomUUID(); + private String path; + private long offset; + private long length; + private byte[] data; + + @Override + public void read(ByteBuf byteBuf) throws IOException { + id = new UUID(byteBuf.readLong(), byteBuf.readLong()); + path = ByteBufUtil.readString(byteBuf); + offset = byteBuf.readLong(); + length = byteBuf.readLong(); + data = ByteBufUtil.readBytes(byteBuf); + } + + @Override + public void write(ByteBuf byteBuf) throws IOException { + byteBuf.writeLong(id.getMostSignificantBits()); + byteBuf.writeLong(id.getLeastSignificantBits()); + ByteBufUtil.writeString(byteBuf, path); + byteBuf.writeLong(offset); + byteBuf.writeLong(length); + ByteBufUtil.writeBytes(byteBuf, data); + } + + public void write(File base) throws IOException { + File f = new File(base, path); + if (!f.getAbsolutePath().startsWith(base.getAbsolutePath())) + throw new IOException("Invalid path " + path); + if (!f.getParentFile().exists() && !f.getParentFile().mkdirs()) + throw new IOException("Failed to create directory " + f.getParentFile()); + + try (var raf = new RandomAccessFile(f, "rws")) { + if (raf.length() < length) + raf.setLength(length); + raf.seek(offset); + raf.write(data); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/init/InfoPacket.java b/core/src/main/java/com/volmit/iris/server/packet/init/InfoPacket.java new file mode 100644 index 000000000..cd688d9e0 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/init/InfoPacket.java @@ -0,0 +1,33 @@ +package com.volmit.iris.server.packet.init; + +import com.volmit.iris.server.packet.Packet; +import io.netty.buffer.ByteBuf; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.IOException; + +@Data +@Accessors(chain = true) +@NoArgsConstructor +public class InfoPacket implements Packet { + private int nodeCount = -1; + private int cps = -1; + private int generated = -1; + + @Override + public void read(ByteBuf byteBuf) throws IOException { + nodeCount = byteBuf.readInt(); + cps = byteBuf.readInt(); + generated = byteBuf.readInt(); + } + + @Override + public void write(ByteBuf byteBuf) throws IOException { + byteBuf.writeInt(nodeCount); + byteBuf.writeInt(cps); + byteBuf.writeInt(generated); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/work/ChunkPacket.java b/core/src/main/java/com/volmit/iris/server/packet/work/ChunkPacket.java new file mode 100644 index 000000000..fdef5d0c4 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/work/ChunkPacket.java @@ -0,0 +1,37 @@ +package com.volmit.iris.server.packet.work; + +import com.volmit.iris.server.util.ByteBufUtil; +import com.volmit.iris.server.packet.Packet; +import io.netty.buffer.ByteBuf; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.IOException; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ChunkPacket implements Packet { + private UUID pregenId; + private int x, z; + private byte[] data; + + @Override + public void read(ByteBuf byteBuf) throws IOException { + pregenId = new UUID(byteBuf.readLong(), byteBuf.readLong()); + x = byteBuf.readInt(); + z = byteBuf.readInt(); + data = ByteBufUtil.readBytes(byteBuf); + } + + @Override + public void write(ByteBuf byteBuf) throws IOException { + byteBuf.writeLong(pregenId.getMostSignificantBits()); + byteBuf.writeLong(pregenId.getLeastSignificantBits()); + byteBuf.writeInt(x); + byteBuf.writeInt(z); + ByteBufUtil.writeBytes(byteBuf, data); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/work/DonePacket.java b/core/src/main/java/com/volmit/iris/server/packet/work/DonePacket.java new file mode 100644 index 000000000..f07f6fc4a --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/work/DonePacket.java @@ -0,0 +1,28 @@ +package com.volmit.iris.server.packet.work; + +import com.volmit.iris.server.packet.Packet; +import io.netty.buffer.ByteBuf; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.IOException; +import java.util.UUID; + +@Data +@Accessors(chain = true) +@NoArgsConstructor +public class DonePacket implements Packet { + private UUID id = UUID.randomUUID(); + + @Override + public void read(ByteBuf byteBuf) throws IOException { + id = new UUID(byteBuf.readLong(), byteBuf.readLong()); + } + + @Override + public void write(ByteBuf byteBuf) throws IOException { + byteBuf.writeLong(id.getMostSignificantBits()); + byteBuf.writeLong(id.getLeastSignificantBits()); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/work/MantleChunkPacket.java b/core/src/main/java/com/volmit/iris/server/packet/work/MantleChunkPacket.java new file mode 100644 index 000000000..e92b482d6 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/work/MantleChunkPacket.java @@ -0,0 +1,103 @@ +package com.volmit.iris.server.packet.work; + +import com.volmit.iris.server.util.ByteBufUtil; +import com.volmit.iris.server.packet.Packet; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.documentation.ChunkCoordinates; +import com.volmit.iris.util.mantle.Mantle; +import com.volmit.iris.util.mantle.MantleChunk; +import com.volmit.iris.util.math.Position2; +import io.netty.buffer.ByteBuf; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import net.jpountz.lz4.LZ4BlockInputStream; +import net.jpountz.lz4.LZ4BlockOutputStream; + +import java.io.*; +import java.util.UUID; + +@Data +@Accessors(chain = true) +@NoArgsConstructor +public class MantleChunkPacket implements Packet { + private UUID pregenId; + private int x, z; + private MantleChunk chunk; + + @Override + public void read(ByteBuf byteBuf) throws IOException { + pregenId = new UUID(byteBuf.readLong(), byteBuf.readLong()); + x = byteBuf.readInt(); + z = byteBuf.readInt(); + int sectionHeight = byteBuf.readInt(); + try (var din = new DataInputStream(new BufferedInputStream(new LZ4BlockInputStream(new ByteArrayInputStream(ByteBufUtil.readBytes(byteBuf)))))) { + chunk = new MantleChunk(sectionHeight, din); + } catch (ClassNotFoundException e) { + throw new IOException("Failed to read chunk", e); + } + } + + @Override + public void write(ByteBuf byteBuf) throws IOException { + byteBuf.writeLong(pregenId.getMostSignificantBits()); + byteBuf.writeLong(pregenId.getLeastSignificantBits()); + byteBuf.writeInt(x); + byteBuf.writeInt(z); + byteBuf.writeInt(chunk.getSectionHeight()); + var out = new ByteArrayOutputStream(); + try (var dos = new DataOutputStream(new LZ4BlockOutputStream(out))) { + chunk.write(dos); + } + ByteBufUtil.writeBytes(byteBuf, out.toByteArray()); + } + + @ChunkCoordinates + public MantleChunkPacket read(Position2 pos, Mantle mantle) { + this.x = pos.getX(); + this.z = pos.getZ(); + this.chunk = mantle.getChunk(x, z); + return this; + } + + public void set(Mantle mantle) { + mantle.setChunk(x, z, chunk); + } + + @Data + @Accessors(chain = true) + @NoArgsConstructor + public static class Request implements Packet { + private UUID pregenId; + private KList positions = new KList<>(); + + @Override + public void read(ByteBuf byteBuf) throws IOException { + pregenId = new UUID(byteBuf.readLong(), byteBuf.readLong()); + var count = byteBuf.readInt(); + positions = new KList<>(count); + for (int i = 0; i < count; i++) { + positions.add(new Position2(byteBuf.readInt(), byteBuf.readInt())); + } + } + + @Override + public void write(ByteBuf byteBuf) throws IOException { + byteBuf.writeLong(pregenId.getMostSignificantBits()); + byteBuf.writeLong(pregenId.getLeastSignificantBits()); + byteBuf.writeInt(positions.size()); + for (Position2 p : positions) { + byteBuf.writeInt(p.getX()); + byteBuf.writeInt(p.getZ()); + } + } + + @ChunkCoordinates + public void add(Position2 chunkPos) { + if (positions == null) + positions = new KList<>(); + positions.add(chunkPos); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/server/packet/work/PregenPacket.java b/core/src/main/java/com/volmit/iris/server/packet/work/PregenPacket.java new file mode 100644 index 000000000..d8f7fc241 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/work/PregenPacket.java @@ -0,0 +1,35 @@ +package com.volmit.iris.server.packet.work; + +import com.volmit.iris.server.packet.Packet; +import io.netty.buffer.ByteBuf; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.IOException; +import java.util.UUID; + +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class PregenPacket implements Packet { + private UUID id = UUID.randomUUID(); + private int x, z; + + @Override + public void read(ByteBuf byteBuf) throws IOException { + id = new UUID(byteBuf.readLong(), byteBuf.readLong()); + x = byteBuf.readInt(); + z = byteBuf.readInt(); + } + + @Override + public void write(ByteBuf byteBuf) throws IOException { + byteBuf.writeLong(id.getMostSignificantBits()); + byteBuf.writeLong(id.getLeastSignificantBits()); + byteBuf.writeInt(x); + byteBuf.writeInt(z); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/pregen/CloudMethod.java b/core/src/main/java/com/volmit/iris/server/pregen/CloudMethod.java new file mode 100644 index 000000000..137c6356c --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/pregen/CloudMethod.java @@ -0,0 +1,264 @@ +package com.volmit.iris.server.pregen; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.gui.PregeneratorJob; +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.server.IrisConnection; +import com.volmit.iris.server.execption.RejectedException; +import com.volmit.iris.server.packet.Packet; +import com.volmit.iris.server.packet.Packets; +import com.volmit.iris.server.packet.init.InfoPacket; +import com.volmit.iris.server.packet.work.ChunkPacket; +import com.volmit.iris.server.packet.work.DonePacket; +import com.volmit.iris.server.packet.work.MantleChunkPacket; +import com.volmit.iris.server.util.*; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.mantle.Mantle; +import com.volmit.iris.util.parallel.MultiBurst; +import lombok.Getter; +import lombok.extern.java.Log; +import org.bukkit.World; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.logging.Level; + +@Log(topic = "CloudPregen") +public class CloudMethod implements PregeneratorMethod, ConnectionHolder, PacketListener { + private final @Getter IrisConnection connection = new IrisConnection(this); + private final Engine engine; + private final IHeadless headless; + private final KMap holders = new KMap<>(); + private final CompletableFuture future = new CompletableFuture<>(); + private final KMap> locks = new KMap<>(); + + public CloudMethod(String address, Engine engine) throws InterruptedException { + var split = address.split(":"); + if (split.length != 2 || !split[1].matches("\\d+")) + throw new IllegalArgumentException("Invalid remote server address: " + address); + + IrisConnection.connect(new InetSocketAddress(split[0], Integer.parseInt(split[1])), () -> this); + + this.engine = engine; + this.headless = INMS.get().createHeadless(engine); + } + + @Override + public void init() { + var studio = engine.isStudio(); + var base = studio ? + engine.getData().getDataFolder() : + engine.getWorld().worldFolder(); + var name = engine.getWorld().name(); + var exit = new AtomicBoolean(false); + var limited = new LimitedSemaphore(IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())); + + log.info(name + ": Uploading pack..."); + iterate(engine.getData().getDataFolder(), f -> { + if (exit.get()) return; + + try { + limited.acquire(); + } catch (InterruptedException e) { + exit.set(true); + return; + } + + MultiBurst.burst.complete(() -> { + try { + upload(exit, base, f, studio, 8192); + } finally { + limited.release(); + } + }); + }); + + try { + limited.acquireAll(); + } catch (InterruptedException ignored) {} + + log.info(name + ": Done uploading pack"); + log.info(name + ": Initializing Engine..."); + CompletableFuture future = new CompletableFuture<>(); + var packet = Packets.ENGINE.newPacket() + .setDimension(engine.getDimension().getLoadKey()) + .setSeed(engine.getWorld().getRawWorldSeed()) + .setRadius(engine.getMantle().getRadius()); + + locks.put(packet.getId(), future); + packet.send(connection); + + try { + future.get(); + } catch (Throwable ignored) {} + log.info(name + ": Done initializing Engine"); + } + + private void upload(AtomicBoolean exit, File base, File f, boolean studio, int packetSize) { + if (exit.get() || (!studio && !f.getAbsolutePath().startsWith(base.getAbsolutePath()))) + return; + + String path = studio ? "iris/pack/" : ""; + path += f.getAbsolutePath().substring(base.getAbsolutePath().length() + 1); + + try (FileInputStream in = new FileInputStream(f)) { + long offset = 0; + byte[] data; + while ((data = in.readNBytes(packetSize)).length > 0 && !exit.get()) { + CompletableFuture future = new CompletableFuture<>(); + var packet = Packets.FILE.newPacket() + .setPath(path) + .setOffset(offset) + .setLength(f.length()) + .setData(data); + + locks.put(packet.getId(), future); + packet.send(connection); + future.get(); + + offset += data.length; + } + } catch (IOException | ExecutionException | InterruptedException e) { + Iris.error("Failed to upload " + f); + e.printStackTrace(); + exit.set(true); + } + } + + private void iterate(File file, Consumer consumer) { + var queue = new ArrayDeque(); + queue.add(file); + + while (!queue.isEmpty()) { + var f = queue.remove(); + if (f.isFile()) + consumer.accept(f); + if (f.isDirectory()) { + var files = f.listFiles(); + if (files == null) continue; + queue.addAll(Arrays.asList(files)); + } + } + } + + @Override + public void close() { + close0(); + connection.disconnect(); + } + + @Override + public void save() {} + + @Override + public boolean supportsRegions(int x, int z, PregenListener listener) { + return true; + } + + @Override + public void generateRegion(int x, int z, PregenListener listener) { + var semaphore = future.join(); + try { + semaphore.acquire(); + } catch (InterruptedException e) { + semaphore.release(); + return; + } + + var p = Packets.PREGEN.newPacket() + .setX(x) + .setZ(z); + new PregenHolder(p, engine.getMantle().getRadius(), true, listener) + .put(holders); + p.send(connection); + } + + @Override + public void generateChunk(int x, int z, PregenListener listener) {} + + @Override + public String getMethod(int x, int z) { + return "Cloud"; + } + + @Override + public Mantle getMantle() { + return engine.getMantle().getMantle(); + } + + @Override + public World getWorld() { + return engine.getWorld().realWorld(); + } + + @Override + public void onPacket(Packet raw) throws Exception { + if (raw instanceof ChunkPacket packet) { + headless.addChunk(packet); + holders.get(packet.getPregenId()) + .getListener() + .onChunkGenerated(packet.getX(), packet.getZ()); + } else if (raw instanceof MantleChunkPacket packet) { + if (holders.get(packet.getPregenId()) + .remove(packet.getX(), packet.getZ())) { + future.join().release(); + } + packet.set(getMantle()); + } else if (raw instanceof MantleChunkPacket.Request packet) { + var mantle = getMantle(); + for (var chunk : packet.getPositions()) { + Packets.MANTLE_CHUNK.newPacket() + .setPregenId(packet.getPregenId()) + .read(chunk, mantle) + .send(connection); + } + } else if (raw instanceof InfoPacket packet) { + if (packet.getNodeCount() > 0 && !future.isDone()) + future.complete(new LimitedSemaphore(packet.getNodeCount())); + //if (packet.getCps() >= 0) + // Iris.info("Cloud CPS: " + packet.getCps()); + } else if (raw instanceof DonePacket packet) { + locks.remove(packet.getId()).complete(null); + } else if (raw instanceof ErrorPacket packet) { + packet.log(log, Level.SEVERE); + } else throw new RejectedException("Unhandled packet: " + raw.getClass().getSimpleName()); + } + + @Override + public void onDisconnect() { + try { + if (!future.isDone()) + future.cancel(false); + } catch (Throwable ignored) {} + PregeneratorJob.shutdownInstance(); + } + + private void close0() { + if (!future.isCancelled()) { + try { + future.join().acquireAll(); + } catch (InterruptedException ignored) {} + } + + try { + headless.close(); + } catch (IOException e) { + log.log(Level.SEVERE, "Failed to close headless", e); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/server/pregen/CloudTask.java b/core/src/main/java/com/volmit/iris/server/pregen/CloudTask.java new file mode 100644 index 000000000..18a3895cc --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/pregen/CloudTask.java @@ -0,0 +1,70 @@ +package com.volmit.iris.server.pregen; + +import com.volmit.iris.core.pregenerator.PregenTask; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.math.Spiraled; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Comparator; +import java.util.Map; + +@ToString +@Builder(builderMethodName = "couldBuilder") +@EqualsAndHashCode(callSuper = true) +public class CloudTask extends PregenTask { + @Builder.Default + private boolean resetCache = false; + @Builder.Default + private boolean gui = false; + @Builder.Default + private Position2 center = new Position2(0, 0); + @Builder.Default + private int width = 1; + @Builder.Default + private int height = 1; + private int distance; + + private CloudTask(boolean resetCache, boolean gui, Position2 center, int width, int height, int distance) { + super(resetCache, gui, center, width, height); + this.resetCache = resetCache; + this.gui = gui; + this.center = center; + this.width = width; + this.height = height; + + int d = distance & 31; + if (d > 0) d = 32 - d; + this.distance = 32 + d + distance >> 5; + } + + @Override + public void iterateRegions(Spiraled s) { + var c = Comparator.comparingInt(DPos2::distance); + for (int oX = 0; oX < distance; oX++) { + for (int oZ = 0; oZ < distance; oZ++) { + var p = new KList(); + for (int x = -width; x <= width - oX; x+=distance) { + for (int z = -height; z <= height - oZ; z+=distance) { + s.on(x + oX, z + oZ); + //p.add(new DPos2(x + oX, z + oZ)); + } + } + p.sort(c); + p.forEach(i -> i.on(s)); + } + } + } + + private record DPos2(int x, int z, int distance) { + private DPos2(int x, int z) { + this(x, z, x * x + z * z); + } + + public void on(Spiraled s) { + s.on(x, z); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/server/util/ByteBufUtil.java b/core/src/main/java/com/volmit/iris/server/util/ByteBufUtil.java new file mode 100644 index 000000000..22c75db0f --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/util/ByteBufUtil.java @@ -0,0 +1,27 @@ +package com.volmit.iris.server.util; + +import io.netty.buffer.ByteBuf; + +import java.nio.charset.StandardCharsets; + +public class ByteBufUtil { + + public static String readString(ByteBuf byteBuf) { + return new String(readBytes(byteBuf), StandardCharsets.UTF_8); + } + + public static void writeString(ByteBuf byteBuf, String s) { + writeBytes(byteBuf, s.getBytes(StandardCharsets.UTF_8)); + } + + public static byte[] readBytes(ByteBuf byteBuf) { + byte[] bytes = new byte[byteBuf.readInt()]; + byteBuf.readBytes(bytes); + return bytes; + } + + public static void writeBytes(ByteBuf byteBuf, byte[] bytes) { + byteBuf.writeInt(bytes.length); + byteBuf.writeBytes(bytes); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/util/CPSLooper.java b/core/src/main/java/com/volmit/iris/server/util/CPSLooper.java new file mode 100644 index 000000000..a5f56ee24 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/util/CPSLooper.java @@ -0,0 +1,67 @@ +package com.volmit.iris.server.util; + +import com.volmit.iris.server.IrisConnection; +import com.volmit.iris.server.packet.Packets; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.RollingSequence; +import com.volmit.iris.util.scheduling.Looper; +import lombok.Setter; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; + +public class CPSLooper extends Looper { + private final RollingSequence chunksPerSecond = new RollingSequence(10); + private final AtomicInteger generated = new AtomicInteger(); + private final AtomicInteger generatedLast = new AtomicInteger(); + private final AtomicBoolean running = new AtomicBoolean(true); + private final IrisConnection connection; + private int nodeCount = 0; + + public CPSLooper(String name, IrisConnection connection) { + this.connection = connection; + setName(name); + setPriority(Thread.MAX_PRIORITY); + start(); + } + + public void addChunks(int count) { + generated.addAndGet(count); + } + + public void exit() { + running.set(false); + } + + public synchronized void setNodeCount(int count) { + if (nodeCount != 0 || count < 1) + return; + nodeCount = count; + Packets.INFO.newPacket() + .setNodeCount(nodeCount) + .send(connection); + } + + @Override + protected long loop() { + if (!running.get()) + return -1; + long t = M.ms(); + + int secondGenerated = generated.get() - generatedLast.get(); + generatedLast.set(generated.get()); + chunksPerSecond.put(secondGenerated); + + if (secondGenerated > 0 && nodeCount > 0) { + Packets.INFO.newPacket() + .setNodeCount(nodeCount) + .setCps((int) Math.round(chunksPerSecond.getAverage())) + .setGenerated(secondGenerated) + .send(connection); + } + + return Math.max(5000 - (M.ms() - t), 0); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/util/ConnectionHolder.java b/core/src/main/java/com/volmit/iris/server/util/ConnectionHolder.java new file mode 100644 index 000000000..acb40d465 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/util/ConnectionHolder.java @@ -0,0 +1,8 @@ +package com.volmit.iris.server.util; + +import com.volmit.iris.server.IrisConnection; + +public interface ConnectionHolder { + + IrisConnection getConnection(); +} diff --git a/core/src/main/java/com/volmit/iris/server/util/ErrorPacket.java b/core/src/main/java/com/volmit/iris/server/util/ErrorPacket.java new file mode 100644 index 000000000..4236246fd --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/util/ErrorPacket.java @@ -0,0 +1,54 @@ +package com.volmit.iris.server.util; + +import com.volmit.iris.server.packet.Packet; +import io.netty.buffer.ByteBuf; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +@Data +@NoArgsConstructor +public class ErrorPacket implements Packet { + private String message; + private String stackTrace; + + public ErrorPacket(String message) { + this.message = message; + } + + public ErrorPacket(String message, Throwable cause) { + this.message = message; + StringWriter writer = new StringWriter(); + cause.printStackTrace(new PrintWriter(writer)); + stackTrace = writer.toString(); + } + + @Override + public void read(ByteBuf byteBuf) throws IOException { + message = ByteBufUtil.readString(byteBuf); + if (byteBuf.readBoolean()) { + stackTrace = ByteBufUtil.readString(byteBuf); + } + } + + @Override + public void write(ByteBuf byteBuf) throws IOException { + ByteBufUtil.writeString(byteBuf, message); + byteBuf.writeBoolean(stackTrace != null); + if (stackTrace != null) { + ByteBufUtil.writeString(byteBuf, stackTrace); + } + } + + public void log(Logger logger, Level level) { + if (stackTrace == null) { + logger.log(level, message); + return; + } + logger.log(level, message + "\n" + stackTrace); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/util/LimitedSemaphore.java b/core/src/main/java/com/volmit/iris/server/util/LimitedSemaphore.java new file mode 100644 index 000000000..ecca55dc4 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/util/LimitedSemaphore.java @@ -0,0 +1,41 @@ +package com.volmit.iris.server.util; + +import lombok.Getter; + +import java.util.concurrent.Semaphore; + +@Getter +public class LimitedSemaphore extends Semaphore { + private final int permits; + + public LimitedSemaphore(int permits) { + super(permits); + this.permits = permits; + } + + public void runBlocking(Runnable runnable) throws InterruptedException { + try { + acquire(); + runnable.run(); + } finally { + release(); + } + } + + public void runAllBlocking(Runnable runnable) throws InterruptedException { + try { + acquireAll(); + runnable.run(); + } finally { + releaseAll(); + } + } + + public void acquireAll() throws InterruptedException { + acquire(permits); + } + + public void releaseAll() { + release(permits); + } +} diff --git a/core/src/main/java/com/volmit/iris/server/util/PacketListener.java b/core/src/main/java/com/volmit/iris/server/util/PacketListener.java new file mode 100644 index 000000000..2a0923a06 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/util/PacketListener.java @@ -0,0 +1,14 @@ +package com.volmit.iris.server.util; + +import com.volmit.iris.server.packet.Packet; + +public interface PacketListener { + + void onPacket(Packet packet) throws Exception; + + default void onDisconnect() {} + + default boolean isAccepting() { + return true; + } +} diff --git a/core/src/main/java/com/volmit/iris/server/util/PacketSendListener.java b/core/src/main/java/com/volmit/iris/server/util/PacketSendListener.java new file mode 100644 index 000000000..000b0790e --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/util/PacketSendListener.java @@ -0,0 +1,26 @@ +package com.volmit.iris.server.util; + +import com.volmit.iris.server.packet.Packet; + +import javax.annotation.Nullable; + +public interface PacketSendListener { + static PacketSendListener thenRun(Runnable runnable) { + return new PacketSendListener() { + public void onSuccess() { + runnable.run(); + } + + @Nullable + public Packet onFailure() { + runnable.run(); + return null; + } + }; + } + + default void onSuccess() {} + + @Nullable + default Packet onFailure() { return null; } +} diff --git a/core/src/main/java/com/volmit/iris/server/util/PregenHolder.java b/core/src/main/java/com/volmit/iris/server/util/PregenHolder.java new file mode 100644 index 000000000..76b1cabb3 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/util/PregenHolder.java @@ -0,0 +1,62 @@ +package com.volmit.iris.server.util; + +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.server.packet.work.PregenPacket; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.math.Position2; +import lombok.AccessLevel; +import lombok.Getter; + +import java.util.UUID; +import java.util.function.Consumer; + +@Getter +public class PregenHolder { + private final UUID id; + private final int x, z, r; + @Getter(AccessLevel.NONE) + private final KList chunks = new KList<>(); + private final PregenListener listener; + + public PregenHolder(PregenPacket packet, int r, boolean fill, PregenListener listener) { + this.id = packet.getId(); + this.x = packet.getX(); + this.z = packet.getZ(); + this.r = r; + this.listener = listener; + + if (fill) + iterate(chunks::add); + } + + public void put(KMap holders) { + holders.put(id, this); + } + + public synchronized boolean remove(int x, int z) { + chunks.remove(new Position2(x, z)); + boolean b = chunks.isEmpty(); + if (b && listener != null) listener.onRegionGenerated(x, z); + return b; + } + + public void iterate(Consumer consumer) { + int cX = x << 5; + int cZ = z << 5; + for (int x = -r; x <= r; x++) { + for (int z = -r; z <= r; z++) { + if (x == 0 && z == 0) { + for (int xx = 0; xx < 32; xx++) { + for (int zz = 0; zz < 32; zz++) { + consumer.accept(new Position2(x + cX + xx, z + cZ + zz)); + } + } + continue; + } + + consumer.accept(new Position2(x + cX + x < 0 ? 0 : 32, z + cZ + z < 0 ? 0 : 32)); + } + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index e4dcbe650..4f607dfb5 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -195,6 +195,11 @@ public class Mantle { return get(x >> 5, z >> 5).getOrCreate(x & 31, z & 31); } + @ChunkCoordinates + public void setChunk(int x, int z, MantleChunk chunk) { + get(x >> 5, z >> 5).set(x & 31, z & 31, chunk); + } + /** * Flag or unflag a chunk * diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index 38278b762..d00d38f3b 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -100,6 +100,10 @@ public class MantleChunk { return flags.get(flag.ordinal()) == 1; } + public int getSectionHeight() { + return sections.length(); + } + /** * Check if a section exists (same as get(section) != null) * diff --git a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java index a32645115..9212aaae2 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java @@ -158,6 +158,20 @@ public class TectonicPlate { return chunk; } + /** + * Set a tectonic plate + * + * @param x the chunk relative x (0-31) + * @param z the chunk relative z (0-31) + * @param chunk the chunk + */ + @ChunkCoordinates + public void set(int x, int z, MantleChunk chunk) { + if (x != chunk.getX() || z != chunk.getZ()) + throw new IllegalArgumentException("X/Z of chunk must match the plate"); + chunks.set(index(x, z), chunk); + } + @ChunkCoordinates private int index(int x, int z) { return Cache.to1D(x, z, 0, 32, 32); diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index bca7e4638..3ebee0f20 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -24,6 +24,7 @@ libraries: - rhino:js:1.7R2 - bsf:bsf:2.4.0 - org.lz4:lz4-java:1.8.0 + - io.netty:netty-all:4.1.112.Final commands: iris: aliases: [ ir, irs ] diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java index d171aa022..f0c5ee5aa 100644 --- a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java @@ -30,6 +30,8 @@ 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.server.node.IrisSession; +import com.volmit.iris.server.packet.work.ChunkPacket; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.context.ChunkContext; @@ -61,6 +63,7 @@ import net.minecraft.world.level.chunk.storage.RegionFile; import org.bukkit.Material; import org.bukkit.block.data.BlockData; +import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; @@ -82,6 +85,8 @@ public class Headless implements IHeadless, LevelHeightAccessor { private final RNG BIOME_RNG; private final @Getter int minBuildHeight; private final @Getter int height; + private IrisSession session; + private CompletingThread regionThread; private boolean closed = false; public Headless(NMSBinding binding, Engine engine) { @@ -127,6 +132,12 @@ public class Headless implements IHeadless, LevelHeightAccessor { cleaner.start(); } + public void setSession(IrisSession session) { + if (this.session != null) + throw new IllegalStateException("Session already set"); + this.session = session; + } + @Override public int getLoadedChunks() { return loadedChunks.get(); @@ -153,21 +164,42 @@ public class Headless implements IHeadless, LevelHeightAccessor { } @Override - public void generateRegion(MultiBurst burst, int x, int z, PregenListener listener) { - if (closed) return; - boolean listening = listener != null; - if (listening) listener.onRegionGenerating(x, z); - CountDownLatch latch = new CountDownLatch(1024); - iterateRegion(x, z, pos -> burst.complete(() -> { - if (listening) listener.onChunkGenerating(pos.x, pos.z); - generateChunk(pos.x, pos.z); - if (listening) listener.onChunkGenerated(pos.x, pos.z); - latch.countDown(); - })); - try { - latch.await(); - } catch (InterruptedException ignored) {} - if (listening) listener.onRegionGenerated(x, z); + public synchronized CompletableFuture 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 @@ -199,6 +231,12 @@ public class Headless implements IHeadless, LevelHeightAccessor { inject(engine, chunk, ctx); chunk.setStatus(ChunkStatus.FULL); + if (session != null) { + session.completeChunk(x, z, write(chunk)); + loadedChunks.decrementAndGet(); + return; + } + long key = Cache.key(pos.getRegionX(), pos.getRegionZ()); regions.computeIfAbsent(key, Region::new) .add(chunk); @@ -209,6 +247,16 @@ public class Headless implements IHeadless, LevelHeightAccessor { } } + @Override + public void addChunk(ChunkPacket packet) { + if (closed) return; + if (session != null) throw new IllegalStateException("Headless running as Server"); + var pos = new ChunkPos(packet.getX(), packet.getZ()); + regions.computeIfAbsent(Cache.key(pos.getRegionX(), pos.getRegionZ()), Region::new) + .add(packet); + loadedChunks.incrementAndGet(); + } + @BlockCoordinates private ChunkContext generate(Engine engine, int x, int z, Hunk vblocks, Hunk vbiomes) throws WrongEngineBroException { if (engine.isClosed()) { @@ -284,6 +332,11 @@ public class Headless implements IHeadless, LevelHeightAccessor { 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()) @@ -304,6 +357,7 @@ public class Headless implements IHeadless, LevelHeightAccessor { private final int x, z; private final long key; private final KList chunks = new KList<>(1024); + private final KList remoteChunks = new KList<>(1024); private final AtomicBoolean full = new AtomicBoolean(); private long lastEntry = M.ms(); @@ -334,13 +388,31 @@ public class Headless implements IHeadless, LevelHeightAccessor { } loadedChunks.decrementAndGet(); } + for (var chunk : remoteChunks) { + var pos = new ChunkPos(chunk.getX(), chunk.getZ()); + try (DataOutputStream dos = regionFile.getChunkDataOutputStream(pos)) { + dos.write(chunk.getData()); + } catch (Throwable e) { + Iris.error("Failed to save remote chunk " + pos.x + ", " + pos.z); + e.printStackTrace(); + } + loadedChunks.decrementAndGet(); + } regions.remove(key); } public synchronized void add(ProtoChunk chunk) { chunks.add(chunk); lastEntry = M.ms(); - if (chunks.size() < 1024) + if (chunks.size() + remoteChunks.size() < 1024) + return; + submit(); + } + + public synchronized void add(ChunkPacket packet) { + remoteChunks.add(packet); + lastEntry = M.ms(); + if (chunks.size() + remoteChunks.size() < 1024) return; submit(); } @@ -350,4 +422,31 @@ public class Headless implements IHeadless, LevelHeightAccessor { executor.submit(this); } } + + private byte[] write(ProtoChunk chunk) throws IOException { + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(out)) { + NbtIo.write(binding.serializeChunk(chunk, Headless.this), dos); + return out.toByteArray(); + } + } + + private static class CompletingThread extends Thread { + private final CompletableFuture 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); + } + } + } } From d22f49492fdeef896a0dc239be94cc1d2e3083a6 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 7 Sep 2024 12:32:09 +0200 Subject: [PATCH 20/20] implement done packet into master and add safety checks for remote server version --- .../volmit/iris/server/IrisConnection.java | 4 +- .../iris/server/master/IrisMasterClient.java | 40 ++++++++++++- .../iris/server/master/IrisMasterServer.java | 36 +++++++++--- .../iris/server/master/IrisMasterSession.java | 48 ++++++++++++--- .../iris/server/master/PingConnection.java | 32 ++++++++++ .../volmit/iris/server/node/IrisSession.java | 3 + .../volmit/iris/server/packet/Packets.java | 5 +- .../iris/server/packet/init/PingPacket.java | 58 +++++++++++++++++++ .../iris/server/pregen/CloudMethod.java | 30 ++++++++-- 9 files changed, 227 insertions(+), 29 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/server/master/PingConnection.java create mode 100644 core/src/main/java/com/volmit/iris/server/packet/init/PingPacket.java diff --git a/core/src/main/java/com/volmit/iris/server/IrisConnection.java b/core/src/main/java/com/volmit/iris/server/IrisConnection.java index 68189936b..e87ef1de0 100644 --- a/core/src/main/java/com/volmit/iris/server/IrisConnection.java +++ b/core/src/main/java/com/volmit/iris/server/IrisConnection.java @@ -24,7 +24,6 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; import java.util.function.Supplier; import java.util.logging.Level; @@ -163,8 +162,7 @@ public class IrisConnection extends SimpleChannelInboundHandler { .addLast("packet_handler", holder.getConnection()); } - public static T connect(InetSocketAddress address, Supplier factory) throws InterruptedException { - var holder = factory.get(); + public static T connect(InetSocketAddress address, T holder) throws InterruptedException { new Bootstrap() .group(getWorker()) .handler(new ChannelInitializer<>() { diff --git a/core/src/main/java/com/volmit/iris/server/master/IrisMasterClient.java b/core/src/main/java/com/volmit/iris/server/master/IrisMasterClient.java index da65a1aec..af4ed90aa 100644 --- a/core/src/main/java/com/volmit/iris/server/master/IrisMasterClient.java +++ b/core/src/main/java/com/volmit/iris/server/master/IrisMasterClient.java @@ -2,18 +2,36 @@ package com.volmit.iris.server.master; import com.volmit.iris.server.IrisConnection; import com.volmit.iris.server.packet.Packet; +import com.volmit.iris.server.packet.Packets; +import com.volmit.iris.server.packet.init.InfoPacket; +import com.volmit.iris.server.packet.init.PingPacket; import com.volmit.iris.server.util.ConnectionHolder; import com.volmit.iris.server.util.PacketListener; import com.volmit.iris.server.util.PacketSendListener; import lombok.Getter; import org.jetbrains.annotations.Nullable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + public class IrisMasterClient implements ConnectionHolder, PacketListener { private final @Getter IrisConnection connection = new IrisConnection(this); private final IrisMasterSession session; + private final CompletableFuture pingResponse = new CompletableFuture<>(); + private final CompletableFuture nodeCount = new CompletableFuture<>(); - IrisMasterClient(IrisMasterSession session) { + IrisMasterClient(String version, IrisMasterSession session){ this.session = session; + Packets.PING.newPacket() + .setVersion(version) + .send(connection); + try { + var packet = pingResponse.get(); + if (!packet.getVersion().contains(version)) + throw new IllegalStateException("Remote server version does not match"); + } catch (ExecutionException | InterruptedException e) { + throw new IllegalStateException("Failed to get ping packet", e); + } } protected void send(Packet packet) { @@ -25,8 +43,24 @@ public class IrisMasterClient implements ConnectionHolder, PacketListener { } @Override - public void onPacket(Packet packet) throws Exception { - session.onClientPacket(packet); + public void onPacket(Packet raw) { + if (!pingResponse.isDone() && raw instanceof PingPacket packet) { + pingResponse.complete(packet); + return; + } + if (!nodeCount.isDone() && raw instanceof InfoPacket packet && packet.getNodeCount() > 0) { + nodeCount.complete(packet.getNodeCount()); + return; + } + session.onClientPacket(this, raw); + } + + public int getNodeCount() { + try { + return nodeCount.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } } public void disconnect() { diff --git a/core/src/main/java/com/volmit/iris/server/master/IrisMasterServer.java b/core/src/main/java/com/volmit/iris/server/master/IrisMasterServer.java index bd1b6b130..e1346005f 100644 --- a/core/src/main/java/com/volmit/iris/server/master/IrisMasterServer.java +++ b/core/src/main/java/com/volmit/iris/server/master/IrisMasterServer.java @@ -3,21 +3,23 @@ package com.volmit.iris.server.master; import com.volmit.iris.server.IrisConnection; import com.volmit.iris.server.node.IrisServer; import com.volmit.iris.server.util.PregenHolder; +import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; import lombok.extern.java.Log; import java.net.InetSocketAddress; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; @Log(topic = "Iris-MasterServer") public class IrisMasterServer extends IrisServer { - private static final Object VALUE = new Object(); private static IrisMasterServer instance; private final KMap>> sessions = new KMap<>(); - private final KMap nodes = new KMap<>(); + private final KMap> nodes = new KMap<>(); public IrisMasterServer(int port, String[] remote) throws InterruptedException { super("Iris-MasterServer", port, IrisMasterSession::new); @@ -40,12 +42,24 @@ public class IrisMasterServer extends IrisServer { } } - public void addNode(InetSocketAddress address) { - nodes.put(address, VALUE); + private void addNode(InetSocketAddress address) throws InterruptedException { + var ping = new PingConnection(address); + try { + for (String version : ping.getVersion().get()) + addNode(address, version); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + public void addNode(InetSocketAddress address, String version) { + nodes.computeIfAbsent(version, v -> new KSet<>()).add(address); } public void removeNode(InetSocketAddress address) { - nodes.remove(address); + for (var set : nodes.values()) { + set.remove(address); + } } public static void close(UUID session) { @@ -55,17 +69,21 @@ public class IrisMasterServer extends IrisServer { map.clear(); } - public static KMap> getNodes(IrisMasterSession session) { + public static KList getVersions() { + return get().nodes.k(); + } + + public static KMap> getNodes(String version, IrisMasterSession session) { var master = get(); var uuid = session.getUuid(); close(uuid); master.getLogger().info("Requesting nodes for session " + uuid); var map = new KMap>(); - for (var address : master.nodes.keySet()) { + for (var address : master.nodes.getOrDefault(version, new KSet<>())) { try { - map.put(IrisConnection.connect(address, () -> new IrisMasterClient(session)), new KMap<>()); - } catch (Exception e) { + map.put(IrisConnection.connect(address, new IrisMasterClient(version, session)), new KMap<>()); + } catch (Throwable e) { master.getLogger().log(Level.WARNING, "Failed to connect to server " + address, e); master.removeNode(address); } diff --git a/core/src/main/java/com/volmit/iris/server/master/IrisMasterSession.java b/core/src/main/java/com/volmit/iris/server/master/IrisMasterSession.java index feb81106d..985555c6b 100644 --- a/core/src/main/java/com/volmit/iris/server/master/IrisMasterSession.java +++ b/core/src/main/java/com/volmit/iris/server/master/IrisMasterSession.java @@ -7,10 +7,13 @@ import com.volmit.iris.server.packet.Packets; import com.volmit.iris.server.packet.init.EnginePacket; import com.volmit.iris.server.packet.init.FilePacket; import com.volmit.iris.server.packet.init.InfoPacket; +import com.volmit.iris.server.packet.init.PingPacket; import com.volmit.iris.server.packet.work.ChunkPacket; +import com.volmit.iris.server.packet.work.DonePacket; import com.volmit.iris.server.packet.work.MantleChunkPacket; import com.volmit.iris.server.packet.work.PregenPacket; import com.volmit.iris.server.util.*; +import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import lombok.Getter; import lombok.extern.java.Log; @@ -19,35 +22,50 @@ import java.util.Comparator; import java.util.UUID; import java.util.logging.Level; -//TODO handle done packet @Log(topic = "IrisMasterSession") public class IrisMasterSession implements ConnectionHolder, PacketListener { private final @Getter IrisConnection connection = new IrisConnection(this); private final @Getter UUID uuid = UUID.randomUUID(); private final KMap map = new KMap<>(); private final CPSLooper cpsLooper = new CPSLooper("IrisMasterSession-" + uuid, connection); + private final KMap waiting = new KMap<>(); private KMap> clients; private int radius = -1; @Override public void onPacket(Packet raw) throws Exception { if (clients == null) { - clients = IrisMasterServer.getNodes(this); - cpsLooper.setNodeCount(clients.size()); + if (raw instanceof PingPacket packet) { + var versions = packet.getVersion(); + PacketSendListener listener = versions.size() != 1 ? PacketSendListener.thenRun(connection::disconnect) : null; + if (listener == null) { + clients = IrisMasterServer.getNodes(versions.get(0), this); + if (clients.isEmpty()) { + connection.disconnect(); + return; + } - var info = Packets.INFO.newPacket(); - info.setNodeCount(clients.size()); - connection.send(info); + var nodeCount = clients.keySet() + .stream() + .mapToInt(IrisMasterClient::getNodeCount) + .sum(); + cpsLooper.setNodeCount(nodeCount); + } + packet.setVersion(IrisMasterServer.getVersions()) + .send(connection, listener); + } else throw new RejectedException("Not a ping packet"); } if (raw instanceof FilePacket packet) { if (radius != -1) throw new RejectedException("Engine already setup"); + waiting.put(packet.getId(), new CompletingHolder(clients.k())); clients.keySet().forEach(client -> client.send(packet)); } else if (raw instanceof EnginePacket packet) { if (radius != -1) throw new RejectedException("Engine already setup"); radius = packet.getRadius(); + waiting.put(packet.getId(), new CompletingHolder(clients.k())); clients.keySet().forEach(client -> client.send(packet)); } else if (raw instanceof PregenPacket packet) { if (radius == -1) @@ -64,13 +82,13 @@ public class IrisMasterSession implements ConnectionHolder, PacketListener { } } - protected void onClientPacket(Packet raw) { + protected void onClientPacket(IrisMasterClient client, Packet raw) { if (raw instanceof ErrorPacket packet) { packet.log(log, Level.SEVERE); } else if (raw instanceof ChunkPacket) { connection.send(raw); } else if (raw instanceof MantleChunkPacket packet) { - var map = clients.get(this.map.get(packet.getPregenId())); + var map = clients.get(client); if (map.get(packet.getPregenId()) .remove(packet.getX(), packet.getZ())) map.get(packet.getPregenId()); @@ -81,6 +99,12 @@ public class IrisMasterSession implements ConnectionHolder, PacketListener { } else if (raw instanceof InfoPacket packet) { int i = packet.getGenerated(); if (i != -1) cpsLooper.addChunks(i); + } else if (raw instanceof DonePacket packet) { + var holder = waiting.get(packet.getId()); + if (holder.remove(client)) { + connection.send(Packets.DONE.newPacket().setId(packet.getId())); + waiting.remove(packet.getId()); + } } } @@ -95,4 +119,12 @@ public class IrisMasterSession implements ConnectionHolder, PacketListener { .min(Comparator.comparingInt(c -> clients.get(c).size())) .orElseThrow(() -> new RejectedException("No clients available")); } + + private record CompletingHolder(KList clients) { + + public synchronized boolean remove(IrisMasterClient client) { + clients.remove(client); + return clients.isEmpty(); + } + } } diff --git a/core/src/main/java/com/volmit/iris/server/master/PingConnection.java b/core/src/main/java/com/volmit/iris/server/master/PingConnection.java new file mode 100644 index 000000000..800549a28 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/master/PingConnection.java @@ -0,0 +1,32 @@ +package com.volmit.iris.server.master; + +import com.volmit.iris.server.IrisConnection; +import com.volmit.iris.server.packet.Packet; +import com.volmit.iris.server.packet.Packets; +import com.volmit.iris.server.packet.init.PingPacket; +import com.volmit.iris.server.util.ConnectionHolder; +import com.volmit.iris.server.util.PacketListener; +import com.volmit.iris.util.collection.KList; +import lombok.Getter; + +import java.net.InetSocketAddress; +import java.util.concurrent.CompletableFuture; + +@Getter +public class PingConnection implements ConnectionHolder, PacketListener { + private final IrisConnection connection = new IrisConnection(this); + private final CompletableFuture> version = new CompletableFuture<>(); + + public PingConnection(InetSocketAddress address) throws InterruptedException { + IrisConnection.connect(address, this); + Packets.PING.newPacket().send(connection); + } + + @Override + public void onPacket(Packet packet) throws Exception { + if (packet instanceof PingPacket p) { + version.complete(p.getVersion()); + connection.disconnect(); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/server/node/IrisSession.java b/core/src/main/java/com/volmit/iris/server/node/IrisSession.java index 960326945..c9242a514 100644 --- a/core/src/main/java/com/volmit/iris/server/node/IrisSession.java +++ b/core/src/main/java/com/volmit/iris/server/node/IrisSession.java @@ -10,6 +10,7 @@ import com.volmit.iris.server.packet.Packet; import com.volmit.iris.server.packet.Packets; import com.volmit.iris.server.packet.init.EnginePacket; import com.volmit.iris.server.packet.init.FilePacket; +import com.volmit.iris.server.packet.init.PingPacket; import com.volmit.iris.server.util.CPSLooper; import com.volmit.iris.server.util.ConnectionHolder; import com.volmit.iris.server.util.PacketListener; @@ -85,6 +86,8 @@ public class IrisSession implements ConnectionHolder, PacketListener { chunks.put(packet.getId(), holder); connection.send(request); + } else if (raw instanceof PingPacket packet) { + packet.setBukkit().send(connection); } else throw new RejectedException("Unhandled packet: " + raw.getClass().getSimpleName()); } diff --git a/core/src/main/java/com/volmit/iris/server/packet/Packets.java b/core/src/main/java/com/volmit/iris/server/packet/Packets.java index ed1baa858..59f7efb29 100644 --- a/core/src/main/java/com/volmit/iris/server/packet/Packets.java +++ b/core/src/main/java/com/volmit/iris/server/packet/Packets.java @@ -3,6 +3,7 @@ package com.volmit.iris.server.packet; import com.volmit.iris.server.packet.init.EnginePacket; import com.volmit.iris.server.packet.init.FilePacket; import com.volmit.iris.server.packet.init.InfoPacket; +import com.volmit.iris.server.packet.init.PingPacket; import com.volmit.iris.server.packet.work.DonePacket; import com.volmit.iris.server.util.ErrorPacket; import com.volmit.iris.server.packet.work.ChunkPacket; @@ -23,6 +24,7 @@ public class Packets { public static final Packets ERROR; public static final Packets INFO; + public static final Packets PING; public static final Packets FILE; public static final Packets ENGINE; @@ -69,6 +71,7 @@ public class Packets { static { ERROR = new Packets<>(ErrorPacket.class, ErrorPacket::new); INFO = new Packets<>(InfoPacket.class, InfoPacket::new); + PING = new Packets<>(PingPacket.class, PingPacket::new); FILE = new Packets<>(FilePacket.class, FilePacket::new); ENGINE = new Packets<>(EnginePacket.class, EnginePacket::new); @@ -78,7 +81,7 @@ public class Packets { MANTLE_CHUNK = new Packets<>(MantleChunkPacket.class, MantleChunkPacket::new); MANTLE_CHUNK_REQUEST = new Packets<>(MantleChunkPacket.Request.class, MantleChunkPacket.Request::new); - REGISTRY = List.of(ERROR, INFO, FILE, ENGINE, DONE, PREGEN, CHUNK, MANTLE_CHUNK, MANTLE_CHUNK_REQUEST); + REGISTRY = List.of(ERROR, INFO, PING, FILE, ENGINE, DONE, PREGEN, CHUNK, MANTLE_CHUNK, MANTLE_CHUNK_REQUEST); var map = new HashMap, Packets>(); for (int i = 0; i < REGISTRY.size(); i++) { diff --git a/core/src/main/java/com/volmit/iris/server/packet/init/PingPacket.java b/core/src/main/java/com/volmit/iris/server/packet/init/PingPacket.java new file mode 100644 index 000000000..8921547fe --- /dev/null +++ b/core/src/main/java/com/volmit/iris/server/packet/init/PingPacket.java @@ -0,0 +1,58 @@ +package com.volmit.iris.server.packet.init; + +import com.volmit.iris.server.packet.Packet; +import com.volmit.iris.server.util.ByteBufUtil; +import com.volmit.iris.util.collection.KList; +import io.netty.buffer.ByteBuf; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.bukkit.Bukkit; + +import java.io.IOException; +import java.util.UUID; + +@Data +@Accessors(chain = true) +@NoArgsConstructor +public class PingPacket implements Packet { + private UUID id = UUID.randomUUID(); + private KList version = new KList<>(); + + @Override + public void read(ByteBuf byteBuf) throws IOException { + id = new UUID(byteBuf.readLong(), byteBuf.readLong()); + int size = byteBuf.readInt(); + + version = new KList<>(); + for (int i = 0; i < size; i++) { + version.add(ByteBufUtil.readString(byteBuf)); + } + } + + @Override + public void write(ByteBuf byteBuf) throws IOException { + byteBuf.writeLong(id.getMostSignificantBits()); + byteBuf.writeLong(id.getLeastSignificantBits()); + + byteBuf.writeInt(version.size()); + for (String s : version) { + ByteBufUtil.writeString(byteBuf, s); + } + } + + public PingPacket setBukkit() { + this.version = new KList<>(Bukkit.getBukkitVersion().split("-")[0]); + return this; + } + + public PingPacket setVersion(String version) { + this.version = new KList<>(version); + return this; + } + + public PingPacket setVersion(KList version) { + this.version = version; + return this; + } +} diff --git a/core/src/main/java/com/volmit/iris/server/pregen/CloudMethod.java b/core/src/main/java/com/volmit/iris/server/pregen/CloudMethod.java index 137c6356c..54134a588 100644 --- a/core/src/main/java/com/volmit/iris/server/pregen/CloudMethod.java +++ b/core/src/main/java/com/volmit/iris/server/pregen/CloudMethod.java @@ -13,6 +13,7 @@ import com.volmit.iris.server.execption.RejectedException; import com.volmit.iris.server.packet.Packet; import com.volmit.iris.server.packet.Packets; import com.volmit.iris.server.packet.init.InfoPacket; +import com.volmit.iris.server.packet.init.PingPacket; import com.volmit.iris.server.packet.work.ChunkPacket; import com.volmit.iris.server.packet.work.DonePacket; import com.volmit.iris.server.packet.work.MantleChunkPacket; @@ -32,7 +33,6 @@ import java.util.ArrayDeque; import java.util.Arrays; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; @@ -45,14 +45,14 @@ public class CloudMethod implements PregeneratorMethod, ConnectionHolder, Packet private final IHeadless headless; private final KMap holders = new KMap<>(); private final CompletableFuture future = new CompletableFuture<>(); - private final KMap> locks = new KMap<>(); + private final KMap> locks = new KMap<>(); public CloudMethod(String address, Engine engine) throws InterruptedException { var split = address.split(":"); if (split.length != 2 || !split[1].matches("\\d+")) throw new IllegalArgumentException("Invalid remote server address: " + address); - IrisConnection.connect(new InetSocketAddress(split[0], Integer.parseInt(split[1])), () -> this); + IrisConnection.connect(new InetSocketAddress(split[0], Integer.parseInt(split[1])), this); this.engine = engine; this.headless = INMS.get().createHeadless(engine); @@ -68,6 +68,24 @@ public class CloudMethod implements PregeneratorMethod, ConnectionHolder, Packet var exit = new AtomicBoolean(false); var limited = new LimitedSemaphore(IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())); + var remoteVersion = new CompletableFuture<>(); + var ping = Packets.PING.newPacket() + .setBukkit(); + locks.put(ping.getId(), remoteVersion); + ping.send(connection); + + try { + var o = remoteVersion.get(); + if (!(o instanceof PingPacket packet)) + throw new IllegalStateException("Invalid response from remote server"); + if (!packet.getVersion().contains(ping.getVersion().get(0))) + throw new IllegalStateException("Remote server version does not match"); + } catch (Throwable e) { + connection.disconnect(); + throw new IllegalStateException("Failed to connect to remote server", e); + } + + log.info(name + ": Uploading pack..."); iterate(engine.getData().getDataFolder(), f -> { if (exit.get()) return; @@ -94,7 +112,7 @@ public class CloudMethod implements PregeneratorMethod, ConnectionHolder, Packet log.info(name + ": Done uploading pack"); log.info(name + ": Initializing Engine..."); - CompletableFuture future = new CompletableFuture<>(); + var future = new CompletableFuture<>(); var packet = Packets.ENGINE.newPacket() .setDimension(engine.getDimension().getLoadKey()) .setSeed(engine.getWorld().getRawWorldSeed()) @@ -120,7 +138,7 @@ public class CloudMethod implements PregeneratorMethod, ConnectionHolder, Packet long offset = 0; byte[] data; while ((data = in.readNBytes(packetSize)).length > 0 && !exit.get()) { - CompletableFuture future = new CompletableFuture<>(); + var future = new CompletableFuture<>(); var packet = Packets.FILE.newPacket() .setPath(path) .setOffset(offset) @@ -234,6 +252,8 @@ public class CloudMethod implements PregeneratorMethod, ConnectionHolder, Packet // Iris.info("Cloud CPS: " + packet.getCps()); } else if (raw instanceof DonePacket packet) { locks.remove(packet.getId()).complete(null); + } else if (raw instanceof PingPacket packet) { + locks.remove(packet.getId()).complete(packet); } else if (raw instanceof ErrorPacket packet) { packet.log(log, Level.SEVERE); } else throw new RejectedException("Unhandled packet: " + raw.getClass().getSimpleName());