diff --git a/build.gradle b/build.gradle index 5ea54b311..134ce43f0 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,6 @@ dependencies { implementation 'org.bukkit.craftbukkit:1.16.5:1.16.5' implementation 'org.bukkit.craftbukkit:1.16.3:1.16.3' implementation 'org.bukkit.craftbukkit:1.16.1:1.16.1' - implementation 'com.bergerkiller.bukkit:BKCommonLib:1.16.4-v2' implementation 'com.sk89q.worldedit:worldedit-bukkit:7.2.0-SNAPSHOT' implementation 'io.lumine.xikage:MythicMobs:4.9.1' implementation 'com.google.code.gson:gson:2.8.5' diff --git a/src/main/java/com/volmit/iris/Iris.java b/src/main/java/com/volmit/iris/Iris.java index 29baaf38b..25929fe7d 100644 --- a/src/main/java/com/volmit/iris/Iris.java +++ b/src/main/java/com/volmit/iris/Iris.java @@ -22,7 +22,6 @@ import com.volmit.iris.core.*; import com.volmit.iris.core.command.CommandIris; import com.volmit.iris.core.command.PermissionIris; import com.volmit.iris.core.command.world.CommandLocate; -import com.volmit.iris.core.link.BKLink; import com.volmit.iris.core.link.MultiverseCoreLink; import com.volmit.iris.core.link.MythicMobsLink; import com.volmit.iris.core.nms.INMS; @@ -75,7 +74,6 @@ public class Iris extends VolmitPlugin implements Listener { public static WandManager wand; public static EditManager edit; public static IrisBoardManager board; - public static BKLink linkBK; public static MultiverseCoreLink linkMultiverseCore; public static MythicMobsLink linkMythicMobs; public static TreeManager saplingManager; @@ -180,7 +178,6 @@ public class Iris extends VolmitPlugin implements Listener { wand = new WandManager(); board = new IrisBoardManager(); linkMultiverseCore = new MultiverseCoreLink(); - linkBK = new BKLink(); linkMythicMobs = new MythicMobsLink(); saplingManager = new TreeManager(); edit = new EditManager(); diff --git a/src/main/java/com/volmit/iris/core/link/BKLink.java b/src/main/java/com/volmit/iris/core/link/BKLink.java deleted file mode 100644 index 49fc20515..000000000 --- a/src/main/java/com/volmit/iris/core/link/BKLink.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.core.link; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.plugin.Plugin; - -public class BKLink { - public BKLink() { - - } - - public void updateBlock(Block b) { - BlockData d = b.getBlockData(); - b.setType(Material.AIR, false); - b.setBlockData(d, true); - } - - public boolean supported() { - return getBK() != null; - } - - public Plugin getBK() { - - return Bukkit.getPluginManager().getPlugin("BKCommonLib"); - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/BlockFaceSetSection.java b/src/main/java/com/volmit/iris/engine/lighting/BlockFaceSetSection.java deleted file mode 100644 index 968021996..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/BlockFaceSetSection.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.collections.BlockFaceSet; - -/** - * Maps {@link BlockFaceSet} values to a 16x16x16 area of blocks - */ -public class BlockFaceSetSection { - private final byte[] _maskData = new byte[4096]; - - public void set(int x, int y, int z, BlockFaceSet faces) { - _maskData[(y << 8) | (z << 4) | x] = (byte) faces.mask(); - } - - public BlockFaceSet get(int x, int y, int z) { - return BlockFaceSet.byMask(_maskData[(y << 8) | (z << 4) | x]); - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/FlatRegionInfo.java b/src/main/java/com/volmit/iris/engine/lighting/FlatRegionInfo.java deleted file mode 100644 index 40f093265..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/FlatRegionInfo.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.utils.WorldUtil; -import org.bukkit.World; - -import java.util.Arrays; -import java.util.BitSet; -import java.util.stream.IntStream; - -/** - * Loads region information, storing whether or not - * the 32x32 (1024) chunks are available. - */ -public class FlatRegionInfo { - private static final int[] DEFAULT_RY_0 = new int[]{0}; // Optimization - public final World world; - public final int rx, rz; - public final int[] ry; - public final int cx, cz; - private final BitSet _chunks; - private boolean _loadedFromDisk; - - public FlatRegionInfo(World world, int rx, int ry, int rz) { - this(world, rx, (ry == 0) ? DEFAULT_RY_0 : new int[]{ry}, rz); - } - - public FlatRegionInfo(World world, int rx, int[] ry, int rz) { - this.world = world; - this.rx = rx; - this.rz = rz; - this.ry = ry; - this.cx = (rx << 5); - this.cz = (rz << 5); - this._chunks = new BitSet(1024); - this._loadedFromDisk = false; - } - - private FlatRegionInfo(FlatRegionInfo copy, int[] new_ry) { - this.world = copy.world; - this.rx = copy.rx; - this.ry = new_ry; - this.rz = copy.rz; - this.cx = copy.cx; - this.cz = copy.cz; - this._chunks = copy._chunks; - this._loadedFromDisk = copy._loadedFromDisk; - } - - public void addChunk(int cx, int cz) { - cx -= this.cx; - cz -= this.cz; - if (cx < 0 || cx >= 32 || cz < 0 || cz >= 32) { - return; - } - this._chunks.set((cz << 5) | cx); - } - - /** - * Gets the number of chunks in this region. - * If not loaded yet, the default 1024 is returned. - * - * @return chunk count - */ - public int getChunkCount() { - return this._chunks.cardinality(); - } - - /** - * Gets the region Y-coordinates as a sorted, immutable distinct stream - * - * @return ry int stream - */ - public IntStream getRYStream() { - return IntStream.of(this.ry); - } - - /** - * Loads the region information, now telling what chunks are contained - */ - public void load() { - if (!this._loadedFromDisk) { - this._loadedFromDisk = true; - for (int ry : this.ry) { - this._chunks.or(WorldUtil.getWorldSavedRegionChunks3(this.world, this.rx, ry, this.rz)); - } - } - } - - /** - * Ignores loading region chunk information from chunks that aren't loaded - */ - public void ignoreLoad() { - this._loadedFromDisk = true; - } - - /** - * Gets whether the chunk coordinates specified are within the range - * of coordinates of this region - * - * @param cx - chunk coordinates (world coordinates) - * @param cz - chunk coordinates (world coordinates) - * @return True if in range - */ - public boolean isInRange(int cx, int cz) { - cx -= this.cx; - cz -= this.cz; - return cx >= 0 && cz >= 0 && cx < 32 && cz < 32; - } - - /** - * Gets whether a chunk is contained and exists inside this region - * - * @param cx - chunk coordinates (world coordinates) - * @param cz - chunk coordinates (world coordinates) - * @return True if the chunk is contained - */ - public boolean containsChunk(int cx, int cz) { - cx -= this.cx; - cz -= this.cz; - if (cx < 0 || cx >= 32 || cz < 0 || cz >= 32) { - return false; - } - - // Load region file information the first time this is accessed - this.load(); - - // Check in bitset - return this._chunks.get((cz << 5) | cx); - } - - /** - * Adds another Region Y-coordinate to the list. - * The set of chunks and other properties are copied. - * - * @return new flat region info object with updated ry - */ - public FlatRegionInfo addRegionYCoordinate(int ry) { - int index = Arrays.binarySearch(this.ry, ry); - if (index >= 0) { - return this; // Already contained - } - - // Insert at this index (undo insertion point - 1) - index = -index - 1; - int[] new_y_coordinates = new int[this.ry.length + 1]; - System.arraycopy(this.ry, 0, new_y_coordinates, 0, index); - new_y_coordinates[index] = ry; - System.arraycopy(this.ry, index, new_y_coordinates, index + 1, this.ry.length - index); - return new FlatRegionInfo(this, new_y_coordinates); - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/FlatRegionInfoMap.java b/src/main/java/com/volmit/iris/engine/lighting/FlatRegionInfoMap.java deleted file mode 100644 index 79cca5998..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/FlatRegionInfoMap.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.bases.IntVector3; -import com.bergerkiller.bukkit.common.utils.MathUtil; -import com.bergerkiller.bukkit.common.utils.WorldUtil; -import com.bergerkiller.bukkit.common.wrappers.LongHashMap; -import org.bukkit.Chunk; -import org.bukkit.World; - -import java.util.Collection; -import java.util.Set; -import java.util.stream.IntStream; - -/** - * A map of region information - */ -public class FlatRegionInfoMap { - private final World _world; - private final LongHashMap _regions; - - private FlatRegionInfoMap(World world, LongHashMap regions) { - this._world = world; - this._regions = regions; - } - - public World getWorld() { - return this._world; - } - - public int getRegionCount() { - return this._regions.size(); - } - - public Collection getRegions() { - return this._regions.getValues(); - } - - public FlatRegionInfo getRegion(int rx, int rz) { - return this._regions.get(rx, rz); - } - - public FlatRegionInfo getRegionAtChunk(int cx, int cz) { - return this._regions.get(cx >> 5, cz >> 5); - } - - /** - * Gets whether a chunk exists - * - * @return True if the chunk exists - */ - public boolean containsChunk(int cx, int cz) { - FlatRegionInfo region = getRegionAtChunk(cx, cz); - return region != null && region.containsChunk(cx, cz); - } - - /** - * Gets whether a chunk, and all its 8 neighbours, exist - * - * @return True if the chunk and all its neighbours exist - */ - public boolean containsChunkAndNeighbours(int cx, int cz) { - FlatRegionInfo region = getRegionAtChunk(cx, cz); - if (region == null) { - return false; - } - for (int dx = -2; dx <= 2; dx++) { - for (int dz = -2; dz <= 2; dz++) { - int mx = cx + dx; - int mz = cz + dz; - if (region.isInRange(mx, mz)) { - if (!region.containsChunk(mx, mz)) { - return false; - } - } else { - if (!this.containsChunk(mx, mz)) { - return false; - } - } - } - } - return true; - } - - /** - * Computes all the region Y-coordinates used by a region and its neighbouring 8 regions. - * The returned array is sorted in increasing order and is distinct (no duplicate values). - * - * @return region and neighbouring regions' Y-coordinates - */ - public int[] getRegionYCoordinatesSelfAndNeighbours(FlatRegionInfo region) { - IntStream region_y_coord_stream = region.getRYStream(); - for (int drx = -1; drx <= 1; drx++) { - for (int drz = -1; drz <= 1; drz++) { - if (drx == 0 && drz == 0) { - continue; - } - - FlatRegionInfo neigh_region = this.getRegion(region.rx + drx, region.rz + drz); - if (neigh_region != null) { - region_y_coord_stream = IntStream.concat(region_y_coord_stream, neigh_region.getRYStream()); - } - } - } - - //TODO: There's technically a way to significantly speed up sorting two concatenated sorted streams - // Sadly, the java 8 SDK doesn't appear to do any optimizations here :( - return region_y_coord_stream.sorted().distinct().toArray(); - } - - /** - * Creates a region information mapping of all existing chunks of a world - * that are currently loaded. No further loading is required. - * - * @return region info map - */ - public static FlatRegionInfoMap createLoaded(World world) { - LongHashMap regions = new LongHashMap<>(); - for (Chunk chunk : world.getLoadedChunks()) { - int rx = WorldUtil.chunkToRegionIndex(chunk.getX()); - int rz = WorldUtil.chunkToRegionIndex(chunk.getZ()); - FlatRegionInfo prev_info = regions.get(rx, rz); - FlatRegionInfo new_info = prev_info; - if (new_info == null) { - new_info = new FlatRegionInfo(world, rx, 0, rz); - new_info.ignoreLoad(); - } - - // Refresh y-coordinates - for (Integer y_coord : WorldUtil.getLoadedSectionCoordinates(chunk)) { - new_info = new_info.addRegionYCoordinate(WorldUtil.chunkToRegionIndex(y_coord)); - } - - // Add chunk to region bitset - new_info.addChunk(chunk.getX(), chunk.getZ()); - - // Store if new or changed - if (new_info != prev_info) { - regions.put(rx, rz, new_info); - } - } - - return new FlatRegionInfoMap(world, regions); - } - - /** - * Creates a region information mapping of all existing chunks of a world - * - * @return region info map - */ - public static FlatRegionInfoMap create(World world) { - LongHashMap regions = new LongHashMap<>(); - - // Obtain the region coordinates in 3d space (vertical too!) - Set regionCoordinates = WorldUtil.getWorldRegions3(world); - - // For each region, create a RegionInfo entry - for (IntVector3 region : regionCoordinates) { - long key = MathUtil.longHashToLong(region.x, region.z); - FlatRegionInfo prev = regions.get(key); - if (prev != null) { - regions.put(key, prev.addRegionYCoordinate(region.y)); - } else { - regions.put(key, new FlatRegionInfo(world, region.x, region.y, region.z)); - } - } - - // For all loaded chunks, add those chunks to their region up-front - // They may not yet have been saved to the region file - for (Chunk chunk : world.getLoadedChunks()) { - int rx = WorldUtil.chunkToRegionIndex(chunk.getX()); - int rz = WorldUtil.chunkToRegionIndex(chunk.getZ()); - FlatRegionInfo info = regions.get(rx, rz); - if (info != null) { - info.addChunk(chunk.getX(), chunk.getZ()); - } - } - - return new FlatRegionInfoMap(world, regions); - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingAutoClean.java b/src/main/java/com/volmit/iris/engine/lighting/LightingAutoClean.java deleted file mode 100644 index 6a76663f0..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingAutoClean.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.Task; -import com.bergerkiller.bukkit.common.utils.WorldUtil; -import com.bergerkiller.bukkit.common.wrappers.LongHashSet; -import com.volmit.iris.Iris; -import org.bukkit.World; - -import java.util.HashMap; - -/** - * Handles the automatic cleanup of chunk lighting when chunks are generated - */ -public class LightingAutoClean { - private static final HashMap queues = new HashMap<>(); - private static Task autoCleanTask = null; - - /** - * Checks all neighbouring chunks to see if they are fully surrounded by chunks (now), and - * schedules lighting repairs. This function only does anything when automatic cleaning is activated. - * - * @param world the chunk is in - * @param chunkX coordinate - * @param chunkZ coordinate - */ - public static void handleChunkGenerated(World world, int chunkX, int chunkZ) { - - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) { - continue; - } - if (!WorldUtil.isChunkAvailable(world, chunkX + dx, chunkZ + dz)) { - continue; - } - - // Check that all chunks surrounding this chunk are all available - boolean allNeighboursLoaded = true; - for (int dx2 = -1; dx2 <= 1 && allNeighboursLoaded; dx2++) { - for (int dz2 = -1; dz2 <= 1 && allNeighboursLoaded; dz2++) { - if (dx2 == 0 && dz2 == 0) { - continue; // ignore self - } - if (dx2 == -dx && dz2 == -dz) { - continue; // ignore the original generated chunk - } - allNeighboursLoaded &= WorldUtil.isChunkAvailable(world, chunkX + dx + dx2, chunkZ + dz + dz2); - } - } - - // If all neighbours are available, schedule it for fixing - if (allNeighboursLoaded) { - schedule(world, chunkX + dx, chunkZ + dz); - } - } - } - } - - private static synchronized void processAutoClean() { - while (queues.size() > 0) { - World world = queues.keySet().iterator().next(); - LongHashSet chunks = queues.remove(world); - LightingService.schedule(world, chunks); - } - } - - public static void schedule(World world, int chunkX, int chunkZ) { - schedule(world, chunkX, chunkZ, 80); - } - - public static synchronized void schedule(World world, int chunkX, int chunkZ, int tickDelay) { - LongHashSet queue = queues.get(world); - if (queue == null) { - queue = new LongHashSet(9); - queues.put(world, queue); - } - - // Queue this chunk, and all its neighbours - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - queue.add(chunkX + dx, chunkZ + dz); - } - } - - // Initialize clean task if it hasn't been yet - if (autoCleanTask == null) { - autoCleanTask = new Task(Iris.instance) { - @Override - public void run() { - processAutoClean(); - } - }; - } - - // Postpone the tick task while there are less than 100 chunks in the queue - if (queue.size() < 100) { - autoCleanTask.stop().start(tickDelay); - } - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingCategory.java b/src/main/java/com/volmit/iris/engine/lighting/LightingCategory.java deleted file mode 100644 index 08b3d52a7..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingCategory.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.collections.BlockFaceSet; - -/** - * Represents a category of light being processed. All conditional logic - * for this is handled by this class. - */ -public enum LightingCategory { - SKY() { - @Override - public String getName() { - return "Sky"; - } - - @Override - public void initialize(LightingChunk chunk) { - if (!chunk.hasSkyLight) { - return; - } - - // Find out the highest possible Y-position - int x, y, z, light, height, opacity; - BlockFaceSet opaqueFaces; - LightingCube cube = null; - // Apply initial sky lighting from top to bottom - for (z = chunk.start.z; z <= chunk.end.z; z++) { - for (x = chunk.start.x; x <= chunk.end.x; x++) { - light = 15; - height = chunk.getHeight(x, z) + 1; - for (y = chunk.maxY; y >= chunk.minY; y--) { - if ((cube = chunk.nextCube(cube, y)) == null) { - // Skip the remaining 15: they are all inaccessible as well - y -= 15; - - // If not full skylight, reset light level, assuming it dimmed out - if (light != 15) { - light = 0; - } - continue; - } - - // Set quickly when light level is at 0, or we are above height level - if (y > height || light <= 0) { - cube.skyLight.set(x, y & 0xf, z, light); - continue; - } - - // If opaque at the top, set light to 0 instantly - opaqueFaces = cube.getOpaqueFaces(x, y & 0xf, z); - if (opaqueFaces.up()) { - light = 0; - } else { - // Apply the opacity to the light level - opacity = cube.opacity.get(x, y & 0xf, z); - if (light < 15 && opacity == 0) { - opacity = 1; - } - if ((light -= opacity) <= 0) { - light = 0; - } - } - - // Apply sky light to block - cube.skyLight.set(x, y & 0xf, z, light); - - // If opaque at the bottom, reset light to 0 for next block - // The block itself is lit - if (opaqueFaces.down()) { - light = 0; - } - } - } - } - } - - @Override - public int getStartY(LightingChunk chunk, int x, int z) { - return chunk.getHeight(x, z); - } - - @Override - public void setDirty(LightingChunk chunk, boolean dirty) { - chunk.isSkyLightDirty = dirty; - } - - @Override - public int get(LightingCube section, int x, int y, int z) { - return section.skyLight.get(x, y, z); - } - - @Override - public void set(LightingCube section, int x, int y, int z, int level) { - section.skyLight.set(x, y, z, level); - } - }, - BLOCK() { - @Override - public String getName() { - return "Block"; - } - - @Override - public void initialize(LightingChunk chunk) { - // Some blocks that emit light, also have opaque faces - // They still emit light through the opaque faces to other blocks - // To fix this, run an initial processing step that spreads all - // emitted light to the neighbouring blocks' block light, ignoring own opaque faces - int x, y, z; - for (LightingCube cube : chunk.getSections()) { - for (y = 0; y < 16; y++) { - for (z = chunk.start.z; z <= chunk.end.z; z++) { - for (x = chunk.start.x; x <= chunk.end.x; x++) { - cube.spreadBlockLight(x, y, z); - } - } - } - } - } - - @Override - public int getStartY(LightingChunk chunk, int x, int z) { - return chunk.maxY; - } - - @Override - public void setDirty(LightingChunk chunk, boolean dirty) { - chunk.isBlockLightDirty = dirty; - } - - @Override - public int get(LightingCube section, int x, int y, int z) { - return section.blockLight.get(x, y, z); - } - - @Override - public void set(LightingCube section, int x, int y, int z, int level) { - section.blockLight.set(x, y, z, level); - } - }; - - /** - * Gets the name of this type of light, used when logging - * - * @return category name - */ - public abstract String getName(); - - /** - * Initializes the lighting in the chunk for this category - */ - public abstract void initialize(LightingChunk chunk); - - /** - * Gets the y-coordinate to start processing from when spreading light around - * - * @return start y-coordinate - */ - public abstract int getStartY(LightingChunk chunk, int x, int z); - - /** - * Sets whether this category of light is dirty, indicating this category of light is all good, - * or that more work is needed spreading light around. - */ - public abstract void setDirty(LightingChunk chunk, boolean dirty); - - /** - * Gets the light level in a section at the coordinates specified. - * No bounds checking is performed. - * - * @return light level - */ - public abstract int get(LightingCube section, int x, int y, int z); - - /** - * Sets the light level in a section at the coordinates specified. - * No bounds checking is performed. - */ - public abstract void set(LightingCube section, int x, int y, int z, int level); -} \ No newline at end of file diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingChunk.java b/src/main/java/com/volmit/iris/engine/lighting/LightingChunk.java deleted file mode 100644 index 55ad08d09..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingChunk.java +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.bases.IntVector2; -import com.bergerkiller.bukkit.common.chunk.ForcedChunk; -import com.bergerkiller.bukkit.common.collections.BlockFaceSet; -import com.bergerkiller.bukkit.common.utils.ChunkUtil; -import com.bergerkiller.bukkit.common.utils.WorldUtil; -import com.bergerkiller.bukkit.common.wrappers.ChunkSection; -import com.bergerkiller.bukkit.common.wrappers.HeightMap; -import com.bergerkiller.bukkit.common.wrappers.IntHashMap; -import com.bergerkiller.generated.net.minecraft.server.ChunkHandle; -import com.volmit.iris.Iris; -import org.bukkit.Chunk; -import org.bukkit.World; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -/** - * Represents a single chunk full with lighting-relevant information. - * Initialization and use of this chunk in the process is as follows:
- * - New lighting chunks are created for all chunks to be processed
- * - notifyAccessible is called for all chunks, passing in all chunks
- * - fill/fillSection is called for all chunks, after which initLight is called
- * - spread is called on all chunks until all spreading is finished
- * - data from all LightingChunks/Sections is gathered and saved to chunks or region files
- * - possible chunk resends are performed - */ -public class LightingChunk { - public static final int OB = ~0xf; // Outside blocks - public static final int OC = ~0xff; // Outside chunk - public IntHashMap sections; - public final LightingChunkNeighboring neighbors = new LightingChunkNeighboring(); - public final int[] heightmap = new int[256]; - public final World world; - public final int chunkX, chunkZ; - public boolean hasSkyLight = true; - public boolean isSkyLightDirty = true; - public boolean isBlockLightDirty = true; - public boolean isFilled = false; - public boolean isApplied = false; - public IntVector2 start = new IntVector2(1, 1); - public IntVector2 end = new IntVector2(14, 14); - public int minY = 0; - public int maxY = 0; - public final ForcedChunk forcedChunk = ForcedChunk.none(); - public volatile boolean loadingStarted = false; - - public LightingChunk(World world, int x, int z) { - this.world = world; - this.chunkX = x; - this.chunkZ = z; - } - - /** - * Gets all the sections inside this chunk. - * Elements are never null. - * - * @return sections - */ - public Collection getSections() { - return this.sections.values(); - } - - /** - * Efficiently iterates the vertical cubes of a chunk, only - * querying the lookup table every 16 blocks - * - * @param previous The previous cube we iterated - * @param y Block y-coordinate - * @return the cube at the Block y-coordinate, or null if this cube does not exist - */ - public LightingCube nextCube(LightingCube previous, int y) { - int cy = y >> 4; - if (previous != null && previous.cy == cy) { - return previous; - } else { - return this.sections.get(cy); - } - } - - /** - * Notifies that a new chunk is accessible. - * - * @param chunk that is accessible - */ - public void notifyAccessible(LightingChunk chunk) { - final int dx = chunk.chunkX - this.chunkX; - final int dz = chunk.chunkZ - this.chunkZ; - // Only check neighbours, ignoring the corners and self - if (Math.abs(dx) > 1 || Math.abs(dz) > 1 || (dx != 0) == (dz != 0)) { - return; - } - // Results in -16, 16 or 0 for the x/z coordinates - neighbors.set(dx, dz, chunk); - // Update start/end coordinates - if (dx == 1) { - end = new IntVector2(15, end.z); - } else if (dx == -1) { - start = new IntVector2(0, start.z); - } else if (dz == 1) { - end = new IntVector2(end.x, 15); - } else if (dz == -1) { - start = new IntVector2(start.x, 0); - } - } - - /** - * Initializes the neighboring cubes of all the cubes of this - * lighting chunk. This initializes the neighbors both within - * the same chunk (vertical) and for neighboring chunks (horizontal). - */ - public void detectCubeNeighbors() { - for (LightingCube cube : this.sections.values()) { - // Neighbors above and below - cube.neighbors.set(0, 1, 0, this.sections.get(cube.cy + 1)); - cube.neighbors.set(0, -1, 0, this.sections.get(cube.cy - 1)); - // Neighbors in neighboring chunks - cube.neighbors.set(-1, 0, 0, this.neighbors.getCube(-1, 0, cube.cy)); - cube.neighbors.set(1, 0, 0, this.neighbors.getCube(1, 0, cube.cy)); - cube.neighbors.set(0, 0, -1, this.neighbors.getCube(0, -1, cube.cy)); - cube.neighbors.set(0, 0, 1, this.neighbors.getCube(0, 1, cube.cy)); - } - } - - public void fill(Chunk chunk, int[] region_y_coordinates) { - // Fill using chunk sections - hasSkyLight = WorldUtil.getDimensionType(chunk.getWorld()).hasSkyLight(); - - List lightingChunkSectionList; - { - // First create a list of ChunkSection objects storing the data - // We must do this sequentially, because asynchronous access is not permitted - List chunkSectionList = IntStream.of(region_y_coordinates) - .map(WorldUtil::regionToChunkIndex) - .flatMap(base_cy -> IntStream.range(base_cy, base_cy + WorldUtil.CHUNKS_PER_REGION_AXIS)) - .mapToObj(cy -> WorldUtil.getSection(chunk, cy)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - // Then process all the gathered chunk sections into a LightingChunkSection in parallel - lightingChunkSectionList = chunkSectionList.stream() - .parallel() - .map(section -> new LightingCube(this, section, hasSkyLight)) - .collect(Collectors.toList()); - } - - // Add to mapping - this.sections = new IntHashMap<>(); - for (LightingCube lightingChunkSection : lightingChunkSectionList) { - this.sections.put(lightingChunkSection.cy, lightingChunkSection); - } - - // Compute min/max y using sections that are available - // Make use of the fact that they are pre-sorted by y-coordinate - this.minY = 0; - this.maxY = 0; - if (!lightingChunkSectionList.isEmpty()) { - this.minY = lightingChunkSectionList.get(0).cy << 4; - this.maxY = (lightingChunkSectionList.get(lightingChunkSectionList.size() - 1).cy << 4) + 15; - } - - // Initialize and then load sky light heightmap information - if (this.hasSkyLight) { - HeightMap heightmap = ChunkUtil.getLightHeightMap(chunk, true); - for (int x = 0; x < 16; ++x) { - for (int z = 0; z < 16; ++z) { - this.heightmap[this.getHeightKey(x, z)] = Math.max(this.minY, heightmap.getHeight(x, z)); - } - } - } else { - Arrays.fill(this.heightmap, this.maxY); - } - - this.isFilled = true; - } - - private int getHeightKey(int x, int z) { - return x | (z << 4); - } - - /** - * Gets the height level (the top block that does not block light) - * - * @param x - coordinate - * @param z - coordinate - * @return height - */ - public int getHeight(int x, int z) { - return this.heightmap[getHeightKey(x, z)]; - } - - private final int getMaxLightLevel(LightingCube section, LightingCategory category, int lightLevel, int x, int y, int z) { - BlockFaceSet selfOpaqueFaces = section.getOpaqueFaces(x, y, z); - if (x >= 1 && z >= 1 && x <= 14 && z <= 14) { - // All within this chunk - simplified calculation - if (!selfOpaqueFaces.west()) { - lightLevel = section.getLightIfHigher(category, lightLevel, - BlockFaceSet.MASK_EAST, x - 1, y, z); - } - if (!selfOpaqueFaces.east()) { - lightLevel = section.getLightIfHigher(category, lightLevel, - BlockFaceSet.MASK_WEST, x + 1, y, z); - } - if (!selfOpaqueFaces.north()) { - lightLevel = section.getLightIfHigher(category, lightLevel, - BlockFaceSet.MASK_SOUTH, x, y, z - 1); - } - if (!selfOpaqueFaces.south()) { - lightLevel = section.getLightIfHigher(category, lightLevel, - BlockFaceSet.MASK_NORTH, x, y, z + 1); - } - - // If dy is also within this section, we can simplify it - if (y >= 1 && y <= 14) { - if (!selfOpaqueFaces.down()) { - lightLevel = section.getLightIfHigher(category, lightLevel, - BlockFaceSet.MASK_UP, x, y - 1, z); - } - if (!selfOpaqueFaces.up()) { - lightLevel = section.getLightIfHigher(category, lightLevel, - BlockFaceSet.MASK_DOWN, x, y + 1, z); - } - return lightLevel; - } - } else { - // Crossing chunk boundaries - requires neighbor checks - if (!selfOpaqueFaces.west()) { - lightLevel = section.getLightIfHigherNeighbor(category, lightLevel, - BlockFaceSet.MASK_EAST, x - 1, y, z); - } - if (!selfOpaqueFaces.east()) { - lightLevel = section.getLightIfHigherNeighbor(category, lightLevel, - BlockFaceSet.MASK_WEST, x + 1, y, z); - } - if (!selfOpaqueFaces.north()) { - lightLevel = section.getLightIfHigherNeighbor(category, lightLevel, - BlockFaceSet.MASK_SOUTH, x, y, z - 1); - } - if (!selfOpaqueFaces.south()) { - lightLevel = section.getLightIfHigherNeighbor(category, lightLevel, - BlockFaceSet.MASK_NORTH, x, y, z + 1); - } - } - - // Above and below, may need to check cube boundaries - // Below - if (!selfOpaqueFaces.down()) { - lightLevel = section.getLightIfHigherNeighbor(category, lightLevel, - BlockFaceSet.MASK_UP, x, y - 1, z); - } - - // Above - if (!selfOpaqueFaces.up()) { - lightLevel = section.getLightIfHigherNeighbor(category, lightLevel, - BlockFaceSet.MASK_DOWN, x, y + 1, z); - } - - return lightLevel; - } - - /** - * Gets whether this lighting chunk has faults that need to be fixed - * - * @return True if there are faults, False if not - */ - public boolean hasFaults() { - return isSkyLightDirty || isBlockLightDirty; - } - - public void forceSpreadBlocks() { - spread(LightingCategory.BLOCK); - } - - /** - * Spreads the light from sources to 'zero' light level blocks - * - * @return Number of processing loops executed. 0 indicates no faults were found. - */ - public int spread() { - if (hasFaults()) { - int count = 0; - if (isSkyLightDirty) { - count += spread(LightingCategory.SKY); - } - if (isBlockLightDirty) { - count += spread(LightingCategory.BLOCK); - } - return count; - } else { - return 0; - } - } - - private int spread(LightingCategory category) { - if ((category == LightingCategory.SKY) && !hasSkyLight) { - this.isSkyLightDirty = false; - return 0; - } - - int x, y, z, light, factor, startY, newlight; - int loops = 0; - int lasterrx = 0, lasterry = 0, lasterrz = 0; - boolean haserror; - - boolean err_neigh_nx = false; - boolean err_neigh_px = false; - boolean err_neigh_nz = false; - boolean err_neigh_pz = false; - - LightingCube cube = null; - // Keep spreading the light in this chunk until it is done - boolean mode = false; - IntVector2 loop_start, loop_end; - int loop_increment; - while (true) { - haserror = false; - - // Alternate iterating positive and negative - // This allows proper optimized spreading in all directions - mode = !mode; - if (mode) { - loop_start = start; - loop_end = end.add(1, 1); - loop_increment = 1; - } else { - loop_start = end; - loop_end = start.subtract(1, 1); - loop_increment = -1; - } - - // Go through all blocks, using the heightmap for sky light to skip a few - for (x = loop_start.x; x != loop_end.x; x += loop_increment) { - for (z = loop_start.z; z != loop_end.z; z += loop_increment) { - startY = category.getStartY(this, x, z); - for (y = startY; y >= this.minY; y--) { - if ((cube = nextCube(cube, y)) == null) { - // Skip this section entirely by setting y to the bottom of the section - y &= ~0xf; - continue; - } - - // Take block opacity into account, skip if fully solid - factor = Math.max(1, cube.opacity.get(x, y & 0xf, z)); - if (factor == 15) { - continue; - } - - // Read the old light level and try to find a light level around it that exceeds - light = category.get(cube, x, y & 0xf, z); - newlight = light + factor; - if (newlight < 15) { - newlight = getMaxLightLevel(cube, category, newlight, x, y & 0xf, z); - } - newlight -= factor; - - // pick the highest value - if (newlight > light) { - category.set(cube, x, y & 0xf, z, newlight); - lasterrx = x; - lasterry = y; - lasterrz = z; - err_neigh_nx |= (x == 0); - err_neigh_nz |= (z == 0); - err_neigh_px |= (x == 15); - err_neigh_pz |= (z == 15); - haserror = true; - } - } - } - } - - if (!haserror) { - break; - } else if (++loops > 100) { - lasterrx += this.chunkX << 4; - lasterrz += this.chunkZ << 4; - String msg = "Failed to fix all " + category.getName() + " lighting at [" + - lasterrx + '/' + lasterry + - '/' + lasterrz + ']'; - Iris.warn(msg); - break; - } - } - - // Set self as no longer dirty, all light is good - category.setDirty(this, false); - - // When we change blocks at our chunk borders, neighbours have to do another spread cycle - if (err_neigh_nx) setNeighbourDirty(-1, 0, category); - if (err_neigh_px) setNeighbourDirty(1, 0, category); - if (err_neigh_nz) setNeighbourDirty(0, -1, category); - if (err_neigh_pz) setNeighbourDirty(0, 1, category); - - return loops; - } - - private void setNeighbourDirty(int dx, int dz, LightingCategory category) { - LightingChunk n = neighbors.get(dx, dz); - if (n != null) { - category.setDirty(n, true); - } - } - - /** - * Applies the lighting information to a chunk. The returned completable future is called - * on the main thread when saving finishes. - * - * @param chunk to save to - * @return completable future completed when the chunk is saved, - * with value True passed when saving occurred, False otherwise - */ - @SuppressWarnings("unchecked") - public CompletableFuture saveToChunk(Chunk chunk) { - // Create futures for saving to all the chunk sections in parallel - List sectionsToSave = this.sections.values(); - final CompletableFuture[] futures = new CompletableFuture[sectionsToSave.size()]; - { - int futureIndex = 0; - for (LightingCube sectionToSave : sectionsToSave) { - ChunkSection sectionToWriteTo = WorldUtil.getSection(chunk, sectionToSave.cy); - if (sectionToWriteTo == null) { - futures[futureIndex++] = CompletableFuture.completedFuture(Boolean.FALSE); - } else { - futures[futureIndex++] = sectionToSave.saveToChunk(sectionToWriteTo); - } - } - } - - // When all of them complete, combine them into a single future - // If any changes were made to the chunk, return True as completed value - return CompletableFuture.allOf(futures).thenApply((o) -> { - isApplied = true; - - try { - for (CompletableFuture future : futures) { - if (future.get()) { - ChunkHandle.fromBukkit(chunk).markDirty(); - return Boolean.TRUE; - } - } - } catch (Throwable t) { - Iris.reportError(t); - t.printStackTrace(); - } - - // None of the futures completed true - return Boolean.FALSE; - }); - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingChunkNeighboring.java b/src/main/java/com/volmit/iris/engine/lighting/LightingChunkNeighboring.java deleted file mode 100644 index 37c976235..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingChunkNeighboring.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -/** - * Keeps track of the 4 x/z neighbors of chunks - */ -public class LightingChunkNeighboring { - public final LightingChunk[] values = new LightingChunk[4]; - - /** - * Generates a key ranging 0 - 3 for fixed x/z combinations
- * - Bit 1 is set to contain which of the two is not 1
- * - Bit 2 is set to contain whether x/z is 1 or -1

- *

- * This system requires that the x/z pairs are one the following:
- * (0, 1) | (0, -1) | (1, 0) | (-1, 0) - * - * @param x value - * @param z value - * @return key - */ - private static final int getIndexByChunk(int x, int z) { - return (x & 1) | ((x + z + 1) & 0x2); - } - - /** - * Gets whether all 4 chunk neighbors are accessible - * - * @return True if all neighbors are accessible - */ - public boolean hasAll() { - for (int i = 0; i < 4; i++) { - if (values[i] == null) { - return false; - } - } - return true; - } - - /** - * Gets the neighbor representing the given relative chunk - * - * @return neighbor - */ - public LightingChunk get(int deltaChunkX, int deltaChunkZ) { - return values[getIndexByChunk(deltaChunkX, deltaChunkZ)]; - } - - /** - * Gets a relative neighboring chunk, and then a vertical cube in that chunk, if possible. - * - * @param cy Cube absolute y-coordinate - * @return cube, null if the chunk or cube is not available - */ - public LightingCube getCube(int deltaChunkX, int deltaChunkZ, int cy) { - LightingChunk chunk = get(deltaChunkX, deltaChunkZ); - return (chunk == null) ? null : chunk.sections.get(cy); - } - - /** - * Sets the neighbor representing the given relative chunk - * - * @param neighbor to set to - */ - public void set(int deltaChunkX, int deltaChunkZ, LightingChunk neighbor) { - values[getIndexByChunk(deltaChunkX, deltaChunkZ)] = neighbor; - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingCube.java b/src/main/java/com/volmit/iris/engine/lighting/LightingCube.java deleted file mode 100644 index 5dc25d054..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingCube.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.collections.BlockFaceSet; -import com.bergerkiller.bukkit.common.utils.WorldUtil; -import com.bergerkiller.bukkit.common.wrappers.BlockData; -import com.bergerkiller.bukkit.common.wrappers.ChunkSection; -import com.bergerkiller.generated.net.minecraft.server.NibbleArrayHandle; -import com.volmit.iris.Iris; - -import java.util.concurrent.CompletableFuture; - -/** - * A single 16x16x16 cube of stored block information - */ -public class LightingCube { - public static final int OOC = ~0xf; // Outside Of Cube - public final LightingChunk owner; - public final LightingCubeNeighboring neighbors = new LightingCubeNeighboring(); - public final int cy; - public final NibbleArrayHandle skyLight; - public final NibbleArrayHandle blockLight; - public final NibbleArrayHandle emittedLight; - public final NibbleArrayHandle opacity; - private final BlockFaceSetSection opaqueFaces; - - public LightingCube(LightingChunk owner, ChunkSection chunkSection, boolean hasSkyLight) { - this.owner = owner; - this.cy = chunkSection.getY(); - - if (owner.neighbors.hasAll()) { - // Block light data (is re-initialized in the fill operation below, no need to read) - this.blockLight = NibbleArrayHandle.createNew(); - - // Sky light data (is re-initialized using heightmap operation later, no need to read) - if (hasSkyLight) { - this.skyLight = NibbleArrayHandle.createNew(); - } else { - this.skyLight = null; - } - } else { - // We need to load the original light data, because we have a border that we do not update - - // Block light data - byte[] blockLightData = WorldUtil.getSectionBlockLight(owner.world, - owner.chunkX, this.cy, owner.chunkZ); - if (blockLightData != null) { - this.blockLight = NibbleArrayHandle.createNew(blockLightData); - } else { - this.blockLight = NibbleArrayHandle.createNew(); - } - - // Sky light data - if (hasSkyLight) { - byte[] skyLightData = WorldUtil.getSectionSkyLight(owner.world, - owner.chunkX, this.cy, owner.chunkZ); - if (skyLightData != null) { - this.skyLight = NibbleArrayHandle.createNew(skyLightData); - } else { - this.skyLight = NibbleArrayHandle.createNew(); - } - } else { - this.skyLight = null; - } - } - - // World coordinates - int worldX = owner.chunkX << 4; - int worldY = chunkSection.getYPosition(); - int worldZ = owner.chunkZ << 4; - - // Fill opacity and initial block lighting values - this.opacity = NibbleArrayHandle.createNew(); - this.emittedLight = NibbleArrayHandle.createNew(); - this.opaqueFaces = new BlockFaceSetSection(); - int x, y, z, opacity, blockEmission; - BlockFaceSet opaqueFaces; - BlockData info; - for (z = owner.start.z; z <= owner.end.z; z++) { - for (x = owner.start.x; x <= owner.end.x; x++) { - for (y = 0; y < 16; y++) { - info = chunkSection.getBlockData(x, y, z); - blockEmission = info.getEmission(); - opacity = info.getOpacity(owner.world, worldX + x, worldY + y, worldZ + z); - if (opacity >= 0xf) { - opacity = 0xf; - opaqueFaces = BlockFaceSet.ALL; - } else { - if (opacity < 0) { - opacity = 0; - } - opaqueFaces = info.getOpaqueFaces(owner.world, worldX + x, worldY + y, worldZ + z); - } - - this.opacity.set(x, y, z, opacity); - this.emittedLight.set(x, y, z, blockEmission); - this.blockLight.set(x, y, z, blockEmission); - this.opaqueFaces.set(x, y, z, opaqueFaces); - } - } - } - } - - /** - * Gets the opaque faces of a block - * - * @param x - coordinate - * @param y - coordinate - * @param z - coordinate - * @return opaque face set - */ - public BlockFaceSet getOpaqueFaces(int x, int y, int z) { - return this.opaqueFaces.get(x, y, z); - } - - /** - * Read light level of a neighboring block. - * If possibly more, also check opaque faces, and then return the - * higher light value if all these tests pass. - * The x/y/z coordinates are allowed to check neighboring cubes. - * - * @param x The X-coordinate of the block (-1 to 16) - * @param y The Y-coordinate of the block (-1 to 16) - * @param z The Z-coordinate of the block (-1 to 16) - * @return higher light level if propagated, otherwise the old light value - */ - public int getLightIfHigherNeighbor(LightingCategory category, int old_light, int faceMask, int x, int y, int z) { - if ((x & OOC | y & OOC | z & OOC) == 0) { - return this.getLightIfHigher(category, old_light, faceMask, x, y, z); - } else { - LightingCube neigh = this.neighbors.get(x >> 4, y >> 4, z >> 4); - if (neigh != null) { - return neigh.getLightIfHigher(category, old_light, faceMask, x & 0xf, y & 0xf, z & 0xf); - } else { - return old_light; - } - } - } - - /** - * Read light level of a neighboring block. - * If possibly more, also check opaque faces, and then return the - * higher light value if all these tests pass. - * Requires the x/y/z coordinates to lay within this cube. - * - * @param category Category of light to check - * @param old_light Previous light value - * @param faceMask The BlockFaceSet mask indicating the light-traveling direction - * @param x The X-coordinate of the block (0 to 15) - * @param y The Y-coordinate of the block (0 to 15) - * @param z The Z-coordinate of the block (0 to 15) - * @return higher light level if propagated, otherwise the old light value - */ - public int getLightIfHigher(LightingCategory category, int old_light, int faceMask, int x, int y, int z) { - int new_light_level = category.get(this, x, y, z); - return (new_light_level > old_light && !this.getOpaqueFaces(x, y, z).get(faceMask)) - ? new_light_level : old_light; - } - - /** - * Called during initialization of block light to spread the light emitted by a block - * to all neighboring blocks. - * - * @param x The X-coordinate of the block (0 to 15) - * @param y The Y-coordinate of the block (0 to 15) - * @param z The Z-coordinate of the block (0 to 15) - */ - public void spreadBlockLight(int x, int y, int z) { - int emitted = this.emittedLight.get(x, y, z); - if (emitted <= 1) { - return; // Skip if neighbouring blocks won't receive light from it - } - if (x >= 1 && z >= 1 && x <= 14 && z <= 14) { - trySpreadBlockLightWithin(emitted, BlockFaceSet.MASK_EAST, x - 1, y, z); - trySpreadBlockLightWithin(emitted, BlockFaceSet.MASK_WEST, x + 1, y, z); - trySpreadBlockLightWithin(emitted, BlockFaceSet.MASK_SOUTH, x, y, z - 1); - trySpreadBlockLightWithin(emitted, BlockFaceSet.MASK_NORTH, x, y, z + 1); - } else { - trySpreadBlockLight(emitted, BlockFaceSet.MASK_EAST, x - 1, y, z); - trySpreadBlockLight(emitted, BlockFaceSet.MASK_WEST, x + 1, y, z); - trySpreadBlockLight(emitted, BlockFaceSet.MASK_SOUTH, x, y, z - 1); - trySpreadBlockLight(emitted, BlockFaceSet.MASK_NORTH, x, y, z + 1); - } - if (y >= 1 && y <= 14) { - trySpreadBlockLightWithin(emitted, BlockFaceSet.MASK_UP, x, y - 1, z); - trySpreadBlockLightWithin(emitted, BlockFaceSet.MASK_DOWN, x, y + 1, z); - } else { - trySpreadBlockLight(emitted, BlockFaceSet.MASK_UP, x, y - 1, z); - trySpreadBlockLight(emitted, BlockFaceSet.MASK_DOWN, x, y + 1, z); - } - } - - /** - * Tries to spread block light from an emitting block to one of the 6 sites. - * The block being spread to is allowed to be outside of the bounds of this cube, - * in which case neighboring cubes are spread to instead. - * - * @param emitted The light that is emitted by the block - * @param faceMask The BlockFaceSet mask indicating the light-traveling direction - * @param x The X-coordinate of the block to spread to (-1 to 16) - * @param y The Y-coordinate of the block to spread to (-1 to 16) - * @param z The Z-coordinate of the block to spread to (-1 to 16) - */ - public void trySpreadBlockLight(int emitted, int faceMask, int x, int y, int z) { - if ((x & OOC | y & OOC | z & OOC) == 0) { - this.trySpreadBlockLightWithin(emitted, faceMask, x, y, z); - } else { - LightingCube neigh = this.neighbors.get(x >> 4, y >> 4, z >> 4); - if (neigh != null) { - neigh.trySpreadBlockLightWithin(emitted, faceMask, x & 0xf, y & 0xf, z & 0xf); - } - } - } - - /** - * Tries to spread block light from an emitting block to one of the 6 sides. - * Assumes that the block being spread to is within this cube. - * - * @param emitted The light that is emitted by the block - * @param faceMask The BlockFaceSet mask indicating the light-traveling direction - * @param x The X-coordinate of the block to spread to (0 to 15) - * @param y The Y-coordinate of the block to spread to (0 to 15) - * @param z The Z-coordinate of the block to spread to (0 to 15) - */ - public void trySpreadBlockLightWithin(int emitted, int faceMask, int x, int y, int z) { - if (!this.getOpaqueFaces(x, y, z).get(faceMask)) { - int new_level = emitted - Math.max(1, this.opacity.get(x, y, z)); - if (new_level > this.blockLight.get(x, y, z)) { - this.blockLight.set(x, y, z, new_level); - } - } - } - - /** - * Applies the lighting information to a chunk section - * - * @param chunkSection to save to - * @return future completed when saving is finished. Future resolves to False if no changes occurred, True otherwise. - */ - public CompletableFuture saveToChunk(ChunkSection chunkSection) { - CompletableFuture blockLightFuture = null; - CompletableFuture skyLightFuture = null; - - try { - if (this.blockLight != null) { - byte[] newBlockLight = this.blockLight.getData(); - byte[] oldBlockLight = WorldUtil.getSectionBlockLight(owner.world, - owner.chunkX, this.cy, owner.chunkZ); - boolean blockLightChanged = false; - if (oldBlockLight == null || newBlockLight.length != oldBlockLight.length) { - blockLightChanged = true; - } else { - for (int i = 0; i < oldBlockLight.length; i++) { - if (oldBlockLight[i] != newBlockLight[i]) { - blockLightChanged = true; - break; - } - } - } - - //TODO: Maybe do blockLightChanged check inside BKCommonLib? - if (blockLightChanged) { - blockLightFuture = WorldUtil.setSectionBlockLightAsync(owner.world, - owner.chunkX, this.cy, owner.chunkZ, - newBlockLight); - } - } - if (this.skyLight != null) { - byte[] newSkyLight = this.skyLight.getData(); - byte[] oldSkyLight = WorldUtil.getSectionSkyLight(owner.world, - owner.chunkX, this.cy, owner.chunkZ); - boolean skyLightChanged = false; - if (oldSkyLight == null || newSkyLight.length != oldSkyLight.length) { - skyLightChanged = true; - } else { - for (int i = 0; i < oldSkyLight.length; i++) { - if (oldSkyLight[i] != newSkyLight[i]) { - skyLightChanged = true; - break; - } - } - } - - //TODO: Maybe do skyLightChanged check inside BKCommonLib? - if (skyLightChanged) { - skyLightFuture = WorldUtil.setSectionSkyLightAsync(owner.world, - owner.chunkX, this.cy, owner.chunkZ, - newSkyLight); - } - } - } catch (Throwable t) { - Iris.reportError(t); - CompletableFuture exceptionally = new CompletableFuture<>(); - exceptionally.completeExceptionally(t); - return exceptionally; - } - - // No updates performed - if (blockLightFuture == null && skyLightFuture == null) { - return CompletableFuture.completedFuture(Boolean.FALSE); - } - - // Join both completable futures as one, if needed - CompletableFuture combined; - if (blockLightFuture == null) { - combined = skyLightFuture; - } else if (skyLightFuture == null) { - combined = blockLightFuture; - } else { - combined = CompletableFuture.allOf(blockLightFuture, skyLightFuture); - } - - // When combined resolves, return one that returns True - return combined.thenApply((c) -> Boolean.TRUE); - } - -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingCubeNeighboring.java b/src/main/java/com/volmit/iris/engine/lighting/LightingCubeNeighboring.java deleted file mode 100644 index 02ee0c434..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingCubeNeighboring.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -/** - * Keeps track of the 6 x/y/z neighbors of cubes - */ -public class LightingCubeNeighboring { - public final LightingCube[] values = new LightingCube[6]; - - /** - * Generates a key ranging 0 - 5 for fixed x/y/z combinations
- * - Bit 1 is set to contain whether x/y/z is 1 or -1 - * - Bit 2 is set to 1 when the axis is x
- * - Bit 3 is set to 1 when the axis is z

- *

- * This system requires that the x/y/z pairs are one the following:
- * (0, 0, 1) | (0, 0, -1) | (0, 1, 0) | (0, -1, 0) | (1, 0, 0) | (-1, 0, 0) - * - * @param x value - * @param y value - * @param z value - * @return key - */ - private static final int getIndexByCube(int x, int y, int z) { - return (((x + y + z + 1) & 0x2) >> 1) | ((x & 0x1) << 1) | ((z & 0x1) << 2); - } - - /** - * Gets whether all 6 cube neighbors are accessible - * - * @return True if all neighbors are accessible - */ - public boolean hasAll() { - for (int i = 0; i < 6; i++) { - if (values[i] == null) { - return false; - } - } - return true; - } - - /** - * Gets the neighbor representing the given relative cube - * - * @return neighbor, null if no neighbor is available here - */ - public LightingCube get(int deltaCubeX, int deltaCubeY, int deltaCubeZ) { - return values[getIndexByCube(deltaCubeX, deltaCubeY, deltaCubeZ)]; - } - - /** - * Sets the neighbor representing the given relative cube - * - * @param neighbor to set to, is allowed to be null to set to 'none' - */ - public void set(int deltaCubeX, int deltaCubeY, int deltaCubeZ, LightingCube neighbor) { - values[getIndexByCube(deltaCubeX, deltaCubeY, deltaCubeZ)] = neighbor; - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingForcedChunkCache.java b/src/main/java/com/volmit/iris/engine/lighting/LightingForcedChunkCache.java deleted file mode 100644 index 436ef2fe9..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingForcedChunkCache.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.chunk.ForcedChunk; -import com.bergerkiller.bukkit.common.utils.WorldUtil; -import org.bukkit.World; - -import java.util.HashMap; -import java.util.Map; - -/** - * Shortly remembers the forced chunks it has kept loaded from a previous operation. - * Reduces chunk unloading-loading grind. - */ -public class LightingForcedChunkCache { - private static final Map _cache = new HashMap<>(); - - public static ForcedChunk get(World world, int x, int z) { - ForcedChunk cached; - synchronized (_cache) { - cached = _cache.get(new Key(world, x, z)); - } - if (cached != null) { - return cached.clone(); - } else { - return WorldUtil.forceChunkLoaded(world, x, z); - } - } - - public static void store(ForcedChunk chunk) { - ForcedChunk prev; - synchronized (_cache) { - prev = _cache.put(new Key(chunk.getWorld(), chunk.getX(), chunk.getZ()), chunk.clone()); - } - if (prev != null) { - prev.close(); - } - } - - public static void reset() { - synchronized (_cache) { - for (ForcedChunk chunk : _cache.values()) { - chunk.close(); - } - _cache.clear(); - } - } - - @SuppressWarnings("ClassCanBeRecord") - private static final class Key { - public final World world; - public final int x; - public final int z; - - public Key(World world, int x, int z) { - this.world = world; - this.x = x; - this.z = z; - } - - @Override - public int hashCode() { - return this.x * 31 + this.z; - } - - @Override - public boolean equals(Object o) { - if (o instanceof Key other) { - return other.x == this.x && - other.z == this.z && - other.world == this.world; - } else { - return false; - } - } - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingService.java b/src/main/java/com/volmit/iris/engine/lighting/LightingService.java deleted file mode 100644 index 59efbb11b..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingService.java +++ /dev/null @@ -1,664 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.AsyncTask; -import com.bergerkiller.bukkit.common.bases.IntVector2; -import com.bergerkiller.bukkit.common.bases.IntVector3; -import com.bergerkiller.bukkit.common.permissions.NoPermissionException; -import com.bergerkiller.bukkit.common.utils.MathUtil; -import com.bergerkiller.bukkit.common.utils.WorldUtil; -import com.bergerkiller.bukkit.common.wrappers.LongHashSet; -import com.bergerkiller.bukkit.common.wrappers.LongHashSet.LongIterator; -import com.volmit.iris.Iris; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.util.*; - -@SuppressWarnings("ALL") -public class LightingService extends AsyncTask { - private static final Set recipientsForDone = new HashSet(); - private static final LinkedList tasks = new LinkedList(); - private static final int PENDING_WRITE_INTERVAL = 10; - private static AsyncTask fixThread = null; - private static int taskChunkCount = 0; - private static int taskCounter = 0; - private static boolean pendingFileInUse = false; - private static LightingTask currentTask; - private static boolean paused = false; - private static final boolean lowOnMemory = false; - - /** - * Gets whether this service is currently processing something - * - * @return True if processing, False if not - */ - public static boolean isProcessing() { - return fixThread != null; - } - - /** - * Starts or stops the processing service. - * Stopping the service does not instantly abort, the current task is continued. - * - * @param process to abort - */ - public static void setProcessing(boolean process) { - if (process == isProcessing()) { - return; - } - if (process) { - fixThread = new LightingService().start(true); - } else { - // Fix thread is running, abort - AsyncTask.stop(fixThread); - fixThread = null; - } - } - - /** - * Gets whether execution is paused, and pending tasks are not being processed - * - * @return True if paused - */ - public static boolean isPaused() { - return paused; - } - - /** - * Sets whether execution is paused. - * - * @param pause state to set to - */ - public static void setPaused(boolean pause) { - if (paused != pause) { - paused = pause; - } - } - - /** - * Gets the status of the currently processed task - * - * @return current task status - */ - public static String getCurrentStatus() { - final LightingTask current = currentTask; - if (lowOnMemory) { - return ChatColor.RED + "Too low on available memory (paused)"; - } else if (current == null) { - return "Finished."; - } else { - return current.getStatus(); - } - } - - /** - * Gets the time the currently processing task was started. If no task is being processed, - * an empty result is returned. If processing didn't start yet, the value will be 0. - * - * @return time when the current task was started - */ - public static java.util.OptionalLong getCurrentStartTime() { - final LightingTask current = currentTask; - return (current == null) ? java.util.OptionalLong.empty() : OptionalLong.of(current.getTimeStarted()); - } - - public static void addRecipient(CommandSender sender) { - synchronized (recipientsForDone) { - recipientsForDone.add(new RecipientWhenDone(sender)); - } - } - - public static void scheduleWorld(final World world) { - ScheduleArguments args = new ScheduleArguments(); - args.setWorld(world); - args.setEntireWorld(); - schedule(args); - } - - /** - * Schedules a square chunk area for lighting fixing - * - * @param world the chunks are in - * @param middleX - * @param middleZ - * @param radius - */ - public static void scheduleArea(World world, int middleX, int middleZ, int radius) { - ScheduleArguments args = new ScheduleArguments(); - args.setWorld(world); - args.setChunksAround(middleX, middleZ, radius); - schedule(args); - } - - @Deprecated - public static void schedule(World world, Collection chunks) { - ScheduleArguments args = new ScheduleArguments(); - args.setWorld(world); - args.setChunks(chunks); - schedule(args); - } - - public static void schedule(World world, LongHashSet chunks) { - ScheduleArguments args = new ScheduleArguments(); - args.setWorld(world); - args.setChunks(chunks); - schedule(args); - } - - public static void schedule(ScheduleArguments args) { - // World not allowed to be null - if (args.getWorld() == null) { - throw new IllegalArgumentException("Schedule arguments 'world' is null"); - } - - // If no chunks specified, entire world - if (args.isEntireWorld()) { - LightingTaskWorld task = new LightingTaskWorld(args.getWorld()); - task.applyOptions(args); - schedule(task); - return; - } - - // If less than 34x34 chunks are requested, schedule as one task - // In that case, be sure to only schedule chunks that actually exist - // This prevents generating new chunks as part of this command - LongHashSet chunks = args.getChunks(); - if (chunks.size() <= (34 * 34)) { - - LongHashSet chunks_filtered = new LongHashSet(chunks.size()); - Set region_coords_filtered = new HashSet(); - LongIterator iter = chunks.longIterator(); - - if (args.getLoadedChunksOnly()) { - // Remove coordinates of chunks that aren't loaded - while (iter.hasNext()) { - long chunk = iter.next(); - int cx = MathUtil.longHashMsw(chunk); - int cz = MathUtil.longHashLsw(chunk); - if (WorldUtil.isLoaded(args.getWorld(), cx, cz)) { - chunks_filtered.add(chunk); - region_coords_filtered.add(new IntVector2( - WorldUtil.chunkToRegionIndex(cx), - WorldUtil.chunkToRegionIndex(cz))); - } - } - } else if (true) { - // Remove coordinates of chunks that don't actually exist (avoid generating new chunks) - // isChunkAvailable isn't very fast, but fast enough below this threshold of chunks - // To check for border chunks, we check that all 9 chunks are are available - Map tmp = new HashMap<>(); - while (iter.hasNext()) { - long chunk = iter.next(); - int cx = MathUtil.longHashMsw(chunk); - int cz = MathUtil.longHashLsw(chunk); - - boolean fully_loaded = true; - for (int dx = -2; dx <= 2 && fully_loaded; dx++) { - for (int dz = -2; dz <= 2 && fully_loaded; dz++) { - IntVector2 pos = new IntVector2(cx + dx, cz + dz); - fully_loaded &= tmp.computeIfAbsent(pos, p -> WorldUtil.isChunkAvailable(args.getWorld(), p.x, p.z)).booleanValue(); - } - } - - if (fully_loaded) { - chunks_filtered.add(chunk); - region_coords_filtered.add(new IntVector2( - WorldUtil.chunkToRegionIndex(cx), - WorldUtil.chunkToRegionIndex(cz))); - } - } - } else { - // Remove coordinates of chunks that don't actually exist (avoid generating new chunks) - // isChunkAvailable isn't very fast, but fast enough below this threshold of chunks - while (iter.hasNext()) { - long chunk = iter.next(); - int cx = MathUtil.longHashMsw(chunk); - int cz = MathUtil.longHashLsw(chunk); - if (WorldUtil.isChunkAvailable(args.getWorld(), cx, cz)) { - chunks_filtered.add(chunk); - region_coords_filtered.add(new IntVector2( - WorldUtil.chunkToRegionIndex(cx), - WorldUtil.chunkToRegionIndex(cz))); - } - } - } - - // For all filtered chunk coordinates, compute regions - int[] regionYCoordinates; - { - Set regions = WorldUtil.getWorldRegions3ForXZ(args.getWorld(), region_coords_filtered); - - // Simplify to just the unique Y-coordinates - regionYCoordinates = regions.stream().mapToInt(r -> r.y).sorted().distinct().toArray(); - } - - // Schedule it - if (!chunks_filtered.isEmpty()) { - LightingTaskBatch task = new LightingTaskBatch(args.getWorld(), regionYCoordinates, chunks_filtered); - task.applyOptions(args); - schedule(task); - } - return; - } - - // Too many chunks requested. Separate the operations per region file with small overlap. - FlatRegionInfoMap regions; - if (args.getLoadedChunksOnly()) { - regions = FlatRegionInfoMap.createLoaded(args.getWorld()); - } else { - regions = FlatRegionInfoMap.create(args.getWorld()); - } - - LongIterator iter = chunks.longIterator(); - LongHashSet scheduledRegions = new LongHashSet(); - while (iter.hasNext()) { - long first_chunk = iter.next(); - int first_chunk_x = MathUtil.longHashMsw(first_chunk); - int first_chunk_z = MathUtil.longHashLsw(first_chunk); - FlatRegionInfo region = regions.getRegionAtChunk(first_chunk_x, first_chunk_z); - if (region == null || scheduledRegions.contains(region.rx, region.rz)) { - continue; // Does not exist or already scheduled - } - if (!region.containsChunk(first_chunk_x, first_chunk_z)) { - continue; // Chunk does not exist in world (not generated yet) or isn't loaded (loaded chunks only option) - } - - // Collect all the region Y coordinates used for this region and the neighbouring regions - // This makes sure we find all chunk slices we might need on an infinite height world - int[] region_y_coordinates = regions.getRegionYCoordinatesSelfAndNeighbours(region); - - // Collect all chunks to process for this region. - // This is an union of the 34x34 area of chunks and the region file data set - LongHashSet buffer = new LongHashSet(); - int rdx, rdz; - for (rdx = -1; rdx < 33; rdx++) { - for (rdz = -1; rdz < 33; rdz++) { - int cx = region.cx + rdx; - int cz = region.cz + rdz; - long chunk_key = MathUtil.longHashToLong(cx, cz); - if (!chunks.contains(chunk_key)) { - continue; - } - - if (true) { - // Check the chunk and the surrounding chunks are all present - if (!regions.containsChunkAndNeighbours(cx, cz)) { - continue; - } - } else { - // Only check chunk - if (!regions.containsChunk(cx, cz)) { - continue; - } - } - buffer.add(chunk_key); - } - } - - // Schedule the region - if (!buffer.isEmpty()) { - scheduledRegions.add(region.rx, region.rz); - LightingTaskBatch task = new LightingTaskBatch(args.getWorld(), region_y_coordinates, buffer); - task.applyOptions(args); - schedule(task); - } - } - } - - public static void schedule(LightingTask task) { - synchronized (tasks) { - tasks.offer(task); - taskChunkCount += task.getChunkCount(); - } - setProcessing(true); - } - - /** - * Loads the pending chunk batch operations from a save file. - * If it is there, it will start processing these again. - */ - public static void loadPendingBatches() { - pendingFileInUse = false; - } - - /** - * Saves all pending chunk batch operations to a save file. - * If the server, for whatever reason, crashes, it can restore using this file. - */ - public static void savePendingBatches() { - if (pendingFileInUse) { - return; - } - } - - /** - * Clears all pending tasks, does continue with the current tasks - */ - public static void clearTasks() { - synchronized (tasks) { - tasks.clear(); - } - final LightingTask current = currentTask; - if (current != null) { - current.abort(); - } - synchronized (tasks) { - tasks.clear(); - } - currentTask = null; - taskChunkCount = 0; - LightingForcedChunkCache.reset(); - } - - /** - * Orders this service to abort all tasks, finishing the current task in an orderly fashion. - * This method can only be called from the main Thread. - */ - public static void abort() { - // Finish the current lighting task if available - final LightingTask current = currentTask; - final AsyncTask service = fixThread; - if (service != null && current != null) { - setProcessing(false); - current.abort(); - } - // Clear lighting tasks - synchronized (tasks) { - if (current != null) { - tasks.addFirst(current); - } - if (!tasks.isEmpty()) { - } - savePendingBatches(); - clearTasks(); - } - } - - /** - * Gets the amount of chunks that are still faulty - * - * @return faulty chunk count - */ - public static int getChunkFaults() { - final LightingTask current = currentTask; - return taskChunkCount + (current == null ? 0 : current.getChunkCount()); - } - - @Override - public void run() { - // While paused, do nothing - while (paused) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - Iris.reportError(e); - e.printStackTrace(); - } - synchronized (tasks) { - if (tasks.isEmpty()) { - break; // Stop processing. - } - } - if (fixThread.isStopRequested()) { - return; - } - } - - synchronized (tasks) { - currentTask = tasks.poll(); - } - if (currentTask == null) { - // No more tasks, end this thread - // Messages - final String message = ChatColor.GREEN + "All lighting operations are completed."; - synchronized (recipientsForDone) { - for (RecipientWhenDone recipient : recipientsForDone) { - CommandSender recip = recipient.player_name == null ? - Bukkit.getConsoleSender() : Bukkit.getPlayer(recipient.player_name); - if (recip != null) { - String timeStr = LightingUtil.formatDuration(System.currentTimeMillis() - recipient.timeStarted); - recip.sendMessage(message + ChatColor.WHITE + " (Took " + timeStr + ")"); - } - } - recipientsForDone.clear(); - } - // Stop task and abort - taskCounter = 0; - setProcessing(false); - LightingForcedChunkCache.reset(); - savePendingBatches(); - return; - } else { - // Write to file? - if (taskCounter++ >= PENDING_WRITE_INTERVAL) { - taskCounter = 0; - // Start saving on another thread (IO access is slow...) - new AsyncTask() { - public void run() { - savePendingBatches(); - } - }.start(); - - // Save the world of the current task being processed - - } - // Subtract task from the task count - taskChunkCount -= currentTask.getChunkCount(); - // Process the task - try { - currentTask.process(); - } catch (Throwable t) { - Iris.reportError(t); - t.printStackTrace(); - Iris.error("Failed to process task: " + currentTask.getStatus()); - } - } - } - - private static long calcAvailableMemory(Runtime runtime) { - long max = runtime.maxMemory(); - if (max == Long.MAX_VALUE) { - return Long.MAX_VALUE; - } else { - long used = (runtime.totalMemory() - runtime.freeMemory()); - return (max - used); - } - } - - public static class ScheduleArguments { - private World world; - private String worldName; - private LongHashSet chunks; - private boolean debugMakeCorrupted = false; - private boolean loadedChunksOnly = false; - private int radius = Bukkit.getServer().getViewDistance(); - - public boolean getDebugMakeCorrupted() { - return this.debugMakeCorrupted; - } - - public boolean getLoadedChunksOnly() { - return this.loadedChunksOnly; - } - - public int getRadius() { - return this.radius; - } - - public boolean isEntireWorld() { - return this.chunks == null; - } - - public World getWorld() { - return this.world; - } - - public String getWorldName() { - return this.worldName; - } - - public LongHashSet getChunks() { - return this.chunks; - } - - /** - * Sets the world itself. Automatically updates the world name. - * - * @param world - * @return these arguments - */ - public ScheduleArguments setWorld(World world) { - this.world = world; - this.worldName = world.getName(); - return this; - } - - /** - * Sets the world name to perform operations on. - * If the world by this name does not exist, the world is null. - * - * @param worldName - * @return these arguments - */ - public ScheduleArguments setWorldName(String worldName) { - this.world = Bukkit.getWorld(worldName); - this.worldName = worldName; - return this; - } - - public ScheduleArguments setEntireWorld() { - this.chunks = null; - return this; - } - - public ScheduleArguments setDebugMakeCorrupted(boolean debug) { - this.debugMakeCorrupted = debug; - return this; - } - - public ScheduleArguments setLoadedChunksOnly(boolean loadedChunksOnly) { - this.loadedChunksOnly = loadedChunksOnly; - return this; - } - - public ScheduleArguments setRadius(int radius) { - this.radius = radius; - return this; - } - - public ScheduleArguments setChunksAround(Location location, int radius) { - this.setWorld(location.getWorld()); - return this.setChunksAround(location.getBlockX() >> 4, location.getBlockZ() >> 4, radius); - } - - public ScheduleArguments setChunksAround(int middleX, int middleZ, int radius) { - this.setRadius(radius); - - LongHashSet chunks_hashset = new LongHashSet((2 * radius) * (2 * radius)); - for (int a = -radius; a <= radius; a++) { - for (int b = -radius; b <= radius; b++) { - int cx = middleX + a; - int cz = middleZ + b; - chunks_hashset.add(cx, cz); - } - } - return this.setChunks(chunks_hashset); - } - - /** - * Sets the chunks to a cuboid area of chunks. - * Make sure the minimum chunk coordinates are less or equal to - * the maximum chunk coordinates. - * - * @param minChunkX Minimum chunk x-coordinate (inclusive) - * @param minChunkZ Minimum chunk z-coordinate (inclusive) - * @param maxChunkX Maximum chunk x-coordinate (inclusive) - * @param maxChunkZ Maximum chunk z-coordinate (inclusive) - * @return this - */ - public ScheduleArguments setChunkFromTo(int minChunkX, int minChunkZ, int maxChunkX, int maxChunkZ) { - int num_dx = (maxChunkX - minChunkX) + 1; - int num_dz = (maxChunkZ - minChunkZ) + 1; - if (num_dx <= 0 || num_dz <= 0) { - return this.setChunks(new LongHashSet()); // nothing - } - - LongHashSet chunks_hashset = new LongHashSet(num_dx * num_dz); - for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { - for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { - chunks_hashset.add(chunkX, chunkZ); - } - } - return this.setChunks(chunks_hashset); - } - - public ScheduleArguments setChunks(Collection chunks) { - LongHashSet chunks_hashset = new LongHashSet(chunks.size()); - for (IntVector2 coord : chunks) { - chunks_hashset.add(coord.x, coord.z); - } - return this.setChunks(chunks_hashset); - } - - public ScheduleArguments setChunks(LongHashSet chunks) { - this.chunks = chunks; - return this; - } - - private boolean checkRadiusPermission(CommandSender sender, int radius) throws NoPermissionException { - return false; - } - - /** - * Parses the arguments specified in a command - * - * @param sender - * @return false if the input is incorrect and operations may not proceed - * @throws NoPermissionException - */ - public boolean handleCommandInput(CommandSender sender, String[] args) throws NoPermissionException { - return true; - } - - /** - * Creates a new ScheduleArguments instance ready to be configured - * - * @return args - */ - public static ScheduleArguments create() { - return new ScheduleArguments(); - } - } - - private static class RecipientWhenDone { - public final String player_name; - public final long timeStarted; - - public RecipientWhenDone(CommandSender sender) { - this.player_name = (sender instanceof Player) ? sender.getName() : null; - this.timeStarted = System.currentTimeMillis(); - } - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingTask.java b/src/main/java/com/volmit/iris/engine/lighting/LightingTask.java deleted file mode 100644 index 9f66ab324..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingTask.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import org.bukkit.World; - -/** - * A single task the Lighting Service can handle - */ -public interface LightingTask { - /** - * Gets the world this task is working on - * - * @return task world - */ - World getWorld(); - - /** - * Gets the amount of chunks this task is going to fix. - * This can be a wild estimate. While processing this amount should be - * updated as well. - * - * @return estimated total chunk count - */ - int getChunkCount(); - - /** - * Gets a descriptive status of the current task being processed - * - * @return status - */ - String getStatus(); - - /** - * Gets the timestamp (milliseconds since epoch) when this task was first started. - * If 0 is returned, then the task wasn't started yet. - * - * @return time this task was started - */ - long getTimeStarted(); - - /** - * Processes this task (called from another thread!) - */ - void process(); - - /** - * Orders this task to abort - */ - void abort(); - - /** - * Whether this task can be saved to PendingLight.dat - * - * @return True if it can be saved - */ - boolean canSave(); - - /** - * Loads additional options - */ - void applyOptions(LightingService.ScheduleArguments args); -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingTaskBatch.java b/src/main/java/com/volmit/iris/engine/lighting/LightingTaskBatch.java deleted file mode 100644 index 0d4e171d9..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingTaskBatch.java +++ /dev/null @@ -1,584 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.bases.IntVector2; -import com.bergerkiller.bukkit.common.utils.CommonUtil; -import com.bergerkiller.bukkit.common.utils.LogicUtil; -import com.bergerkiller.bukkit.common.utils.MathUtil; -import com.bergerkiller.bukkit.common.utils.WorldUtil; -import com.bergerkiller.bukkit.common.wrappers.LongHashSet; -import com.volmit.iris.Iris; -import com.volmit.iris.core.IrisSettings; -import org.bukkit.Chunk; -import org.bukkit.World; - -import java.util.Arrays; -import java.util.Comparator; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Stream; - -/** - * Contains all the chunk coordinates that have to be fixed, - * and handles the full process of this fixing. - * It is literally a batch of chunks being processed. - */ -public class LightingTaskBatch implements LightingTask { - private static final boolean DEBUG_LOG = false; // logs performance stats - public final World world; - private final Object chunks_lock = new Object(); - private final int[] region_y_coords; - private volatile LightingChunk[] chunks = null; - private volatile long[] chunks_coords; - private boolean done = false; - private boolean aborted = false; - private volatile long timeStarted = 0; - private int numBeingLoaded = 0; - private volatile Stage stage = Stage.LOADING; - private LightingService.ScheduleArguments options = new LightingService.ScheduleArguments(); - - public LightingTaskBatch(World world, int[] regionYCoordinates, long[] chunkCoordinates) { - this.world = world; - this.region_y_coords = regionYCoordinates; - this.chunks_coords = chunkCoordinates; - } - - public LightingTaskBatch(World world, int[] regionYCoordinates, LongHashSet chunkCoordinates) { - this.world = world; - this.region_y_coords = regionYCoordinates; - - // Turn contents of the long hash set into an easily sortable IntVector2[] array - IntVector2[] coordinates = new IntVector2[chunkCoordinates.size()]; - { - LongHashSet.LongIterator iter = chunkCoordinates.longIterator(); - for (int i = 0; iter.hasNext(); i++) { - long coord = iter.next(); - coordinates[i] = new IntVector2(MathUtil.longHashMsw(coord), MathUtil.longHashLsw(coord)); - } - } - - // Sort the array along the axis. This makes chunk loading more efficient. - Arrays.sort(coordinates, Comparator.comparingInt((IntVector2 a) -> a.x).thenComparingInt(a -> a.z)); - - // Turn back into a long[] array for memory efficiency - this.chunks_coords = Stream.of(coordinates).mapToLong(c -> MathUtil.longHashToLong(c.x, c.z)).toArray(); - } - - @Override - public World getWorld() { - return world; - } - - /** - * Gets the X and Z-coordinates of all the chunk columns to process. - * The coordinates are combined into a single Long, which can be decoded - * using {@link MathUtil#longHashMsw(long)} for X and {@link MathUtil#longHashLsw(long) for Z. - * - * @return chunk coordinates - */ - public long[] getChunks() { - synchronized (this.chunks_lock) { - LightingChunk[] chunks = this.chunks; - if (chunks != null) { - long[] coords = new long[chunks.length]; - for (int i = 0; i < chunks.length; i++) { - coords[i] = MathUtil.longHashToLong(chunks[i].chunkX, chunks[i].chunkZ); - } - return coords; - } else //noinspection ReplaceNullCheck - if (this.chunks_coords != null) { - return this.chunks_coords; - } else { - return new long[0]; - } - } - } - - /** - * Gets the Y-coordinates of all the regions to look for chunk data. A region stores 32 chunk - * slices vertically, and goes up/down 512 blocks every coordinate increase/decrease. - * - * @return region Y-coordinates - */ - public int[] getRegionYCoordinates() { - return this.region_y_coords; - } - - @Override - public int getChunkCount() { - synchronized (this.chunks_lock) { - if (this.chunks == null) { - return this.done ? 0 : this.chunks_coords.length; - } else { - int faults = 0; - for (LightingChunk chunk : this.chunks) { - if (chunk.hasFaults()) { - faults++; - } - } - return faults; - } - } - } - - @Override - public long getTimeStarted() { - return this.timeStarted; - } - - @SuppressWarnings("ClassCanBeRecord") - private static final class BatchChunkInfo { - public final int cx; - public final int cz; - public final int count; - - public BatchChunkInfo(int cx, int cz, int count) { - this.cx = cx; - this.cz = cz; - this.count = count; - } - } - - public BatchChunkInfo getAverageChunk() { - int count = 0; - long cx = 0; - long cz = 0; - synchronized (this.chunks_lock) { - if (this.chunks != null) { - count = this.chunks.length; - for (LightingChunk chunk : this.chunks) { - cx += chunk.chunkX; - cz += chunk.chunkZ; - } - } else if (this.chunks_coords != null) { - count = this.chunks_coords.length; - for (long chunk : this.chunks_coords) { - cx += MathUtil.longHashMsw(chunk); - cz += MathUtil.longHashLsw(chunk); - } - } else { - return null; - } - } - if (count > 0) { - cx /= count; - cz /= count; - } - return new BatchChunkInfo((int) cx, (int) cz, count); - } - - @Override - public String getStatus() { - BatchChunkInfo chunk = this.getAverageChunk(); - if (chunk != null) { - String postfix = " chunks near " + - "x=" + (chunk.cx * 16) + " z=" + (chunk.cz * 16); - if (this.stage == Stage.LOADING) { - synchronized (this.chunks_lock) { - if (this.chunks != null) { - int num_loaded = 0; - for (LightingChunk lc : this.chunks) { - if (!lc.forcedChunk.isNone() && lc.forcedChunk.getChunkAsync().isDone()) { - num_loaded++; - } - } - return "Loaded " + num_loaded + "/" + chunk.count + postfix; - } - } - } else if (this.stage == Stage.APPLYING) { - synchronized (this.chunks_lock) { - if (this.chunks != null) { - int num_saved = 0; - for (LightingChunk lc : this.chunks) { - if (lc.isApplied) { - num_saved++; - } - } - return "Saved " + num_saved + "/" + chunk.count + postfix; - } - } - } - - return "Cleaning " + chunk.count + postfix; - } else { - return done ? "Done" : "No Data"; - } - } - - private String getShortStatus() { - BatchChunkInfo chunk = this.getAverageChunk(); - if (chunk != null) { - return "[x=" + (chunk.cx * 16) + " z=" + (chunk.cz * 16) + " count=" + chunk.count + "]"; - } else { - return "[Unknown]"; - } - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean waitForCheckAborted(CompletableFuture future) { - while (!aborted) { - try { - future.get(200, TimeUnit.MILLISECONDS); - return true; - } catch (InterruptedException | TimeoutException e1) { - Iris.reportError(e1); - } catch (ExecutionException ex) { - Iris.reportError(ex); - ex.printStackTrace(); - Iris.error("Error while processing"); - return false; - } - } - return false; - } - - private void tryLoadMoreChunks(final CompletableFuture[] chunkFutures) { - if (this.aborted) { - return; - } - - int i = 0; - while (true) { - // While synchronized, pick the next chunk to load - LightingChunk nextChunk = null; - CompletableFuture nextChunkFuture = null; - synchronized (chunks_lock) { - for (; i < chunks.length && numBeingLoaded < IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getEngineThreadCount()); i++) { - LightingChunk lc = chunks[i]; - if (lc.loadingStarted) { - continue; // Already (being) loaded - } - - // Pick it - numBeingLoaded++; - lc.loadingStarted = true; - nextChunk = lc; - nextChunkFuture = chunkFutures[i]; - break; - } - } - - // No more chunks to load / capacity reached - if (nextChunk == null) { - break; - } - - // This shouldn't happen, but just in case, a check - if (nextChunkFuture.isDone()) { - continue; - } - - // Outside of the lock, start loading the next chunk - final CompletableFuture f_nextChunkFuture = nextChunkFuture; - nextChunk.forcedChunk.move(LightingForcedChunkCache.get(world, nextChunk.chunkX, nextChunk.chunkZ)); - nextChunk.forcedChunk.getChunkAsync().whenComplete((chunk, t) -> { - synchronized (chunks_lock) { - numBeingLoaded--; - } - - f_nextChunkFuture.complete(null); - tryLoadMoreChunks(chunkFutures); - }); - } - } - - @SuppressWarnings("unchecked") - private CompletableFuture loadChunks() { - // For every LightingChunk, make a completable future - // Once all these futures are resolved the returned completable future resolves - CompletableFuture[] chunkFutures; - synchronized (this.chunks_lock) { - chunkFutures = new CompletableFuture[this.chunks.length]; - } - for (int i = 0; i < chunkFutures.length; i++) { - chunkFutures[i] = new CompletableFuture<>(); - } - - // Start loading up to [asyncLoadConcurrency] number of chunks right now - // When a callback for a chunk load completes, we start loading additional chunks - tryLoadMoreChunks(chunkFutures); - - return CompletableFuture.allOf(chunkFutures); - } - - @Override - public void process() { - // Begin - this.stage = Stage.LOADING; - this.timeStarted = System.currentTimeMillis(); - - // Initialize lighting chunks - synchronized (this.chunks_lock) { - LightingChunk[] chunks_new = new LightingChunk[this.chunks_coords.length]; - this.done = false; - int chunkIdx = 0; - for (long longCoord : this.chunks_coords) { - int x = MathUtil.longHashMsw(longCoord); - int z = MathUtil.longHashLsw(longCoord); - chunks_new[chunkIdx++] = new LightingChunk(this.world, x, z); - if (this.aborted) { - return; - } - } - - // Update fields. We can remove the coordinates to free memory. - this.chunks = chunks_new; - this.chunks_coords = null; - } - - // Check aborted - if (aborted) { - return; - } - - // Load all the chunks. Wait for loading to finish. - // Regularly check that this task is not aborted - CompletableFuture loadChunksFuture = this.loadChunks(); - if (!waitForCheckAborted(loadChunksFuture)) { - return; - } - - // Causes all chunks in cache not used for this task to unload - // All chunks of this task are put into the cache, instead - LightingForcedChunkCache.reset(); - for (LightingChunk lc : LightingTaskBatch.this.chunks) { - LightingForcedChunkCache.store(lc.forcedChunk); - } - - // All chunks that can be loaded, are now loaded. - // Some chunks may have failed to be loaded, get rid of those now! - // To avoid massive spam, only show the average x/z coordinates of the chunk affected - synchronized (this.chunks_lock) { - long failed_chunk_avg_x = 0; - long failed_chunk_avg_z = 0; - int failed_chunk_count = 0; - - LightingChunk[] new_chunks = this.chunks; - for (int i = new_chunks.length - 1; i >= 0; i--) { - LightingChunk lc = new_chunks[i]; - if (lc.forcedChunk.getChunkAsync().isCompletedExceptionally()) { - failed_chunk_avg_x += lc.chunkX; - failed_chunk_avg_z += lc.chunkZ; - failed_chunk_count++; - new_chunks = LogicUtil.removeArrayElement(new_chunks, i); - } - } - this.chunks = new_chunks; - - // Tell all the (remaining) chunks about other neighbouring chunks before initialization - for (LightingChunk lc : new_chunks) { - for (LightingChunk neigh : new_chunks) { - lc.notifyAccessible(neigh); - } - } - - // Log when chunks fail to be loaded - if (failed_chunk_count > 0) { - failed_chunk_avg_x = ((failed_chunk_avg_x / failed_chunk_count) << 4); - failed_chunk_avg_z = ((failed_chunk_avg_z / failed_chunk_count) << 4); - Iris.error("Failed to load " + failed_chunk_count + " chunks near " + - "world=" + world.getName() + " x=" + failed_chunk_avg_x + " z=" + failed_chunk_avg_z); - } - } - - // Schedule, on the main thread, to fill all the loaded chunks with data - CompletableFuture chunkFillFuture = CompletableFuture.runAsync(() -> { - synchronized (this.chunks_lock) { - for (LightingChunk lc : chunks) { - lc.fill(lc.forcedChunk.getChunk(), region_y_coords); - } - } - }, CommonUtil.getPluginExecutor(Iris.instance)); - - if (!waitForCheckAborted(chunkFillFuture)) { - return; - } - - // Now that all chunks we can process are filled, let all the 16x16x16 cubes know of their neighbors - // This neighboring data is only used during the fix() (initialize + spread) phase - synchronized (this.chunks_lock) { - for (LightingChunk lc : chunks) { - lc.detectCubeNeighbors(); - } - } - - // Fix - this.stage = Stage.FIXING; - fix(); - if (this.aborted) { - return; - } - - // Apply and wait for it to be finished - // Wait in 200ms intervals to allow for aborting - // After 2 minutes of inactivity, stop waiting and consider applying failed - this.stage = Stage.APPLYING; - try { - CompletableFuture future = apply(); - int max_num_of_waits = (5 * 120); - while (true) { - if (--max_num_of_waits == 0) { - Iris.error("Failed to apply lighting data for " + getShortStatus() + ": Timeout"); - break; - } - try { - future.get(200, TimeUnit.MILLISECONDS); - break; - } catch (TimeoutException e) { - Iris.reportError(e); - if (this.aborted) { - return; - } - } - } - } catch (InterruptedException e) { - Iris.reportError(e); - // Ignore - } catch (ExecutionException e) { - Iris.reportError(e); - e.printStackTrace(); - Iris.error("Failed to apply lighting data for " + getShortStatus()); - - } - - this.done = true; - synchronized (this.chunks_lock) { - this.chunks = null; - } - } - - @Override - public void abort() { - this.aborted = true; - - // Close chunks kept loaded - LightingChunk[] chunks; - synchronized (this.chunks_lock) { - chunks = this.chunks; - } - if (chunks != null) { - for (LightingChunk lc : chunks) { - lc.forcedChunk.close(); - } - } - } - - /** - * Starts applying the new data to the world. - * This is done in several ticks on the main thread. - * The completable future is resolved when applying is finished. - */ - public CompletableFuture apply() { - // Apply data to chunks and unload if needed - LightingChunk[] chunks = LightingTaskBatch.this.chunks; - CompletableFuture[] applyFutures = new CompletableFuture[chunks.length]; - for (int i = 0; i < chunks.length; i++) { - LightingChunk lc = chunks[i]; - Chunk bchunk = lc.forcedChunk.getChunk(); - - // Save to chunk - applyFutures[i] = lc.saveToChunk(bchunk).whenComplete((changed, t) -> { - if (t != null) { - t.printStackTrace(); - } else if (changed) { - WorldUtil.queueChunkSendLight(world, lc.chunkX, lc.chunkZ); - } - - // Closes our forced chunk, may cause the chunk to now unload - lc.forcedChunk.close(); - }); - } - return CompletableFuture.allOf(applyFutures); - } - - /** - * Performs the (slow) fixing procedure (call from another thread) - */ - public void fix() { - // Initialize light - for (LightingCategory category : LightingCategory.values()) { - for (LightingChunk chunk : chunks) { - category.initialize(chunk); - if (this.aborted) { - return; - } - } - } - - // Skip spread phase when debug mode is active - if (this.options.getDebugMakeCorrupted()) { - return; - } - - // Before spreading, change the opacity values to have a minimum of 1 - // Spreading can never be done without losing light - // This isn't done during initialization because it is important - // for calculating the first opacity>0 block for sky light. - for (LightingChunk chunk : chunks) { - for (LightingCube section : chunk.getSections()) { - //TODO: Maybe build something into BKCommonLib for this - int x, y, z; - for (y = 0; y < 16; y++) { - for (z = 0; z < 16; z++) { - for (x = 0; x < 16; x++) { - if (section.opacity.get(x, y, z) == 0) { - section.opacity.set(x, y, z, 1); - } - } - } - } - } - } - - // Spread (timed, for debug) - boolean hasFaults; - long startTime = System.currentTimeMillis(); - int totalLoops = 0; - do { - hasFaults = false; - for (LightingChunk chunk : chunks) { - int count = chunk.spread(); - totalLoops += count; - hasFaults |= count > 0; - } - } while (hasFaults && !this.aborted); - - long duration = System.currentTimeMillis() - startTime; - if (DEBUG_LOG) { - System.out.println("Processed " + totalLoops + " in " + duration + " ms"); - } - } - - @Override - public void applyOptions(LightingService.ScheduleArguments args) { - this.options = args; - } - - @Override - public boolean canSave() { - return !this.options.getLoadedChunksOnly() && !this.options.getDebugMakeCorrupted(); - } - - private enum Stage { - LOADING, FIXING, APPLYING - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingTaskWorld.java b/src/main/java/com/volmit/iris/engine/lighting/LightingTaskWorld.java deleted file mode 100644 index 7cfefa20b..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingTaskWorld.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.utils.CommonUtil; -import com.bergerkiller.bukkit.common.wrappers.LongHashSet; -import com.volmit.iris.Iris; -import org.bukkit.World; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class LightingTaskWorld implements LightingTask { - private static final int ASSUMED_CHUNKS_PER_REGION = 34 * 34; - private final World world; - private volatile FlatRegionInfoMap regions = null; - private volatile int regionCountLoaded; - private volatile int chunkCount; - private volatile long timeStarted; - private volatile boolean aborted; - private LightingService.ScheduleArguments options = new LightingService.ScheduleArguments(); - - public LightingTaskWorld(World world) { - this.world = world; - this.regionCountLoaded = 0; - this.aborted = false; - this.chunkCount = 0; - this.timeStarted = 0; - } - - @Override - public World getWorld() { - return this.world; - } - - @Override - public int getChunkCount() { - return chunkCount; - } - - @Override - public long getTimeStarted() { - return this.timeStarted; - } - - @Override - public String getStatus() { - if (regions == null) { - return "Reading available regions from world " + getWorld().getName(); - } else { - return "Reading available chunks from world " + getWorld().getName() + " (region " + (regionCountLoaded + 1) + "/" + regions.getRegionCount() + ")"; - } - } - - @SuppressWarnings("NonAtomicOperationOnVolatileField") - @Override - public void process() { - // Load regions on the main thread - // TODO: Can use main thread executor instead - this.timeStarted = System.currentTimeMillis(); - final CompletableFuture regionsLoadedFuture = new CompletableFuture<>(); - CommonUtil.nextTick(() -> { - try { - if (this.options.getLoadedChunksOnly()) { - this.regions = FlatRegionInfoMap.createLoaded(this.getWorld()); - this.regionCountLoaded = this.regions.getRegionCount(); - this.chunkCount = 0; - for (FlatRegionInfo region : this.regions.getRegions()) { - this.chunkCount += region.getChunkCount(); - } - } else { - this.regions = FlatRegionInfoMap.create(this.getWorld()); - this.regionCountLoaded = 0; - this.chunkCount = this.regions.getRegionCount() * ASSUMED_CHUNKS_PER_REGION; - } - regionsLoadedFuture.complete(null); - } catch (Throwable ex) { - Iris.reportError(ex); - regionsLoadedFuture.completeExceptionally(ex); - } - }); - - // Wait until region list is loaded synchronously - try { - regionsLoadedFuture.get(); - } catch (InterruptedException ex) { - Iris.reportError(ex); - // Ignore - } catch (ExecutionException ex) { - Iris.reportError(ex); - throw new RuntimeException("Failed to load regions", ex.getCause()); - } - - // Check aborted - if (this.aborted) { - return; - } - - // Start loading all chunks contained in the regions - if (!this.options.getLoadedChunksOnly()) { - for (FlatRegionInfo region : this.regions.getRegions()) { - // Abort handling - if (this.aborted) { - return; - } - - // Load and update stats - region.load(); - this.chunkCount -= ASSUMED_CHUNKS_PER_REGION - region.getChunkCount(); - this.regionCountLoaded++; - } - } - - // We now know of all the regions to be processed, convert all of them into tasks - // Use a slightly larger area to avoid cross-region errors - for (FlatRegionInfo region : regions.getRegions()) { - // Abort handling - if (this.aborted) { - return; - } - - // If empty, skip - if (region.getChunkCount() == 0) { - continue; - } - - // Find region Y-coordinates for this 34x34 section of chunks - int[] region_y_coordinates = regions.getRegionYCoordinatesSelfAndNeighbours(region); - - // Reduce count, schedule and clear the buffer - // Put the coordinates that are available - final LongHashSet buffer = new LongHashSet(34 * 34); - if (true) { - int dx, dz; - for (dx = -1; dx < 33; dx++) { - for (dz = -1; dz < 33; dz++) { - int cx = region.cx + dx; - int cz = region.cz + dz; - if (this.regions.containsChunkAndNeighbours(cx, cz)) { - buffer.add(cx, cz); - } - } - } - } else { - int dx, dz; - for (dx = -1; dx < 33; dx++) { - for (dz = -1; dz < 33; dz++) { - int cx = region.cx + dx; - int cz = region.cz + dz; - if (this.regions.containsChunk(cx, cz)) { - buffer.add(cx, cz); - } - } - } - } - - // Schedule and return amount of chunks - this.chunkCount -= buffer.size(); - LightingTaskBatch batch_task = new LightingTaskBatch(this.getWorld(), region_y_coordinates, buffer); - batch_task.applyOptions(this.options); - LightingService.schedule(batch_task); - } - } - - @Override - public void abort() { - this.aborted = true; - } - - @Override - public void applyOptions(LightingService.ScheduleArguments args) { - this.options = args; - } - - @Override - public boolean canSave() { - return false; - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/LightingUtil.java b/src/main/java/com/volmit/iris/engine/lighting/LightingUtil.java deleted file mode 100644 index c1d854e71..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/LightingUtil.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import com.bergerkiller.bukkit.common.utils.MathUtil; - -/** - * Just some utilities used by Light Cleaner - */ -public class LightingUtil { - private static final TimeDurationFormat timeFormat_hh_mm = new TimeDurationFormat("HH 'hours' mm 'minutes'"); - private static final TimeDurationFormat timeFormat_mm_ss = new TimeDurationFormat("mm 'minutes' ss 'seconds'"); - - private static final long SECOND_MILLIS = 1000L; - private static final long MINUTE_MILLIS = 60L * SECOND_MILLIS; - private static final long HOUR_MILLIS = 60L * MINUTE_MILLIS; - private static final long DAY_MILLIS = 24L * HOUR_MILLIS; - - public static String formatDuration(long duration) { - if (duration < MINUTE_MILLIS) { - return MathUtil.round((double) duration / (double) SECOND_MILLIS, 2) + " seconds"; - } else if (duration < HOUR_MILLIS) { - return timeFormat_mm_ss.format(duration); - } else if (duration < (2 * DAY_MILLIS)) { - return timeFormat_hh_mm.format(duration); - } else { - long num_days = duration / DAY_MILLIS; - long num_hours = (duration % DAY_MILLIS) / HOUR_MILLIS; - return num_days + " days " + num_hours + " hours"; - } - } -} diff --git a/src/main/java/com/volmit/iris/engine/lighting/TimeDurationFormat.java b/src/main/java/com/volmit/iris/engine/lighting/TimeDurationFormat.java deleted file mode 100644 index 4df8f23d3..000000000 --- a/src/main/java/com/volmit/iris/engine/lighting/TimeDurationFormat.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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.engine.lighting; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; - -/** - * Formatter for a duration String. - * Can represent a duration in milliseconds as a String. - * Taken from Traincarts (permission granted by same author)
- *
- * https://github.com/bergerhealer/TrainCarts/blob/master/src/main/java/com/bergerkiller/bukkit/tc/utils/TimeDurationFormat.java - */ -public class TimeDurationFormat { - private final TimeZone timeZone; - private final SimpleDateFormat sdf; - - /** - * Creates a new time duration format. The format accepts the same formatting - * tokens as the Date formatter does. - * - * @throws IllegalArgumentException if the input format is invalid - */ - public TimeDurationFormat(String format) { - if (format == null) { - throw new IllegalArgumentException("Input format should not be null"); - } - this.timeZone = TimeZone.getTimeZone("GMT+0"); - this.sdf = new SimpleDateFormat(format, Locale.getDefault()); - this.sdf.setTimeZone(this.timeZone); - } - - /** - * Formats the duration - * - * @return formatted string - */ - public String format(long durationMillis) { - return this.sdf.format(new Date(durationMillis - this.timeZone.getRawOffset())); - } -}