From 17df8f23c54b03d4bfe9b08c0ccbc6b4c2d0255c Mon Sep 17 00:00:00 2001 From: Daniel Mills Date: Sun, 10 May 2020 20:43:30 -0400 Subject: [PATCH] Cleanup --- src/main/java/ninja/bytecode/iris/Iris.java | 5 +- .../java/ninja/bytecode/iris/IrisContext.java | 12 +- .../ninja/bytecode/iris/IrisGenerator.java | 373 ------------------ .../iris/generator/BiomeChunkGenerator.java | 64 +++ .../generator/ContextualChunkGenerator.java | 293 ++++++++++++++ .../generator/DimensionChunkGenerator.java | 46 +++ .../iris/generator/IrisGenerator.java | 80 ++++ .../generator/ParallaxChunkGenerator.java | 22 ++ .../generator/ParallelChunkGenerator.java | 92 +++++ .../iris/generator/TerrainChunkGenerator.java | 96 +++++ .../bytecode/iris/layer/GenLayerBiome.java | 4 +- .../iris/object/atomics/AtomicSliver.java | 21 +- .../iris/object/atomics/AtomicSliverMap.java | 42 ++ .../ninja/bytecode/iris/util/GenLayer.java | 6 +- .../ninja/bytecode/iris/util/HeightMap.java | 24 ++ 15 files changed, 797 insertions(+), 383 deletions(-) delete mode 100644 src/main/java/ninja/bytecode/iris/IrisGenerator.java create mode 100644 src/main/java/ninja/bytecode/iris/generator/BiomeChunkGenerator.java create mode 100644 src/main/java/ninja/bytecode/iris/generator/ContextualChunkGenerator.java create mode 100644 src/main/java/ninja/bytecode/iris/generator/DimensionChunkGenerator.java create mode 100644 src/main/java/ninja/bytecode/iris/generator/IrisGenerator.java create mode 100644 src/main/java/ninja/bytecode/iris/generator/ParallaxChunkGenerator.java create mode 100644 src/main/java/ninja/bytecode/iris/generator/ParallelChunkGenerator.java create mode 100644 src/main/java/ninja/bytecode/iris/generator/TerrainChunkGenerator.java create mode 100644 src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliverMap.java create mode 100644 src/main/java/ninja/bytecode/iris/util/HeightMap.java diff --git a/src/main/java/ninja/bytecode/iris/Iris.java b/src/main/java/ninja/bytecode/iris/Iris.java index fce273cb3..ab17e7db6 100644 --- a/src/main/java/ninja/bytecode/iris/Iris.java +++ b/src/main/java/ninja/bytecode/iris/Iris.java @@ -17,6 +17,7 @@ import org.bukkit.entity.Player; import org.bukkit.generator.ChunkGenerator; import org.bukkit.plugin.java.JavaPlugin; +import ninja.bytecode.iris.generator.IrisGenerator; import ninja.bytecode.iris.object.IrisBiome; import ninja.bytecode.iris.object.IrisDimension; import ninja.bytecode.iris.util.BiomeResult; @@ -154,7 +155,7 @@ public class Iris extends JavaPlugin implements BoardProvider imsg(i, "Creating Iris " + dimm + "..."); } - IrisGenerator gx = new IrisGenerator("overworld"); + IrisGenerator gx = new IrisGenerator("overworld", 16); O done = new O(); done.set(false); @@ -207,7 +208,7 @@ public class Iris extends JavaPlugin implements BoardProvider @Override public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { - return new IrisGenerator("overworld"); + return new IrisGenerator("overworld", 16); } public static void msg(String string) diff --git a/src/main/java/ninja/bytecode/iris/IrisContext.java b/src/main/java/ninja/bytecode/iris/IrisContext.java index 4b1e44533..b742a5435 100644 --- a/src/main/java/ninja/bytecode/iris/IrisContext.java +++ b/src/main/java/ninja/bytecode/iris/IrisContext.java @@ -2,13 +2,15 @@ package ninja.bytecode.iris; import org.bukkit.World; +import ninja.bytecode.iris.object.IrisDimension; +import ninja.bytecode.iris.object.IrisRegion; import ninja.bytecode.iris.util.BiomeResult; import ninja.bytecode.shuriken.collections.KMap; public interface IrisContext { static KMap contexts = new KMap<>(); - + public static void pushContext(IrisContext context) { contexts.put(context.getWorld(), context); @@ -20,8 +22,14 @@ public interface IrisContext } public BiomeResult getBiome(int x, int z); - + + public IrisDimension getDimension(); + + public IrisRegion getRegion(int x, int z); + public IrisMetrics getMetrics(); + public int getHeight(int x, int z); + public World getWorld(); } diff --git a/src/main/java/ninja/bytecode/iris/IrisGenerator.java b/src/main/java/ninja/bytecode/iris/IrisGenerator.java deleted file mode 100644 index 2133c8b71..000000000 --- a/src/main/java/ninja/bytecode/iris/IrisGenerator.java +++ /dev/null @@ -1,373 +0,0 @@ -package ninja.bytecode.iris; - -import java.util.List; -import java.util.Random; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.event.world.WorldUnloadEvent; -import org.bukkit.generator.BlockPopulator; -import org.bukkit.generator.ChunkGenerator; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import net.md_5.bungee.api.ChatColor; -import ninja.bytecode.iris.layer.GenLayerBiome; -import ninja.bytecode.iris.object.IrisBiome; -import ninja.bytecode.iris.object.IrisDimension; -import ninja.bytecode.iris.object.IrisRegion; -import ninja.bytecode.iris.object.atomics.AtomicSliver; -import ninja.bytecode.iris.util.BiomeResult; -import ninja.bytecode.iris.util.BlockDataTools; -import ninja.bytecode.iris.util.CNG; -import ninja.bytecode.iris.util.ChronoLatch; -import ninja.bytecode.iris.util.GroupedExecutor; -import ninja.bytecode.iris.util.IrisInterpolation; -import ninja.bytecode.iris.util.RNG; -import ninja.bytecode.shuriken.bench.PrecisionStopwatch; -import ninja.bytecode.shuriken.collections.KList; -import ninja.bytecode.shuriken.format.Form; -import ninja.bytecode.shuriken.logging.L; - -@Data -@EqualsAndHashCode(callSuper = false) -public class IrisGenerator extends ChunkGenerator implements IrisContext, Listener -{ - private String dimensionName; - private IrisMetrics metrics; - private World world; - private ChronoLatch perSecond; - private ChronoLatch pushLatch; - private BlockData STONE = Material.STONE.createBlockData(); - private BlockData WATER = Material.WATER.createBlockData(); - private GenLayerBiome glBiome; - private CNG terrainNoise; - private RNG masterRandom; - private GroupedExecutor tx; - private boolean failing = false; - private boolean initialized = false; - private int generated = 0; - private boolean pregenDone = false; - private int task = -1; - - public IrisGenerator(String dimensionName) - { - this.dimensionName = dimensionName; - pushLatch = new ChronoLatch(3000); - perSecond = new ChronoLatch(1000); - CNG.creates = 0; - } - - public IrisDimension getDimension() - { - IrisDimension d = Iris.data.getDimensionLoader().load(dimensionName); - - if(d == null) - { - Iris.error("Can't find dimension: " + dimensionName); - } - - return d; - } - - public void onInit(World world, RNG rng) - { - if(initialized) - { - return; - } - - this.world = world; - this.masterRandom = new RNG(world.getSeed()); - glBiome = new GenLayerBiome(this, masterRandom.nextParallelRNG(1)); - terrainNoise = CNG.signature(masterRandom.nextParallelRNG(2)); - metrics = new IrisMetrics(128); - initialized = true; - tx = new GroupedExecutor(16, Thread.MIN_PRIORITY, "Iris Generator"); - Iris.executors.add(tx); - Bukkit.getServer().getPluginManager().registerEvents(this, Iris.instance); - task = Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, this::tick, 0, 0); - } - - public void tick() - { - if(perSecond.flip()) - { - if(generated > 770) - { - pregenDone = true; - } - - if(pregenDone) - { - metrics.getPerSecond().put(generated); - generated = 0; - } - } - } - - @EventHandler - public void on(PlayerTeleportEvent e) - { - if(e.getFrom().getWorld().equals(world) && !e.getTo().getWorld().equals(world)) - { - - } - - if(!e.getFrom().getWorld().equals(world) && e.getTo().getWorld().equals(world)) - { - - } - } - - @EventHandler - public void on(WorldUnloadEvent e) - { - if(world != null && e.getWorld().equals(world)) - { - close(); - } - } - - public void close() - { - HandlerList.unregisterAll(this); - Bukkit.getScheduler().cancelTask(getTask()); - } - - @Override - public boolean canSpawn(World world, int x, int z) - { - return super.canSpawn(world, x, z); - } - - public ChunkData generateChunkDataFailure(World world, Random no, int x, int z, BiomeGrid biomeGrid) - { - ChunkData c = Bukkit.createChunkData(world); - - for(int i = 0; i < 16; i++) - { - for(int j = 0; j < 16; j++) - { - int h = 0; - - if(j == i || j + i == 16) - { - c.setBlock(i, h, j, BlockDataTools.getBlockData("RED_TERRACOTTA")); - } - - else - { - c.setBlock(i, h, j, BlockDataTools.getBlockData("BLACK_TERRACOTTA")); - } - } - } - - return c; - } - - @Override - public ChunkData generateChunkData(World world, Random no, int x, int z, BiomeGrid biomeGrid) - { - PrecisionStopwatch sx = PrecisionStopwatch.start(); - - if(failing) - { - return generateChunkDataFailure(world, no, x, z, biomeGrid); - } - - try - { - if(pushLatch.flip()) - { - if(this.world == null) - { - this.world = world; - } - - Iris.hotloader.check(); - IrisContext.pushContext(this); - if(metrics != null) - { - Iris.info("Generating " + Form.f(1000D / metrics.getTotal().getAverage(), 0) + "/s (" + Form.duration(metrics.getTotal().getAverage(), 2) + ")"); - } - } - - String key = "c" + x + "," + z; - PrecisionStopwatch s = PrecisionStopwatch.start(); - RNG random = new RNG(world.getSeed()); - onInit(world, random.nextParallelRNG(0)); - ChunkData c = Bukkit.createChunkData(world); - int ii, jj; - int fluidHeight = getDimension().getFluidHeight(); - KList collapse = new KList<>(); - ReentrantLock l = new ReentrantLock(); - - for(ii = 0; ii < 16; ii++) - { - int i = ii; - for(jj = 0; jj < 16; jj++) - { - int j = jj; - tx.queue(key, () -> - { - BlockData block; - int rx = (x * 16) + i; - int rz = (z * 16) + j; - AtomicSliver sliver = new AtomicSliver(i, j); - double ox = (getDimension().cosRotate() * rx) + (-getDimension().sinRotate() * rz) + getDimension().getCoordFracture(random, 39392).fitDoubleD(-getDimension().getCoordFractureDistance() / 2, getDimension().getCoordFractureDistance() / 2, rx, rz); - double oz = (getDimension().sinRotate() * rx) + (getDimension().cosRotate() * rz) + getDimension().getCoordFracture(random, 39392).fitDoubleD(-getDimension().getCoordFractureDistance() / 2, getDimension().getCoordFractureDistance() / 2, rx, rz); - double wx = (double) (ox) / getDimension().getTerrainZoom(); - double wz = (double) (oz) / getDimension().getTerrainZoom(); - int depth = 0; - IrisRegion region = glBiome.getRegion(wx, wz); - BiomeResult biomeResult = glBiome.generateRegionData(wx, wz, region); - IrisBiome biome = biomeResult.getBiome(); - double lo = interpolateHeight(ox, oz, (b) -> b.getLowHeight()); - double hi = interpolateSurface(ox, oz, (b) -> b.getHighHeight()); - double noise = lo + (terrainNoise.fitDoubleD(0, hi - lo, wx, wz)); - int height = (int) Math.round(noise) + fluidHeight; - - // Remove Land biome surfaces from underwater - if(height < fluidHeight + 1) - { - if(biome.isLand()) - { - biome = glBiome.generateShoreData(wx, wz, region).getBiome(); - } - } - - KList layers = biome.generateLayers(wx, wz, random, height); - - for(int k = Math.max(height, fluidHeight); k >= 0; k--) - { - boolean underwater = k > height && k <= fluidHeight; - sliver.set(k, biome.getDerivative()); - // biomeGrid.setBiome(i, k, j, biome.getDerivative()); - - if(underwater) - { - block = WATER; - } - - else - { - block = layers.hasIndex(depth) ? layers.get(depth) : STONE; - depth++; - } - - sliver.set(k, block); - // c.setBlock(i, k, j, block); - } - - l.lock(); - collapse.add(() -> - { - sliver.write(c); - sliver.write(biomeGrid); - }); - l.unlock(); - }); - } - } - - tx.waitFor(key); - collapse.forEach((i) -> i.run()); - metrics.getTotal().put(s.getMilliseconds()); - generated++; - long hits = CNG.hits; - CNG.hits = 0; - Iris.instance.hit(hits); - metrics.getLoss().put(sx.getMilliseconds() - s.getMilliseconds()); - - return c; - } - - catch(Throwable e) - { - failing = true; - Iris.error("ERROR! Failed to generate chunk! Iris has entered a failed state!"); - - for(Player i : world.getPlayers()) - { - Iris.instance.imsg(i, ChatColor.DARK_RED + "Iris Generator has entered a failed state!"); - Iris.instance.imsg(i, ChatColor.RED + "- Check the console for the error."); - Iris.instance.imsg(i, ChatColor.RED + "- Then simply run /iris dev"); - } - - L.ex(e); - } - - return generateChunkDataFailure(world, no, x, z, biomeGrid); - } - - public double interpolateHeight(double rx, double rz, Function property) - { - return IrisInterpolation.getNoise(getDimension().getInterpolationFunction(), (int) Math.round(rx), (int) Math.round(rz), getDimension().getInterpolationScale(), (xx, zz) -> - { - BiomeResult neighborResult = glBiome.generateData(xx / getDimension().getTerrainZoom(), zz / getDimension().getTerrainZoom()); - return property.apply(neighborResult.getBiome()); - }); - } - - public double interpolateSurface(double rx, double rz, Function property) - { - return IrisInterpolation.getNoise(getDimension().getInterpolationSurfaceFunction(), (int) Math.round(rx), (int) Math.round(rz), getDimension().getInterpolationSurfaceScale(), (xx, zz) -> - { - BiomeResult neighborResult = glBiome.generateData(xx / getDimension().getTerrainZoom(), zz / getDimension().getTerrainZoom()); - return property.apply(neighborResult.getBiome()); - }); - } - - @Override - public List getDefaultPopulators(World world) - { - return super.getDefaultPopulators(world); - } - - @Override - public Location getFixedSpawnLocation(World world, Random random) - { - return super.getFixedSpawnLocation(world, random); - } - - @Override - public boolean isParallelCapable() - { - return true; - } - - @Override - public IrisMetrics getMetrics() - { - return metrics; - } - - @Override - public World getWorld() - { - return world; - } - - @Override - public BiomeResult getBiome(int rx, int rz) - { - RNG random = new RNG(world.getSeed()); - double ox = (getDimension().cosRotate() * rx) + (-getDimension().sinRotate() * rz) + getDimension().getCoordFracture(random, 39392).fitDoubleD(-getDimension().getCoordFractureDistance() / 2, getDimension().getCoordFractureDistance() / 2, rx, rz); - double oz = (getDimension().sinRotate() * rx) + (getDimension().cosRotate() * rz) + getDimension().getCoordFracture(random, 39392).fitDoubleD(-getDimension().getCoordFractureDistance() / 2, getDimension().getCoordFractureDistance() / 2, rx, rz); - double wx = (double) (ox) / getDimension().getTerrainZoom(); - double wz = (double) (oz) / getDimension().getTerrainZoom(); - IrisRegion region = glBiome.getRegion(wx, wz); - return glBiome.generateRegionData(wx, wz, region); - } -} diff --git a/src/main/java/ninja/bytecode/iris/generator/BiomeChunkGenerator.java b/src/main/java/ninja/bytecode/iris/generator/BiomeChunkGenerator.java new file mode 100644 index 000000000..7f1c6baae --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/generator/BiomeChunkGenerator.java @@ -0,0 +1,64 @@ +package ninja.bytecode.iris.generator; + +import java.util.function.Function; + +import org.bukkit.World; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import ninja.bytecode.iris.layer.GenLayerBiome; +import ninja.bytecode.iris.object.IrisBiome; +import ninja.bytecode.iris.object.IrisRegion; +import ninja.bytecode.iris.util.BiomeResult; +import ninja.bytecode.iris.util.IrisInterpolation; +import ninja.bytecode.iris.util.RNG; + +@Data +@EqualsAndHashCode(callSuper = false) +public abstract class BiomeChunkGenerator extends DimensionChunkGenerator +{ + protected GenLayerBiome glBiome; + + public BiomeChunkGenerator(String dimensionName) + { + super(dimensionName); + } + + public void onInit(World world, RNG rng) + { + glBiome = new GenLayerBiome(this, masterRandom.nextParallelRNG(1)); + } + + public IrisRegion sampleRegion(int x, int z) + { + double wx = getZoomed(getModifiedX(x, z)); + double wz = getZoomed(getModifiedZ(x, z)); + return glBiome.getRegion(wx, wz); + } + + public BiomeResult sampleBiome(int x, int z) + { + double wx = getZoomed(getModifiedX(x, z)); + double wz = getZoomed(getModifiedZ(x, z)); + IrisRegion region = glBiome.getRegion(wx, wz); + return glBiome.generateRegionData(wx, wz, region); + } + + protected double interpolateHeight(double rx, double rz, Function property) + { + return IrisInterpolation.getNoise(getDimension().getInterpolationFunction(), (int) Math.round(rx), (int) Math.round(rz), getDimension().getInterpolationScale(), (xx, zz) -> + { + BiomeResult neighborResult = glBiome.generateData(xx / getDimension().getTerrainZoom(), zz / getDimension().getTerrainZoom()); + return property.apply(neighborResult.getBiome()); + }); + } + + protected double interpolateSurface(double rx, double rz, Function property) + { + return IrisInterpolation.getNoise(getDimension().getInterpolationSurfaceFunction(), (int) Math.round(rx), (int) Math.round(rz), getDimension().getInterpolationSurfaceScale(), (xx, zz) -> + { + BiomeResult neighborResult = glBiome.generateData(xx / getDimension().getTerrainZoom(), zz / getDimension().getTerrainZoom()); + return property.apply(neighborResult.getBiome()); + }); + } +} diff --git a/src/main/java/ninja/bytecode/iris/generator/ContextualChunkGenerator.java b/src/main/java/ninja/bytecode/iris/generator/ContextualChunkGenerator.java new file mode 100644 index 000000000..ed27df212 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/generator/ContextualChunkGenerator.java @@ -0,0 +1,293 @@ +package ninja.bytecode.iris.generator; + +import java.util.List; +import java.util.Random; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.event.world.WorldUnloadEvent; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import net.md_5.bungee.api.ChatColor; +import ninja.bytecode.iris.Iris; +import ninja.bytecode.iris.IrisContext; +import ninja.bytecode.iris.IrisMetrics; +import ninja.bytecode.iris.util.BlockDataTools; +import ninja.bytecode.iris.util.CNG; +import ninja.bytecode.iris.util.ChronoLatch; +import ninja.bytecode.iris.util.RNG; +import ninja.bytecode.shuriken.bench.PrecisionStopwatch; +import ninja.bytecode.shuriken.logging.L; + +@Data +@EqualsAndHashCode(callSuper = false) +public abstract class ContextualChunkGenerator extends ChunkGenerator implements Listener +{ + protected boolean failing; + protected int task; + protected boolean initialized; + protected RNG masterRandom; + protected ChronoLatch perSecond; + protected ChronoLatch pushLatch; + protected IrisMetrics metrics; + protected World world; + protected int generated; + protected int ticks; + protected boolean pregenDone; + + public ContextualChunkGenerator() + { + pushLatch = new ChronoLatch(3000); + perSecond = new ChronoLatch(1000); + CNG.creates = 0; + generated = 0; + ticks = 0; + task = -1; + initialized = false; + failing = false; + pregenDone = false; + } + + protected abstract void onGenerate(RNG masterRandom, int x, int z, ChunkData data, BiomeGrid grid); + + protected abstract void onInit(World world, RNG masterRandom); + + protected abstract void onTick(int ticks); + + protected abstract void onClose(); + + protected abstract void onFailure(Throwable e); + + protected abstract void onChunkLoaded(Chunk c); + + protected abstract void onChunkUnloaded(Chunk c); + + protected abstract void onPlayerJoin(Player p); + + protected abstract void onPlayerLeft(Player p); + + private void init(World world, RNG rng) + { + if(initialized) + { + return; + } + + this.world = world; + this.masterRandom = new RNG(world.getSeed()); + metrics = new IrisMetrics(128); + initialized = true; + Bukkit.getServer().getPluginManager().registerEvents(this, Iris.instance); + task = Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, this::tick, 0, 0); + onInit(world, masterRandom); + } + + private void tick() + { + if(perSecond.flip()) + { + if(generated > 770) + { + pregenDone = true; + } + + if(pregenDone) + { + metrics.getPerSecond().put(generated); + generated = 0; + } + } + + onTick(ticks++); + } + + @EventHandler + public void on(PlayerTeleportEvent e) + { + if(e.getFrom().getWorld().equals(world) && !e.getTo().getWorld().equals(world)) + { + onPlayerLeft(e.getPlayer()); + } + + if(!e.getFrom().getWorld().equals(world) && e.getTo().getWorld().equals(world)) + { + onPlayerJoin(e.getPlayer()); + } + } + + @EventHandler + public void on(PlayerQuitEvent e) + { + if(e.getPlayer().getWorld().equals(world)) + { + onPlayerLeft(e.getPlayer()); + } + } + + @EventHandler + public void on(PlayerJoinEvent e) + { + if(e.getPlayer().getWorld().equals(world)) + { + onPlayerJoin(e.getPlayer()); + } + } + + @EventHandler + public void on(ChunkLoadEvent e) + { + if(e.getWorld().equals(world)) + { + onChunkLoaded(e.getChunk()); + } + } + + @EventHandler + public void on(ChunkUnloadEvent e) + { + if(e.getWorld().equals(world)) + { + onChunkUnloaded(e.getChunk()); + } + } + + @EventHandler + public void on(WorldUnloadEvent e) + { + if(world != null && e.getWorld().equals(world)) + { + close(); + } + } + + protected void close() + { + HandlerList.unregisterAll(this); + Bukkit.getScheduler().cancelTask(getTask()); + onClose(); + } + + @Override + public boolean canSpawn(World world, int x, int z) + { + return super.canSpawn(world, x, z); + } + + protected ChunkData generateChunkDataFailure(World world, Random no, int x, int z, BiomeGrid biomeGrid) + { + ChunkData c = Bukkit.createChunkData(world); + + for(int i = 0; i < 16; i++) + { + for(int j = 0; j < 16; j++) + { + int h = 0; + + if(j == i || j + i == 16) + { + c.setBlock(i, h, j, BlockDataTools.getBlockData("RED_TERRACOTTA")); + } + + else + { + c.setBlock(i, h, j, BlockDataTools.getBlockData("BLACK_TERRACOTTA")); + } + } + } + + return c; + } + + @Override + public ChunkData generateChunkData(World world, Random no, int x, int z, BiomeGrid biomeGrid) + { + PrecisionStopwatch sx = PrecisionStopwatch.start(); + + if(failing) + { + return generateChunkDataFailure(world, no, x, z, biomeGrid); + } + + try + { + if(pushLatch.flip()) + { + if(this.world == null) + { + this.world = world; + } + + Iris.hotloader.check(); + + if(this instanceof IrisContext) + { + IrisContext.pushContext((IrisContext) this); + } + } + + PrecisionStopwatch s = PrecisionStopwatch.start(); + RNG random = new RNG(world.getSeed()); + init(world, random.nextParallelRNG(0)); + ChunkData c = Bukkit.createChunkData(world); + onGenerate(random, x, z, c, biomeGrid); + metrics.getTotal().put(s.getMilliseconds()); + generated++; + long hits = CNG.hits; + CNG.hits = 0; + Iris.instance.hit(hits); + metrics.getLoss().put(sx.getMilliseconds() - s.getMilliseconds()); + + return c; + } + + catch(Throwable e) + { + failing = true; + Iris.error("ERROR! Failed to generate chunk! Iris has entered a failed state!"); + + for(Player i : world.getPlayers()) + { + Iris.instance.imsg(i, ChatColor.DARK_RED + "Iris Generator has entered a failed state!"); + Iris.instance.imsg(i, ChatColor.RED + "- Check the console for the error."); + Iris.instance.imsg(i, ChatColor.RED + "- Then simply run /iris dev"); + } + + L.ex(e); + onFailure(e); + } + + return generateChunkDataFailure(world, no, x, z, biomeGrid); + } + + @Override + public List getDefaultPopulators(World world) + { + return super.getDefaultPopulators(world); + } + + @Override + public Location getFixedSpawnLocation(World world, Random random) + { + return super.getFixedSpawnLocation(world, random); + } + + @Override + public boolean isParallelCapable() + { + return true; + } +} diff --git a/src/main/java/ninja/bytecode/iris/generator/DimensionChunkGenerator.java b/src/main/java/ninja/bytecode/iris/generator/DimensionChunkGenerator.java new file mode 100644 index 000000000..33409913c --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/generator/DimensionChunkGenerator.java @@ -0,0 +1,46 @@ +package ninja.bytecode.iris.generator; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import ninja.bytecode.iris.Iris; +import ninja.bytecode.iris.object.IrisDimension; + +@Data +@EqualsAndHashCode(callSuper = false) +public abstract class DimensionChunkGenerator extends ContextualChunkGenerator +{ + protected final String dimensionName; + + public DimensionChunkGenerator(String dimensionName) + { + super(); + this.dimensionName = dimensionName; + } + + public IrisDimension getDimension() + { + IrisDimension d = Iris.data.getDimensionLoader().load(dimensionName); + + if(d == null) + { + Iris.error("Can't find dimension: " + dimensionName); + } + + return d; + } + + public double getModifiedX(int rx, int rz) + { + return (getDimension().cosRotate() * rx) + (-getDimension().sinRotate() * rz) + getDimension().getCoordFracture(masterRandom, 39392).fitDoubleD(-getDimension().getCoordFractureDistance() / 2, getDimension().getCoordFractureDistance() / 2, rx, rz); + } + + public double getModifiedZ(int rx, int rz) + { + return (getDimension().sinRotate() * rx) + (getDimension().cosRotate() * rz) + getDimension().getCoordFracture(masterRandom, 39392).fitDoubleD(-getDimension().getCoordFractureDistance() / 2, getDimension().getCoordFractureDistance() / 2, rx, rz); + } + + public double getZoomed(double modified) + { + return (double) (modified) / getDimension().getTerrainZoom(); + } +} diff --git a/src/main/java/ninja/bytecode/iris/generator/IrisGenerator.java b/src/main/java/ninja/bytecode/iris/generator/IrisGenerator.java new file mode 100644 index 000000000..c5e6d32b8 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/generator/IrisGenerator.java @@ -0,0 +1,80 @@ +package ninja.bytecode.iris.generator; + +import org.bukkit.Chunk; +import org.bukkit.entity.Player; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import ninja.bytecode.iris.IrisContext; +import ninja.bytecode.iris.object.IrisRegion; +import ninja.bytecode.iris.util.BiomeResult; + +@Data +@EqualsAndHashCode(callSuper = false) +public class IrisGenerator extends ParallaxChunkGenerator implements IrisContext +{ + public IrisGenerator(String dimensionName, int threads) + { + super(dimensionName, threads); + } + + @Override + public BiomeResult getBiome(int x, int z) + { + return sampleBiome(x, z); + } + + @Override + public IrisRegion getRegion(int x, int z) + { + return sampleRegion(x, z); + } + + @Override + public int getHeight(int x, int z) + { + return sampleHeight(x, z); + } + + @Override + protected void onTick(int ticks) + { + + } + + @Override + protected void onClose() + { + + } + + @Override + protected void onFailure(Throwable e) + { + + } + + @Override + protected void onChunkLoaded(Chunk c) + { + + } + + @Override + protected void onChunkUnloaded(Chunk c) + { + + } + + @Override + protected void onPlayerJoin(Player p) + { + + } + + @Override + protected void onPlayerLeft(Player p) + { + + } +} diff --git a/src/main/java/ninja/bytecode/iris/generator/ParallaxChunkGenerator.java b/src/main/java/ninja/bytecode/iris/generator/ParallaxChunkGenerator.java new file mode 100644 index 000000000..5702635fd --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/generator/ParallaxChunkGenerator.java @@ -0,0 +1,22 @@ +package ninja.bytecode.iris.generator; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import ninja.bytecode.iris.util.HeightMap; +import ninja.bytecode.iris.util.RNG; + +@Data +@EqualsAndHashCode(callSuper = false) +public abstract class ParallaxChunkGenerator extends TerrainChunkGenerator +{ + public ParallaxChunkGenerator(String dimensionName, int threads) + { + super(dimensionName, threads); + } + + @Override + protected void onPostGenerate(RNG random, int x, int z, ChunkData data, BiomeGrid grid, HeightMap height) + { + + } +} diff --git a/src/main/java/ninja/bytecode/iris/generator/ParallelChunkGenerator.java b/src/main/java/ninja/bytecode/iris/generator/ParallelChunkGenerator.java new file mode 100644 index 000000000..0e265986f --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/generator/ParallelChunkGenerator.java @@ -0,0 +1,92 @@ +package ninja.bytecode.iris.generator; + +import org.bukkit.World; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import ninja.bytecode.iris.Iris; +import ninja.bytecode.iris.object.atomics.AtomicSliver; +import ninja.bytecode.iris.object.atomics.AtomicSliverMap; +import ninja.bytecode.iris.util.GroupedExecutor; +import ninja.bytecode.iris.util.HeightMap; +import ninja.bytecode.iris.util.RNG; + +@Data +@EqualsAndHashCode(callSuper = false) +public abstract class ParallelChunkGenerator extends BiomeChunkGenerator +{ + private GroupedExecutor tx; + private int threads; + + public ParallelChunkGenerator(String dimensionName, int threads) + { + super(dimensionName); + this.threads = threads; + } + + public void changeThreadCount(int tc) + { + threads = tc; + GroupedExecutor e = tx; + tx = new GroupedExecutor(threads, Thread.NORM_PRIORITY, "Iris Generator - " + world.getName()); + Iris.executors.add(tx); + + if(e != null) + { + e.close(); + } + } + + protected abstract void onGenerateColumn(int cx, int cz, int wx, int wz, int x, int z, AtomicSliver sliver); + + protected abstract int onSampleColumnHeight(int cx, int cz, int wx, int wz, int x, int z); + + protected abstract void onPostGenerate(RNG random, int x, int z, ChunkData data, BiomeGrid grid, HeightMap height); + + protected int sampleHeight(int x, int z) + { + return onSampleColumnHeight(x >> 4, z >> 4, x, z, x & 15, z & 15); + } + + protected void onGenerate(RNG random, int x, int z, ChunkData data, BiomeGrid grid) + { + AtomicSliverMap map = new AtomicSliverMap(); + HeightMap height = new HeightMap(); + String key = "c" + x + "," + z; + int ii, jj; + + for(ii = 0; ii < 16; ii++) + { + int i = ii; + int wx = (x * 16) + i; + + for(jj = 0; jj < 16; jj++) + { + int j = jj; + int wz = (z * 16) + j; + AtomicSliver sliver = map.getSliver(i, j); + + tx.queue(key, () -> + { + onGenerateColumn(x, z, wx, wz, i, j, sliver); + }); + } + } + + tx.waitFor(key); + map.write(data, grid, height); + onPostGenerate(random, x, z, data, grid, height); + } + + public void onInit(World world, RNG rng) + { + super.onInit(world, rng); + changeThreadCount(threads); + } + + @Override + public boolean isParallelCapable() + { + return true; + } +} diff --git a/src/main/java/ninja/bytecode/iris/generator/TerrainChunkGenerator.java b/src/main/java/ninja/bytecode/iris/generator/TerrainChunkGenerator.java new file mode 100644 index 000000000..8c6ccbcc5 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/generator/TerrainChunkGenerator.java @@ -0,0 +1,96 @@ +package ninja.bytecode.iris.generator; + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.data.BlockData; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import ninja.bytecode.iris.object.IrisBiome; +import ninja.bytecode.iris.object.IrisRegion; +import ninja.bytecode.iris.object.atomics.AtomicSliver; +import ninja.bytecode.iris.util.BiomeResult; +import ninja.bytecode.iris.util.CNG; +import ninja.bytecode.iris.util.RNG; +import ninja.bytecode.shuriken.collections.KList; + +@Data +@EqualsAndHashCode(callSuper = false) +public abstract class TerrainChunkGenerator extends ParallelChunkGenerator +{ + protected static final BlockData STONE = Material.STONE.createBlockData(); + protected static final BlockData WATER = Material.WATER.createBlockData(); + protected CNG terrainNoise; + + public TerrainChunkGenerator(String dimensionName, int threads) + { + super(dimensionName, threads); + } + + public void onInit(World world, RNG rng) + { + super.onInit(world, rng); + terrainNoise = CNG.signature(masterRandom.nextParallelRNG(2)); + } + + @Override + protected void onGenerateColumn(int cx, int cz, int rx, int rz, int x, int z, AtomicSliver sliver) + { + BlockData block; + int fluidHeight = getDimension().getFluidHeight(); + double ox = getModifiedX(rx, rz); + double oz = getModifiedZ(rx, rz); + double wx = getZoomed(ox); + double wz = getZoomed(oz); + int depth = 0; + IrisRegion region = glBiome.getRegion(wx, wz); + BiomeResult biomeResult = glBiome.generateRegionData(wx, wz, region); + IrisBiome biome = biomeResult.getBiome(); + double lo = interpolateHeight(ox, oz, (b) -> b.getLowHeight()); + double hi = interpolateSurface(ox, oz, (b) -> b.getHighHeight()); + double noise = lo + (terrainNoise.fitDoubleD(0, hi - lo, wx, wz)); + int height = (int) Math.round(noise) + fluidHeight; + + if(height < fluidHeight + 1) + { + if(biome.isLand()) + { + biome = glBiome.generateShoreData(wx, wz, region).getBiome(); + } + } + + KList layers = biome.generateLayers(wx, wz, masterRandom, height); + + for(int k = Math.max(height, fluidHeight); k >= 0; k--) + { + boolean underwater = k > height && k <= fluidHeight; + sliver.set(k, biome.getDerivative()); + + if(underwater) + { + block = WATER; + } + + else + { + block = layers.hasIndex(depth) ? layers.get(depth) : STONE; + depth++; + } + + sliver.set(k, block); + } + } + + @Override + protected int onSampleColumnHeight(int cx, int cz, int rx, int rz, int x, int z) + { + int fluidHeight = getDimension().getFluidHeight(); + double ox = getModifiedX(rx, rz); + double oz = getModifiedZ(rx, rz); + double lo = interpolateHeight(ox, oz, (b) -> b.getLowHeight()); + double hi = interpolateSurface(ox, oz, (b) -> b.getHighHeight()); + double noise = lo + (terrainNoise.fitDoubleD(0, hi - lo, getZoomed(ox), getZoomed(oz))); + + return (int) Math.round(noise) + fluidHeight; + } +} diff --git a/src/main/java/ninja/bytecode/iris/layer/GenLayerBiome.java b/src/main/java/ninja/bytecode/iris/layer/GenLayerBiome.java index bb187c836..024c9b5e6 100644 --- a/src/main/java/ninja/bytecode/iris/layer/GenLayerBiome.java +++ b/src/main/java/ninja/bytecode/iris/layer/GenLayerBiome.java @@ -1,7 +1,7 @@ package ninja.bytecode.iris.layer; import ninja.bytecode.iris.Iris; -import ninja.bytecode.iris.IrisGenerator; +import ninja.bytecode.iris.generator.DimensionChunkGenerator; import ninja.bytecode.iris.object.InferredType; import ninja.bytecode.iris.object.IrisBiome; import ninja.bytecode.iris.object.IrisRegion; @@ -19,7 +19,7 @@ public class GenLayerBiome extends GenLayer private CellGenerator shore; private CellGenerator sea; - public GenLayerBiome(IrisGenerator iris, RNG rng) + public GenLayerBiome(DimensionChunkGenerator iris, RNG rng) { super(iris, rng); region = new CellGenerator(rng.nextParallelRNG(1188519)); diff --git a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliver.java b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliver.java index 10c421874..d88a617aa 100644 --- a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliver.java +++ b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliver.java @@ -5,8 +5,14 @@ import org.bukkit.block.data.BlockData; import org.bukkit.generator.ChunkGenerator.BiomeGrid; import org.bukkit.generator.ChunkGenerator.ChunkData; +import lombok.Data; +import ninja.bytecode.iris.util.BlockDataTools; +import ninja.bytecode.iris.util.HeightMap; + +@Data public class AtomicSliver { + private static final BlockData AIR = BlockDataTools.getBlockData("AIR"); private BlockData[] block; private Biome[] biome; private int highestBlock = 0; @@ -38,7 +44,15 @@ public class AtomicSliver { for(int i = 0; i <= highestBlock; i++) { - d.setBlock(x, i, z, block[i]); + if(block[i] == null) + { + d.setBlock(x, i, z, AIR); + } + + else + { + d.setBlock(x, i, z, block[i]); + } } } @@ -49,4 +63,9 @@ public class AtomicSliver d.setBiome(x, i, z, biome[i]); } } + + public void write(HeightMap height) + { + height.setHeight(x, z, highestBlock); + } } diff --git a/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliverMap.java b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliverMap.java new file mode 100644 index 000000000..74b0eeeb2 --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/object/atomics/AtomicSliverMap.java @@ -0,0 +1,42 @@ +package ninja.bytecode.iris.object.atomics; + +import org.bukkit.generator.ChunkGenerator.BiomeGrid; +import org.bukkit.generator.ChunkGenerator.ChunkData; + +import ninja.bytecode.iris.util.HeightMap; + +public class AtomicSliverMap +{ + private final AtomicSliver[] slivers; + + public AtomicSliverMap() + { + slivers = new AtomicSliver[256]; + + for(int i = 0; i < 16; i++) + { + for(int j = 0; j < 16; j++) + { + slivers[i * 16 + j] = new AtomicSliver(i, j); + } + } + } + + public AtomicSliver getSliver(int x, int z) + { + return slivers[x * 16 + z]; + } + + public void write(ChunkData data, BiomeGrid grid, HeightMap height) + { + for(AtomicSliver i : slivers) + { + if(i != null) + { + i.write(data); + i.write(grid); + i.write(height); + } + } + } +} diff --git a/src/main/java/ninja/bytecode/iris/util/GenLayer.java b/src/main/java/ninja/bytecode/iris/util/GenLayer.java index bca1bcc9f..adbb88c7a 100644 --- a/src/main/java/ninja/bytecode/iris/util/GenLayer.java +++ b/src/main/java/ninja/bytecode/iris/util/GenLayer.java @@ -1,13 +1,13 @@ package ninja.bytecode.iris.util; -import ninja.bytecode.iris.IrisGenerator; +import ninja.bytecode.iris.generator.DimensionChunkGenerator; public abstract class GenLayer { protected final RNG rng; - protected final IrisGenerator iris; + protected final DimensionChunkGenerator iris; - public GenLayer(IrisGenerator iris, RNG rng) + public GenLayer(DimensionChunkGenerator iris, RNG rng) { this.iris = iris; this.rng = rng; diff --git a/src/main/java/ninja/bytecode/iris/util/HeightMap.java b/src/main/java/ninja/bytecode/iris/util/HeightMap.java new file mode 100644 index 000000000..e2cede5ba --- /dev/null +++ b/src/main/java/ninja/bytecode/iris/util/HeightMap.java @@ -0,0 +1,24 @@ +package ninja.bytecode.iris.util; + +import java.util.Arrays; + +public class HeightMap +{ + private final byte[] height; + + public HeightMap() + { + height = new byte[256]; + Arrays.fill(height, Byte.MIN_VALUE); + } + + public void setHeight(int x, int z, int h) + { + height[(x & 15) * 16 + (z & 15)] = (byte) (h + Byte.MIN_VALUE); + } + + public int getHeight(int x, int z) + { + return height[(x & 15) * 16 + (z & 15)] - Byte.MIN_VALUE; + } +}