Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-10-05 05:10:07 +02:00
Merge branch 'v3' into feature/anvil
Dieser Commit ist enthalten in:
Commit
32c9a80d63
23
.github/workflows/label-merge-conflicts.yaml
vendored
Normale Datei
23
.github/workflows/label-merge-conflicts.yaml
vendored
Normale Datei
@ -0,0 +1,23 @@
|
|||||||
|
name: "Label conflicting PRs"
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request_target:
|
||||||
|
types: [ synchronize ]
|
||||||
|
pull_request:
|
||||||
|
types: [ synchronize ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
if: github.event.pull_request.user.login != 'dependabot[bot]'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Label conflicting PRs
|
||||||
|
uses: eps1lon/actions-label-merge-conflict@v2.1.0
|
||||||
|
with:
|
||||||
|
dirtyLabel: "unresolved-merge-conflict"
|
||||||
|
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
commentOnDirty: "Please take a moment and address the merge conflicts of your pull request. Thanks!"
|
||||||
|
continueOnMissingPermissions: true
|
@ -34,7 +34,7 @@ logger.lifecycle("""
|
|||||||
*******************************************
|
*******************************************
|
||||||
""")
|
""")
|
||||||
|
|
||||||
var rootVersion by extra("2.8.2")
|
var rootVersion by extra("2.8.3")
|
||||||
var snapshot by extra("SNAPSHOT")
|
var snapshot by extra("SNAPSHOT")
|
||||||
var revision: String by extra("")
|
var revision: String by extra("")
|
||||||
var buildNumber by extra("")
|
var buildNumber by extra("")
|
||||||
|
@ -22,7 +22,7 @@ val properties = Properties().also { props ->
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(gradleApi())
|
implementation(gradleApi())
|
||||||
implementation("org.ajoberstar.grgit:grgit-gradle:5.2.0")
|
implementation("org.ajoberstar.grgit:grgit-gradle:5.2.1")
|
||||||
implementation("com.github.johnrengelman:shadow:8.1.1")
|
implementation("com.github.johnrengelman:shadow:8.1.1")
|
||||||
implementation("io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin:1.5.5")
|
implementation("io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin:1.5.5")
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,8 @@ mapmanager = "1.8.0-SNAPSHOT"
|
|||||||
griefprevention = "16.18.1"
|
griefprevention = "16.18.1"
|
||||||
griefdefender = "2.1.0-SNAPSHOT"
|
griefdefender = "2.1.0-SNAPSHOT"
|
||||||
residence = "4.5._13.1"
|
residence = "4.5._13.1"
|
||||||
towny = "0.99.6.0"
|
towny = "0.100.0.1"
|
||||||
plotsquared = "7.0.0"
|
plotsquared = "7.1.0"
|
||||||
|
|
||||||
# Third party
|
# Third party
|
||||||
bstats = "3.0.2"
|
bstats = "3.0.2"
|
||||||
@ -23,7 +23,7 @@ sparsebitset = "1.3"
|
|||||||
parallelgzip = "1.0.5"
|
parallelgzip = "1.0.5"
|
||||||
adventure = "4.14.0"
|
adventure = "4.14.0"
|
||||||
adventure-bukkit = "4.3.1"
|
adventure-bukkit = "4.3.1"
|
||||||
checkerqual = "3.39.0"
|
checkerqual = "3.40.0"
|
||||||
truezip = "6.8.4"
|
truezip = "6.8.4"
|
||||||
auto-value = "1.10.4"
|
auto-value = "1.10.4"
|
||||||
findbugs = "3.0.2"
|
findbugs = "3.0.2"
|
||||||
@ -35,7 +35,7 @@ jlibnoise = "1.0.0"
|
|||||||
jchronic = "0.2.4a"
|
jchronic = "0.2.4a"
|
||||||
lz4-java = "1.8.0"
|
lz4-java = "1.8.0"
|
||||||
lz4-stream = "1.0.0"
|
lz4-stream = "1.0.0"
|
||||||
commons-cli = "1.5.0"
|
commons-cli = "1.6.0"
|
||||||
paperlib = "1.0.8"
|
paperlib = "1.0.8"
|
||||||
paster = "1.1.5"
|
paster = "1.1.5"
|
||||||
vault = "1.7.1"
|
vault = "1.7.1"
|
||||||
|
@ -72,9 +72,11 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -91,6 +93,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
.getInstance()
|
.getInstance()
|
||||||
.getBukkitImplAdapter());
|
.getBukkitImplAdapter());
|
||||||
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
|
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
|
||||||
|
private final ReentrantLock callLock = new ReentrantLock();
|
||||||
private final ServerLevel serverLevel;
|
private final ServerLevel serverLevel;
|
||||||
private final int chunkX;
|
private final int chunkX;
|
||||||
private final int chunkZ;
|
private final int chunkZ;
|
||||||
@ -98,14 +101,16 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
private final int maxHeight;
|
private final int maxHeight;
|
||||||
private final int minSectionPosition;
|
private final int minSectionPosition;
|
||||||
private final int maxSectionPosition;
|
private final int maxSectionPosition;
|
||||||
|
private final ConcurrentHashMap<Integer, PaperweightGetBlocks_Copy> copies = new ConcurrentHashMap<>();
|
||||||
|
private final Object sendLock = new Object();
|
||||||
private LevelChunkSection[] sections;
|
private LevelChunkSection[] sections;
|
||||||
private LevelChunk levelChunk;
|
private LevelChunk levelChunk;
|
||||||
private DataLayer[] blockLight;
|
private DataLayer[] blockLight;
|
||||||
private DataLayer[] skyLight;
|
private DataLayer[] skyLight;
|
||||||
private boolean createCopy = false;
|
private boolean createCopy = false;
|
||||||
private PaperweightGetBlocks_Copy copy = null;
|
|
||||||
private boolean forceLoadSections = true;
|
private boolean forceLoadSections = true;
|
||||||
private boolean lightUpdate = false;
|
private boolean lightUpdate = false;
|
||||||
|
private int copyKey = 0;
|
||||||
|
|
||||||
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
|
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
|
||||||
this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
|
this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
|
||||||
@ -138,13 +143,27 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
if (!callLock.isHeldByCurrentThread()) {
|
||||||
|
throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked.");
|
||||||
|
}
|
||||||
this.createCopy = createCopy;
|
this.createCopy = createCopy;
|
||||||
|
return ++this.copyKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IChunkGet getCopy() {
|
public IChunkGet getCopy(final int key) {
|
||||||
return copy;
|
return copies.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lockCall() {
|
||||||
|
this.callLock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unlockCall() {
|
||||||
|
this.callLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -393,8 +412,17 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
|
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
|
||||||
|
if (!callLock.isHeldByCurrentThread()) {
|
||||||
|
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
|
||||||
|
}
|
||||||
forceLoadSections = false;
|
forceLoadSections = false;
|
||||||
copy = createCopy ? new PaperweightGetBlocks_Copy(getChunk()) : null;
|
PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
|
||||||
|
if (createCopy) {
|
||||||
|
if (copies.containsKey(copyKey)) {
|
||||||
|
throw new IllegalStateException("Copy key already used.");
|
||||||
|
}
|
||||||
|
copies.put(copyKey, copy);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ServerLevel nmsWorld = serverLevel;
|
ServerLevel nmsWorld = serverLevel;
|
||||||
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
|
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
|
||||||
@ -831,9 +859,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
if (super.sections[layer] != null) {
|
if (super.sections[layer] != null) {
|
||||||
synchronized (super.sectionLocks[layer]) {
|
synchronized (super.sectionLocks[layer]) {
|
||||||
if (super.sections[layer].isFull() && super.blocks[layer] != null) {
|
if (super.sections[layer].isFull() && super.blocks[layer] != null) {
|
||||||
char[] blocks = new char[4096];
|
return super.blocks[layer];
|
||||||
System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096);
|
|
||||||
return blocks;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -841,8 +867,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void send(int mask, boolean lighting) {
|
public void send(int mask, boolean lighting) {
|
||||||
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
|
synchronized (sendLock) {
|
||||||
|
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -946,9 +974,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
|
|
||||||
public LevelChunkSection[] getSections(boolean force) {
|
public LevelChunkSection[] getSections(boolean force) {
|
||||||
force &= forceLoadSections;
|
force &= forceLoadSections;
|
||||||
sectionLock.readLock().lock();
|
|
||||||
LevelChunkSection[] tmp = sections;
|
LevelChunkSection[] tmp = sections;
|
||||||
sectionLock.readLock().unlock();
|
|
||||||
if (tmp == null || force) {
|
if (tmp == null || force) {
|
||||||
try {
|
try {
|
||||||
sectionLock.writeLock().lock();
|
sectionLock.writeLock().lock();
|
||||||
|
@ -22,6 +22,7 @@ import net.minecraft.world.level.chunk.ChunkBiomeContainer;
|
|||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -99,7 +100,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -196,6 +198,10 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
|
|||||||
@Override
|
@Override
|
||||||
public char[] load(int layer) {
|
public char[] load(int layer) {
|
||||||
layer -= getMinSectionPosition();
|
layer -= getMinSectionPosition();
|
||||||
|
if (blocks[layer] == null) {
|
||||||
|
blocks[layer] = new char[4096];
|
||||||
|
Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR);
|
||||||
|
}
|
||||||
return blocks[layer];
|
return blocks[layer];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +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.IQueueChunk;
|
|
||||||
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;
|
||||||
@ -19,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);
|
||||||
|
|
||||||
@ -58,22 +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;
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
super(serverLevel, queue);
|
||||||
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
}
|
||||||
private final ReentrantLock areaLock = new ReentrantLock();
|
|
||||||
private final NMSRelighter delegate;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@Override
|
||||||
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
|
protected ChunkPos createChunkPos(final long chunkKey) {
|
||||||
this.serverLevel = serverLevel;
|
return new ChunkPos(chunkKey);
|
||||||
this.delegate = new NMSRelighter(queue);
|
}
|
||||||
|
|
||||||
|
@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() {
|
||||||
@ -81,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
|
||||||
@ -184,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;
|
||||||
@ -196,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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
|
|||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||||
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
|
||||||
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@ -15,9 +14,7 @@ import javax.annotation.Nonnull;
|
|||||||
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nonnull
|
public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
|
|
||||||
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
||||||
if (w == null) {
|
if (w == null) {
|
||||||
return NullRelighter.INSTANCE;
|
return NullRelighter.INSTANCE;
|
||||||
|
@ -14,7 +14,6 @@ import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks;
|
|||||||
import com.fastasyncworldedit.core.util.MathMan;
|
import com.fastasyncworldedit.core.util.MathMan;
|
||||||
import com.fastasyncworldedit.core.util.collection.AdaptedMap;
|
import com.fastasyncworldedit.core.util.collection.AdaptedMap;
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.sk89q.jnbt.CompoundTag;
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
import com.sk89q.jnbt.ListTag;
|
import com.sk89q.jnbt.ListTag;
|
||||||
import com.sk89q.jnbt.StringTag;
|
import com.sk89q.jnbt.StringTag;
|
||||||
@ -66,7 +65,6 @@ import javax.annotation.Nonnull;
|
|||||||
import java.util.AbstractSet;
|
import java.util.AbstractSet;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -76,13 +74,14 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks {
|
public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks {
|
||||||
|
|
||||||
@ -95,6 +94,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
.getInstance()
|
.getInstance()
|
||||||
.getBukkitImplAdapter());
|
.getBukkitImplAdapter());
|
||||||
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
|
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
|
||||||
|
private final ReentrantLock callLock = new ReentrantLock();
|
||||||
private final ServerLevel serverLevel;
|
private final ServerLevel serverLevel;
|
||||||
private final int chunkX;
|
private final int chunkX;
|
||||||
private final int chunkZ;
|
private final int chunkZ;
|
||||||
@ -104,14 +104,16 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
private final int maxSectionPosition;
|
private final int maxSectionPosition;
|
||||||
private final Registry<Biome> biomeRegistry;
|
private final Registry<Biome> biomeRegistry;
|
||||||
private final IdMap<Holder<Biome>> biomeHolderIdMap;
|
private final IdMap<Holder<Biome>> biomeHolderIdMap;
|
||||||
|
private final ConcurrentHashMap<Integer, PaperweightGetBlocks_Copy> copies = new ConcurrentHashMap<>();
|
||||||
|
private final Object sendLock = new Object();
|
||||||
private LevelChunkSection[] sections;
|
private LevelChunkSection[] sections;
|
||||||
private LevelChunk levelChunk;
|
private LevelChunk levelChunk;
|
||||||
private DataLayer[] blockLight;
|
private DataLayer[] blockLight;
|
||||||
private DataLayer[] skyLight;
|
private DataLayer[] skyLight;
|
||||||
private boolean createCopy = false;
|
private boolean createCopy = false;
|
||||||
private PaperweightGetBlocks_Copy copy = null;
|
|
||||||
private boolean forceLoadSections = true;
|
private boolean forceLoadSections = true;
|
||||||
private boolean lightUpdate = false;
|
private boolean lightUpdate = false;
|
||||||
|
private int copyKey = 0;
|
||||||
|
|
||||||
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
|
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
|
||||||
this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
|
this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
|
||||||
@ -146,13 +148,27 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
if (!callLock.isHeldByCurrentThread()) {
|
||||||
|
throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked.");
|
||||||
|
}
|
||||||
this.createCopy = createCopy;
|
this.createCopy = createCopy;
|
||||||
|
return ++this.copyKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IChunkGet getCopy() {
|
public IChunkGet getCopy(final int key) {
|
||||||
return copy;
|
return copies.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lockCall() {
|
||||||
|
this.callLock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unlockCall() {
|
||||||
|
this.callLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -387,8 +403,17 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
|
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
|
||||||
|
if (!callLock.isHeldByCurrentThread()) {
|
||||||
|
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
|
||||||
|
}
|
||||||
forceLoadSections = false;
|
forceLoadSections = false;
|
||||||
copy = createCopy ? new PaperweightGetBlocks_Copy(getChunk()) : null;
|
PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
|
||||||
|
if (createCopy) {
|
||||||
|
if (copies.containsKey(copyKey)) {
|
||||||
|
throw new IllegalStateException("Copy key already used.");
|
||||||
|
}
|
||||||
|
copies.put(copyKey, copy);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ServerLevel nmsWorld = serverLevel;
|
ServerLevel nmsWorld = serverLevel;
|
||||||
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
|
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
|
||||||
@ -882,9 +907,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
if (super.sections[layer] != null) {
|
if (super.sections[layer] != null) {
|
||||||
synchronized (super.sectionLocks[layer]) {
|
synchronized (super.sectionLocks[layer]) {
|
||||||
if (super.sections[layer].isFull() && super.blocks[layer] != null) {
|
if (super.sections[layer].isFull() && super.blocks[layer] != null) {
|
||||||
char[] blocks = new char[4096];
|
return super.blocks[layer];
|
||||||
System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096);
|
|
||||||
return blocks;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -892,8 +915,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void send(int mask, boolean lighting) {
|
public void send(int mask, boolean lighting) {
|
||||||
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
|
synchronized (sendLock) {
|
||||||
|
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1004,9 +1029,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
|
|
||||||
public LevelChunkSection[] getSections(boolean force) {
|
public LevelChunkSection[] getSections(boolean force) {
|
||||||
force &= forceLoadSections;
|
force &= forceLoadSections;
|
||||||
sectionLock.readLock().lock();
|
|
||||||
LevelChunkSection[] tmp = sections;
|
LevelChunkSection[] tmp = sections;
|
||||||
sectionLock.readLock().unlock();
|
|
||||||
if (tmp == null || force) {
|
if (tmp == null || force) {
|
||||||
try {
|
try {
|
||||||
sectionLock.writeLock().lock();
|
sectionLock.writeLock().lock();
|
||||||
|
@ -11,7 +11,6 @@ import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
|||||||
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R2.nbt.PaperweightLazyCompoundTag;
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R2.nbt.PaperweightLazyCompoundTag;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
|
||||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
import com.sk89q.worldedit.world.block.BlockState;
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
@ -24,6 +23,7 @@ import net.minecraft.world.level.chunk.LevelChunk;
|
|||||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -101,7 +101,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -187,6 +188,10 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
|
|||||||
@Override
|
@Override
|
||||||
public char[] load(int layer) {
|
public char[] load(int layer) {
|
||||||
layer -= getMinSectionPosition();
|
layer -= getMinSectionPosition();
|
||||||
|
if (blocks[layer] == null) {
|
||||||
|
blocks[layer] = new char[4096];
|
||||||
|
Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR);
|
||||||
|
}
|
||||||
return blocks[layer];
|
return blocks[layer];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,140 +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.IQueueChunk;
|
|
||||||
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);
|
||||||
|
|
||||||
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
||||||
private final ServerLevel serverLevel;
|
super(serverLevel, queue);
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
|
||||||
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
|
||||||
private final ReentrantLock areaLock = new ReentrantLock();
|
|
||||||
private final NMSRelighter delegate;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
|
|
||||||
this.serverLevel = serverLevel;
|
|
||||||
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
|
||||||
@ -150,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;
|
||||||
@ -162,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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
|
|||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||||
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
|
||||||
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@ -15,9 +14,7 @@ import javax.annotation.Nonnull;
|
|||||||
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nonnull
|
public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
|
|
||||||
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
||||||
if (w == null) {
|
if (w == null) {
|
||||||
return NullRelighter.INSTANCE;
|
return NullRelighter.INSTANCE;
|
||||||
|
@ -75,9 +75,11 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -95,6 +97,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
.getInstance()
|
.getInstance()
|
||||||
.getBukkitImplAdapter());
|
.getBukkitImplAdapter());
|
||||||
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
|
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
|
||||||
|
private final ReentrantLock callLock = new ReentrantLock();
|
||||||
private final ServerLevel serverLevel;
|
private final ServerLevel serverLevel;
|
||||||
private final int chunkX;
|
private final int chunkX;
|
||||||
private final int chunkZ;
|
private final int chunkZ;
|
||||||
@ -104,14 +107,16 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
private final int maxSectionPosition;
|
private final int maxSectionPosition;
|
||||||
private final Registry<Biome> biomeRegistry;
|
private final Registry<Biome> biomeRegistry;
|
||||||
private final IdMap<Holder<Biome>> biomeHolderIdMap;
|
private final IdMap<Holder<Biome>> biomeHolderIdMap;
|
||||||
|
private final ConcurrentHashMap<Integer, PaperweightGetBlocks_Copy> copies = new ConcurrentHashMap<>();
|
||||||
|
private final Object sendLock = new Object();
|
||||||
private LevelChunkSection[] sections;
|
private LevelChunkSection[] sections;
|
||||||
private LevelChunk levelChunk;
|
private LevelChunk levelChunk;
|
||||||
private DataLayer[] blockLight;
|
private DataLayer[] blockLight;
|
||||||
private DataLayer[] skyLight;
|
private DataLayer[] skyLight;
|
||||||
private boolean createCopy = false;
|
private boolean createCopy = false;
|
||||||
private PaperweightGetBlocks_Copy copy = null;
|
|
||||||
private boolean forceLoadSections = true;
|
private boolean forceLoadSections = true;
|
||||||
private boolean lightUpdate = false;
|
private boolean lightUpdate = false;
|
||||||
|
private int copyKey = 0;
|
||||||
|
|
||||||
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
|
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
|
||||||
this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
|
this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
|
||||||
@ -146,13 +151,27 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
if (!callLock.isHeldByCurrentThread()) {
|
||||||
|
throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked.");
|
||||||
|
}
|
||||||
this.createCopy = createCopy;
|
this.createCopy = createCopy;
|
||||||
|
return ++this.copyKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IChunkGet getCopy() {
|
public IChunkGet getCopy(final int key) {
|
||||||
return copy;
|
return copies.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lockCall() {
|
||||||
|
this.callLock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unlockCall() {
|
||||||
|
this.callLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -388,8 +407,17 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
|
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
|
||||||
|
if (!callLock.isHeldByCurrentThread()) {
|
||||||
|
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
|
||||||
|
}
|
||||||
forceLoadSections = false;
|
forceLoadSections = false;
|
||||||
copy = createCopy ? new PaperweightGetBlocks_Copy(getChunk()) : null;
|
PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
|
||||||
|
if (createCopy) {
|
||||||
|
if (copies.containsKey(copyKey)) {
|
||||||
|
throw new IllegalStateException("Copy key already used.");
|
||||||
|
}
|
||||||
|
copies.put(copyKey, copy);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ServerLevel nmsWorld = serverLevel;
|
ServerLevel nmsWorld = serverLevel;
|
||||||
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
|
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
|
||||||
@ -882,9 +910,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
if (super.sections[layer] != null) {
|
if (super.sections[layer] != null) {
|
||||||
synchronized (super.sectionLocks[layer]) {
|
synchronized (super.sectionLocks[layer]) {
|
||||||
if (super.sections[layer].isFull() && super.blocks[layer] != null) {
|
if (super.sections[layer].isFull() && super.blocks[layer] != null) {
|
||||||
char[] blocks = new char[4096];
|
return super.blocks[layer];
|
||||||
System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096);
|
|
||||||
return blocks;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -892,8 +918,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void send(int mask, boolean lighting) {
|
public void send(int mask, boolean lighting) {
|
||||||
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
|
synchronized (sendLock) {
|
||||||
|
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1004,9 +1032,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
|
|
||||||
public LevelChunkSection[] getSections(boolean force) {
|
public LevelChunkSection[] getSections(boolean force) {
|
||||||
force &= forceLoadSections;
|
force &= forceLoadSections;
|
||||||
sectionLock.readLock().lock();
|
|
||||||
LevelChunkSection[] tmp = sections;
|
LevelChunkSection[] tmp = sections;
|
||||||
sectionLock.readLock().unlock();
|
|
||||||
if (tmp == null || force) {
|
if (tmp == null || force) {
|
||||||
try {
|
try {
|
||||||
sectionLock.writeLock().lock();
|
sectionLock.writeLock().lock();
|
||||||
|
@ -26,6 +26,7 @@ import net.minecraft.world.level.chunk.PalettedContainerRO;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -105,7 +106,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -199,6 +201,10 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
|
|||||||
@Override
|
@Override
|
||||||
public char[] load(int layer) {
|
public char[] load(int layer) {
|
||||||
layer -= getMinSectionPosition();
|
layer -= getMinSectionPosition();
|
||||||
|
if (blocks[layer] == null) {
|
||||||
|
blocks[layer] = new char[4096];
|
||||||
|
Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR);
|
||||||
|
}
|
||||||
return blocks[layer];
|
return blocks[layer];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,140 +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.IQueueChunk;
|
|
||||||
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);
|
||||||
|
|
||||||
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
||||||
private final ServerLevel serverLevel;
|
super(serverLevel, queue);
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
|
||||||
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
|
||||||
private final ReentrantLock areaLock = new ReentrantLock();
|
|
||||||
private final NMSRelighter delegate;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
|
|
||||||
this.serverLevel = serverLevel;
|
|
||||||
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
|
||||||
@ -150,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;
|
||||||
@ -162,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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
|
|||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||||
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
|
||||||
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@ -15,9 +14,7 @@ import javax.annotation.Nonnull;
|
|||||||
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nonnull
|
public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
|
|
||||||
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
||||||
if (w == null) {
|
if (w == null) {
|
||||||
return NullRelighter.INSTANCE;
|
return NullRelighter.INSTANCE;
|
||||||
|
@ -152,7 +152,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
|
delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
|
||||||
delegateField.setAccessible(true);
|
delegateField.setAccessible(true);
|
||||||
|
|
||||||
chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "M"));
|
chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "H"));
|
||||||
chunkSourceField.setAccessible(true);
|
chunkSourceField.setAccessible(true);
|
||||||
|
|
||||||
generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "w"));
|
generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "w"));
|
||||||
|
@ -75,9 +75,11 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -95,6 +97,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
.getInstance()
|
.getInstance()
|
||||||
.getBukkitImplAdapter());
|
.getBukkitImplAdapter());
|
||||||
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
|
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
|
||||||
|
private final ReentrantLock callLock = new ReentrantLock();
|
||||||
private final ServerLevel serverLevel;
|
private final ServerLevel serverLevel;
|
||||||
private final int chunkX;
|
private final int chunkX;
|
||||||
private final int chunkZ;
|
private final int chunkZ;
|
||||||
@ -104,14 +107,16 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
private final int maxSectionPosition;
|
private final int maxSectionPosition;
|
||||||
private final Registry<Biome> biomeRegistry;
|
private final Registry<Biome> biomeRegistry;
|
||||||
private final IdMap<Holder<Biome>> biomeHolderIdMap;
|
private final IdMap<Holder<Biome>> biomeHolderIdMap;
|
||||||
|
private final ConcurrentHashMap<Integer, PaperweightGetBlocks_Copy> copies = new ConcurrentHashMap<>();
|
||||||
|
private final Object sendLock = new Object();
|
||||||
private LevelChunkSection[] sections;
|
private LevelChunkSection[] sections;
|
||||||
private LevelChunk levelChunk;
|
private LevelChunk levelChunk;
|
||||||
private DataLayer[] blockLight;
|
private DataLayer[] blockLight;
|
||||||
private DataLayer[] skyLight;
|
private DataLayer[] skyLight;
|
||||||
private boolean createCopy = false;
|
private boolean createCopy = false;
|
||||||
private PaperweightGetBlocks_Copy copy = null;
|
|
||||||
private boolean forceLoadSections = true;
|
private boolean forceLoadSections = true;
|
||||||
private boolean lightUpdate = false;
|
private boolean lightUpdate = false;
|
||||||
|
private int copyKey = 0;
|
||||||
|
|
||||||
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
|
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
|
||||||
this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
|
this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
|
||||||
@ -146,13 +151,27 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
if (!callLock.isHeldByCurrentThread()) {
|
||||||
|
throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked.");
|
||||||
|
}
|
||||||
this.createCopy = createCopy;
|
this.createCopy = createCopy;
|
||||||
|
return ++this.copyKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IChunkGet getCopy() {
|
public IChunkGet getCopy(final int key) {
|
||||||
return copy;
|
return copies.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lockCall() {
|
||||||
|
this.callLock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unlockCall() {
|
||||||
|
this.callLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -387,8 +406,17 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
|
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
|
||||||
|
if (!callLock.isHeldByCurrentThread()) {
|
||||||
|
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
|
||||||
|
}
|
||||||
forceLoadSections = false;
|
forceLoadSections = false;
|
||||||
copy = createCopy ? new PaperweightGetBlocks_Copy(getChunk()) : null;
|
PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
|
||||||
|
if (createCopy) {
|
||||||
|
if (copies.containsKey(copyKey)) {
|
||||||
|
throw new IllegalStateException("Copy key already used.");
|
||||||
|
}
|
||||||
|
copies.put(copyKey, copy);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ServerLevel nmsWorld = serverLevel;
|
ServerLevel nmsWorld = serverLevel;
|
||||||
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
|
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
|
||||||
@ -880,9 +908,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
if (super.sections[layer] != null) {
|
if (super.sections[layer] != null) {
|
||||||
synchronized (super.sectionLocks[layer]) {
|
synchronized (super.sectionLocks[layer]) {
|
||||||
if (super.sections[layer].isFull() && super.blocks[layer] != null) {
|
if (super.sections[layer].isFull() && super.blocks[layer] != null) {
|
||||||
char[] blocks = new char[4096];
|
return super.blocks[layer];
|
||||||
System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096);
|
|
||||||
return blocks;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -890,8 +916,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void send(int mask, boolean lighting) {
|
public void send(int mask, boolean lighting) {
|
||||||
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
|
synchronized (sendLock) {
|
||||||
|
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1002,9 +1030,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
|
|
||||||
public LevelChunkSection[] getSections(boolean force) {
|
public LevelChunkSection[] getSections(boolean force) {
|
||||||
force &= forceLoadSections;
|
force &= forceLoadSections;
|
||||||
sectionLock.readLock().lock();
|
|
||||||
LevelChunkSection[] tmp = sections;
|
LevelChunkSection[] tmp = sections;
|
||||||
sectionLock.readLock().unlock();
|
|
||||||
if (tmp == null || force) {
|
if (tmp == null || force) {
|
||||||
try {
|
try {
|
||||||
sectionLock.writeLock().lock();
|
sectionLock.writeLock().lock();
|
||||||
|
@ -26,6 +26,7 @@ import net.minecraft.world.level.chunk.PalettedContainerRO;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -105,7 +106,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -199,6 +201,10 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
|
|||||||
@Override
|
@Override
|
||||||
public char[] load(int layer) {
|
public char[] load(int layer) {
|
||||||
layer -= getMinSectionPosition();
|
layer -= getMinSectionPosition();
|
||||||
|
if (blocks[layer] == null) {
|
||||||
|
blocks[layer] = new char[4096];
|
||||||
|
Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR);
|
||||||
|
}
|
||||||
return blocks[layer];
|
return blocks[layer];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,140 +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.IQueueChunk;
|
|
||||||
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);
|
||||||
|
|
||||||
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
||||||
private final ServerLevel serverLevel;
|
super(serverLevel, queue);
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
|
||||||
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
|
||||||
private final ReentrantLock areaLock = new ReentrantLock();
|
|
||||||
private final NMSRelighter delegate;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
|
|
||||||
this.serverLevel = serverLevel;
|
|
||||||
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
|
||||||
@ -150,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;
|
||||||
@ -162,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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
|
|||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||||
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
|
||||||
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@ -15,9 +14,7 @@ import javax.annotation.Nonnull;
|
|||||||
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nonnull
|
public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
|
|
||||||
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
||||||
if (w == null) {
|
if (w == null) {
|
||||||
return NullRelighter.INSTANCE;
|
return NullRelighter.INSTANCE;
|
||||||
|
@ -12,6 +12,6 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/
|
// https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/
|
||||||
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.20.2-R0.1-20231008.101509-26")
|
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.20.2-R0.1-20231029.153906-63")
|
||||||
compileOnly(libs.paperlib)
|
compileOnly(libs.paperlib)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2;
|
package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2;
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
@ -28,8 +28,6 @@ import com.google.common.util.concurrent.Futures;
|
|||||||
import com.mojang.datafixers.util.Either;
|
import com.mojang.datafixers.util.Either;
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
import com.sk89q.jnbt.NBTConstants;
|
import com.sk89q.jnbt.NBTConstants;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2.PaperweightDataConverters;
|
|
||||||
import com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightFakePlayer;
|
|
||||||
import com.sk89q.worldedit.WorldEditException;
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
import com.sk89q.worldedit.blocks.BaseItem;
|
import com.sk89q.worldedit.blocks.BaseItem;
|
||||||
import com.sk89q.worldedit.blocks.BaseItemStack;
|
import com.sk89q.worldedit.blocks.BaseItemStack;
|
||||||
@ -358,8 +356,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WorldNativeAccess<?, ?, ?> createWorldNativeAccess(org.bukkit.World world) {
|
public WorldNativeAccess<?, ?, ?> createWorldNativeAccess(org.bukkit.World world) {
|
||||||
return new com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightWorldNativeAccess(this,
|
return new PaperweightWorldNativeAccess(this, new WeakReference<>(((CraftWorld) world).getHandle()));
|
||||||
new WeakReference<>(((CraftWorld) world).getHandle()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static net.minecraft.core.Direction adapt(Direction face) {
|
private static net.minecraft.core.Direction adapt(Direction face) {
|
||||||
|
@ -168,7 +168,7 @@ public class PaperweightDataConverters extends DataFixerBuilder implements com.s
|
|||||||
.getValue().getAsString();
|
.getValue().getAsString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter;
|
private final PaperweightAdapter adapter;
|
||||||
|
|
||||||
private static final NbtOps OPS_NBT = NbtOps.INSTANCE;
|
private static final NbtOps OPS_NBT = NbtOps.INSTANCE;
|
||||||
private static final int LEGACY_VERSION = 1343;
|
private static final int LEGACY_VERSION = 1343;
|
||||||
@ -204,8 +204,7 @@ public class PaperweightDataConverters extends DataFixerBuilder implements com.s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaperweightDataConverters(int dataVersion,
|
public PaperweightDataConverters(int dataVersion, PaperweightAdapter adapter) {
|
||||||
com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter) {
|
|
||||||
super(dataVersion);
|
super(dataVersion);
|
||||||
DATA_VERSION = dataVersion;
|
DATA_VERSION = dataVersion;
|
||||||
INSTANCE = this;
|
INSTANCE = this;
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2;
|
package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2;
|
||||||
|
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2;
|
package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2;
|
||||||
|
|
||||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||||
@ -43,11 +43,11 @@ public class PaperweightWorldNativeAccess implements WorldNativeAccess<LevelChun
|
|||||||
private static final int UPDATE = 1;
|
private static final int UPDATE = 1;
|
||||||
private static final int NOTIFY = 2;
|
private static final int NOTIFY = 2;
|
||||||
|
|
||||||
private final com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter;
|
private final PaperweightAdapter adapter;
|
||||||
private final WeakReference<ServerLevel> world;
|
private final WeakReference<ServerLevel> world;
|
||||||
private SideEffectSet sideEffectSet;
|
private SideEffectSet sideEffectSet;
|
||||||
|
|
||||||
public PaperweightWorldNativeAccess(com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter, WeakReference<ServerLevel> world) {
|
public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference<ServerLevel> world) {
|
||||||
this.adapter = adapter;
|
this.adapter = adapter;
|
||||||
this.world = world;
|
this.world = world;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import com.sk89q.worldedit.blocks.BaseItemStack;
|
|||||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.BukkitWorld;
|
import com.sk89q.worldedit.bukkit.BukkitWorld;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter;
|
|
||||||
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag;
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen;
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen;
|
||||||
import com.sk89q.worldedit.entity.BaseEntity;
|
import com.sk89q.worldedit.entity.BaseEntity;
|
||||||
@ -124,7 +123,7 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final PaperweightAdapter parent;
|
private final com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2.PaperweightAdapter parent;
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Code that may break between versions of Minecraft
|
// Code that may break between versions of Minecraft
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@ -135,7 +134,7 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
|
|||||||
private Map<String, List<Property<?>>> allBlockProperties = null;
|
private Map<String, List<Property<?>>> allBlockProperties = null;
|
||||||
|
|
||||||
public PaperweightFaweAdapter() throws NoSuchFieldException, NoSuchMethodException {
|
public PaperweightFaweAdapter() throws NoSuchFieldException, NoSuchMethodException {
|
||||||
this.parent = new PaperweightAdapter();
|
this.parent = new com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2.PaperweightAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -54,9 +54,11 @@ import org.bukkit.event.entity.CreatureSpawnEvent;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -74,6 +76,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
.getInstance()
|
.getInstance()
|
||||||
.getBukkitImplAdapter());
|
.getBukkitImplAdapter());
|
||||||
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
|
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
|
||||||
|
private final ReentrantLock callLock = new ReentrantLock();
|
||||||
private final ServerLevel serverLevel;
|
private final ServerLevel serverLevel;
|
||||||
private final int chunkX;
|
private final int chunkX;
|
||||||
private final int chunkZ;
|
private final int chunkZ;
|
||||||
@ -83,14 +86,16 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
private final int maxSectionPosition;
|
private final int maxSectionPosition;
|
||||||
private final Registry<Biome> biomeRegistry;
|
private final Registry<Biome> biomeRegistry;
|
||||||
private final IdMap<Holder<Biome>> biomeHolderIdMap;
|
private final IdMap<Holder<Biome>> biomeHolderIdMap;
|
||||||
|
private final ConcurrentHashMap<Integer, PaperweightGetBlocks_Copy> copies = new ConcurrentHashMap<>();
|
||||||
|
private final Object sendLock = new Object();
|
||||||
private LevelChunkSection[] sections;
|
private LevelChunkSection[] sections;
|
||||||
private LevelChunk levelChunk;
|
private LevelChunk levelChunk;
|
||||||
private DataLayer[] blockLight;
|
private DataLayer[] blockLight;
|
||||||
private DataLayer[] skyLight;
|
private DataLayer[] skyLight;
|
||||||
private boolean createCopy = false;
|
private boolean createCopy = false;
|
||||||
private PaperweightGetBlocks_Copy copy = null;
|
|
||||||
private boolean forceLoadSections = true;
|
private boolean forceLoadSections = true;
|
||||||
private boolean lightUpdate = false;
|
private boolean lightUpdate = false;
|
||||||
|
private int copyKey = 0;
|
||||||
|
|
||||||
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
|
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
|
||||||
this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
|
this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
|
||||||
@ -125,13 +130,27 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
if (!callLock.isHeldByCurrentThread()) {
|
||||||
|
throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked.");
|
||||||
|
}
|
||||||
this.createCopy = createCopy;
|
this.createCopy = createCopy;
|
||||||
|
return ++this.copyKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IChunkGet getCopy() {
|
public IChunkGet getCopy(final int key) {
|
||||||
return copy;
|
return copies.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lockCall() {
|
||||||
|
this.callLock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unlockCall() {
|
||||||
|
this.callLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -374,8 +393,17 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
|
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
|
||||||
|
if (!callLock.isHeldByCurrentThread()) {
|
||||||
|
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
|
||||||
|
}
|
||||||
forceLoadSections = false;
|
forceLoadSections = false;
|
||||||
copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
|
PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
|
||||||
|
if (createCopy) {
|
||||||
|
if (copies.containsKey(copyKey)) {
|
||||||
|
throw new IllegalStateException("Copy key already used.");
|
||||||
|
}
|
||||||
|
copies.put(copyKey, copy);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ServerLevel nmsWorld = serverLevel;
|
ServerLevel nmsWorld = serverLevel;
|
||||||
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
|
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
|
||||||
@ -867,9 +895,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
if (super.sections[layer] != null) {
|
if (super.sections[layer] != null) {
|
||||||
synchronized (super.sectionLocks[layer]) {
|
synchronized (super.sectionLocks[layer]) {
|
||||||
if (super.sections[layer].isFull() && super.blocks[layer] != null) {
|
if (super.sections[layer].isFull() && super.blocks[layer] != null) {
|
||||||
char[] blocks = new char[4096];
|
return super.blocks[layer];
|
||||||
System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096);
|
|
||||||
return blocks;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -877,8 +903,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void send(int mask, boolean lighting) {
|
public void send(int mask, boolean lighting) {
|
||||||
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
|
synchronized (sendLock) {
|
||||||
|
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -989,9 +1017,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
|
|||||||
|
|
||||||
public LevelChunkSection[] getSections(boolean force) {
|
public LevelChunkSection[] getSections(boolean force) {
|
||||||
force &= forceLoadSections;
|
force &= forceLoadSections;
|
||||||
sectionLock.readLock().lock();
|
|
||||||
LevelChunkSection[] tmp = sections;
|
LevelChunkSection[] tmp = sections;
|
||||||
sectionLock.readLock().unlock();
|
|
||||||
if (tmp == null || force) {
|
if (tmp == null || force) {
|
||||||
try {
|
try {
|
||||||
sectionLock.writeLock().lock();
|
sectionLock.writeLock().lock();
|
||||||
|
@ -26,6 +26,7 @@ import net.minecraft.world.level.chunk.PalettedContainerRO;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -105,7 +106,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -199,6 +201,10 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
|
|||||||
@Override
|
@Override
|
||||||
public char[] load(int layer) {
|
public char[] load(int layer) {
|
||||||
layer -= getMinSectionPosition();
|
layer -= getMinSectionPosition();
|
||||||
|
if (blocks[layer] == null) {
|
||||||
|
blocks[layer] = new char[4096];
|
||||||
|
Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR);
|
||||||
|
}
|
||||||
return blocks[layer];
|
return blocks[layer];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,140 +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.IQueueChunk;
|
|
||||||
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);
|
||||||
|
|
||||||
|
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
|
||||||
private final ServerLevel serverLevel;
|
super(serverLevel, queue);
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
|
||||||
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
|
|
||||||
private final ReentrantLock areaLock = new ReentrantLock();
|
|
||||||
private final NMSRelighter delegate;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
|
|
||||||
this.serverLevel = serverLevel;
|
|
||||||
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
|
||||||
@ -150,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;
|
||||||
@ -162,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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
|
|||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||||
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
|
||||||
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@ -15,9 +14,7 @@ import javax.annotation.Nonnull;
|
|||||||
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
public class PaperweightStarlightRelighterFactory implements RelighterFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nonnull
|
public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
|
|
||||||
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
org.bukkit.World w = Bukkit.getWorld(world.getName());
|
||||||
if (w == null) {
|
if (w == null) {
|
||||||
return NullRelighter.INSTANCE;
|
return NullRelighter.INSTANCE;
|
||||||
|
@ -5,7 +5,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter;
|
|||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
|
||||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||||
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
|
||||||
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
|
|
||||||
@ -14,8 +13,7 @@ import javax.annotation.Nonnull;
|
|||||||
public class NMSRelighterFactory implements RelighterFactory {
|
public class NMSRelighterFactory implements RelighterFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nonnull
|
public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
|
||||||
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
|
|
||||||
return new NMSRelighter(
|
return new NMSRelighter(
|
||||||
queue,
|
queue,
|
||||||
relightMode != null ? relightMode : RelightMode.valueOf(Settings.settings().LIGHTING.MODE)
|
relightMode != null ? relightMode : RelightMode.valueOf(Settings.settings().LIGHTING.MODE)
|
||||||
|
@ -41,6 +41,7 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -158,16 +159,14 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean generate() throws Exception {
|
private boolean generate() throws Exception {
|
||||||
|
ThreadFactory factory = new ThreadFactoryBuilder()
|
||||||
|
.setNameFormat("FAWE Regenerator - %d")
|
||||||
|
.build();
|
||||||
if (generateConcurrent) {
|
if (generateConcurrent) {
|
||||||
//Using concurrent chunk generation
|
//Using concurrent chunk generation
|
||||||
executor = Executors.newFixedThreadPool(Settings.settings().QUEUE.PARALLEL_THREADS, new ThreadFactoryBuilder()
|
executor = Executors.newFixedThreadPool(Settings.settings().QUEUE.PARALLEL_THREADS, factory);
|
||||||
.setNameFormat("fawe-regen-%d")
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
} else { // else using sequential chunk generation, concurrent not supported
|
} else { // else using sequential chunk generation, concurrent not supported
|
||||||
executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
|
executor = Executors.newSingleThreadExecutor(factory);
|
||||||
.setNameFormat("fawe-regen-%d")
|
|
||||||
.build());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: can we get that required radius down without affecting chunk generation (e.g. strucures, features, ...)?
|
//TODO: can we get that required radius down without affecting chunk generation (e.g. strucures, features, ...)?
|
||||||
|
@ -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 2.8.2
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -144,7 +144,7 @@ public class Fawe {
|
|||||||
0L,
|
0L,
|
||||||
TimeUnit.MILLISECONDS,
|
TimeUnit.MILLISECONDS,
|
||||||
new LinkedBlockingQueue<>(),
|
new LinkedBlockingQueue<>(),
|
||||||
new ThreadFactoryBuilder().setNameFormat("fawe-clipboard-%d").build()
|
new ThreadFactoryBuilder().setNameFormat("FAWE Clipboard - %d").build()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import com.fastasyncworldedit.core.util.collection.CleanableThreadLocal;
|
|||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
import com.sk89q.jnbt.ByteArrayTag;
|
import com.sk89q.jnbt.ByteArrayTag;
|
||||||
import com.sk89q.jnbt.ByteTag;
|
import com.sk89q.jnbt.ByteTag;
|
||||||
import com.sk89q.jnbt.CompoundTag;
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
@ -48,7 +49,6 @@ import java.util.Map.Entry;
|
|||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -60,16 +60,17 @@ import java.util.function.Supplier;
|
|||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
public enum FaweCache implements Trimable {
|
public enum FaweCache implements Trimable {
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #INSTANCE} to get an instance.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true, since = "2.0.0")
|
|
||||||
IMP,
|
|
||||||
/**
|
/**
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
INSTANCE;
|
INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #INSTANCE} to get an instance.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = true, since = "2.0.0")
|
||||||
|
public static final FaweCache IMP = INSTANCE;
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
|
||||||
public final int BLOCKS_PER_LAYER = 4096;
|
public final int BLOCKS_PER_LAYER = 4096;
|
||||||
@ -616,7 +617,7 @@ public enum FaweCache implements Trimable {
|
|||||||
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(nThreads, true);
|
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(nThreads, true);
|
||||||
return new ThreadPoolExecutor(nThreads, nThreads,
|
return new ThreadPoolExecutor(nThreads, nThreads,
|
||||||
0L, TimeUnit.MILLISECONDS, queue,
|
0L, TimeUnit.MILLISECONDS, queue,
|
||||||
Executors.defaultThreadFactory(),
|
new ThreadFactoryBuilder().setNameFormat("FAWE Blocking Executor - %d").build(),
|
||||||
new ThreadPoolExecutor.CallerRunsPolicy()
|
new ThreadPoolExecutor.CallerRunsPolicy()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -19,14 +19,14 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
public class Settings extends Config {
|
public class Settings extends Config {
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
static Settings INSTANCE = new Settings();
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #settings()} instead to get an instance.
|
* @deprecated Use {@link #settings()} instead to get an instance.
|
||||||
*/
|
*/
|
||||||
@Ignore
|
@Ignore
|
||||||
@Deprecated(forRemoval = true, since = "2.0.0")
|
@Deprecated(forRemoval = true, since = "2.0.0")
|
||||||
public static final Settings IMP = new Settings();
|
public static final Settings IMP = INSTANCE;
|
||||||
@Ignore
|
|
||||||
static Settings INSTANCE = new Settings();
|
|
||||||
@Ignore
|
@Ignore
|
||||||
public boolean PROTOCOL_SUPPORT_FIX = false;
|
public boolean PROTOCOL_SUPPORT_FIX = false;
|
||||||
@Comment("These first 6 aren't configurable") // This is a comment
|
@Comment("These first 6 aren't configurable") // This is a comment
|
||||||
|
@ -13,7 +13,7 @@ public class DBHandler {
|
|||||||
* @deprecated Use {@link #dbHandler()} instead.
|
* @deprecated Use {@link #dbHandler()} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated(forRemoval = true, since = "2.0.0")
|
@Deprecated(forRemoval = true, since = "2.0.0")
|
||||||
public static final DBHandler IMP = new DBHandler();
|
public static final DBHandler IMP = dbHandler();
|
||||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
private static DBHandler INSTANCE;
|
private static DBHandler INSTANCE;
|
||||||
private final Map<World, RollbackDatabase> databases = new ConcurrentHashMap<>(8, 0.9f, 1);
|
private final Map<World, RollbackDatabase> databases = new ConcurrentHashMap<>(8, 0.9f, 1);
|
||||||
|
@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.Fawe;
|
|||||||
import com.fastasyncworldedit.core.configuration.Settings;
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
import com.fastasyncworldedit.core.math.BlockVectorSet;
|
import com.fastasyncworldedit.core.math.BlockVectorSet;
|
||||||
import com.fastasyncworldedit.core.math.MutableBlockVector3;
|
import com.fastasyncworldedit.core.math.MutableBlockVector3;
|
||||||
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
|
||||||
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkHolder;
|
import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkHolder;
|
||||||
import com.fastasyncworldedit.core.util.MathMan;
|
import com.fastasyncworldedit.core.util.MathMan;
|
||||||
@ -34,7 +33,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public class NMSRelighter implements Relighter {
|
public class NMSRelighter implements Relighter {
|
||||||
|
|
||||||
private static final int DISPATCH_SIZE = 64;
|
private static final int DISPATCH_SIZE = 64;
|
||||||
@ -51,7 +49,7 @@ public class NMSRelighter implements Relighter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final MutableBlockVector3 mutableBlockPos = new MutableBlockVector3(0, 0, 0);
|
public final MutableBlockVector3 mutableBlockPos = new MutableBlockVector3(0, 0, 0);
|
||||||
private final IQueueExtent<IQueueChunk> queue;
|
private final IQueueExtent<?> queue;
|
||||||
private final Map<Long, RelightSkyEntry> skyToRelight;
|
private final Map<Long, RelightSkyEntry> skyToRelight;
|
||||||
private final Object present = new Object();
|
private final Object present = new Object();
|
||||||
private final Map<Long, Integer> chunksToSend;
|
private final Map<Long, Integer> chunksToSend;
|
||||||
@ -66,11 +64,11 @@ public class NMSRelighter implements Relighter {
|
|||||||
private final AtomicBoolean finished = new AtomicBoolean(false);
|
private final AtomicBoolean finished = new AtomicBoolean(false);
|
||||||
private boolean removeFirst;
|
private boolean removeFirst;
|
||||||
|
|
||||||
public NMSRelighter(IQueueExtent<IQueueChunk> queue) {
|
public NMSRelighter(IQueueExtent<?> queue) {
|
||||||
this(queue, null);
|
this(queue, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NMSRelighter(IQueueExtent<IQueueChunk> queue, RelightMode relightMode) {
|
public NMSRelighter(IQueueExtent<?> queue, RelightMode relightMode) {
|
||||||
this.queue = queue;
|
this.queue = queue;
|
||||||
this.skyToRelight = new Long2ObjectOpenHashMap<>(12);
|
this.skyToRelight = new Long2ObjectOpenHashMap<>(12);
|
||||||
this.lightQueue = new Long2ObjectOpenHashMap<>(12);
|
this.lightQueue = new Long2ObjectOpenHashMap<>(12);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.fastasyncworldedit.core.extent.processor.lighting;
|
package com.fastasyncworldedit.core.extent.processor.lighting;
|
||||||
|
|
||||||
import com.fastasyncworldedit.core.queue.IQueueChunk;
|
|
||||||
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
import com.fastasyncworldedit.core.queue.IQueueExtent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
|
|
||||||
@ -25,6 +24,6 @@ public interface RelighterFactory {
|
|||||||
* @return a new Relighter instance with the specified settings.
|
* @return a new Relighter instance with the specified settings.
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue);
|
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -51,13 +51,31 @@ public interface IChunkGet extends IBlocks, Trimable, InputExtent, ITileInput {
|
|||||||
|
|
||||||
boolean isCreateCopy();
|
boolean isCreateCopy();
|
||||||
|
|
||||||
void setCreateCopy(boolean createCopy);
|
/**
|
||||||
|
* Not for external API use. Internal use only.
|
||||||
|
*/
|
||||||
|
int setCreateCopy(boolean createCopy);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
default IChunkGet getCopy() {
|
default IChunkGet getCopy(int key) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock the {@link IChunkGet#call(IChunkSet, Runnable)} method to the current thread using a reentrant lock. Also locks
|
||||||
|
* related methods e.g. {@link IChunkGet#setCreateCopy(boolean)}
|
||||||
|
*
|
||||||
|
* @since 2.8.2
|
||||||
|
*/
|
||||||
|
default void lockCall() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlock {@link IChunkGet#call(IChunkSet, Runnable)} (and other related methods) to executions from other threads
|
||||||
|
*
|
||||||
|
* @since 2.8.2
|
||||||
|
*/
|
||||||
|
default void unlockCall() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush the block lighting array (section*blocks) to the chunk GET between the given section indices. Negative allowed.
|
* Flush the block lighting array (section*blocks) to the chunk GET between the given section indices. Negative allowed.
|
||||||
*
|
*
|
||||||
|
@ -36,4 +36,18 @@ public interface IQueueChunk<T extends Future<T>> extends IChunk, Callable<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get if the thank has any running tasks, locked locks, etc.
|
||||||
|
*/
|
||||||
|
default boolean hasRunning() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent set operations to the chunk, should typically be used when a chunk is submitted before the edit is necessarily
|
||||||
|
* completed.
|
||||||
|
*/
|
||||||
|
default void lockSet() {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -135,9 +135,6 @@ public interface IQueueExtent<T extends IChunk> extends Flushable, Trimable, ICh
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
T chunk = this.getOrCreateChunk(chunkX, chunkZ);
|
T chunk = this.getOrCreateChunk(chunkX, chunkZ);
|
||||||
// Initialize
|
|
||||||
chunk.init(this, chunkX, chunkZ);
|
|
||||||
chunk.setFastMode(isFastMode());
|
|
||||||
|
|
||||||
T newChunk = filter.applyChunk(chunk, region);
|
T newChunk = filter.applyChunk(chunk, region);
|
||||||
if (newChunk != null) {
|
if (newChunk != null) {
|
||||||
|
@ -64,8 +64,8 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
|
|||||||
private boolean initialized;
|
private boolean initialized;
|
||||||
private Thread currentThread;
|
private Thread currentThread;
|
||||||
// Last access pointers
|
// Last access pointers
|
||||||
private IQueueChunk lastChunk;
|
private volatile IQueueChunk lastChunk;
|
||||||
private long lastPair = Long.MAX_VALUE;
|
private volatile long lastPair = Long.MAX_VALUE;
|
||||||
private boolean enabledQueue = true;
|
private boolean enabledQueue = true;
|
||||||
private boolean fastmode = false;
|
private boolean fastmode = false;
|
||||||
// Array for lazy avoidance of concurrent modification exceptions and needless overcomplication of code (synchronisation is
|
// Array for lazy avoidance of concurrent modification exceptions and needless overcomplication of code (synchronisation is
|
||||||
@ -294,6 +294,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
|
|||||||
private ChunkHolder poolOrCreate(int chunkX, int chunkZ) {
|
private ChunkHolder poolOrCreate(int chunkX, int chunkZ) {
|
||||||
ChunkHolder next = create(false);
|
ChunkHolder next = create(false);
|
||||||
next.init(this, chunkX, chunkZ);
|
next.init(this, chunkX, chunkZ);
|
||||||
|
next.setFastMode(isFastMode());
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,7 +466,8 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
|
|||||||
if (!chunks.isEmpty()) {
|
if (!chunks.isEmpty()) {
|
||||||
getChunkLock.lock();
|
getChunkLock.lock();
|
||||||
if (MemUtil.isMemoryLimited()) {
|
if (MemUtil.isMemoryLimited()) {
|
||||||
for (IQueueChunk chunk : chunks.values()) {
|
while (!chunks.isEmpty()) {
|
||||||
|
IQueueChunk chunk = chunks.removeFirst();
|
||||||
final Future future = submitUnchecked(chunk);
|
final Future future = submitUnchecked(chunk);
|
||||||
if (future != null && !future.isDone()) {
|
if (future != null && !future.isDone()) {
|
||||||
pollSubmissions(Settings.settings().QUEUE.PARALLEL_THREADS, true);
|
pollSubmissions(Settings.settings().QUEUE.PARALLEL_THREADS, true);
|
||||||
@ -473,14 +475,14 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (IQueueChunk chunk : chunks.values()) {
|
while (!chunks.isEmpty()) {
|
||||||
|
IQueueChunk chunk = chunks.removeFirst();
|
||||||
final Future future = submitUnchecked(chunk);
|
final Future future = submitUnchecked(chunk);
|
||||||
if (future != null && !future.isDone()) {
|
if (future != null && !future.isDone()) {
|
||||||
submissions.add(future);
|
submissions.add(future);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chunks.clear();
|
|
||||||
getChunkLock.unlock();
|
getChunkLock.unlock();
|
||||||
}
|
}
|
||||||
pollSubmissions(0, true);
|
pollSubmissions(0, true);
|
||||||
|
@ -121,6 +121,7 @@ public abstract class CharBlocks implements IBlocks {
|
|||||||
public synchronized IChunkSet reset() {
|
public synchronized IChunkSet reset() {
|
||||||
for (int i = 0; i < sectionCount; i++) {
|
for (int i = 0; i < sectionCount; i++) {
|
||||||
sections[i] = EMPTY;
|
sections[i] = EMPTY;
|
||||||
|
blocks[i] = null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,8 @@ public final class NullChunkGet implements IChunkGet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -25,8 +25,6 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract {@link IChunk} class that implements basic get/set blocks.
|
* An abstract {@link IChunk} class that implements basic get/set blocks.
|
||||||
@ -44,8 +42,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
return POOL.poll();
|
return POOL.poll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Lock calledLock = new ReentrantLock();
|
|
||||||
|
|
||||||
private volatile IChunkGet chunkExisting; // The existing chunk (e.g. a clipboard, or the world, before changes)
|
private volatile IChunkGet chunkExisting; // The existing chunk (e.g. a clipboard, or the world, before changes)
|
||||||
private volatile IChunkSet chunkSet; // The blocks to be set to the chunkExisting
|
private volatile IChunkSet chunkSet; // The blocks to be set to the chunkExisting
|
||||||
private IBlockDelegate delegate; // delegate handles the abstraction of the chunk layers
|
private IBlockDelegate delegate; // delegate handles the abstraction of the chunk layers
|
||||||
@ -68,7 +64,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void recycle() {
|
public synchronized void recycle() {
|
||||||
calledLock.lock();
|
|
||||||
delegate = NULL;
|
delegate = NULL;
|
||||||
if (chunkSet != null) {
|
if (chunkSet != null) {
|
||||||
chunkSet.recycle();
|
chunkSet.recycle();
|
||||||
@ -77,7 +72,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
chunkExisting = null;
|
chunkExisting = null;
|
||||||
extent = null;
|
extent = null;
|
||||||
POOL.offer(this);
|
POOL.offer(this);
|
||||||
calledLock.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long initAge() {
|
public long initAge() {
|
||||||
@ -88,68 +82,49 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
return delegate;
|
return delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If the chunk is currently being "called", this method will block until completed.
|
|
||||||
*/
|
|
||||||
private void checkAndWaitOnCalledLock() {
|
|
||||||
if (!calledLock.tryLock()) {
|
|
||||||
calledLock.lock();
|
|
||||||
}
|
|
||||||
calledLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean setTile(int x, int y, int z, CompoundTag tag) {
|
public boolean setTile(int x, int y, int z, CompoundTag tag) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.set(this).setTile(x, y, z, tag);
|
return delegate.set(this).setTile(x, y, z, tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompoundTag getTile(int x, int y, int z) {
|
public CompoundTag getTile(int x, int y, int z) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.set(this).getTile(x, y, z);
|
return delegate.set(this).getTile(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEntity(CompoundTag tag) {
|
public void setEntity(CompoundTag tag) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.set(this).setEntity(tag);
|
delegate.set(this).setEntity(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeEntity(UUID uuid) {
|
public void removeEntity(UUID uuid) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.set(this).removeEntity(uuid);
|
delegate.set(this).removeEntity(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<UUID> getEntityRemoves() {
|
public Set<UUID> getEntityRemoves() {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.set(this).getEntityRemoves();
|
return delegate.set(this).getEntityRemoves();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BiomeType[][] getBiomes() {
|
public BiomeType[][] getBiomes() {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
// Uses set as this method is only used to retrieve biomes that have been set to the extent/chunk.
|
// Uses set as this method is only used to retrieve biomes that have been set to the extent/chunk.
|
||||||
return delegate.set(this).getBiomes();
|
return delegate.set(this).getBiomes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public char[][] getLight() {
|
public char[][] getLight() {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.set(this).getLight();
|
return delegate.set(this).getLight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public char[][] getSkyLight() {
|
public char[][] getSkyLight() {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.set(this).getSkyLight();
|
return delegate.set(this).getSkyLight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBlocks(int layer, char[] data) {
|
public void setBlocks(int layer, char[] data) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.set(this).setBlocks(layer, data);
|
delegate.set(this).setBlocks(layer, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,12 +149,10 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFastMode(boolean fastmode) {
|
public void setFastMode(boolean fastmode) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
this.fastmode = fastmode;
|
this.fastmode = fastmode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBitMask(int bitMask) {
|
public void setBitMask(int bitMask) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
this.bitMask = bitMask;
|
this.bitMask = bitMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +162,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasBiomes(final int layer) {
|
public boolean hasBiomes(final int layer) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
// No need to go through delegate. hasBiomes is SET only.
|
// No need to go through delegate. hasBiomes is SET only.
|
||||||
return chunkSet != null && chunkSet.hasBiomes(layer);
|
return chunkSet != null && chunkSet.hasBiomes(layer);
|
||||||
}
|
}
|
||||||
@ -200,14 +172,13 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompoundTag getEntity(UUID uuid) {
|
public CompoundTag getEntity(UUID uuid) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.get(this).getEntity(uuid);
|
return delegate.get(this).getEntity(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
this.createCopy = createCopy;
|
this.createCopy = createCopy;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -217,19 +188,16 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) {
|
public void setLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.setLightingToGet(this, lighting);
|
delegate.setLightingToGet(this, lighting);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSkyLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) {
|
public void setSkyLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.setSkyLightingToGet(this, lighting);
|
delegate.setSkyLightingToGet(this, lighting);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setHeightmapToGet(HeightMapType type, int[] data) {
|
public void setHeightmapToGet(HeightMapType type, int[] data) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.setHeightmapToGet(this, type, data);
|
delegate.setHeightmapToGet(this, type, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +222,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void flushLightToGet() {
|
public void flushLightToGet() {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.flushLightToGet(this);
|
delegate.flushLightToGet(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -921,19 +888,16 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<BlockVector3, CompoundTag> getTiles() {
|
public Map<BlockVector3, CompoundTag> getTiles() {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.get(this).getTiles();
|
return delegate.get(this).getTiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<CompoundTag> getEntities() {
|
public Set<CompoundTag> getEntities() {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.get(this).getEntities();
|
return delegate.get(this).getEntities();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasSection(int layer) {
|
public boolean hasSection(int layer) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return chunkExisting != null && chunkExisting.hasSection(layer);
|
return chunkExisting != null && chunkExisting.hasSection(layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -958,6 +922,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
if (result) {
|
if (result) {
|
||||||
delegate = NULL;
|
delegate = NULL;
|
||||||
chunkExisting = null;
|
chunkExisting = null;
|
||||||
|
chunkSet.recycle();
|
||||||
chunkSet = null;
|
chunkSet = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -985,7 +950,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return chunkSet == null || chunkSet.isEmpty();
|
return chunkSet == null || chunkSet.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -993,7 +957,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
* Get or create the existing part of this chunk.
|
* Get or create the existing part of this chunk.
|
||||||
*/
|
*/
|
||||||
public final IChunkGet getOrCreateGet() {
|
public final IChunkGet getOrCreateGet() {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
if (chunkExisting == null) {
|
if (chunkExisting == null) {
|
||||||
chunkExisting = newWrappedGet();
|
chunkExisting = newWrappedGet();
|
||||||
chunkExisting.trim(false);
|
chunkExisting.trim(false);
|
||||||
@ -1005,7 +968,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
* Get or create the settable part of this chunk.
|
* Get or create the settable part of this chunk.
|
||||||
*/
|
*/
|
||||||
public final IChunkSet getOrCreateSet() {
|
public final IChunkSet getOrCreateSet() {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
if (chunkSet == null) {
|
if (chunkSet == null) {
|
||||||
chunkSet = newWrappedSet();
|
chunkSet = newWrappedSet();
|
||||||
}
|
}
|
||||||
@ -1026,7 +988,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
* - The purpose of wrapping is to allow different extents to intercept / alter behavior
|
* - The purpose of wrapping is to allow different extents to intercept / alter behavior
|
||||||
* - e.g., caching, optimizations, filtering
|
* - e.g., caching, optimizations, filtering
|
||||||
*/
|
*/
|
||||||
private synchronized IChunkGet newWrappedGet() {
|
private IChunkGet newWrappedGet() {
|
||||||
return extent.getCachedGet(chunkX, chunkZ);
|
return extent.getCachedGet(chunkX, chunkZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1048,45 +1010,42 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized T call() {
|
public synchronized T call() {
|
||||||
calledLock.lock();
|
|
||||||
if (chunkSet != null && !chunkSet.isEmpty()) {
|
if (chunkSet != null && !chunkSet.isEmpty()) {
|
||||||
this.delegate = GET;
|
|
||||||
chunkSet.setBitMask(bitMask);
|
chunkSet.setBitMask(bitMask);
|
||||||
try {
|
IChunkSet copy = chunkSet.createCopy();
|
||||||
IChunkSet copy = chunkSet.createCopy();
|
return this.call(copy, () -> {
|
||||||
chunkSet = null;
|
// Do nothing
|
||||||
return this.call(copy, () -> {
|
});
|
||||||
// Do nothing
|
|
||||||
});
|
|
||||||
} catch (Throwable t) {
|
|
||||||
try {
|
|
||||||
calledLock.unlock();
|
|
||||||
} catch (IllegalMonitorStateException ignored) {
|
|
||||||
}
|
|
||||||
throw t;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
recycle();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should never be called from outside ChunkHolder
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public synchronized T call(IChunkSet set, Runnable finalize) {
|
public synchronized T call(IChunkSet set, Runnable finalize) {
|
||||||
if (set != null) {
|
if (set != null) {
|
||||||
IChunkGet get = getOrCreateGet();
|
IChunkGet get = getOrCreateGet();
|
||||||
boolean postProcess = !(getExtent().getPostProcessor() instanceof EmptyBatchProcessor);
|
try {
|
||||||
get.setCreateCopy(postProcess);
|
get.lockCall();
|
||||||
final IChunkSet iChunkSet = getExtent().processSet(this, get, set);
|
boolean postProcess = !(getExtent().getPostProcessor() instanceof EmptyBatchProcessor);
|
||||||
Runnable finalizer;
|
final IChunkSet iChunkSet = getExtent().processSet(this, get, set);
|
||||||
if (postProcess) {
|
Runnable finalizer;
|
||||||
finalizer = () -> {
|
if (postProcess) {
|
||||||
getExtent().postProcess(this, get.getCopy(), iChunkSet);
|
int copyKey = get.setCreateCopy(true);
|
||||||
finalize.run();
|
finalizer = () -> {
|
||||||
};
|
getExtent().postProcess(this, get.getCopy(copyKey), iChunkSet);
|
||||||
} else {
|
finalize.run();
|
||||||
finalizer = finalize;
|
};
|
||||||
|
} else {
|
||||||
|
finalizer = finalize;
|
||||||
|
}
|
||||||
|
return get.call(set, finalizer);
|
||||||
|
} finally {
|
||||||
|
get.unlockCall();
|
||||||
}
|
}
|
||||||
calledLock.unlock();
|
|
||||||
return get.call(set, finalizer);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -1110,103 +1069,86 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean setBiome(int x, int y, int z, BiomeType biome) {
|
public boolean setBiome(int x, int y, int z, BiomeType biome) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.setBiome(this, x, y, z, biome);
|
return delegate.setBiome(this, x, y, z, biome);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block) {
|
public <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.setBlock(this, x, y, z, block);
|
return delegate.setBlock(this, x, y, z, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BiomeType getBiomeType(int x, int y, int z) {
|
public BiomeType getBiomeType(int x, int y, int z) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.getBiome(this, x, y, z);
|
return delegate.getBiome(this, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlockState getBlock(int x, int y, int z) {
|
public BlockState getBlock(int x, int y, int z) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.getBlock(this, x, y, z);
|
return delegate.getBlock(this, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseBlock getFullBlock(int x, int y, int z) {
|
public BaseBlock getFullBlock(int x, int y, int z) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.getFullBlock(this, x, y, z);
|
return delegate.getFullBlock(this, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSkyLight(int x, int y, int z, int value) {
|
public void setSkyLight(int x, int y, int z, int value) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.setSkyLight(this, x, y, z, value);
|
delegate.setSkyLight(this, x, y, z, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setHeightMap(HeightMapType type, int[] heightMap) {
|
public void setHeightMap(HeightMapType type, int[] heightMap) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.setHeightMap(this, type, heightMap);
|
delegate.setHeightMap(this, type, heightMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeSectionLighting(int layer, boolean sky) {
|
public void removeSectionLighting(int layer, boolean sky) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.removeSectionLighting(this, layer, sky);
|
delegate.removeSectionLighting(this, layer, sky);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFullBright(int layer) {
|
public void setFullBright(int layer) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.setFullBright(this, layer);
|
delegate.setFullBright(this, layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBlockLight(int x, int y, int z, int value) {
|
public void setBlockLight(int x, int y, int z, int value) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.setBlockLight(this, x, y, z, value);
|
delegate.setBlockLight(this, x, y, z, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLightLayer(int layer, char[] toSet) {
|
public void setLightLayer(int layer, char[] toSet) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.setLightLayer(this, layer, toSet);
|
delegate.setLightLayer(this, layer, toSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSkyLightLayer(int layer, char[] toSet) {
|
public void setSkyLightLayer(int layer, char[] toSet) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
delegate.setSkyLightLayer(this, layer, toSet);
|
delegate.setSkyLightLayer(this, layer, toSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSkyLight(int x, int y, int z) {
|
public int getSkyLight(int x, int y, int z) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.getSkyLight(this, x, y, z);
|
return delegate.getSkyLight(this, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getEmittedLight(int x, int y, int z) {
|
public int getEmittedLight(int x, int y, int z) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.getEmittedLight(this, x, y, z);
|
return delegate.getEmittedLight(this, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBrightness(int x, int y, int z) {
|
public int getBrightness(int x, int y, int z) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.getBrightness(this, x, y, z);
|
return delegate.getBrightness(this, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getOpacity(int x, int y, int z) {
|
public int getOpacity(int x, int y, int z) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.getOpacity(this, x, y, z);
|
return delegate.getOpacity(this, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getHeightMap(HeightMapType type) {
|
public int[] getHeightMap(HeightMapType type) {
|
||||||
checkAndWaitOnCalledLock();
|
|
||||||
return delegate.getHeightMap(this, type);
|
return delegate.getHeightMap(this, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +184,8 @@ public final class NullChunk implements IQueueChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCreateCopy(boolean createCopy) {
|
public int setCreateCopy(boolean createCopy) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -32,7 +32,7 @@ public class WEManager {
|
|||||||
* @deprecated Use {@link #weManager()} instead.
|
* @deprecated Use {@link #weManager()} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated(forRemoval = true, since = "2.0.0")
|
@Deprecated(forRemoval = true, since = "2.0.0")
|
||||||
public static WEManager IMP = new WEManager();
|
public static WEManager IMP = weManager();
|
||||||
private final ArrayDeque<FaweMaskManager> managers = new ArrayDeque<>();
|
private final ArrayDeque<FaweMaskManager> managers = new ArrayDeque<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,19 +2,22 @@ package com.fastasyncworldedit.core.util.task;
|
|||||||
|
|
||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
import java.util.concurrent.ForkJoinWorkerThread;
|
import java.util.concurrent.ForkJoinWorkerThread;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class FaweForkJoinWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
|
public class FaweForkJoinWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
|
||||||
|
|
||||||
private final String nameFormat;
|
private final String nameFormat;
|
||||||
|
private final AtomicInteger idCounter;
|
||||||
|
|
||||||
public FaweForkJoinWorkerThreadFactory(String nameFormat) {
|
public FaweForkJoinWorkerThreadFactory(String nameFormat) {
|
||||||
this.nameFormat = nameFormat;
|
this.nameFormat = nameFormat;
|
||||||
|
this.idCounter = new AtomicInteger(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
|
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
|
||||||
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
|
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
|
||||||
worker.setName(String.format(nameFormat, worker.getPoolIndex()));
|
worker.setName(String.format(nameFormat, idCounter.getAndIncrement()));
|
||||||
return worker;
|
return worker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import com.fastasyncworldedit.core.limit.FaweLimit;
|
|||||||
import com.fastasyncworldedit.core.util.BrushCache;
|
import com.fastasyncworldedit.core.util.BrushCache;
|
||||||
import com.fastasyncworldedit.core.util.MainUtil;
|
import com.fastasyncworldedit.core.util.MainUtil;
|
||||||
import com.fastasyncworldedit.core.util.StringMan;
|
import com.fastasyncworldedit.core.util.StringMan;
|
||||||
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
import com.fastasyncworldedit.core.util.TextureHolder;
|
import com.fastasyncworldedit.core.util.TextureHolder;
|
||||||
import com.fastasyncworldedit.core.util.TextureUtil;
|
import com.fastasyncworldedit.core.util.TextureUtil;
|
||||||
import com.fastasyncworldedit.core.wrappers.WorldWrapper;
|
import com.fastasyncworldedit.core.wrappers.WorldWrapper;
|
||||||
@ -99,7 +100,6 @@ import java.util.TimeZone;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ public class LocalSession implements TextureHolder {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
private transient volatile Integer historyNegativeIndex;
|
private transient volatile Integer historyNegativeIndex;
|
||||||
private transient final Lock historyWriteLock = new ReentrantLock(true);
|
private transient final ReentrantLock historyWriteLock = new ReentrantLock(true);
|
||||||
private final transient Int2ObjectOpenHashMap<Tool> tools = new Int2ObjectOpenHashMap<>(0);
|
private final transient Int2ObjectOpenHashMap<Tool> tools = new Int2ObjectOpenHashMap<>(0);
|
||||||
private transient Mask sourceMask;
|
private transient Mask sourceMask;
|
||||||
private transient TextureUtil texture;
|
private transient TextureUtil texture;
|
||||||
@ -405,6 +405,23 @@ public class LocalSession implements TextureHolder {
|
|||||||
*/
|
*/
|
||||||
public void clearHistory() {
|
public void clearHistory() {
|
||||||
//FAWE start
|
//FAWE start
|
||||||
|
boolean mainThread = Fawe.isMainThread();
|
||||||
|
if (mainThread && !historyWriteLock.tryLock()) {
|
||||||
|
// Do not make main thread wait if we cannot immediately clear history (on player logout usually)
|
||||||
|
TaskManager.taskManager().async(this::clearHistoryTask);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
clearHistoryTask();
|
||||||
|
} finally {
|
||||||
|
// only if we are on the main thread, we ever called tryLock -> need to unlock again
|
||||||
|
if (mainThread) {
|
||||||
|
historyWriteLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearHistoryTask() {
|
||||||
historyWriteLock.lock();
|
historyWriteLock.lock();
|
||||||
try {
|
try {
|
||||||
// Ensure that changesets are properly removed
|
// Ensure that changesets are properly removed
|
||||||
@ -420,8 +437,8 @@ public class LocalSession implements TextureHolder {
|
|||||||
save();
|
save();
|
||||||
historySize = 0;
|
historySize = 0;
|
||||||
currentWorld = null;
|
currentWorld = null;
|
||||||
//FAWE end
|
|
||||||
}
|
}
|
||||||
|
//FAWE end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remember an edit session for the undo history. If the history maximum
|
* Remember an edit session for the undo history. If the history maximum
|
||||||
|
@ -29,7 +29,6 @@ import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard;
|
|||||||
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
||||||
import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard;
|
import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard;
|
||||||
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
|
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
|
||||||
import com.fastasyncworldedit.core.internal.exception.FaweException;
|
|
||||||
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
|
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
|
||||||
import com.fastasyncworldedit.core.limit.FaweLimit;
|
import com.fastasyncworldedit.core.limit.FaweLimit;
|
||||||
import com.fastasyncworldedit.core.util.ImgurUtility;
|
import com.fastasyncworldedit.core.util.ImgurUtility;
|
||||||
@ -449,6 +448,8 @@ public class ClipboardCommands {
|
|||||||
boolean atOrigin,
|
boolean atOrigin,
|
||||||
@Switch(name = 's', desc = "Select the region after pasting")
|
@Switch(name = 's', desc = "Select the region after pasting")
|
||||||
boolean selectPasted,
|
boolean selectPasted,
|
||||||
|
@Switch(name = 'n', desc = "No paste, select only. (Implies -s)")
|
||||||
|
boolean onlySelect,
|
||||||
@Switch(name = 'e', desc = "Paste entities if available")
|
@Switch(name = 'e', desc = "Paste entities if available")
|
||||||
boolean pasteEntities,
|
boolean pasteEntities,
|
||||||
@Switch(name = 'b', desc = "Paste biomes if available")
|
@Switch(name = 'b', desc = "Paste biomes if available")
|
||||||
@ -460,10 +461,12 @@ public class ClipboardCommands {
|
|||||||
final BlockVector3 to = atOrigin ? origin : session.getPlacementPosition(actor);
|
final BlockVector3 to = atOrigin ? origin : session.getPlacementPosition(actor);
|
||||||
checkPaste(actor, editSession, to, holder, clipboard);
|
checkPaste(actor, editSession, to, holder, clipboard);
|
||||||
|
|
||||||
clipboard.paste(editSession, to, !ignoreAirBlocks, pasteEntities, pasteBiomes);
|
if (!onlySelect) {
|
||||||
|
clipboard.paste(editSession, to, !ignoreAirBlocks, pasteEntities, pasteBiomes);
|
||||||
|
}
|
||||||
|
|
||||||
Region region = clipboard.getRegion().clone();
|
Region region = clipboard.getRegion().clone();
|
||||||
if (selectPasted) {
|
if (selectPasted || onlySelect) {
|
||||||
BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
|
BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
|
||||||
BlockVector3 realTo = to.add(holder.getTransform().apply(clipboardOffset.toVector3()).toBlockPoint());
|
BlockVector3 realTo = to.add(holder.getTransform().apply(clipboardOffset.toVector3()).toBlockPoint());
|
||||||
BlockVector3 max = realTo.add(holder
|
BlockVector3 max = realTo.add(holder
|
||||||
@ -475,7 +478,11 @@ public class ClipboardCommands {
|
|||||||
selector.learnChanges();
|
selector.learnChanges();
|
||||||
selector.explainRegionAdjust(actor, session);
|
selector.explainRegionAdjust(actor, session);
|
||||||
}
|
}
|
||||||
actor.print(Caption.of("fawe.worldedit.paste.command.paste", to));
|
if (onlySelect) {
|
||||||
|
actor.print(Caption.of("worldedit.paste.selected"));
|
||||||
|
} else {
|
||||||
|
actor.print(Caption.of("fawe.worldedit.paste.command.paste", to));
|
||||||
|
}
|
||||||
|
|
||||||
if (!actor.hasPermission("fawe.tips")) {
|
if (!actor.hasPermission("fawe.tips")) {
|
||||||
actor.print(Caption.of("fawe.tips.tip.copypaste"));
|
actor.print(Caption.of("fawe.tips.tip.copypaste"));
|
||||||
@ -512,7 +519,7 @@ public class ClipboardCommands {
|
|||||||
ClipboardHolder holder = session.getClipboard();
|
ClipboardHolder holder = session.getClipboard();
|
||||||
//FAWE start - use place
|
//FAWE start - use place
|
||||||
if (holder.getTransform().isIdentity() && sourceMask == null) {
|
if (holder.getTransform().isIdentity() && sourceMask == null) {
|
||||||
place(actor, world, session, editSession, ignoreAirBlocks, atOrigin, selectPasted,
|
place(actor, world, session, editSession, ignoreAirBlocks, atOrigin, selectPasted, onlySelect,
|
||||||
pasteEntities, pasteBiomes
|
pasteEntities, pasteBiomes
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -201,7 +201,7 @@ public class HistorySubCommands {
|
|||||||
.at(summary.maxX, world.getMaxY(), summary.maxZ)
|
.at(summary.maxX, world.getMaxY(), summary.maxZ)
|
||||||
);
|
);
|
||||||
rollback.setTime(historyFile.lastModified());
|
rollback.setTime(historyFile.lastModified());
|
||||||
RollbackDatabase db = DBHandler.IMP
|
RollbackDatabase db = DBHandler.dbHandler()
|
||||||
.getDatabase(world);
|
.getDatabase(world);
|
||||||
db.logEdit(rollback);
|
db.logEdit(rollback);
|
||||||
actor.print(TextComponent.of("Logging: " + historyFile));
|
actor.print(TextComponent.of("Logging: " + historyFile));
|
||||||
|
@ -313,9 +313,9 @@ public class SchematicCommands {
|
|||||||
Actor actor, LocalSession session,
|
Actor actor, LocalSession session,
|
||||||
@Arg(desc = "File name.")
|
@Arg(desc = "File name.")
|
||||||
String filename,
|
String filename,
|
||||||
@Arg(desc = "Format name.", def = "fast")
|
|
||||||
String formatName,
|
|
||||||
//FAWE start - random rotation
|
//FAWE start - random rotation
|
||||||
|
@Arg(desc = "Format name.", def = "")
|
||||||
|
String formatName,
|
||||||
@Switch(name = 'r', desc = "Apply random rotation to the clipboard")
|
@Switch(name = 'r', desc = "Apply random rotation to the clipboard")
|
||||||
boolean randomRotate
|
boolean randomRotate
|
||||||
//FAWE end
|
//FAWE end
|
||||||
@ -325,6 +325,11 @@ public class SchematicCommands {
|
|||||||
//FAWE start
|
//FAWE start
|
||||||
ClipboardFormat format;
|
ClipboardFormat format;
|
||||||
InputStream in = null;
|
InputStream in = null;
|
||||||
|
// if format is set explicitly, do not look up by extension!
|
||||||
|
boolean noExplicitFormat = formatName == null;
|
||||||
|
if (noExplicitFormat) {
|
||||||
|
formatName = "fast";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
URI uri;
|
URI uri;
|
||||||
if (formatName.startsWith("url:")) {
|
if (formatName.startsWith("url:")) {
|
||||||
@ -369,7 +374,7 @@ public class SchematicCommands {
|
|||||||
actor.print(Caption.of("fawe.error.no-perm", "worldedit.schematic.load.other"));
|
actor.print(Caption.of("fawe.error.no-perm", "worldedit.schematic.load.other"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (filename.matches(".*\\.[\\w].*")) {
|
if (noExplicitFormat && filename.matches(".*\\.[\\w].*")) {
|
||||||
format = ClipboardFormats
|
format = ClipboardFormats
|
||||||
.findByExtension(filename.substring(filename.lastIndexOf('.') + 1));
|
.findByExtension(filename.substring(filename.lastIndexOf('.') + 1));
|
||||||
} else {
|
} else {
|
||||||
|
@ -26,7 +26,6 @@ import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
|||||||
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
|
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
|
||||||
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
|
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
|
||||||
import com.fastasyncworldedit.core.util.MainUtil;
|
import com.fastasyncworldedit.core.util.MainUtil;
|
||||||
import com.google.common.collect.HashMultimap;
|
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
import com.google.common.collect.Multimaps;
|
import com.google.common.collect.Multimaps;
|
||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
@ -51,6 +50,7 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -64,7 +64,9 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
public class ClipboardFormats {
|
public class ClipboardFormats {
|
||||||
|
|
||||||
private static final Map<String, ClipboardFormat> aliasMap = new HashMap<>();
|
private static final Map<String, ClipboardFormat> aliasMap = new HashMap<>();
|
||||||
private static final Multimap<String, ClipboardFormat> fileExtensionMap = HashMultimap.create();
|
// FAWE start - keep order of ClipboardFormat entries -> prefer FAST over SPONGE_SCHEMATIC
|
||||||
|
private static final Multimap<String, ClipboardFormat> fileExtensionMap = Multimaps.newMultimap(new HashMap<>(), LinkedHashSet::new);
|
||||||
|
// FAWE end
|
||||||
private static final List<ClipboardFormat> registeredFormats = new ArrayList<>();
|
private static final List<ClipboardFormat> registeredFormats = new ArrayList<>();
|
||||||
|
|
||||||
public static void registerClipboardFormat(ClipboardFormat format) {
|
public static void registerClipboardFormat(ClipboardFormat format) {
|
||||||
|
@ -164,14 +164,23 @@ public class RegionIntersection extends AbstractRegion {
|
|||||||
int bz = chunk.getZ() << 4;
|
int bz = chunk.getZ() << 4;
|
||||||
int tx = bx + 15;
|
int tx = bx + 15;
|
||||||
int tz = bz + 15;
|
int tz = bz + 15;
|
||||||
|
List<Region> intersecting = new ArrayList<>(2);
|
||||||
for (Region region : regions) {
|
for (Region region : regions) {
|
||||||
BlockVector3 regMin = region.getMinimumPoint();
|
BlockVector3 regMin = region.getMinimumPoint();
|
||||||
BlockVector3 regMax = region.getMaximumPoint();
|
BlockVector3 regMax = region.getMaximumPoint();
|
||||||
if (tx >= regMin.getX() && bx <= regMax.getX() && tz >= regMin.getZ() && bz <= regMax.getZ()) {
|
if (tx >= regMin.getX() && bx <= regMax.getX() && tz >= regMin.getZ() && bz <= regMax.getZ()) {
|
||||||
return region.processSet(chunk, get, set);
|
intersecting.add(region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
if (intersecting.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (intersecting.size() == 1) {
|
||||||
|
return intersecting.get(0).processSet(chunk, get, set);
|
||||||
|
}
|
||||||
|
// if multiple regions intersect with this chunk, we must be more careful, otherwise one region might trim content of
|
||||||
|
// another region
|
||||||
|
return super.processSet(chunk, get, set);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -254,6 +254,8 @@ public class CylinderRegionSelector implements RegionSelector, CUIRegion {
|
|||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
region = new CylinderRegion(region.getWorld());
|
region = new CylinderRegion(region.getWorld());
|
||||||
|
selectedCenter = false;
|
||||||
|
selectedRadius = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -227,6 +227,8 @@ public class EllipsoidRegionSelector implements RegionSelector, CUIRegion {
|
|||||||
public void clear() {
|
public void clear() {
|
||||||
region.setCenter(BlockVector3.ZERO);
|
region.setCenter(BlockVector3.ZERO);
|
||||||
region.setRadius(Vector3.ZERO);
|
region.setRadius(Vector3.ZERO);
|
||||||
|
started = false;
|
||||||
|
selectedRadius = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren