From 6bd13828725a398c5bf83230fa8c5e467789ac0c Mon Sep 17 00:00:00 2001 From: Hannes Greule Date: Tue, 31 Oct 2023 21:00:46 +0100 Subject: [PATCH] Reuse starlight relighting code across versions (#2474) --- .../PaperweightStarlightRelighter.java | 182 +++------------- .../PaperweightStarlightRelighter.java | 161 ++------------ .../PaperweightStarlightRelighter.java | 161 ++------------ .../PaperweightStarlightRelighter.java | 161 ++------------ .../PaperweightStarlightRelighter.java | 161 ++------------ .../bukkit/adapter/StarlightRelighter.java | 196 ++++++++++++++++++ 6 files changed, 293 insertions(+), 729 deletions(-) create mode 100644 worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/StarlightRelighter.java diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightStarlightRelighter.java index d80c82aa2..ea2afede3 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightStarlightRelighter.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightStarlightRelighter.java @@ -1,16 +1,8 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_17_R1_2; +import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter; import com.fastasyncworldedit.core.configuration.Settings; -import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter; -import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; import com.fastasyncworldedit.core.queue.IQueueExtent; -import com.fastasyncworldedit.core.util.MathMan; -import com.fastasyncworldedit.core.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.MCUtil; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ThreadedLevelLightEngine; @@ -18,27 +10,18 @@ import net.minecraft.server.level.TicketType; import net.minecraft.util.Unit; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkStatus; -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 PaperweightStarlightRelighter implements Relighter { - - public static final MethodHandle RELIGHT; - private static final Logger LOGGER = LogManagerCompat.getLogger(); - private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32 - private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting +public class PaperweightStarlightRelighter extends StarlightRelighter { + private static final MethodHandle RELIGHT; private static final TicketType FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT); @@ -57,21 +40,36 @@ public class PaperweightStarlightRelighter implements Relighter { IntConsumer.class ) ); + tmp = MethodHandles.dropReturn(tmp); } catch (NoSuchMethodException | IllegalAccessException e) { LOGGER.error("Failed to locate 'relight' method in ThreadedLevelLightEngine. Is everything up to date?", e); } RELIGHT = tmp; } - private final ServerLevel serverLevel; - private final ReentrantLock lock = new ReentrantLock(); - private final Long2ObjectLinkedOpenHashMap regions = new Long2ObjectLinkedOpenHashMap<>(); - private final ReentrantLock areaLock = new ReentrantLock(); - private final NMSRelighter delegate; - public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent queue) { - this.serverLevel = serverLevel; - this.delegate = new NMSRelighter(queue); + super(serverLevel, queue); + } + + @Override + protected ChunkPos createChunkPos(final long chunkKey) { + return new ChunkPos(chunkKey); + } + + @Override + protected long asLong(final int chunkX, final int chunkZ) { + return ChunkPos.asLong(chunkX, chunkZ); + } + + @Override + protected CompletableFuture chunkLoadFuture(final ChunkPos chunkPos) { + return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z) + .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel( + FAWE_TICKET, + chunkPos, + LIGHT_LEVEL, + Unit.INSTANCE + )); } public static boolean isUsable() { @@ -79,95 +77,13 @@ public class PaperweightStarlightRelighter implements Relighter { } @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(ChunkPos.asLong(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 ChunkPos - Set coords = new HashSet<>(); - LongIterator iterator = chunks.iterator(); - while (iterator.hasNext()) { - coords.add(new ChunkPos(iterator.nextLong())); - } - TaskManager.taskManager().task(() -> { - // trigger chunk load and apply ticket on main thread - List> futures = new ArrayList<>(); - for (ChunkPos pos : coords) { - futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z) - .thenAccept(c -> serverLevel.getChunkSource().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 {} chunks instead of {}", i, coords.size()); - } - // post process chunks on main thread - TaskManager.taskManager().task(() -> postProcessChunks(coords)); - // call callback on our own threads - TaskManager.taskManager().async(andThen); - } - ) - ); - }); - } - - private void invokeRelight( + protected void invokeRelight( Set coords, Consumer chunkCallback, IntConsumer processCallback ) { try { - int unused = (int) RELIGHT.invokeExact( + RELIGHT.invokeExact( serverLevel.getChunkSource().getLightEngine(), coords, chunkCallback, // callback per chunk @@ -182,7 +98,7 @@ public class PaperweightStarlightRelighter implements Relighter { * 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) { + protected void postProcessChunks(Set coords) { boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; for (ChunkPos pos : coords) { int x = pos.x; @@ -194,44 +110,4 @@ public class PaperweightStarlightRelighter implements Relighter { } } - @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/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightStarlightRelighter.java index 0e27ec7ee..c832fc98a 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightStarlightRelighter.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightStarlightRelighter.java @@ -1,138 +1,51 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R2; +import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter; import com.fastasyncworldedit.core.configuration.Settings; -import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter; -import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; import com.fastasyncworldedit.core.queue.IQueueExtent; -import com.fastasyncworldedit.core.util.MathMan; -import com.fastasyncworldedit.core.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.MCUtil; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.TicketType; import net.minecraft.util.Unit; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkStatus; -import org.apache.logging.log4j.Logger; -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 PaperweightStarlightRelighter implements Relighter { - - private static final Logger LOGGER = LogManagerCompat.getLogger(); - private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32 - private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting +public class PaperweightStarlightRelighter extends StarlightRelighter { private static final TicketType FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT); - - private final ServerLevel serverLevel; - private final ReentrantLock lock = new ReentrantLock(); - private final Long2ObjectLinkedOpenHashMap regions = new Long2ObjectLinkedOpenHashMap<>(); - private final ReentrantLock areaLock = new ReentrantLock(); - private final NMSRelighter delegate; - public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent queue) { - this.serverLevel = serverLevel; - this.delegate = new NMSRelighter(queue); + super(serverLevel, queue); } @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(ChunkPos.asLong(cx, cz)); - } finally { - areaLock.unlock(); - } - return true; + protected ChunkPos createChunkPos(final long chunkKey) { + return new ChunkPos(chunkKey); } @Override - public void addLightUpdate(int x, int y, int z) { - delegate.addLightUpdate(x, y, z); + protected long asLong(final int chunkX, final int chunkZ) { + return ChunkPos.asLong(chunkX, chunkZ); } - /* - * 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(); - } + protected CompletableFuture chunkLoadFuture(final ChunkPos chunkPos) { + return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z) + .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel( + FAWE_TICKET, + chunkPos, + LIGHT_LEVEL, + Unit.INSTANCE + )); } - /* - * 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 ChunkPos - Set coords = new HashSet<>(); - LongIterator iterator = chunks.iterator(); - while (iterator.hasNext()) { - coords.add(new ChunkPos(iterator.nextLong())); - } - TaskManager.taskManager().task(() -> { - // trigger chunk load and apply ticket on main thread - List> futures = new ArrayList<>(); - for (ChunkPos pos : coords) { - futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z) - .thenAccept(c -> serverLevel.getChunkSource().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 {} chunks instead of {}", i, coords.size()); - } - // post process chunks on main thread - TaskManager.taskManager().task(() -> postProcessChunks(coords)); - // call callback on our own threads - TaskManager.taskManager().async(andThen); - } - ) - ); - }); - } - - private void invokeRelight( + protected void invokeRelight( Set coords, Consumer chunkCallback, IntConsumer processCallback @@ -148,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter { * 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) { + protected void postProcessChunks(Set coords) { boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; for (ChunkPos pos : coords) { int x = pos.x; @@ -160,44 +73,4 @@ public class PaperweightStarlightRelighter implements Relighter { } } - @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/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightStarlightRelighter.java index 7fe32f616..580bbf5a6 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightStarlightRelighter.java +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightStarlightRelighter.java @@ -1,138 +1,51 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_19_R3; +import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter; import com.fastasyncworldedit.core.configuration.Settings; -import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter; -import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; import com.fastasyncworldedit.core.queue.IQueueExtent; -import com.fastasyncworldedit.core.util.MathMan; -import com.fastasyncworldedit.core.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.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.TicketType; import net.minecraft.util.Unit; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkStatus; -import org.apache.logging.log4j.Logger; -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 PaperweightStarlightRelighter implements Relighter { - - private static final Logger LOGGER = LogManagerCompat.getLogger(); - private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32 - private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting +public class PaperweightStarlightRelighter extends StarlightRelighter { private static final TicketType FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT); - - private final ServerLevel serverLevel; - private final ReentrantLock lock = new ReentrantLock(); - private final Long2ObjectLinkedOpenHashMap regions = new Long2ObjectLinkedOpenHashMap<>(); - private final ReentrantLock areaLock = new ReentrantLock(); - private final NMSRelighter delegate; - public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent queue) { - this.serverLevel = serverLevel; - this.delegate = new NMSRelighter(queue); + super(serverLevel, queue); } @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(ChunkPos.asLong(cx, cz)); - } finally { - areaLock.unlock(); - } - return true; + protected ChunkPos createChunkPos(final long chunkKey) { + return new ChunkPos(chunkKey); } @Override - public void addLightUpdate(int x, int y, int z) { - delegate.addLightUpdate(x, y, z); + protected long asLong(final int chunkX, final int chunkZ) { + return ChunkPos.asLong(chunkX, chunkZ); } - /* - * 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(); - } + protected CompletableFuture chunkLoadFuture(final ChunkPos chunkPos) { + return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z) + .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel( + FAWE_TICKET, + chunkPos, + LIGHT_LEVEL, + Unit.INSTANCE + )); } - /* - * 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 ChunkPos - Set coords = new HashSet<>(); - LongIterator iterator = chunks.iterator(); - while (iterator.hasNext()) { - coords.add(new ChunkPos(iterator.nextLong())); - } - TaskManager.taskManager().task(() -> { - // trigger chunk load and apply ticket on main thread - List> futures = new ArrayList<>(); - for (ChunkPos pos : coords) { - futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z) - .thenAccept(c -> serverLevel.getChunkSource().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 {} chunks instead of {}", i, coords.size()); - } - // post process chunks on main thread - TaskManager.taskManager().task(() -> postProcessChunks(coords)); - // call callback on our own threads - TaskManager.taskManager().async(andThen); - } - ) - ); - }); - } - - private void invokeRelight( + protected void invokeRelight( Set coords, Consumer chunkCallback, IntConsumer processCallback @@ -148,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter { * 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) { + protected void postProcessChunks(Set coords) { boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; for (ChunkPos pos : coords) { int x = pos.x; @@ -160,44 +73,4 @@ public class PaperweightStarlightRelighter implements Relighter { } } - @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/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightStarlightRelighter.java index 235078ee0..30df91459 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightStarlightRelighter.java +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightStarlightRelighter.java @@ -1,138 +1,51 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R1; +import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter; import com.fastasyncworldedit.core.configuration.Settings; -import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter; -import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; import com.fastasyncworldedit.core.queue.IQueueExtent; -import com.fastasyncworldedit.core.util.MathMan; -import com.fastasyncworldedit.core.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.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.TicketType; import net.minecraft.util.Unit; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkStatus; -import org.apache.logging.log4j.Logger; -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 PaperweightStarlightRelighter implements Relighter { - - private static final Logger LOGGER = LogManagerCompat.getLogger(); - private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32 - private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting +public class PaperweightStarlightRelighter extends StarlightRelighter { private static final TicketType FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT); - - private final ServerLevel serverLevel; - private final ReentrantLock lock = new ReentrantLock(); - private final Long2ObjectLinkedOpenHashMap regions = new Long2ObjectLinkedOpenHashMap<>(); - private final ReentrantLock areaLock = new ReentrantLock(); - private final NMSRelighter delegate; - public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent queue) { - this.serverLevel = serverLevel; - this.delegate = new NMSRelighter(queue); + super(serverLevel, queue); } @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(ChunkPos.asLong(cx, cz)); - } finally { - areaLock.unlock(); - } - return true; + protected ChunkPos createChunkPos(final long chunkKey) { + return new ChunkPos(chunkKey); } @Override - public void addLightUpdate(int x, int y, int z) { - delegate.addLightUpdate(x, y, z); + protected long asLong(final int chunkX, final int chunkZ) { + return ChunkPos.asLong(chunkX, chunkZ); } - /* - * 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(); - } + protected CompletableFuture chunkLoadFuture(final ChunkPos chunkPos) { + return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z) + .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel( + FAWE_TICKET, + chunkPos, + LIGHT_LEVEL, + Unit.INSTANCE + )); } - /* - * 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 ChunkPos - Set coords = new HashSet<>(); - LongIterator iterator = chunks.iterator(); - while (iterator.hasNext()) { - coords.add(new ChunkPos(iterator.nextLong())); - } - TaskManager.taskManager().task(() -> { - // trigger chunk load and apply ticket on main thread - List> futures = new ArrayList<>(); - for (ChunkPos pos : coords) { - futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z) - .thenAccept(c -> serverLevel.getChunkSource().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 {} chunks instead of {}", i, coords.size()); - } - // post process chunks on main thread - TaskManager.taskManager().task(() -> postProcessChunks(coords)); - // call callback on our own threads - TaskManager.taskManager().async(andThen); - } - ) - ); - }); - } - - private void invokeRelight( + protected void invokeRelight( Set coords, Consumer chunkCallback, IntConsumer processCallback @@ -148,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter { * 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) { + protected void postProcessChunks(Set coords) { boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; for (ChunkPos pos : coords) { int x = pos.x; @@ -160,44 +73,4 @@ public class PaperweightStarlightRelighter implements Relighter { } } - @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/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java index d02b784e8..e869046da 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java @@ -1,138 +1,51 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; +import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter; import com.fastasyncworldedit.core.configuration.Settings; -import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter; -import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; import com.fastasyncworldedit.core.queue.IQueueExtent; -import com.fastasyncworldedit.core.util.MathMan; -import com.fastasyncworldedit.core.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.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.TicketType; import net.minecraft.util.Unit; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkStatus; -import org.apache.logging.log4j.Logger; -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 PaperweightStarlightRelighter implements Relighter { - - private static final Logger LOGGER = LogManagerCompat.getLogger(); - private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32 - private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting +public class PaperweightStarlightRelighter extends StarlightRelighter { private static final TicketType FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT); - - private final ServerLevel serverLevel; - private final ReentrantLock lock = new ReentrantLock(); - private final Long2ObjectLinkedOpenHashMap regions = new Long2ObjectLinkedOpenHashMap<>(); - private final ReentrantLock areaLock = new ReentrantLock(); - private final NMSRelighter delegate; - public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent queue) { - this.serverLevel = serverLevel; - this.delegate = new NMSRelighter(queue); + super(serverLevel, queue); } @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(ChunkPos.asLong(cx, cz)); - } finally { - areaLock.unlock(); - } - return true; + protected ChunkPos createChunkPos(final long chunkKey) { + return new ChunkPos(chunkKey); } @Override - public void addLightUpdate(int x, int y, int z) { - delegate.addLightUpdate(x, y, z); + protected long asLong(final int chunkX, final int chunkZ) { + return ChunkPos.asLong(chunkX, chunkZ); } - /* - * 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(); - } + protected CompletableFuture chunkLoadFuture(final ChunkPos chunkPos) { + return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z) + .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel( + FAWE_TICKET, + chunkPos, + LIGHT_LEVEL, + Unit.INSTANCE + )); } - /* - * 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 ChunkPos - Set coords = new HashSet<>(); - LongIterator iterator = chunks.iterator(); - while (iterator.hasNext()) { - coords.add(new ChunkPos(iterator.nextLong())); - } - TaskManager.taskManager().task(() -> { - // trigger chunk load and apply ticket on main thread - List> futures = new ArrayList<>(); - for (ChunkPos pos : coords) { - futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z) - .thenAccept(c -> serverLevel.getChunkSource().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 {} chunks instead of {}", i, coords.size()); - } - // post process chunks on main thread - TaskManager.taskManager().task(() -> postProcessChunks(coords)); - // call callback on our own threads - TaskManager.taskManager().async(andThen); - } - ) - ); - }); - } - - private void invokeRelight( + protected void invokeRelight( Set coords, Consumer chunkCallback, IntConsumer processCallback @@ -148,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter { * 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) { + protected void postProcessChunks(Set coords) { boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; for (ChunkPos pos : coords) { int x = pos.x; @@ -160,44 +73,4 @@ public class PaperweightStarlightRelighter implements Relighter { } } - @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/fastasyncworldedit/bukkit/adapter/StarlightRelighter.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/StarlightRelighter.java new file mode 100644 index 000000000..95200054e --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/StarlightRelighter.java @@ -0,0 +1,196 @@ +package com.fastasyncworldedit.bukkit.adapter; + +import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter; +import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; +import com.fastasyncworldedit.core.queue.IQueueExtent; +import com.fastasyncworldedit.core.util.MathMan; +import com.fastasyncworldedit.core.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 org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +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; + +/** + * A base class for version-specific implementations of the starlight relighting mechanism + * + * @param the version-specific ServerLevel type + * @param the version-specific ChunkPos type + * @since TODO + */ +public abstract class StarlightRelighter implements Relighter { + + protected static final Logger LOGGER = LogManagerCompat.getLogger(); + + private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32 + private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting + + private final ReentrantLock lock = new ReentrantLock(); + private final Long2ObjectLinkedOpenHashMap regions = new Long2ObjectLinkedOpenHashMap<>(); + private final ReentrantLock areaLock = new ReentrantLock(); + private final NMSRelighter delegate; + protected final SERVER_LEVEL serverLevel; + + protected StarlightRelighter(SERVER_LEVEL serverLevel, IQueueExtent queue) { + this.serverLevel = serverLevel; + this.delegate = new NMSRelighter(queue); + } + + protected Set convertChunkKeysToChunkPos(LongSet chunks) { + // convert from long keys to ChunkPos + Set coords = new HashSet<>(); + LongIterator iterator = chunks.iterator(); + while (iterator.hasNext()) { + coords.add(createChunkPos(iterator.nextLong())); + } + return coords; + } + + protected abstract CHUNK_POS createChunkPos(long chunkKey); + + protected abstract long asLong(int chunkX, int chunkZ); + + protected abstract CompletableFuture chunkLoadFuture(CHUNK_POS pos); + + protected List> chunkLoadFutures(Set coords) { + List> futures = new ArrayList<>(); + for (final CHUNK_POS coord : coords) { + futures.add(chunkLoadFuture(coord)); + } + return futures; + } + + @NotNull + protected IntConsumer postProcessCallback(Runnable andThen, Set coords) { + return i -> { + if (i != coords.size()) { + LOGGER.warn("Processed {} chunks instead of {}", i, coords.size()); + } + // post process chunks on main thread + TaskManager.taskManager().task(() -> postProcessChunks(coords)); + // call callback on our own threads + TaskManager.taskManager().async(andThen); + }; + } + + protected abstract void invokeRelight( + Set coords, + Consumer chunkCallback, + IntConsumer processCallback + ); + + protected abstract void postProcessChunks(Set coords); + + /* + * 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). + */ + protected void fixLighting(LongSet chunks, Runnable andThen) { + Set coords = convertChunkKeysToChunkPos(chunks); + TaskManager.taskManager().task(() -> { + // trigger chunk load and apply ticket on main thread + List> futures = chunkLoadFutures(coords); + // 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 + postProcessCallback(andThen, coords) + ) + ); + }); + } + + @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(asLong(cx, cz)); + } finally { + areaLock.unlock(); + } + return true; + } + + /* + * 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(); + } + } + + @Override + public void addLightUpdate(int x, int y, int z) { + this.delegate.addLightUpdate(x, y, z); + } + + @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); + } + +}