3
0
Mirror von https://github.com/IntellectualSites/FastAsyncWorldEdit.git synchronisiert 2024-11-16 16:10:07 +01:00

fix some bugs/todos

Dieser Commit ist enthalten in:
SirYwell 2024-01-02 16:12:13 +01:00
Ursprung c0903e71f7
Commit 0bee4a6bf2
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
11 geänderte Dateien mit 220 neuen und 181 gelöschten Zeilen

Datei anzeigen

@ -95,7 +95,7 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
net.minecraft.world.level.block.state.BlockState blockState
) {
int currentTick = MinecraftServer.currentTick;
if (Fawe.isMainThread()) {
if (Fawe.isTickThread()) {
return levelChunk.setBlockState(blockPos, blockState,
this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE)
);
@ -250,7 +250,7 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
}
}
};
// TODO global sync is not correct on folia
// we don't support Folia on that version, we can run this globally
TaskManager.taskManager().async(() -> TaskManager.taskManager().syncGlobal(runnableVal));
}
@ -267,10 +267,10 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
}
}
};
if (Fawe.isMainThread()) {
if (Fawe.isTickThread()) {
runnableVal.run();
} else {
// TODO global sync is not correct on folia
// we don't support Folia on that version, we can run this globally
TaskManager.taskManager().syncGlobal(runnableVal);
}
cachedChanges.clear();

Datei anzeigen

@ -95,7 +95,7 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
net.minecraft.world.level.block.state.BlockState blockState
) {
int currentTick = MinecraftServer.currentTick;
if (Fawe.isMainThread()) {
if (Fawe.isTickThread()) {
return levelChunk.setBlockState(blockPos, blockState,
this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE)
);
@ -250,8 +250,8 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
}
}
};
// TODO ???
// TaskManager.taskManager().async(() -> TaskManager.taskManager().sync(runnableVal));
// we don't support Folia on that version, we can run this globally
TaskManager.taskManager().async(() -> TaskManager.taskManager().syncGlobal(runnableVal));
}
@Override
@ -267,11 +267,11 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
}
}
};
if (Fawe.isMainThread()) {
if (Fawe.isTickThread()) {
runnableVal.run();
} else {
// TODO
// TaskManager.taskManager().sync(runnableVal);
// we don't support Folia on that version, we can run this globally
TaskManager.taskManager().syncGlobal(runnableVal);
}
cachedChanges.clear();
cachedChunksToSend.clear();

Datei anzeigen

@ -1,8 +1,9 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R1;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.util.FoliaSupport;
import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.collection.FlushingPartitionedCache;
import com.fastasyncworldedit.core.util.task.RunnableVal;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
@ -14,21 +15,19 @@ import com.sk89q.worldedit.world.block.BlockState;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.LevelChunk;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData;
import org.bukkit.event.block.BlockPhysicsEvent;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@ -50,8 +49,7 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
private final PaperweightFaweAdapter paperweightFaweAdapter;
private final WeakReference<Level> level;
private final AtomicInteger lastTick;
private final Set<CachedChange> cachedChanges = new HashSet<>();
private final Set<IntPair> cachedChunksToSend = new HashSet<>();
private final FlushingPartitionedCache<IntPair, CachedChange, Set<CachedChange>> cache;
private SideEffectSet sideEffectSet;
public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAdapter, WeakReference<Level> level) {
@ -60,7 +58,29 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
// Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging.
// - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway.
// TODO
this.lastTick = new AtomicInteger(0);
this.lastTick = new AtomicInteger();
if (!FoliaSupport.isFolia()) {
this.lastTick.set(Bukkit.getCurrentTick());
}
this.cache = new FlushingPartitionedCache<>(
cachedChange -> new IntPair(cachedChange.blockPos.getX() >> 4, cachedChange.blockPos.getZ() >> 4),
HashSet::new,
(chunk, changes) -> {
// TODO only send chunks based on ticks? Need to make sure everything actually flushed in the end
/*boolean nextTick = true;
if (!FoliaSupport.isFolia()) {
int currentTick = MinecraftServer.currentTick;
nextTick = lastTick.get() > currentTick;
if (nextTick) {
lastTick.set(currentTick);
}
}*/
flushAsync(chunk, changes, true);
},
2048,
16
);
}
private Level getLevel() {
@ -96,22 +116,13 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
LevelChunk levelChunk, BlockPos blockPos,
net.minecraft.world.level.block.state.BlockState blockState
) {
int currentTick = MinecraftServer.currentTick;
if (Fawe.isMainThread()) {
if (PaperweightPlatformAdapter.isTickThreadFor(levelChunk)) {
return levelChunk.setBlockState(blockPos, blockState,
this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE)
);
}
// Since FAWE is.. Async we need to do it on the main thread (wooooo.. :( )
cachedChanges.add(new CachedChange(levelChunk, blockPos, blockState));
cachedChunksToSend.add(new IntPair(levelChunk.locX, levelChunk.locZ));
boolean nextTick = lastTick.get() > currentTick;
if (nextTick || cachedChanges.size() >= 1024) {
if (nextTick) {
lastTick.set(currentTick);
}
flushAsync(nextTick);
}
cache.insert(new CachedChange(levelChunk, blockPos, blockState));
return blockState;
}
@ -228,16 +239,7 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
getLevel().onBlockStateChange(blockPos, oldState, newState);
}
private synchronized void flushAsync(final boolean sendChunks) {
final Set<CachedChange> changes = Set.copyOf(cachedChanges);
cachedChanges.clear();
final Set<IntPair> toSend;
if (sendChunks) {
toSend = Set.copyOf(cachedChunksToSend);
cachedChunksToSend.clear();
} else {
toSend = Collections.emptySet();
}
private synchronized void flushAsync(IntPair chunk, Set<CachedChange> changes, final boolean sendChunks) {
RunnableVal<Object> runnableVal = new RunnableVal<>() {
@Override
public void run(Object value) {
@ -247,36 +249,18 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
if (!sendChunks) {
return;
}
for (IntPair chunk : toSend) {
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
}
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
}
};
// TODO ???
// TaskManager.taskManager().async(() -> TaskManager.taskManager().sync(runnableVal));
TaskManager.taskManager().async(
() -> PaperweightPlatformAdapter.task(runnableVal, getLevel().getWorld().getHandle(), chunk.x(), chunk.z())
);
}
@Override
public synchronized void flush() {
RunnableVal<Object> runnableVal = new RunnableVal<>() {
@Override
public void run(Object value) {
cachedChanges.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState,
sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE)
));
for (IntPair chunk : cachedChunksToSend) {
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
}
}
};
if (Fawe.isMainThread()) {
runnableVal.run();
} else {
// TODO
// TaskManager.taskManager().sync(runnableVal);
}
cachedChanges.clear();
cachedChunksToSend.clear();
this.cache.flush();
}
private record CachedChange(

Datei anzeigen

@ -7,6 +7,7 @@ import com.fastasyncworldedit.bukkit.adapter.NMSAdapter;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.util.FoliaSupport;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager;
@ -24,6 +25,7 @@ import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import io.papermc.lib.PaperLib;
import io.papermc.paper.util.TickThread;
import io.papermc.paper.world.ChunkEntitySlices;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
@ -304,9 +306,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
}
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
try {
CraftChunk chunk;
try {
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
future.get(10, TimeUnit.SECONDS);
} catch (TimeoutException e) {
String world = serverLevel.getWorld().getName();
// We've already taken 10 seconds we can afford to wait a little here.
@ -314,13 +315,14 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
if (loaded) {
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
// Retry chunk load
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
} else {
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
}
}
addTicket(serverLevel, chunkX, chunkZ);
return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk);
// chunk is loaded now, can access it directly
return serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
} catch (Throwable e) {
e.printStackTrace();
}
@ -721,6 +723,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
TaskManager.taskManager().task(task, BukkitAdapter.adapt(level.getWorld()), chunkX, chunkZ);
}
public static boolean isTickThreadFor(LevelChunk levelChunk) {
if (FoliaSupport.isFolia()) {
return TickThread.isTickThreadFor(levelChunk.level, levelChunk.locX, levelChunk.locZ);
}
return Fawe.isTickThread();
}
record FakeIdMapBlock(int size) implements IdMap<net.minecraft.world.level.block.state.BlockState> {
@Override

Datei anzeigen

@ -1,9 +1,9 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.util.FoliaSupport;
import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.collection.FlushingPartitionedCache;
import com.fastasyncworldedit.core.util.task.RunnableVal;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
@ -15,7 +15,6 @@ import com.sk89q.worldedit.world.block.BlockState;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.world.level.Level;
@ -29,7 +28,6 @@ import org.bukkit.event.block.BlockPhysicsEvent;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@ -51,8 +49,7 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
private final PaperweightFaweAdapter paperweightFaweAdapter;
private final WeakReference<Level> level;
private final AtomicInteger lastTick;
private final Set<CachedChange> cachedChanges = new HashSet<>();
private final Set<IntPair> cachedChunksToSend = new HashSet<>();
private final FlushingPartitionedCache<IntPair, CachedChange, Set<CachedChange>> cache;
private SideEffectSet sideEffectSet;
public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAdapter, WeakReference<Level> level) {
@ -64,6 +61,24 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
if (!FoliaSupport.isFolia()) {
this.lastTick.set(Bukkit.getCurrentTick());
}
this.cache = new FlushingPartitionedCache<>(
cachedChange -> new IntPair(cachedChange.blockPos.getX() >> 4, cachedChange.blockPos.getZ() >> 4),
HashSet::new,
(chunk, changes) -> {
// TODO only send chunks based on ticks? Need to make sure everything actually flushed in the end
/*boolean nextTick = true;
if (!FoliaSupport.isFolia()) {
int currentTick = MinecraftServer.currentTick;
nextTick = lastTick.get() > currentTick;
if (nextTick) {
lastTick.set(currentTick);
}
}*/
flushAsync(chunk, changes, true);
},
2048,
16
);
}
private Level getLevel() {
@ -99,22 +114,13 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
LevelChunk levelChunk, BlockPos blockPos,
net.minecraft.world.level.block.state.BlockState blockState
) {
int currentTick = MinecraftServer.currentTick;
if (Fawe.isMainThread()) {
if (PaperweightPlatformAdapter.isTickThreadFor(levelChunk)) {
return levelChunk.setBlockState(blockPos, blockState,
this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE)
);
}
// Since FAWE is.. Async we need to do it on the main thread (wooooo.. :( )
cachedChanges.add(new CachedChange(levelChunk, blockPos, blockState));
cachedChunksToSend.add(new IntPair(levelChunk.locX, levelChunk.locZ));
boolean nextTick = lastTick.get() > currentTick;
if (nextTick || cachedChanges.size() >= 1024) {
if (nextTick) {
lastTick.set(currentTick);
}
flushAsync(nextTick);
}
cache.insert(new CachedChange(levelChunk, blockPos, blockState));
return blockState;
}
@ -231,16 +237,7 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
getLevel().onBlockStateChange(blockPos, oldState, newState);
}
private synchronized void flushAsync(final boolean sendChunks) {
final Set<CachedChange> changes = Set.copyOf(cachedChanges);
cachedChanges.clear();
final Set<IntPair> toSend;
if (sendChunks) {
toSend = Set.copyOf(cachedChunksToSend);
cachedChunksToSend.clear();
} else {
toSend = Collections.emptySet();
}
private synchronized void flushAsync(IntPair chunk, Set<CachedChange> changes, final boolean sendChunks) {
RunnableVal<Object> runnableVal = new RunnableVal<>() {
@Override
public void run(Object value) {
@ -250,36 +247,17 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
if (!sendChunks) {
return;
}
for (IntPair chunk : toSend) {
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
}
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
}
};
// TODO global sync is not correct on folia
TaskManager.taskManager().async(() -> TaskManager.taskManager().syncGlobal(runnableVal));
TaskManager.taskManager().async(
() -> PaperweightPlatformAdapter.task(runnableVal, getLevel().getWorld().getHandle(), chunk.x(), chunk.z())
);
}
@Override
public synchronized void flush() {
RunnableVal<Object> runnableVal = new RunnableVal<>() {
@Override
public void run(Object value) {
cachedChanges.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState,
sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE)
));
for (IntPair chunk : cachedChunksToSend) {
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
}
}
};
if (Fawe.isMainThread()) {
runnableVal.run();
} else {
// TODO global sync is not correct on folia
TaskManager.taskManager().syncGlobal(runnableVal);
}
cachedChanges.clear();
cachedChunksToSend.clear();
this.cache.flush();
}
private record CachedChange(

Datei anzeigen

@ -7,6 +7,7 @@ import com.fastasyncworldedit.bukkit.adapter.NMSAdapter;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.util.FoliaSupport;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager;
@ -21,6 +22,7 @@ import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import io.papermc.lib.PaperLib;
import io.papermc.paper.util.TickThread;
import io.papermc.paper.world.ChunkEntitySlices;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
@ -292,9 +294,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
}
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
try {
CraftChunk chunk;
try {
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
future.get(10, TimeUnit.SECONDS);
} catch (TimeoutException e) {
String world = serverLevel.getWorld().getName();
// We've already taken 10 seconds we can afford to wait a little here.
@ -302,26 +303,28 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
if (loaded) {
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
// Retry chunk load
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
} else {
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
}
}
addTicket(serverLevel, chunkX, chunkZ);
return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk);
// chunk is loaded now, can access it directly
return serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
} catch (Throwable e) {
e.printStackTrace();
}
}
return TaskManager.taskManager().syncAt(() -> serverLevel.getChunk(chunkX, chunkZ),
BukkitAdapter.adapt(serverLevel.getWorld()), chunkX, chunkZ);
BukkitAdapter.adapt(serverLevel.getWorld()), chunkX, chunkZ
);
}
private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) {
// Ensure chunk is definitely loaded before applying a ticket
PaperweightPlatformAdapter.task(() -> serverLevel
.getChunkSource()
.addRegionTicket(TicketType.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE),
.getChunkSource()
.addRegionTicket(TicketType.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE),
serverLevel, chunkX, chunkZ
);
}
@ -681,6 +684,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
TaskManager.taskManager().task(task, BukkitAdapter.adapt(level.getWorld()), chunkX, chunkZ);
}
public static boolean isTickThreadFor(LevelChunk levelChunk) {
if (FoliaSupport.isFolia()) {
return TickThread.isTickThreadFor(levelChunk.level, levelChunk.locX, levelChunk.locZ);
}
return Fawe.isTickThread();
}
record FakeIdMapBlock(int size) implements IdMap<net.minecraft.world.level.block.state.BlockState> {
@Override

Datei anzeigen

@ -1,8 +1,9 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.util.FoliaSupport;
import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.collection.FlushingPartitionedCache;
import com.fastasyncworldedit.core.util.task.RunnableVal;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
@ -14,7 +15,6 @@ import com.sk89q.worldedit.world.block.BlockState;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.world.level.Level;
@ -28,7 +28,6 @@ import org.bukkit.event.block.BlockPhysicsEvent;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@ -50,8 +49,7 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
private final PaperweightFaweAdapter paperweightFaweAdapter;
private final WeakReference<Level> level;
private final AtomicInteger lastTick;
private final Set<CachedChange> cachedChanges = new HashSet<>();
private final Set<IntPair> cachedChunksToSend = new HashSet<>();
private final FlushingPartitionedCache<IntPair, CachedChange, Set<CachedChange>> cache;
private SideEffectSet sideEffectSet;
public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAdapter, WeakReference<Level> level) {
@ -63,6 +61,24 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
if (!FoliaSupport.isFolia()) {
this.lastTick.set(Bukkit.getCurrentTick());
}
this.cache = new FlushingPartitionedCache<>(
cachedChange -> new IntPair(cachedChange.blockPos.getX() >> 4, cachedChange.blockPos.getZ() >> 4),
HashSet::new,
(chunk, changes) -> {
// TODO only send chunks based on ticks? Need to make sure everything actually flushed in the end
/*boolean nextTick = true;
if (!FoliaSupport.isFolia()) {
int currentTick = MinecraftServer.currentTick;
nextTick = lastTick.get() > currentTick;
if (nextTick) {
lastTick.set(currentTick);
}
}*/
flushAsync(chunk, changes, true);
},
2048,
16
);
}
private Level getLevel() {
@ -98,22 +114,13 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
LevelChunk levelChunk, BlockPos blockPos,
net.minecraft.world.level.block.state.BlockState blockState
) {
int currentTick = MinecraftServer.currentTick;
if (Fawe.isMainThread()) {
if (PaperweightPlatformAdapter.isTickThreadFor(levelChunk)) {
return levelChunk.setBlockState(blockPos, blockState,
this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE)
);
}
// Since FAWE is.. Async we need to do it on the main thread (wooooo.. :( )
cachedChanges.add(new CachedChange(levelChunk, blockPos, blockState));
cachedChunksToSend.add(new IntPair(levelChunk.locX, levelChunk.locZ));
boolean nextTick = lastTick.get() > currentTick;
if (nextTick || cachedChanges.size() >= 1024) {
if (nextTick) {
lastTick.set(currentTick);
}
flushAsync(nextTick);
}
cache.insert(new CachedChange(levelChunk, blockPos, blockState));
return blockState;
}
@ -230,16 +237,7 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
getLevel().onBlockStateChange(blockPos, oldState, newState);
}
private synchronized void flushAsync(final boolean sendChunks) {
final Set<CachedChange> changes = Set.copyOf(cachedChanges);
cachedChanges.clear();
final Set<IntPair> toSend;
if (sendChunks) {
toSend = Set.copyOf(cachedChunksToSend);
cachedChunksToSend.clear();
} else {
toSend = Collections.emptySet();
}
private synchronized void flushAsync(IntPair chunk, Set<CachedChange> changes, final boolean sendChunks) {
RunnableVal<Object> runnableVal = new RunnableVal<>() {
@Override
public void run(Object value) {
@ -249,36 +247,17 @@ public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess<Level
if (!sendChunks) {
return;
}
for (IntPair chunk : toSend) {
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
}
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
}
};
// TODO
// TaskManager.taskManager().async(() -> TaskManager.taskManager().sync(runnableVal));
TaskManager.taskManager().async(
() -> PaperweightPlatformAdapter.task(runnableVal, getLevel().getWorld().getHandle(), chunk.x(), chunk.z())
);
}
@Override
public synchronized void flush() {
RunnableVal<Object> runnableVal = new RunnableVal<>() {
@Override
public void run(Object value) {
cachedChanges.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState,
sideEffectSet != null && sideEffectSet.shouldApply(SideEffect.UPDATE)
));
for (IntPair chunk : cachedChunksToSend) {
PaperweightPlatformAdapter.sendChunk(getLevel().getWorld().getHandle(), chunk.x(), chunk.z(), false);
}
}
};
if (Fawe.isMainThread()) {
runnableVal.run();
} else {
// TODO
// TaskManager.taskManager().sync(runnableVal);
}
cachedChanges.clear();
cachedChunksToSend.clear();
this.cache.flush();
}
private record CachedChange(

Datei anzeigen

@ -7,6 +7,7 @@ import com.fastasyncworldedit.bukkit.adapter.NMSAdapter;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.util.FoliaSupport;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager;
@ -21,6 +22,7 @@ import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import io.papermc.lib.PaperLib;
import io.papermc.paper.util.TickThread;
import io.papermc.paper.world.ChunkEntitySlices;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
@ -292,9 +294,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
}
CompletableFuture<org.bukkit.Chunk> future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true);
try {
CraftChunk chunk;
try {
chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS);
future.get(10, TimeUnit.SECONDS);
} catch (TimeoutException e) {
String world = serverLevel.getWorld().getName();
// We've already taken 10 seconds we can afford to wait a little here.
@ -302,13 +303,14 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
if (loaded) {
LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world);
// Retry chunk load
chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get();
} else {
throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!");
}
}
addTicket(serverLevel, chunkX, chunkZ);
return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk);
// chunk is loaded now, can access it directly
return serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ);
} catch (Throwable e) {
e.printStackTrace();
}
@ -678,6 +680,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
TaskManager.taskManager().task(task, BukkitAdapter.adapt(level.getWorld()), chunkX, chunkZ);
}
public static boolean isTickThreadFor(LevelChunk levelChunk) {
if (FoliaSupport.isFolia()) {
return TickThread.isTickThreadFor(levelChunk.level, levelChunk.locX, levelChunk.locZ);
}
return Fawe.isTickThread();
}
record FakeIdMapBlock(int size) implements IdMap<net.minecraft.world.level.block.state.BlockState> {
@Override

Datei anzeigen

@ -135,7 +135,7 @@ public class Fawe {
WEManager.weManager().addManagers(Fawe.this.implementation.getMaskManagers());
} catch (Throwable ignored) {
}
}, 0);
}, 1);
if (!FoliaSupport.isFolia()) {
// TODO TaskManager.taskManager().repeat(timer, 1);

Datei anzeigen

@ -10,8 +10,8 @@ public final class FoliaSupport {
static {
boolean isFolia = false;
try {
// Assume API is present
Class.forName("io.papermc.paper.threadedregions.scheduler.EntityScheduler");
// Assume implementation details are present
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
isFolia = true;
} catch (Exception unused) {

Datei anzeigen

@ -0,0 +1,70 @@
package com.fastasyncworldedit.core.util.collection;
import org.jetbrains.annotations.ApiStatus;
import java.io.Flushable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
*
* @param <P> the partition key type
* @param <E> the element type
* @param <C> the partition value type
* @since TODO
*/
@ApiStatus.Internal
public class FlushingPartitionedCache<P, E, C extends Collection<E>> implements Flushable {
private final Map<P, C> map;
private final Function<? super E, ? extends P> partition;
private final Supplier<? extends C> constructor;
private final BiConsumer<? super P, ? super C> flusher;
private final int maxEntriesPerCollection;
public FlushingPartitionedCache(
Function<? super E, ? extends P> partition,
Supplier<? extends C> constructor,
BiConsumer<? super P, ? super C> flusher,
int maxEntriesPerCollection,
int maxCollections
) {
this.partition = partition;
this.constructor = constructor;
this.flusher = flusher;
this.maxEntriesPerCollection = maxEntriesPerCollection;
this.map = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(final Map.Entry<P, C> eldest) {
final boolean remove = size() > maxCollections;
if (remove) {
flusher.accept(eldest.getKey(), eldest.getValue());
}
return remove;
}
};
}
public void insert(E element) {
final P partition = this.partition.apply(element);
final C coll = this.map.computeIfAbsent(partition, k -> this.constructor.get());
coll.add(element);
if (coll.size() > this.maxEntriesPerCollection) {
this.flusher.accept(partition, coll);
this.map.remove(partition);
}
}
@Override
public void flush() {
for (final Map.Entry<P, C> entry : this.map.entrySet()) {
this.flusher.accept(entry.getKey(), entry.getValue());
}
this.map.clear();
}
}