mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-22 09:17:39 +00:00
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 <mc.cache@web.de> * 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 <unconfigured@null.spigotmc.org> Co-authored-by: NotMyFault <mc.cache@web.de> Co-authored-by: Aurora <21148213+aurorasmiles@users.noreply.github.com>
This commit is contained in:
parent
46f1882496
commit
ff728478c6
@ -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<IQueueChunk> queue) {
|
||||
return new NMSRelighter(queue,
|
||||
relightMode != null ? relightMode : RelightMode.valueOf(Settings.IMP.LIGHTING.MODE));
|
||||
}
|
||||
}
|
@ -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<Chunk> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Chunk> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Chunk> 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<Chunk> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Chunk> 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<Chunk> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<IQueueChunk> 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);
|
||||
}
|
||||
}
|
@ -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<Unit> 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<LongSet> 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<IQueueChunk> 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<ChunkCoordIntPair> 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<CompletableFuture<?>> 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<ChunkCoordIntPair> coords,
|
||||
Consumer<ChunkCoordIntPair> 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<ChunkCoordIntPair> 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);
|
||||
}
|
||||
}
|
@ -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> 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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.<br>
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<Boolean> 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);
|
||||
}
|
||||
|
@ -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<Long, RelightSkyEntry> skyToRelight;
|
||||
private final Object present = new Object();
|
||||
private final Map<Long, Integer> chunksToSend;
|
||||
private final Map<Long, Map<HeightMapType, int[]>> heightMaps;
|
||||
private final ConcurrentLinkedQueue<RelightSkyEntry> extentdSkyToRelight = new ConcurrentLinkedQueue<>();
|
||||
private final Map<Long, long[][][] /* z y x */> lightQueue;
|
||||
private final AtomicBoolean lightLock = new AtomicBoolean(false);
|
||||
private final ConcurrentHashMap<Long, long[][][]> 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<IQueueChunk> queue, boolean calculateHeightMaps) {
|
||||
this(queue, calculateHeightMaps, null);
|
||||
this(queue, null);
|
||||
}
|
||||
|
||||
public NMSRelighter(IQueueExtent<IQueueChunk> queue, boolean calculateHeightMaps, RelightMode relightMode) {
|
||||
public NMSRelighter(IQueueExtent<IQueueChunk> 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<Map.Entry<Long, Integer>> iter = chunksToSend.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<Long, Integer> 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<HeightMapType, int[]> heightMapList = heightMaps.get(pair);
|
||||
if (heightMapList != null) {
|
||||
for (Map.Entry<HeightMapType, int[]> 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<Object> runnable = new RunnableVal<Object>() {
|
||||
@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<HeightMapType, int[]> heightMapList = heightMaps.get(pair);
|
||||
if (heightMapList != null) {
|
||||
for (Map.Entry<HeightMapType, int[]> 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<RelightSkyEntry> 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<HeightMapType, int[]> 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<HeightMapType, int[]> 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<Property<?>, 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];
|
||||
|
@ -58,4 +58,9 @@ public class NullRelighter implements Relighter {
|
||||
public boolean isFinished() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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}.
|
||||
* <p>
|
||||
* 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<IQueueChunk> queue);
|
||||
}
|
@ -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<IChunkSet> 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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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()) {
|
||||
|
@ -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<SideEffect> 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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user