From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Mon, 16 Dec 2024 09:03:35 -0800 Subject: [PATCH] Add PaperHooks diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java new file mode 100644 index 0000000000000000000000000000000000000000..2988c418b34d6f699a9c24406cfd6949465b64f0 --- /dev/null +++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java @@ -0,0 +1,241 @@ +package ca.spottedleaf.moonrise.paper; + +import ca.spottedleaf.moonrise.common.PlatformHooks; +import ca.spottedleaf.moonrise.paper.util.BaseChunkSystemHooks; +import com.mojang.datafixers.DSL; +import com.mojang.datafixers.DataFixer; +import com.mojang.serialization.Dynamic; +import java.util.Collection; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.GenerationChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.boss.EnderDragonPart; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.storage.SerializableChunkData; +import net.minecraft.world.level.entity.EntityTypeTest; +import net.minecraft.world.phys.AABB; +import java.util.List; +import java.util.function.Predicate; + +public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHooks { + + @Override + public String getBrand() { + return "Paper"; + } + + @Override + public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) { + return blockState.getLightEmission(); + } + + @Override + public Predicate maybeHasLightEmission() { + return (final BlockState state) -> { + return state.getLightEmission() != 0; + }; + } + + @Override + public boolean hasCurrentlyLoadingChunk() { + return false; + } + + @Override + public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) { + return null; + } + + @Override + public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) { + + } + + @Override + public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) { + + } + + @Override + public boolean allowAsyncTicketUpdates() { + return true; + } + + @Override + public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) { + + } + + @Override + public void chunkUnloadFromWorld(final LevelChunk chunk) { + + } + + @Override + public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) { + + } + + @Override + public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) { + + } + + @Override + public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) { + + } + + @Override + public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate predicate, final List into) { + final Collection parts = world.dragonParts(); + if (parts.isEmpty()) { + return; + } + + for (final EnderDragonPart part : parts) { + if (part != entity && part.getBoundingBox().intersects(boundingBox) && (predicate == null || predicate.test(part))) { + into.add(part); + } + } + } + + @Override + public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, final AABB boundingBox, final Predicate predicate, final List into, final int maxCount) { + if (into.size() >= maxCount) { + // fix neoforge issue: do not add if list is already full + return; + } + + final Collection parts = world.dragonParts(); + if (parts.isEmpty()) { + return; + } + for (final EnderDragonPart part : parts) { + if (!part.getBoundingBox().intersects(boundingBox)) { + continue; + } + final T casted = (T)entityTypeTest.tryCast(part); + if (casted != null && (predicate == null || predicate.test(casted))) { + into.add(casted); + if (into.size() >= maxCount) { + break; + } + } + } + } + + @Override + public void entityMove(final Entity entity, final long oldSection, final long newSection) { + + } + + @Override + public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) { + return true; + } + + @Override + public boolean configFixMC224294() { + return true; + } + + @Override + public boolean configAutoConfigSendDistance() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance; + } + + @Override + public double configPlayerMaxLoadRate() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; + } + + @Override + public double configPlayerMaxGenRate() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; + } + + @Override + public double configPlayerMaxSendRate() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; + } + + @Override + public int configPlayerMaxConcurrentLoads() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; + } + + @Override + public int configPlayerMaxConcurrentGens() { + return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; + } + + @Override + public long configAutoSaveInterval(final ServerLevel world) { + return world.paperConfig().chunks.autoSaveInterval.value(); + } + + @Override + public int configMaxAutoSavePerTick(final ServerLevel world) { + return world.paperConfig().chunks.maxAutoSaveChunksPerTick; + } + + @Override + public boolean configFixMC159283() { + return true; + } + + @Override + public boolean forceNoSave(final ChunkAccess chunk) { + return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave; + } + + @Override + public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt, + final int fromVersion, final int toVersion) { + return (CompoundTag)dataFixer.update( + type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion + ).getValue(); + } + + @Override + public boolean hasMainChunkLoadHook() { + return false; + } + + @Override + public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) { + + } + + @Override + public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities) { + return entities; + } + + @Override + public void unloadEntity(final Entity entity) { + entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD); + } + + @Override + public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) { + net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities()); + } + + @Override + public int modifyEntityTrackingRange(final Entity entity, final int currentRange) { + return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange); + } +} diff --git a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java new file mode 100644 index 0000000000000000000000000000000000000000..34b45bc11124efb22f0f3ae5b2ad8f445c719476 --- /dev/null +++ b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java @@ -0,0 +1,332 @@ +package ca.spottedleaf.moonrise.paper.util; + +import ca.spottedleaf.concurrentutil.util.Priority; +import com.mojang.logging.LogUtils; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkResult; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.TicketType; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.status.ChunkPyramid; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkStep; +import org.bukkit.Bukkit; +import org.slf4j.Logger; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +public abstract class BaseChunkSystemHooks implements ca.spottedleaf.moonrise.common.util.ChunkSystemHooks { + + private static final Logger LOGGER = LogUtils.getLogger(); + private static final ChunkStep FULL_CHUNK_STEP = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL); + private static final TicketType CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo); + + private long chunkLoadCounter = 0L; + + private static int getDistance(final ChunkStatus status) { + return FULL_CHUNK_STEP.getAccumulatedRadiusOf(status); + } + + @Override + public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) { + this.scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL); + } + + @Override + public void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) { + level.chunkSource.mainThreadProcessor.execute(run); + } + + @Override + public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen, + final ChunkStatus toStatus, final boolean addTicket, final Priority priority, + final Consumer onComplete) { + if (gen) { + this.scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + return; + } + this.scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { + if (chunk == null) { + if (onComplete != null) { + onComplete.accept(null); + } + } else { + if (chunk.getPersistedStatus().isOrAfter(toStatus)) { + BaseChunkSystemHooks.this.scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + } else { + if (onComplete != null) { + onComplete.accept(null); + } + } + } + }); + } + + @Override + public void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, + final boolean addTicket, final Priority priority, final Consumer onComplete) { + if (!Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) { + this.scheduleChunkTask(level, chunkX, chunkZ, () -> { + BaseChunkSystemHooks.this.scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + }, priority); + return; + } + + final int minLevel = 33 + getDistance(toStatus); + final Long chunkReference = addTicket ? Long.valueOf(++this.chunkLoadCounter) : null; + final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); + + if (addTicket) { + level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); + } + level.chunkSource.runDistanceManagerUpdates(); + + final Consumer loadCallback = (final ChunkAccess chunk) -> { + try { + if (onComplete != null) { + onComplete.accept(chunk); + } + } catch (final Throwable thr) { + LOGGER.error("Exception handling chunk load callback", thr); + com.destroystokyo.paper.util.SneakyThrow.sneaky(thr); + } finally { + if (addTicket) { + level.chunkSource.addTicketAtLevel(net.minecraft.server.level.TicketType.UNKNOWN, chunkPos, minLevel, chunkPos); + level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); + } + } + }; + + final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); + + if (holder == null || holder.getTicketLevel() > minLevel) { + loadCallback.accept(null); + return; + } + + final CompletableFuture> loadFuture = holder.scheduleChunkGenerationTask(toStatus, level.chunkSource.chunkMap); + + if (loadFuture.isDone()) { + loadCallback.accept(loadFuture.join().orElse(null)); + return; + } + + loadFuture.whenCompleteAsync((final ChunkResult result, final Throwable thr) -> { + if (thr != null) { + loadCallback.accept(null); + return; + } + loadCallback.accept(result.orElse(null)); + }, (final Runnable r) -> { + BaseChunkSystemHooks.this.scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); + }); + } + + @Override + public void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ, + final FullChunkStatus toStatus, final boolean addTicket, + final Priority priority, final Consumer onComplete) { + // This method goes unused until the chunk system rewrite + if (toStatus == FullChunkStatus.INACCESSIBLE) { + throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); + } + + if (!Bukkit.isOwnedByCurrentRegion(level.getWorld(), chunkX, chunkZ)) { + this.scheduleChunkTask(level, chunkX, chunkZ, () -> { + BaseChunkSystemHooks.this.scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + }, priority); + return; + } + + final int minLevel = 33 - (toStatus.ordinal() - 1); + final int radius = toStatus.ordinal() - 1; + final Long chunkReference = addTicket ? Long.valueOf(++this.chunkLoadCounter) : null; + final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); + + if (addTicket) { + level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); + } + level.chunkSource.runDistanceManagerUpdates(); + + final Consumer loadCallback = (final LevelChunk chunk) -> { + try { + if (onComplete != null) { + onComplete.accept(chunk); + } + } catch (final Throwable thr) { + LOGGER.error("Exception handling chunk load callback", thr); + com.destroystokyo.paper.util.SneakyThrow.sneaky(thr); + } finally { + if (addTicket) { + level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos); + level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); + } + } + }; + + final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); + + if (holder == null || holder.getTicketLevel() > minLevel) { + loadCallback.accept(null); + return; + } + + final CompletableFuture> tickingState; + switch (toStatus) { + case FULL: { + tickingState = holder.getFullChunkFuture(); + break; + } + case BLOCK_TICKING: { + tickingState = holder.getTickingChunkFuture(); + break; + } + case ENTITY_TICKING: { + tickingState = holder.getEntityTickingChunkFuture(); + break; + } + default: { + throw new IllegalStateException("Cannot reach here"); + } + } + + if (tickingState.isDone()) { + loadCallback.accept(tickingState.join().orElse(null)); + return; + } + + tickingState.whenCompleteAsync((final ChunkResult result, final Throwable thr) -> { + if (thr != null) { + loadCallback.accept(null); + return; + } + loadCallback.accept(result.orElse(null)); + }, (final Runnable r) -> { + BaseChunkSystemHooks.this.scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST); + }); + } + + @Override + public List getVisibleChunkHolders(final ServerLevel level) { + return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values()); + } + + @Override + public List getUpdatingChunkHolders(final ServerLevel level) { + return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values()); + } + + @Override + public int getVisibleChunkHolderCount(final ServerLevel level) { + return level.chunkSource.chunkMap.visibleChunkMap.size(); + } + + @Override + public int getUpdatingChunkHolderCount(final ServerLevel level) { + return level.chunkSource.chunkMap.updatingChunkMap.size(); + } + + @Override + public boolean hasAnyChunkHolders(final ServerLevel level) { + return this.getUpdatingChunkHolderCount(level) != 0; + } + + @Override + public void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) { + + } + + @Override + public void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) { + + } + + @Override + public void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder holder) { + + } + + @Override + public void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { + + } + + @Override + public void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) { + + } + + @Override + public void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder) { + + } + + @Override + public void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) { + + } + + @Override + public void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) { + + } + + @Override + public void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { + + } + + @Override + public void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { + + } + + @Override + public ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) { + return level.chunkSource.chunkMap.getUnloadingChunkHolder(chunkX, chunkZ); + } + + @Override + public int getSendViewDistance(final ServerPlayer player) { + return this.getViewDistance(player); + } + + @Override + public int getViewDistance(final ServerPlayer player) { + final ServerLevel level = player.serverLevel(); + if (level == null) { + return Bukkit.getViewDistance(); + } + return level.chunkSource.chunkMap.serverViewDistance; + } + + @Override + public int getTickViewDistance(final ServerPlayer player) { + final ServerLevel level = player.serverLevel(); + if (level == null) { + return Bukkit.getSimulationDistance(); + } + return level.chunkSource.chunkMap.distanceManager.simulationDistance; + } + + @Override + public void addPlayerToDistanceMaps(final ServerLevel world, final ServerPlayer player) { + + } + + @Override + public void removePlayerFromDistanceMaps(final ServerLevel world, final ServerPlayer player) { + + } + + @Override + public void updateMaps(final ServerLevel world, final ServerPlayer player) { + + } +}