mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-22 17:27:38 +00:00
Reuse starlight relighting code across versions (#2474)
This commit is contained in:
parent
b8434f891e
commit
6bd1382872
@ -1,16 +1,8 @@
|
|||||||
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_17_R1_2;
|
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.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.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.MCUtil;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
||||||
@ -18,27 +10,18 @@ import net.minecraft.server.level.TicketType;
|
|||||||
import net.minecraft.util.Unit;
|
import net.minecraft.util.Unit;
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.IntConsumer;
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
public class PaperweightStarlightRelighter implements Relighter {
|
public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLevel, ChunkPos> {
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
private static final MethodHandle RELIGHT;
|
||||||
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
||||||
private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT);
|
private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT);
|
||||||
|
|
||||||
@ -57,21 +40,36 @@ public class PaperweightStarlightRelighter implements Relighter {
|
|||||||
IntConsumer.class
|
IntConsumer.class
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
tmp = MethodHandles.dropReturn(tmp);
|
||||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
LOGGER.error("Failed to locate 'relight' method in ThreadedLevelLightEngine. Is everything up to date?", e);
|
LOGGER.error("Failed to locate 'relight' method in ThreadedLevelLightEngine. Is everything up to date?", e);
|
||||||
}
|
}
|
||||||
RELIGHT = tmp;
|
RELIGHT = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ServerLevel serverLevel;
|
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
|
||||||
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
|
||||||
private final ReentrantLock areaLock = new ReentrantLock();
|
|
||||||
private final NMSRelighter delegate;
|
|
||||||
|
|
||||||
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
||||||
this.serverLevel = serverLevel;
|
super(serverLevel, queue);
|
||||||
this.delegate = new NMSRelighter(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() {
|
public static boolean isUsable() {
|
||||||
@ -79,95 +77,13 @@ public class PaperweightStarlightRelighter implements Relighter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) {
|
protected void invokeRelight(
|
||||||
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<ChunkPos> 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<CompletableFuture<?>> 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(
|
|
||||||
Set<ChunkPos> coords,
|
Set<ChunkPos> coords,
|
||||||
Consumer<ChunkPos> chunkCallback,
|
Consumer<ChunkPos> chunkCallback,
|
||||||
IntConsumer processCallback
|
IntConsumer processCallback
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
int unused = (int) RELIGHT.invokeExact(
|
RELIGHT.invokeExact(
|
||||||
serverLevel.getChunkSource().getLightEngine(),
|
serverLevel.getChunkSource().getLightEngine(),
|
||||||
coords,
|
coords,
|
||||||
chunkCallback, // callback per chunk
|
chunkCallback, // callback per chunk
|
||||||
@ -182,7 +98,7 @@ public class PaperweightStarlightRelighter implements Relighter {
|
|||||||
* Allow the server to unload the chunks again.
|
* Allow the server to unload the chunks again.
|
||||||
* Also, if chunk packets are sent delayed, we need to do that here
|
* Also, if chunk packets are sent delayed, we need to do that here
|
||||||
*/
|
*/
|
||||||
private void postProcessChunks(Set<ChunkPos> coords) {
|
protected void postProcessChunks(Set<ChunkPos> coords) {
|
||||||
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
||||||
for (ChunkPos pos : coords) {
|
for (ChunkPos pos : coords) {
|
||||||
int x = pos.x;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,138 +1,51 @@
|
|||||||
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R2;
|
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.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.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.MCUtil;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.TicketType;
|
import net.minecraft.server.level.TicketType;
|
||||||
import net.minecraft.util.Unit;
|
import net.minecraft.util.Unit;
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
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.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.IntConsumer;
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
public class PaperweightStarlightRelighter implements Relighter {
|
public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLevel, ChunkPos> {
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
||||||
private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT);
|
private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT);
|
||||||
|
|
||||||
|
|
||||||
private final ServerLevel serverLevel;
|
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
|
||||||
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
|
||||||
private final ReentrantLock areaLock = new ReentrantLock();
|
|
||||||
private final NMSRelighter delegate;
|
|
||||||
|
|
||||||
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
||||||
this.serverLevel = serverLevel;
|
super(serverLevel, queue);
|
||||||
this.delegate = new NMSRelighter(queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) {
|
protected ChunkPos createChunkPos(final long chunkKey) {
|
||||||
areaLock.lock();
|
return new ChunkPos(chunkKey);
|
||||||
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
|
@Override
|
||||||
public void addLightUpdate(int x, int y, int z) {
|
protected long asLong(final int chunkX, final int chunkZ) {
|
||||||
delegate.addLightUpdate(x, y, z);
|
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
|
@Override
|
||||||
public void fixLightingSafe(boolean sky) {
|
protected CompletableFuture<?> chunkLoadFuture(final ChunkPos chunkPos) {
|
||||||
this.areaLock.lock();
|
return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z)
|
||||||
try {
|
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
|
||||||
if (regions.isEmpty()) {
|
FAWE_TICKET,
|
||||||
return;
|
chunkPos,
|
||||||
}
|
LIGHT_LEVEL,
|
||||||
LongSet first = regions.removeFirst();
|
Unit.INSTANCE
|
||||||
fixLighting(first, () -> fixLightingSafe(true));
|
));
|
||||||
} finally {
|
|
||||||
this.areaLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
protected void invokeRelight(
|
||||||
* 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<ChunkPos> 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<CompletableFuture<?>> 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(
|
|
||||||
Set<ChunkPos> coords,
|
Set<ChunkPos> coords,
|
||||||
Consumer<ChunkPos> chunkCallback,
|
Consumer<ChunkPos> chunkCallback,
|
||||||
IntConsumer processCallback
|
IntConsumer processCallback
|
||||||
@ -148,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter {
|
|||||||
* Allow the server to unload the chunks again.
|
* Allow the server to unload the chunks again.
|
||||||
* Also, if chunk packets are sent delayed, we need to do that here
|
* Also, if chunk packets are sent delayed, we need to do that here
|
||||||
*/
|
*/
|
||||||
private void postProcessChunks(Set<ChunkPos> coords) {
|
protected void postProcessChunks(Set<ChunkPos> coords) {
|
||||||
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
||||||
for (ChunkPos pos : coords) {
|
for (ChunkPos pos : coords) {
|
||||||
int x = pos.x;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,138 +1,51 @@
|
|||||||
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_19_R3;
|
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.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.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.ChunkMap;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.TicketType;
|
import net.minecraft.server.level.TicketType;
|
||||||
import net.minecraft.util.Unit;
|
import net.minecraft.util.Unit;
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
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.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.IntConsumer;
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
public class PaperweightStarlightRelighter implements Relighter {
|
public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLevel, ChunkPos> {
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
||||||
private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT);
|
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<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
|
||||||
private final ReentrantLock areaLock = new ReentrantLock();
|
|
||||||
private final NMSRelighter delegate;
|
|
||||||
|
|
||||||
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
||||||
this.serverLevel = serverLevel;
|
super(serverLevel, queue);
|
||||||
this.delegate = new NMSRelighter(queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) {
|
protected ChunkPos createChunkPos(final long chunkKey) {
|
||||||
areaLock.lock();
|
return new ChunkPos(chunkKey);
|
||||||
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
|
@Override
|
||||||
public void addLightUpdate(int x, int y, int z) {
|
protected long asLong(final int chunkX, final int chunkZ) {
|
||||||
delegate.addLightUpdate(x, y, z);
|
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
|
@Override
|
||||||
public void fixLightingSafe(boolean sky) {
|
protected CompletableFuture<?> chunkLoadFuture(final ChunkPos chunkPos) {
|
||||||
this.areaLock.lock();
|
return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z)
|
||||||
try {
|
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
|
||||||
if (regions.isEmpty()) {
|
FAWE_TICKET,
|
||||||
return;
|
chunkPos,
|
||||||
}
|
LIGHT_LEVEL,
|
||||||
LongSet first = regions.removeFirst();
|
Unit.INSTANCE
|
||||||
fixLighting(first, () -> fixLightingSafe(true));
|
));
|
||||||
} finally {
|
|
||||||
this.areaLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
protected void invokeRelight(
|
||||||
* 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<ChunkPos> 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<CompletableFuture<?>> 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(
|
|
||||||
Set<ChunkPos> coords,
|
Set<ChunkPos> coords,
|
||||||
Consumer<ChunkPos> chunkCallback,
|
Consumer<ChunkPos> chunkCallback,
|
||||||
IntConsumer processCallback
|
IntConsumer processCallback
|
||||||
@ -148,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter {
|
|||||||
* Allow the server to unload the chunks again.
|
* Allow the server to unload the chunks again.
|
||||||
* Also, if chunk packets are sent delayed, we need to do that here
|
* Also, if chunk packets are sent delayed, we need to do that here
|
||||||
*/
|
*/
|
||||||
private void postProcessChunks(Set<ChunkPos> coords) {
|
protected void postProcessChunks(Set<ChunkPos> coords) {
|
||||||
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
||||||
for (ChunkPos pos : coords) {
|
for (ChunkPos pos : coords) {
|
||||||
int x = pos.x;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,138 +1,51 @@
|
|||||||
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R1;
|
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.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.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.ChunkMap;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.TicketType;
|
import net.minecraft.server.level.TicketType;
|
||||||
import net.minecraft.util.Unit;
|
import net.minecraft.util.Unit;
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
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.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.IntConsumer;
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
public class PaperweightStarlightRelighter implements Relighter {
|
public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLevel, ChunkPos> {
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
||||||
private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT);
|
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<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
|
||||||
private final ReentrantLock areaLock = new ReentrantLock();
|
|
||||||
private final NMSRelighter delegate;
|
|
||||||
|
|
||||||
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
||||||
this.serverLevel = serverLevel;
|
super(serverLevel, queue);
|
||||||
this.delegate = new NMSRelighter(queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) {
|
protected ChunkPos createChunkPos(final long chunkKey) {
|
||||||
areaLock.lock();
|
return new ChunkPos(chunkKey);
|
||||||
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
|
@Override
|
||||||
public void addLightUpdate(int x, int y, int z) {
|
protected long asLong(final int chunkX, final int chunkZ) {
|
||||||
delegate.addLightUpdate(x, y, z);
|
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
|
@Override
|
||||||
public void fixLightingSafe(boolean sky) {
|
protected CompletableFuture<?> chunkLoadFuture(final ChunkPos chunkPos) {
|
||||||
this.areaLock.lock();
|
return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z)
|
||||||
try {
|
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
|
||||||
if (regions.isEmpty()) {
|
FAWE_TICKET,
|
||||||
return;
|
chunkPos,
|
||||||
}
|
LIGHT_LEVEL,
|
||||||
LongSet first = regions.removeFirst();
|
Unit.INSTANCE
|
||||||
fixLighting(first, () -> fixLightingSafe(true));
|
));
|
||||||
} finally {
|
|
||||||
this.areaLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
protected void invokeRelight(
|
||||||
* 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<ChunkPos> 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<CompletableFuture<?>> 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(
|
|
||||||
Set<ChunkPos> coords,
|
Set<ChunkPos> coords,
|
||||||
Consumer<ChunkPos> chunkCallback,
|
Consumer<ChunkPos> chunkCallback,
|
||||||
IntConsumer processCallback
|
IntConsumer processCallback
|
||||||
@ -148,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter {
|
|||||||
* Allow the server to unload the chunks again.
|
* Allow the server to unload the chunks again.
|
||||||
* Also, if chunk packets are sent delayed, we need to do that here
|
* Also, if chunk packets are sent delayed, we need to do that here
|
||||||
*/
|
*/
|
||||||
private void postProcessChunks(Set<ChunkPos> coords) {
|
protected void postProcessChunks(Set<ChunkPos> coords) {
|
||||||
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
||||||
for (ChunkPos pos : coords) {
|
for (ChunkPos pos : coords) {
|
||||||
int x = pos.x;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,138 +1,51 @@
|
|||||||
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
|
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.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.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.ChunkMap;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.TicketType;
|
import net.minecraft.server.level.TicketType;
|
||||||
import net.minecraft.util.Unit;
|
import net.minecraft.util.Unit;
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
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.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.IntConsumer;
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
public class PaperweightStarlightRelighter implements Relighter {
|
public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLevel, ChunkPos> {
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
|
||||||
private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT);
|
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<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
|
||||||
private final ReentrantLock areaLock = new ReentrantLock();
|
|
||||||
private final NMSRelighter delegate;
|
|
||||||
|
|
||||||
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
||||||
this.serverLevel = serverLevel;
|
super(serverLevel, queue);
|
||||||
this.delegate = new NMSRelighter(queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) {
|
protected ChunkPos createChunkPos(final long chunkKey) {
|
||||||
areaLock.lock();
|
return new ChunkPos(chunkKey);
|
||||||
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
|
@Override
|
||||||
public void addLightUpdate(int x, int y, int z) {
|
protected long asLong(final int chunkX, final int chunkZ) {
|
||||||
delegate.addLightUpdate(x, y, z);
|
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
|
@Override
|
||||||
public void fixLightingSafe(boolean sky) {
|
protected CompletableFuture<?> chunkLoadFuture(final ChunkPos chunkPos) {
|
||||||
this.areaLock.lock();
|
return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z)
|
||||||
try {
|
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
|
||||||
if (regions.isEmpty()) {
|
FAWE_TICKET,
|
||||||
return;
|
chunkPos,
|
||||||
}
|
LIGHT_LEVEL,
|
||||||
LongSet first = regions.removeFirst();
|
Unit.INSTANCE
|
||||||
fixLighting(first, () -> fixLightingSafe(true));
|
));
|
||||||
} finally {
|
|
||||||
this.areaLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
protected void invokeRelight(
|
||||||
* 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<ChunkPos> 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<CompletableFuture<?>> 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(
|
|
||||||
Set<ChunkPos> coords,
|
Set<ChunkPos> coords,
|
||||||
Consumer<ChunkPos> chunkCallback,
|
Consumer<ChunkPos> chunkCallback,
|
||||||
IntConsumer processCallback
|
IntConsumer processCallback
|
||||||
@ -148,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter {
|
|||||||
* Allow the server to unload the chunks again.
|
* Allow the server to unload the chunks again.
|
||||||
* Also, if chunk packets are sent delayed, we need to do that here
|
* Also, if chunk packets are sent delayed, we need to do that here
|
||||||
*/
|
*/
|
||||||
private void postProcessChunks(Set<ChunkPos> coords) {
|
protected void postProcessChunks(Set<ChunkPos> coords) {
|
||||||
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
|
||||||
for (ChunkPos pos : coords) {
|
for (ChunkPos pos : coords) {
|
||||||
int x = pos.x;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 <SERVER_LEVEL> the version-specific ServerLevel type
|
||||||
|
* @param <CHUNK_POS> the version-specific ChunkPos type
|
||||||
|
* @since TODO
|
||||||
|
*/
|
||||||
|
public abstract class StarlightRelighter<SERVER_LEVEL, CHUNK_POS> 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<LongSet> 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<CHUNK_POS> convertChunkKeysToChunkPos(LongSet chunks) {
|
||||||
|
// convert from long keys to ChunkPos
|
||||||
|
Set<CHUNK_POS> 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<CompletableFuture<?>> chunkLoadFutures(Set<CHUNK_POS> coords) {
|
||||||
|
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||||
|
for (final CHUNK_POS coord : coords) {
|
||||||
|
futures.add(chunkLoadFuture(coord));
|
||||||
|
}
|
||||||
|
return futures;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
protected IntConsumer postProcessCallback(Runnable andThen, Set<CHUNK_POS> 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<CHUNK_POS> coords,
|
||||||
|
Consumer<CHUNK_POS> chunkCallback,
|
||||||
|
IntConsumer processCallback
|
||||||
|
);
|
||||||
|
|
||||||
|
protected abstract void postProcessChunks(Set<CHUNK_POS> 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<CHUNK_POS> coords = convertChunkKeysToChunkPos(chunks);
|
||||||
|
TaskManager.taskManager().task(() -> {
|
||||||
|
// trigger chunk load and apply ticket on main thread
|
||||||
|
List<CompletableFuture<?>> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user