From ff728478c6984b1f639ea335b5360e553aba4e9e Mon Sep 17 00:00:00 2001 From: Hannes Greule Date: Thu, 13 May 2021 17:29:11 +0200 Subject: [PATCH] Relight using starlight engine on Tuinity & perform heightmap updates (#1023) * Relight using starlight engine on Tuinity * Make use of invokeExact * Cache MethodHandle * Address some requested changes * Remove random * Co-authored-by: NotMyFault * Simplify and clean up sendChunk Hopefully, that doesn't cause any issues * Add naive HeightmapProcessor * Make HeightmapProcessor more efficient * Remove heightmap code from NMSRelighter * Recognize fluid for waterlogged blocks * Remove config option for heightmaps as they should always be updated * Batch relighting for Starlight * Dirty workaround for CharBlocks blocks NPE * Revert "Dirty workaround for CharBlocks blocks NPE" This reverts commit 737606a7 It only caused the heightmap to be wrong again and didn't help much with the original issue * Adapt better chunk sending on older versions * Adapt requested changes for HeightMapType * Relight all changed chunks, batched Also, address some requested changes * Avoid deadlocks * Clean up tuinity relighter and add some comments * Minor changes to HeightmapProcessor Co-authored-by: BuildTools Co-authored-by: NotMyFault Co-authored-by: Aurora <21148213+aurorasmiles@users.noreply.github.com> --- .../fawe/bukkit/NMSRelighterFactory.java | 19 ++ .../mc1_15_2/BukkitAdapter_1_15_2.java | 46 ++-- .../mc1_16_1/BukkitAdapter_1_16_1.java | 50 ++-- .../mc1_16_2/BukkitAdapter_1_16_2.java | 66 ++--- .../mc1_16_5/BukkitAdapter_1_16_5.java | 65 ++--- .../TuinityRelighterFactory_1_16_5.java | 21 ++ .../mc1_16_5/TuinityRelighter_1_16_5.java | 228 ++++++++++++++++++ .../bukkit/BukkitServerInterface.java | 24 ++ .../com/sk89q/worldedit/cli/CLIPlatform.java | 8 + .../main/java/com/boydti/fawe/FaweAPI.java | 41 ++-- .../lighting/HeightMapType.java | 65 ++++- .../implementation/lighting/NMSRelighter.java | 114 ++------- .../lighting/NullRelighter.java | 5 + .../implementation/lighting/Relighter.java | 2 +- .../lighting/RelighterFactory.java | 29 +++ .../processors/HeightmapProcessor.java | 103 ++++++++ .../java/com/boydti/fawe/config/Settings.java | 2 - .../boydti/fawe/util/EditSessionBuilder.java | 13 +- .../extension/platform/Platform.java | 11 + .../world/block/BlockCategories.java | 4 +- 20 files changed, 653 insertions(+), 263 deletions(-) create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/NMSRelighterFactory.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/TuinityRelighterFactory_1_16_5.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/TuinityRelighter_1_16_5.java create mode 100644 worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/RelighterFactory.java create mode 100644 worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/processors/HeightmapProcessor.java diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/NMSRelighterFactory.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/NMSRelighterFactory.java new file mode 100644 index 000000000..52d2383e4 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/NMSRelighterFactory.java @@ -0,0 +1,19 @@ +package com.boydti.fawe.bukkit; + +import com.boydti.fawe.beta.IQueueChunk; +import com.boydti.fawe.beta.IQueueExtent; +import com.boydti.fawe.beta.implementation.lighting.NMSRelighter; +import com.boydti.fawe.beta.implementation.lighting.Relighter; +import com.boydti.fawe.beta.implementation.lighting.RelighterFactory; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.RelightMode; +import com.sk89q.worldedit.world.World; +import org.jetbrains.annotations.NotNull; + +public class NMSRelighterFactory implements RelighterFactory { + @Override + public @NotNull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent queue) { + return new NMSRelighter(queue, + relightMode != null ? relightMode : RelightMode.valueOf(Settings.IMP.LIGHTING.MODE)); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java index ea5fea77b..a32a24cf2 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java @@ -9,6 +9,7 @@ import com.boydti.fawe.object.collection.BitArray; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.ReflectionUtils; import com.boydti.fawe.util.TaskManager; +import com.mojang.datafixers.util.Either; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; @@ -29,6 +30,7 @@ import net.minecraft.server.v1_15_R1.IBlockData; import net.minecraft.server.v1_15_R1.LightEngineStorage; import net.minecraft.server.v1_15_R1.NibbleArray; import net.minecraft.server.v1_15_R1.PacketPlayOutLightUpdate; +import net.minecraft.server.v1_15_R1.PacketPlayOutMapChunk; import net.minecraft.server.v1_15_R1.PlayerChunk; import net.minecraft.server.v1_15_R1.PlayerChunkMap; import net.minecraft.server.v1_15_R1.World; @@ -44,6 +46,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; @@ -189,32 +192,23 @@ public final class BukkitAdapter_1_15_2 extends NMSAdapter { if (playerChunk == null) { return; } - if (playerChunk.hasBeenLoaded()) { - try { - int dirtyBits = fieldDirtyBits.getInt(playerChunk); - if (dirtyBits == 0) { - nmsWorld.getChunkProvider().playerChunkMap.a(playerChunk); - } - if (mask == 0) { - dirtyBits = 65535; - } else { - dirtyBits |= mask; - } - - fieldDirtyBits.set(playerChunk, dirtyBits); - fieldDirtyCount.set(playerChunk, 64); - - if (lighting) { - ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(chunkX, chunkZ); - PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine()); - playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { - p.playerConnection.sendPacket(packet); - }); - } - - } catch (IllegalAccessException e) { - e.printStackTrace(); - } + ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(chunkX, chunkZ); + Optional optional = ((Either) playerChunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + Chunk chunk = optional.orElseGet(() -> + nmsWorld.getChunkProvider().getChunkAtIfLoadedImmediately(chunkX, chunkZ)); + if (chunk == null) { + return; + } + PacketPlayOutMapChunk chunkPacket = new PacketPlayOutMapChunk(chunk, 65535); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(chunkPacket); + }); + if (lighting) { + PacketPlayOutLightUpdate packet = + new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine()); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(packet); + }); } } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java index d00432cb7..06b81d549 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java @@ -9,6 +9,7 @@ import com.boydti.fawe.object.collection.BitArrayUnstretched; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.ReflectionUtils; import com.boydti.fawe.util.TaskManager; +import com.mojang.datafixers.util.Either; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; @@ -27,6 +28,7 @@ import net.minecraft.server.v1_16_R1.DataPaletteLinear; import net.minecraft.server.v1_16_R1.GameProfileSerializer; import net.minecraft.server.v1_16_R1.IBlockData; import net.minecraft.server.v1_16_R1.PacketPlayOutLightUpdate; +import net.minecraft.server.v1_16_R1.PacketPlayOutMapChunk; import net.minecraft.server.v1_16_R1.PlayerChunk; import net.minecraft.server.v1_16_R1.PlayerChunkMap; import net.minecraft.server.v1_16_R1.World; @@ -42,6 +44,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; @@ -186,33 +189,26 @@ public final class BukkitAdapter_1_16_1 extends NMSAdapter { if (playerChunk == null) { return; } - if (playerChunk.hasBeenLoaded()) { - try { - int dirtyBits = fieldDirtyBits.getInt(playerChunk); - if (dirtyBits == 0) { - nmsWorld.getChunkProvider().playerChunkMap.a(playerChunk); - } - if (mask == 0) { - dirtyBits = 65535; - } else { - dirtyBits |= mask; - } - - fieldDirtyBits.set(playerChunk, dirtyBits); - fieldDirtyCount.set(playerChunk, 64); - - if (lighting) { - ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(chunkX, chunkZ); - boolean trustEdges = false; //Added in 1.16.1 Not sure what it does. - PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), trustEdges); - playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { - p.playerConnection.sendPacket(packet); - }); - } - - } catch (IllegalAccessException e) { - e.printStackTrace(); - } + ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(chunkX, chunkZ); + Optional optional = ((Either) playerChunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + Chunk chunk = optional.orElseGet(() -> + nmsWorld.getChunkProvider().getChunkAtIfLoadedImmediately(chunkX, chunkZ)); + if (chunk == null) { + return; + } + PacketPlayOutMapChunk chunkPacket = new PacketPlayOutMapChunk(chunk, 65535, true); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(chunkPacket); + }); + if (lighting) { + //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) + boolean trustEdges = true; + PacketPlayOutLightUpdate packet = + new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), + trustEdges); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(packet); + }); } } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitAdapter_1_16_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitAdapter_1_16_2.java index 9ac20c248..7a69eee9f 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitAdapter_1_16_2.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitAdapter_1_16_2.java @@ -9,7 +9,6 @@ import com.boydti.fawe.object.collection.BitArrayUnstretched; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.ReflectionUtils; import com.boydti.fawe.util.TaskManager; -import com.destroystokyo.paper.util.misc.PooledLinkedHashSets; import com.mojang.datafixers.util.Either; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.block.BlockState; @@ -26,7 +25,6 @@ import net.minecraft.server.v1_16_R2.DataBits; import net.minecraft.server.v1_16_R2.DataPalette; import net.minecraft.server.v1_16_R2.DataPaletteBlock; import net.minecraft.server.v1_16_R2.DataPaletteLinear; -import net.minecraft.server.v1_16_R2.EntityPlayer; import net.minecraft.server.v1_16_R2.GameProfileSerializer; import net.minecraft.server.v1_16_R2.IBlockData; import net.minecraft.server.v1_16_R2.PacketPlayOutLightUpdate; @@ -198,50 +196,26 @@ public final class BukkitAdapter_1_16_2 extends NMSAdapter { if (playerChunk == null) { return; } - if (playerChunk.hasBeenLoaded()) { - ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(chunkX, chunkZ); - Optional optional = ((Either) playerChunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); - if (optional.isPresent()) { - PacketPlayOutMapChunk chunkpacket = new PacketPlayOutMapChunk(optional.get(), 65535); - playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { - p.playerConnection.sendPacket(chunkpacket); - }); - - if (lighting) { - //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) - boolean trustEdges = true; - PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), trustEdges); - playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { - p.playerConnection.sendPacket(packet); - }); - } - } else if (PaperLib.isPaper()) { - //Require generic here to work with multiple dependencies trying to take control. - PooledLinkedHashSets.PooledObjectLinkedOpenHashSet objects = - nmsWorld.getChunkProvider().playerChunkMap.playerViewDistanceNoTickMap.getObjectsInRange(chunkX, chunkZ); - if (objects == null) { - return; - } - for (Object obj : objects.getBackingSet()) { - if (obj == null) { - continue; - } - EntityPlayer p = (EntityPlayer) obj; - Chunk chunk = nmsWorld.getChunkProvider().getChunkAtIfLoadedImmediately(chunkX, chunkZ); - if (chunk != null) { - PacketPlayOutMapChunk chunkpacket = new PacketPlayOutMapChunk(chunk, 65535); - p.playerConnection.sendPacket(chunkpacket); - - if (lighting) { - //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) - boolean trustEdges = true; - PacketPlayOutLightUpdate packet = - new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), trustEdges); - p.playerConnection.sendPacket(packet); - } - } - } - } + ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(chunkX, chunkZ); + Optional optional = ((Either) playerChunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + Chunk chunk = optional.orElseGet(() -> + nmsWorld.getChunkProvider().getChunkAtIfLoadedImmediately(chunkX, chunkZ)); + if (chunk == null) { + return; + } + PacketPlayOutMapChunk chunkPacket = new PacketPlayOutMapChunk(chunk, 65535); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(chunkPacket); + }); + if (lighting) { + //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) + boolean trustEdges = true; + PacketPlayOutLightUpdate packet = + new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), + trustEdges); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(packet); + }); } } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/BukkitAdapter_1_16_5.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/BukkitAdapter_1_16_5.java index b044166a3..c34cb0f4c 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/BukkitAdapter_1_16_5.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/BukkitAdapter_1_16_5.java @@ -198,53 +198,26 @@ public final class BukkitAdapter_1_16_5 extends NMSAdapter { if (playerChunk == null) { return; } - if (playerChunk.hasBeenLoaded()) { - ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(chunkX, chunkZ); - Optional optional = ((Either) playerChunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); - if (optional.isPresent()) { - PacketPlayOutMapChunk chunkpacket = new PacketPlayOutMapChunk(optional.get(), 65535); - playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { - p.playerConnection.sendPacket(chunkpacket); - }); - - if (lighting) { - //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) - boolean trustEdges = true; - PacketPlayOutLightUpdate packet = - new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), + ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(chunkX, chunkZ); + Optional optional = ((Either) playerChunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + Chunk chunk = optional.orElseGet(() -> + nmsWorld.getChunkProvider().getChunkAtIfLoadedImmediately(chunkX, chunkZ)); + if (chunk == null) { + return; + } + PacketPlayOutMapChunk chunkPacket = new PacketPlayOutMapChunk(chunk, 65535); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(chunkPacket); + }); + if (lighting) { + //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) + boolean trustEdges = true; + PacketPlayOutLightUpdate packet = + new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), trustEdges); - playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { - p.playerConnection.sendPacket(packet); - }); - } - } else if (PaperLib.isPaper()) { - //Require generic here to work with multiple dependencies trying to take control. - PooledLinkedHashSets.PooledObjectLinkedOpenHashSet objects = - nmsWorld.getChunkProvider().playerChunkMap.playerViewDistanceNoTickMap - .getObjectsInRange(chunkX, chunkZ); - if (objects == null) { - return; - } - for (Object obj : objects.getBackingSet()) { - if (obj == null) { - continue; - } - EntityPlayer p = (EntityPlayer) obj; - Chunk chunk = nmsWorld.getChunkProvider().getChunkAtIfLoadedImmediately(chunkX, chunkZ); - if (chunk != null) { - PacketPlayOutMapChunk chunkpacket = new PacketPlayOutMapChunk(chunk, 65535); - p.playerConnection.sendPacket(chunkpacket); - - if (lighting) { - //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) - boolean trustEdges = true; - PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, - nmsWorld.getChunkProvider().getLightEngine(), trustEdges); - p.playerConnection.sendPacket(packet); - } - } - } - } + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(packet); + }); } } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/TuinityRelighterFactory_1_16_5.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/TuinityRelighterFactory_1_16_5.java new file mode 100644 index 000000000..6002b9eea --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/TuinityRelighterFactory_1_16_5.java @@ -0,0 +1,21 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_5; + +import com.boydti.fawe.beta.IQueueChunk; +import com.boydti.fawe.beta.IQueueExtent; +import com.boydti.fawe.beta.implementation.lighting.NullRelighter; +import com.boydti.fawe.beta.implementation.lighting.Relighter; +import com.boydti.fawe.beta.implementation.lighting.RelighterFactory; +import com.boydti.fawe.object.RelightMode; +import com.sk89q.worldedit.world.World; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.jetbrains.annotations.NotNull; + +public class TuinityRelighterFactory_1_16_5 implements RelighterFactory { + @Override + public @NotNull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent queue) { + org.bukkit.World w = Bukkit.getWorld(world.getName()); + if (w == null) return NullRelighter.INSTANCE; + return new TuinityRelighter_1_16_5(((CraftWorld) w).getHandle(), queue); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/TuinityRelighter_1_16_5.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/TuinityRelighter_1_16_5.java new file mode 100644 index 000000000..1043843c4 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_5/TuinityRelighter_1_16_5.java @@ -0,0 +1,228 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_5; + +import com.boydti.fawe.beta.IQueueChunk; +import com.boydti.fawe.beta.IQueueExtent; +import com.boydti.fawe.beta.implementation.lighting.NMSRelighter; +import com.boydti.fawe.beta.implementation.lighting.Relighter; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArraySet; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.server.v1_16_R3.ChunkCoordIntPair; +import net.minecraft.server.v1_16_R3.ChunkStatus; +import net.minecraft.server.v1_16_R3.LightEngineThreaded; +import net.minecraft.server.v1_16_R3.MCUtil; +import net.minecraft.server.v1_16_R3.TicketType; +import net.minecraft.server.v1_16_R3.Unit; +import net.minecraft.server.v1_16_R3.WorldServer; +import org.apache.logging.log4j.Logger; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.IntConsumer; + +public class TuinityRelighter_1_16_5 implements Relighter { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private static final MethodHandle RELIGHT; + + private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32 + private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting + + private static final TicketType FAWE_TICKET = TicketType.a("fawe_ticket", (a, b) -> 0); + private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT); + + private final WorldServer world; + private final ReentrantLock lock = new ReentrantLock(); + + private final Long2ObjectLinkedOpenHashMap regions = new Long2ObjectLinkedOpenHashMap<>(); + + private final ReentrantLock areaLock = new ReentrantLock(); + private final NMSRelighter delegate; + + static { + MethodHandle tmp = null; + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + tmp = lookup.findVirtual(LightEngineThreaded.class, + "relight", + MethodType.methodType( + int.class, // return type + // params + Set.class, + Consumer.class, + IntConsumer.class + ) + ); + } catch (NoSuchMethodException | IllegalAccessException e) { + LOGGER.error("Failed to locate relight method in LightEngineThreaded on Tuinity. " + + "Is everything up to date?", e); + } + RELIGHT = tmp; + } + + public TuinityRelighter_1_16_5(WorldServer world, IQueueExtent queue) { + this.world = world; + this.delegate = new NMSRelighter(queue, false); + } + + @Override + public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) { + areaLock.lock(); + try { + long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2); + // TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH? + LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2)); + chunks.add(ChunkCoordIntPair.pair(cx, cz)); + } finally { + areaLock.unlock(); + } + return true; + } + + @Override + public void addLightUpdate(int x, int y, int z) { + delegate.addLightUpdate(x, y, z); + } + + /* + * This method is called "recursively", iterating and removing elements + * from the regions linked map. This way, chunks are loaded in batches to avoid + * OOMEs. + */ + @Override + public void fixLightingSafe(boolean sky) { + this.areaLock.lock(); + try { + if (regions.isEmpty()) return; + LongSet first = regions.removeFirst(); + fixLighting(first, () -> fixLightingSafe(true)); + } finally { + this.areaLock.unlock(); + } + } + + /* + * Processes a set of chunks and runs an action afterwards. + * The action is run async, the chunks are partly processed on the main thread + * (as required by the server). + */ + private void fixLighting(LongSet chunks, Runnable andThen) { + // convert from long keys to ChunkCoordIntPairs + Set coords = new HashSet<>(); + LongIterator iterator = chunks.iterator(); + while (iterator.hasNext()) { + coords.add(new ChunkCoordIntPair(iterator.nextLong())); + } + TaskManager.IMP.task(() -> { + // trigger chunk load and apply ticket on main thread + List> futures = new ArrayList<>(); + for (ChunkCoordIntPair pos : coords) { + futures.add(world.getWorld().getChunkAtAsync(pos.x, pos.z) + .thenAccept(c -> world.getChunkProvider().addTicketAtLevel( + FAWE_TICKET, + pos, + LIGHT_LEVEL, + Unit.INSTANCE)) + ); + } + // collect futures and trigger relight once all chunks are loaded + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v -> + invokeRelight(coords, + c -> { }, // no callback for single chunks required + i -> { + if (i != coords.size()) { + LOGGER.warn("Processed " + i + " chunks instead of " + coords.size()); + } + // post process chunks on main thread + TaskManager.IMP.task(() -> postProcessChunks(coords)); + // call callback on our own threads + TaskManager.IMP.async(andThen); + }) + ); + }); + } + + private void invokeRelight(Set coords, + Consumer chunkCallback, + IntConsumer processCallback) { + try { + int unused = (int) RELIGHT.invokeExact(world.getChunkProvider().getLightEngine(), + coords, + chunkCallback, // callback per chunk + processCallback // callback for all chunks + ); + } catch (Throwable throwable) { + LOGGER.error("Error occurred on relighting", throwable); + } + } + + /* + * Allow the server to unload the chunks again. + * Also, if chunk packets are sent delayed, we need to do that here + */ + private void postProcessChunks(Set coords) { + boolean delay = Settings.IMP.LIGHTING.DELAY_PACKET_SENDING; + for (ChunkCoordIntPair pos : coords) { + int x = pos.x; + int z = pos.z; + if (delay) { // we still need to send the block changes of that chunk + BukkitAdapter_1_16_5.sendChunk(world, x, z, -1, false); + } + world.getChunkProvider().removeTicketAtLevel(FAWE_TICKET, pos, LIGHT_LEVEL, Unit.INSTANCE); + } + } + + @Override + public void clear() { + + } + + @Override + public void removeLighting() { + this.delegate.removeLighting(); + } + + @Override + public void fixBlockLighting() { + fixLightingSafe(true); + } + + @Override + public void fixSkyLighting() { + fixLightingSafe(true); + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public ReentrantLock getLock() { + return this.lock; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public void close() throws Exception { + fixLightingSafe(true); + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java index ce2c246e3..40acebd3e 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java @@ -19,6 +19,9 @@ package com.sk89q.worldedit.bukkit; +import com.boydti.fawe.beta.implementation.lighting.RelighterFactory; +import com.boydti.fawe.bukkit.NMSRelighterFactory; +import com.boydti.fawe.bukkit.adapter.mc1_16_5.TuinityRelighterFactory_1_16_5; import com.google.common.collect.Sets; import com.sk89q.bukkit.util.CommandInfo; import com.sk89q.bukkit.util.CommandRegistration; @@ -32,15 +35,18 @@ import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.MultiUserPlatform; import com.sk89q.worldedit.extension.platform.Preference; import com.sk89q.worldedit.extension.platform.Watchdog; +import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.concurrency.LazyReference; import com.sk89q.worldedit.world.DataFixer; import com.sk89q.worldedit.world.registry.Registries; +import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.World; import org.bukkit.entity.EntityType; import org.enginehub.piston.CommandManager; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; @@ -56,10 +62,13 @@ import static com.sk89q.worldedit.util.formatting.WorldEditText.reduceToText; public class BukkitServerInterface extends AbstractPlatform implements MultiUserPlatform { + private static final Logger LOGGER = LogManagerCompat.getLogger(); + public final Server server; public final WorldEditPlugin plugin; private final CommandRegistration dynamicCommands; private final LazyReference watchdog; + private final RelighterFactory religherFactory; private boolean hookingEvents; public BukkitServerInterface(WorldEditPlugin plugin, Server server) { @@ -74,6 +83,16 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser } return null; }); + RelighterFactory tempFactory; + try { + Class.forName("com.tuinity.tuinity.config.TuinityConfig"); + tempFactory = new TuinityRelighterFactory_1_16_5(); + LOGGER.info("Using Tuinity internals for relighting"); + } catch (ClassNotFoundException e) { + tempFactory = new NMSRelighterFactory(); + LOGGER.info("Using FAWE for relighting"); + } + this.religherFactory = tempFactory; } CommandRegistration getDynamicCommands() { @@ -244,6 +263,11 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser return SUPPORTED_SIDE_EFFECTS; } + @Override + public @NotNull RelighterFactory getRelighterFactory() { + return this.religherFactory; + } + public void unregisterCommands() { dynamicCommands.unregisterCommands(); } diff --git a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIPlatform.java b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIPlatform.java index d620c4da9..e762d6b4d 100644 --- a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIPlatform.java +++ b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIPlatform.java @@ -19,6 +19,8 @@ package com.sk89q.worldedit.cli; +import com.boydti.fawe.beta.implementation.lighting.NullRelighter; +import com.boydti.fawe.beta.implementation.lighting.RelighterFactory; import com.google.common.collect.ImmutableSet; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.AbstractPlatform; @@ -30,6 +32,7 @@ import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.entity.EntityTypes; import com.sk89q.worldedit.world.registry.Registries; import org.enginehub.piston.CommandManager; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.EnumMap; @@ -160,6 +163,11 @@ class CLIPlatform extends AbstractPlatform { return ImmutableSet.of(); } + @Override + public @NotNull RelighterFactory getRelighterFactory() { + return (_a, _b, _c) -> NullRelighter.INSTANCE; + } + public void addWorld(World world) { worlds.add(world); } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java b/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java index 09b7a9691..92b99d553 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/FaweAPI.java @@ -2,7 +2,7 @@ package com.boydti.fawe; import com.boydti.fawe.beta.IQueueChunk; import com.boydti.fawe.beta.IQueueExtent; -import com.boydti.fawe.beta.implementation.lighting.NMSRelighter; +import com.boydti.fawe.beta.implementation.lighting.Relighter; import com.boydti.fawe.beta.implementation.queue.ParallelQueueExtent; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.RegionWrapper; @@ -29,12 +29,15 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; +import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.World; +import org.apache.logging.log4j.Logger; +import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.net.URL; @@ -45,7 +48,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.UUID; -import javax.annotation.Nullable; /** * The FaweAPI class offers a few useful functions.
@@ -56,6 +58,8 @@ import javax.annotation.Nullable; */ public class FaweAPI { + private static final Logger LOGGER = LogManagerCompat.getLogger(); + /** * Offers a lot of options for building an EditSession. * @@ -344,24 +348,29 @@ public class FaweAPI { } } - NMSRelighter relighter = new NMSRelighter(queue, Settings.IMP.LIGHTING.DO_HEIGHTMAPS); - for (int x = minX; x <= maxX; x++) { - for (int z = minZ; z <= maxZ; z++) { - relighter.addChunk(x, z, null, 65535); - count++; + try (Relighter relighter = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING) + .getRelighterFactory() + .createRelighter(mode, world, queue)) { + + for (int x = minX; x <= maxX; x++) { + for (int z = minZ; z <= maxZ; z++) { + relighter.addChunk(x, z, null, 65535); + count++; + } } - } - if (mode != RelightMode.NONE) { - if (Settings.IMP.LIGHTING.REMOVE_FIRST) { - relighter.removeAndRelight(true); + if (mode != RelightMode.NONE) { + if (Settings.IMP.LIGHTING.REMOVE_FIRST) { + relighter.removeAndRelight(true); + } else { + relighter.fixSkyLighting(); + relighter.fixBlockLighting(); + } } else { - relighter.fixSkyLighting(); - relighter.fixBlockLighting(); + relighter.removeLighting(); } - } else { - relighter.removeLighting(); + } catch (Exception e) { + LOGGER.error("Error occurred on fix lighting", e); } - relighter.flush(); return count; } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/HeightMapType.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/HeightMapType.java index 0ff367285..a0f91bff8 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/HeightMapType.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/HeightMapType.java @@ -1,5 +1,68 @@ package com.boydti.fawe.beta.implementation.lighting; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.registry.state.PropertyKey; +import com.sk89q.worldedit.world.block.BlockCategories; +import com.sk89q.worldedit.world.block.BlockState; + +/** + * This enum represents the different types of height maps available in minecraft. + * + * Heightmaps are used to describe the highest position for given {@code (x, z)} coordinates. + * What's considered as highest position depends on the height map type and the blocks at that column. + * The highest position is a {@code max(y + 1)} such that the block at {@code (x, y, z)} is + * {@link #includes(BlockState) included} by the height map type. + */ public enum HeightMapType { - MOTION_BLOCKING, MOTION_BLOCKING_NO_LEAVES, OCEAN_FLOOR, WORLD_SURFACE + MOTION_BLOCKING { + @Override + public boolean includes(BlockState state) { + return state.getMaterial().isSolid() || HeightMapType.hasFluid(state); + } + }, + MOTION_BLOCKING_NO_LEAVES { + @Override + public boolean includes(BlockState state) { + return (state.getMaterial().isSolid() || HeightMapType.hasFluid(state)) && !HeightMapType.isLeaf(state); + } + }, + OCEAN_FLOOR { + @Override + public boolean includes(BlockState state) { + return state.getMaterial().isSolid(); + } + }, + WORLD_SURFACE { + @Override + public boolean includes(BlockState state) { + return !state.isAir(); + } + }; + + private static boolean isLeaf(BlockState state) { + return BlockCategories.LEAVES.contains(state); + } + + /** + * Returns whether the block state is a fluid or has an attribute that indicates the presence + * of fluid. + * + * @param state the block state to check. + * @return {@code true} if the block state has any fluid present. + */ + private static boolean hasFluid(BlockState state) { + if (state.getMaterial().isLiquid()) return true; + if (!state.getBlockType().hasProperty(PropertyKey.WATERLOGGED)) return false; + Property waterlogged = state.getBlockType().getProperty(PropertyKey.WATERLOGGED); + if (waterlogged == null) return false; + return state.getState(waterlogged); + } + + /** + * Returns whether the given block state is included by this height map. + * + * @param state the block state to check. + * @return {@code true} if the block is included. + */ + public abstract boolean includes(BlockState state); } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NMSRelighter.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NMSRelighter.java index ecde93ef5..463737aa0 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NMSRelighter.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NMSRelighter.java @@ -12,7 +12,6 @@ import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.TaskManager; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.MutableBlockVector3; -import com.sk89q.worldedit.registry.state.BooleanProperty; import com.sk89q.worldedit.registry.state.DirectionalProperty; import com.sk89q.worldedit.registry.state.EnumProperty; import com.sk89q.worldedit.registry.state.Property; @@ -37,7 +36,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Collectors; public class NMSRelighter implements Relighter { @@ -47,14 +45,12 @@ public class NMSRelighter implements Relighter { private static final EnumProperty stairHalf; private static final EnumProperty stairShape; private static final EnumProperty slabHalf; - private static final BooleanProperty waterLogged; static { stairDirection = (DirectionalProperty) (Property) BlockTypes.SANDSTONE_STAIRS.getProperty("facing"); stairHalf = (EnumProperty) (Property) BlockTypes.SANDSTONE_STAIRS.getProperty("half"); stairShape = (EnumProperty) (Property) BlockTypes.SANDSTONE_STAIRS.getProperty("shape"); slabHalf = (EnumProperty) (Property) BlockTypes.SANDSTONE_SLAB.getProperty("type"); - waterLogged = (BooleanProperty) (Property) BlockTypes.SANDSTONE_SLAB.getProperty("waterlogged"); } public final MutableBlockVector3 mutableBlockPos = new MutableBlockVector3(0, 0, 0); @@ -62,31 +58,27 @@ public class NMSRelighter implements Relighter { private final Map skyToRelight; private final Object present = new Object(); private final Map chunksToSend; - private final Map> heightMaps; private final ConcurrentLinkedQueue extentdSkyToRelight = new ConcurrentLinkedQueue<>(); private final Map lightQueue; private final AtomicBoolean lightLock = new AtomicBoolean(false); private final ConcurrentHashMap concurrentLightQueue; private final RelightMode relightMode; private final int maxY; - private final boolean calculateHeightMaps; private final ReentrantLock lightingLock; private final AtomicBoolean finished = new AtomicBoolean(false); private boolean removeFirst; public NMSRelighter(IQueueExtent queue, boolean calculateHeightMaps) { - this(queue, calculateHeightMaps, null); + this(queue, null); } - public NMSRelighter(IQueueExtent queue, boolean calculateHeightMaps, RelightMode relightMode) { + public NMSRelighter(IQueueExtent queue, RelightMode relightMode) { this.queue = queue; this.skyToRelight = new Long2ObjectOpenHashMap<>(12); this.lightQueue = new Long2ObjectOpenHashMap<>(12); this.chunksToSend = new Long2ObjectOpenHashMap<>(12); this.concurrentLightQueue = new ConcurrentHashMap<>(12); - this.heightMaps = new Long2ObjectOpenHashMap<>(12); this.maxY = queue.getMaxY(); - this.calculateHeightMaps = calculateHeightMaps; this.relightMode = relightMode != null ? relightMode : RelightMode.valueOf(Settings.IMP.LIGHTING.MODE); this.lightingLock = new ReentrantLock(); } @@ -160,12 +152,11 @@ public class NMSRelighter implements Relighter { extentdSkyToRelight.clear(); skyToRelight.clear(); chunksToSend.clear(); - heightMaps.clear(); lightQueue.clear(); } public boolean addChunk(int cx, int cz, byte[] fix, int bitmask) { - RelightSkyEntry toPut = new RelightSkyEntry(cx, cz, fix, bitmask, calculateHeightMaps); + RelightSkyEntry toPut = new RelightSkyEntry(cx, cz, fix, bitmask); extentdSkyToRelight.add(toPut); return true; } @@ -823,7 +814,8 @@ public class NMSRelighter implements Relighter { } } - public synchronized void flush() { + @Override + public synchronized void close() { Iterator> iter = chunksToSend.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -833,14 +825,6 @@ public class NMSRelighter implements Relighter { int z = MathMan.unpairIntY(pair); ChunkHolder chunk = (ChunkHolder) queue.getOrCreateChunk(x, z); chunk.setBitMask(bitMask); - if (calculateHeightMaps && heightMaps != null) { - Map heightMapList = heightMaps.get(pair); - if (heightMapList != null) { - for (Map.Entry heightMapEntry : heightMapList.entrySet()) { - chunk.setHeightMap(heightMapEntry.getKey(), heightMapEntry.getValue()); - } - } - } iter.remove(); } if (Settings.IMP.LIGHTING.ASYNC) { @@ -856,6 +840,14 @@ public class NMSRelighter implements Relighter { } } + public void flush() { + try { + close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + public synchronized void sendChunks() { RunnableVal runnable = new RunnableVal() { @Override @@ -869,14 +861,6 @@ public class NMSRelighter implements Relighter { int z = MathMan.unpairIntY(pair); ChunkHolder chunk = (ChunkHolder) queue.getOrCreateChunk(x, z); chunk.setBitMask(bitMask); - if (calculateHeightMaps && heightMaps != null) { - Map heightMapList = heightMaps.get(pair); - if (heightMapList != null) { - for (Map.Entry heightMapEntry : heightMapList.entrySet()) { - chunk.setHeightMap(heightMapEntry.getKey(), heightMapEntry.getValue()); - } - } - } chunk.flushLightToGet(true); Fawe.imp().getPlatformAdapter().sendChunk(chunk.getOrCreateGet(), bitMask, true); iter.remove(); @@ -943,33 +927,19 @@ public class NMSRelighter implements Relighter { private void fixSkyLighting(List sorted) { RelightSkyEntry[] chunks = sorted.toArray(new RelightSkyEntry[sorted.size()]); boolean remove = this.removeFirst; - boolean heightMaps = this.calculateHeightMaps; BlockVectorSet chunkSet = null; - if (remove || heightMaps) { + if (remove) { BlockVectorSet tmpSet = new BlockVectorSet(); - if (remove) { - chunkSet = new BlockVectorSet(); - for (RelightSkyEntry chunk : chunks) { - tmpSet.add(chunk.x, 0, chunk.z); - } + chunkSet = new BlockVectorSet(); + for (RelightSkyEntry chunk : chunks) { + tmpSet.add(chunk.x, 0, chunk.z); } for (RelightSkyEntry chunk : chunks) { - if (remove) { - int x = chunk.x; - int z = chunk.z; - if (tmpSet.contains(x + 1, 0, z) && tmpSet.contains(x - 1, 0, z) && tmpSet.contains(x, 0, z + 1) && tmpSet - .contains(x, 0, z - 1)) { - chunkSet.add(x, 0, z); - } - } - if (heightMaps) { - long pair = MathMan.pairInt(chunk.x, chunk.z); - this.heightMaps.putIfAbsent(pair, new HashMap<>()); - Map heightMapList = this.heightMaps.get(pair); - heightMapList.putIfAbsent(HeightMapType.WORLD_SURFACE, new int[256]); - heightMapList.putIfAbsent(HeightMapType.OCEAN_FLOOR, new int[256]); - heightMapList.putIfAbsent(HeightMapType.MOTION_BLOCKING, new int[256]); - heightMapList.putIfAbsent(HeightMapType.MOTION_BLOCKING_NO_LEAVES, new int[256]); + int x = chunk.x; + int z = chunk.z; + if (tmpSet.contains(x + 1, 0, z) && tmpSet.contains(x - 1, 0, z) && tmpSet.contains(x, 0, z + 1) && tmpSet + .contains(x, 0, z - 1)) { + chunkSet.add(x, 0, z); } } } @@ -995,12 +965,6 @@ public class NMSRelighter implements Relighter { iChunk.removeSectionLighting(y >> 4, true); } - Map heightMapList = null; - if (heightMaps) { - long pair = MathMan.pairInt(chunk.x, chunk.z); - heightMapList = this.heightMaps.get(pair); - } - for (int j = 0; j < 256; j++) { int x = j & 15; int z = j >> 4; @@ -1013,38 +977,6 @@ public class NMSRelighter implements Relighter { addLightUpdate(bx + x, y, bz + z); } - if (heightMaps) { - if (heightMapList.get(HeightMapType.WORLD_SURFACE)[j] == 0 && !material.isAir()) { - // MC Requires y+1 - heightMapList.get(HeightMapType.WORLD_SURFACE)[j] = y + 1; - } - if (heightMapList.get(HeightMapType.OCEAN_FLOOR)[j] == 0 && material.isSolid()) { - heightMapList.get(HeightMapType.OCEAN_FLOOR)[j] = y + 1; - } - Map, Object> states = state.getStates(); - try { - if (heightMapList.get(HeightMapType.MOTION_BLOCKING)[j] == 0 && (material.isSolid() || material.isLiquid() || ( - states.containsKey(waterLogged) && state.getState(waterLogged)))) { - heightMapList.get(HeightMapType.MOTION_BLOCKING)[j] = y + 1; - } - } catch (Exception ignored) { - LOGGER.debug("Error calculating waterlogged state for BlockState: " + state.getBlockType().getId() + ". States:"); - LOGGER.debug(states.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()) - .collect(Collectors.joining(", ", "{", "}"))); - } - try { - if (heightMapList.get(HeightMapType.MOTION_BLOCKING_NO_LEAVES)[j] == 0 && (material.isSolid() || material.isLiquid() || ( - states.containsKey(waterLogged) && state.getState(waterLogged))) && !state.getBlockType().getId() - .toLowerCase().contains("leaves")) { - heightMapList.get(HeightMapType.MOTION_BLOCKING_NO_LEAVES)[j] = y + 1; - } - } catch (Exception ignored) { - LOGGER.debug("Error calculating waterlogged state for BlockState: " + state.getBlockType().getId() + ". States:"); - LOGGER.debug(states.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()) - .collect(Collectors.joining(", ", "{", "}"))); - } - } - switch (value) { case 0: if (opacity > 1) { @@ -1222,7 +1154,7 @@ public class NMSRelighter implements Relighter { public int bitmask; public boolean smooth; - public RelightSkyEntry(int x, int z, byte[] fix, int bitmask, boolean heightmaps) { + public RelightSkyEntry(int x, int z, byte[] fix, int bitmask) { this.x = x; this.z = z; byte[] array = new byte[256]; diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NullRelighter.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NullRelighter.java index 1c345df1b..8798e8023 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NullRelighter.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/NullRelighter.java @@ -58,4 +58,9 @@ public class NullRelighter implements Relighter { public boolean isFinished() { return true; } + + @Override + public void close() throws Exception { + + } } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/Relighter.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/Relighter.java index 61fbc2d87..bdf776c0f 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/Relighter.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/Relighter.java @@ -2,7 +2,7 @@ package com.boydti.fawe.beta.implementation.lighting; import java.util.concurrent.locks.ReentrantLock; -public interface Relighter { +public interface Relighter extends AutoCloseable { /** * Add a chunk to be relit when {@link Relighter#removeLighting} etc are called. diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/RelighterFactory.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/RelighterFactory.java new file mode 100644 index 000000000..dae58bcd4 --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/lighting/RelighterFactory.java @@ -0,0 +1,29 @@ +package com.boydti.fawe.beta.implementation.lighting; + +import com.boydti.fawe.beta.IQueueChunk; +import com.boydti.fawe.beta.IQueueExtent; +import com.boydti.fawe.object.RelightMode; +import com.sk89q.worldedit.world.World; +import org.jetbrains.annotations.NotNull; + +/** + * This abstracts the creation of {@link Relighter}s to allow more modular code. + */ +@FunctionalInterface +public interface RelighterFactory { + + /** + * Create a new {@link Relighter} that can be used by a {@link RelightProcessor}. + *

+ * Implementations are meant to configure an appropriate Relighter using the specified + * parameters. There are no guarantees about the returned objects other than being non-null. + * If no valid Relighter can be created, {@link NullRelighter#INSTANCE} should be returned. + * + * @param relightMode the relight mode to use during relighting. + * @param world the world in which relighting should be done. + * @param queue the queue extent to work with. + * @return a new Relighter instance with the specified settings. + */ + @NotNull + Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent queue); +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/processors/HeightmapProcessor.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/processors/HeightmapProcessor.java new file mode 100644 index 000000000..29597037d --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/processors/HeightmapProcessor.java @@ -0,0 +1,103 @@ +package com.boydti.fawe.beta.implementation.processors; + +import com.boydti.fawe.beta.IBatchProcessor; +import com.boydti.fawe.beta.IChunk; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.beta.IChunkSet; +import com.boydti.fawe.beta.implementation.lighting.HeightMapType; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import org.jetbrains.annotations.Nullable; + +import java.util.BitSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +public class HeightmapProcessor implements IBatchProcessor { + private static final HeightMapType[] TYPES = HeightMapType.values(); + private static final BlockType RESERVED = BlockTypes.__RESERVED__; + private static final int SECTION_SIDE_LENGTH = 16; + private static final int BLOCKS_PER_Y_LEVEL = SECTION_SIDE_LENGTH * SECTION_SIDE_LENGTH; + + private final int maxY; + private final int minY; + + public HeightmapProcessor(World world) { + this.maxY = world.getMaxY(); + this.minY = world.getMinY(); + } + + @Override + public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) { + // each heightmap gets one 16*16 array + int[][] heightmaps = new int[TYPES.length][BLOCKS_PER_Y_LEVEL]; + BitSet[] updated = new BitSet[TYPES.length]; + for (int i = 0; i < updated.length; i++) { + updated[i] = new BitSet(BLOCKS_PER_Y_LEVEL); + } + int skip = 0; + int allSkipped = (1 << TYPES.length) - 1; // lowest types.length bits are set + for (int y = maxY; y >= minY; y--) { + boolean hasSectionSet = set.hasSection(y >> 4); + boolean hasSectionGet = get.hasSection(y >> 4); + if (!(hasSectionSet || hasSectionGet)) { + y -= (SECTION_SIDE_LENGTH - 1); // - 1, as we do y-- in the loop head + continue; + } + for (int z = 0; z < SECTION_SIDE_LENGTH; z++) { + for (int x = 0; x < SECTION_SIDE_LENGTH; x++) { + BlockState block = null; + if (hasSectionSet) { + block = set.getBlock(x, y, z); + } + if (block == null || block.getBlockType() == RESERVED) { + if (!hasSectionGet) continue; + block = get.getBlock(x, y, z); + } + // fast skip if block isn't relevant for any height map + if (block.isAir()) continue; + for (int i = 0; i < TYPES.length; i++) { + if ((skip & (1 << i)) != 0) continue; // skip finished height map + HeightMapType type = TYPES[i]; + int index = (z << 4) | x; + if (!updated[i].get(index) // ignore if that position was already set + && type.includes(block)) { + heightmaps[i][index] = y + 1; // mc requires + 1 + updated[i].set(index); // mark as updated + } + } + } + } + for (int i = 0; i < updated.length; i++) { + if ((skip & (1 << i)) == 0 // if already true, skip cardinality calculation + && updated[i].cardinality() == BLOCKS_PER_Y_LEVEL) { + skip |= 1 << i; + } + } + if (skip != allSkipped) continue; + break; // all maps are processed + } + for (int i = 0; i < TYPES.length; i++) { + set.setHeightMap(TYPES[i], heightmaps[i]); + } + return set; + } + + @Override + public Future postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) { + return CompletableFuture.completedFuture(set); + } + + @Override + public @Nullable Extent construct(Extent child) { + throw new UnsupportedOperationException("Processing only"); + } + + @Override + public ProcessorScope getScope() { + return ProcessorScope.READING_SET_BLOCKS; + } +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/config/Settings.java b/worldedit-core/src/main/java/com/boydti/fawe/config/Settings.java index c9beed2bd..087c4dd77 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/config/Settings.java @@ -482,8 +482,6 @@ public class Settings extends Config { public int MODE = 1; @Comment({"If existing lighting should be removed before relighting"}) public boolean REMOVE_FIRST = true; - @Comment({"Calculate and set heightmaps when relighting"}) - public boolean DO_HEIGHTMAPS = true; } public void reload(File file) { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/util/EditSessionBuilder.java b/worldedit-core/src/main/java/com/boydti/fawe/util/EditSessionBuilder.java index ca72fac43..e3f867907 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/util/EditSessionBuilder.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/util/EditSessionBuilder.java @@ -4,10 +4,10 @@ import com.boydti.fawe.Fawe; import com.boydti.fawe.FaweCache; import com.boydti.fawe.beta.IQueueChunk; import com.boydti.fawe.beta.IQueueExtent; -import com.boydti.fawe.beta.implementation.lighting.NMSRelighter; import com.boydti.fawe.beta.implementation.lighting.NullRelighter; import com.boydti.fawe.beta.implementation.lighting.RelightProcessor; import com.boydti.fawe.beta.implementation.lighting.Relighter; +import com.boydti.fawe.beta.implementation.processors.HeightmapProcessor; import com.boydti.fawe.beta.implementation.processors.LimitExtent; import com.boydti.fawe.beta.implementation.queue.ParallelQueueExtent; import com.boydti.fawe.config.Caption; @@ -34,7 +34,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.event.extent.EditSessionEvent; -import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.internal.util.LogManagerCompat; @@ -42,14 +42,13 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Identifiable; import com.sk89q.worldedit.util.eventbus.EventBus; import com.sk89q.worldedit.util.formatting.text.TextComponent; -import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.World; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; import java.util.Locale; import java.util.UUID; -import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; @@ -401,12 +400,14 @@ public class EditSessionBuilder { } // There's no need to do lighting (and it'll also just be a pain to implement) if we're not placing chunks if (placeChunks && ((relightMode != null && relightMode != RelightMode.NONE) || (relightMode == null && Settings.IMP.LIGHTING.MODE > 0))) { - relighter = new NMSRelighter(queue, Settings.IMP.LIGHTING.DO_HEIGHTMAPS, - relightMode != null ? relightMode : RelightMode.valueOf(Settings.IMP.LIGHTING.MODE)); + relighter = WorldEdit.getInstance().getPlatformManager() + .queryCapability(Capability.WORLD_EDITING) + .getRelighterFactory().createRelighter(relightMode, world, queue); extent.addProcessor(new RelightProcessor(relighter)); } else { relighter = NullRelighter.INSTANCE; } + extent.addProcessor(new HeightmapProcessor(world)); if (limit != null && !limit.isUnlimited() && regionExtent != null) { this.extent = new LimitExtent(regionExtent, limit); } else if (limit != null && !limit.isUnlimited()) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java index 709492a97..d3051e17b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.extension.platform; +import com.boydti.fawe.beta.implementation.lighting.RelighterFactory; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.internal.util.NonAbstractForCompatibility; @@ -29,6 +30,7 @@ import com.sk89q.worldedit.world.DataFixer; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.registry.Registries; import org.enginehub.piston.CommandManager; +import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Locale; @@ -206,4 +208,13 @@ public interface Platform extends Keyed { * @return A set of supported side effects */ Set getSupportedSideEffects(); + + /** + * Get the {@link RelighterFactory} that can be used to obtain + * {@link com.boydti.fawe.beta.implementation.lighting.Relighter}s. + * + * @return the relighter factory to be used. + */ + @NotNull + RelighterFactory getRelighterFactory(); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java index 1789a4789..29980fd82 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java @@ -123,7 +123,9 @@ public final class BlockCategories { public static BlockCategory get(String id) { BlockCategory entry = BlockCategory.REGISTRY.get(id); if (entry == null) { - return new BlockCategory(id); + BlockCategory blockCategory = new BlockCategory(id); + blockCategory.load(); + return blockCategory; } return entry; }