From 58b2bbe8fcc7f21cc9d9792064c2261e2948f903 Mon Sep 17 00:00:00 2001 From: Steveplays28 Date: Sat, 20 Jul 2024 15:00:26 +0200 Subject: [PATCH] feat: Add ticket tracker Also fixed loading into saves and added a note about ModernFix compatibility to the readme. --- README.md | 6 +- .../config/NoisiumChunkManagerConfig.java | 5 ++ .../mixin/server/MinecraftServerMixin.java | 10 ++- .../server/world/ServerChunkManagerMixin.java | 23 +++--- .../mixin/server/world/ServerWorldMixin.java | 12 +++ .../world/ticket/ServerWorldTicketEvent.java | 43 ++++++++++ .../world/ticket/ServerWorldTicket.java | 11 +++ .../ticket/ServerWorldTicketTracker.java | 82 +++++++++++++++++++ .../noisiumchunkmanager/lang/en_us.json | 1 + fabric/src/main/resources/fabric.mod.json | 3 +- 10 files changed, 179 insertions(+), 17 deletions(-) create mode 100644 common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/event/world/ticket/ServerWorldTicketEvent.java create mode 100644 common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/world/ticket/ServerWorldTicket.java create mode 100644 common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/world/ticket/ServerWorldTicketTracker.java diff --git a/README.md b/README.md index fc07ca5..b2e5574 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,10 @@ None. Noisium Chunk Manager should be compatible with most of the popular world generation mods, as long as they do not interact with `ThreadedAnvilChunkStorage`, see the [description](#noisium-chunk-manager) above. -- [Distant Horizons](https://modrinth.com/mod/distanthorizons): a compatibility mixin is included that adds compatibility for Distant - Horizons. +- [Distant Horizons](https://modrinth.com/mod/distanthorizons) + - A compatibility mixin is included in Noisium Chunk Manager +- [ModernFix](https://modrinth.com/mod/modernfix) + - `mixin.perf.remove_spawn_chunks=false` must be added to `config/modernfix-mixins.properties` ### Incompatibilities diff --git a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/config/NoisiumChunkManagerConfig.java b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/config/NoisiumChunkManagerConfig.java index 2315169..55d8bc1 100644 --- a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/config/NoisiumChunkManagerConfig.java +++ b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/config/NoisiumChunkManagerConfig.java @@ -4,6 +4,7 @@ import dev.isxander.yacl3.config.v2.api.SerialEntry; import dev.isxander.yacl3.config.v2.api.autogen.AutoGen; import dev.isxander.yacl3.config.v2.api.autogen.IntField; +import dev.isxander.yacl3.config.v2.api.autogen.TickBox; import dev.isxander.yacl3.config.v2.api.serializer.GsonConfigSerializerBuilder; import io.github.steveplays28.noisiumchunkmanager.util.ModLoaderUtil; import net.minecraft.util.Identifier; @@ -22,6 +23,10 @@ public class NoisiumChunkManagerConfig { private static final @NotNull String SERVER_CATEGORY = "server"; private static final @NotNull String SERVER_WORLD_CHUNK_MANAGER_GROUP = "serverWorldChunkManager"; + @AutoGen(category = SERVER_CATEGORY, group = SERVER_WORLD_CHUNK_MANAGER_GROUP) + @SerialEntry(comment = "Determines if the server world's chunk manager will load spawn chunks. Spawn chunks are an 11x11 chunk radius around the overworld's spawn position. After changing this option you MUST restart the server.") + @TickBox + public boolean loadSpawnChunks = true; @AutoGen(category = SERVER_CATEGORY, group = SERVER_WORLD_CHUNK_MANAGER_GROUP) @SerialEntry(comment = "The amount of threads used by a server world's chunk manager. Every world has its own chunk manager, and thus its own threads. After changing this option you MUST restart the server.") @IntField(min = 1, format = "%i threads") diff --git a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/MinecraftServerMixin.java b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/MinecraftServerMixin.java index ad661b3..779923e 100644 --- a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/MinecraftServerMixin.java +++ b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/MinecraftServerMixin.java @@ -1,6 +1,7 @@ package io.github.steveplays28.noisiumchunkmanager.mixin.server; import com.llamalad7.mixinextras.sugar.Local; +import io.github.steveplays28.noisiumchunkmanager.config.NoisiumChunkManagerConfig; import io.github.steveplays28.noisiumchunkmanager.extension.world.server.ServerWorldExtension; import net.minecraft.server.MinecraftServer; import net.minecraft.server.WorldGenerationProgressListener; @@ -52,10 +53,15 @@ private void prepareStartRegion(@NotNull WorldGenerationProgressListener worldGe worldGenerationProgressListener.start(); this.timeReference = Util.getMeasuringTimeMs(); + @NotNull var noisiumServerWorldChunkManager = ((ServerWorldExtension) overworld).noisiumchunkmanager$getServerWorldChunkManager(); @NotNull var overworldSpawnChunkPosition = new ChunkPos(overworld.getSpawnPos()); - overworld.getChunkManager().addTicket(ChunkTicketType.START, overworldSpawnChunkPosition, START_TICKET_CHUNK_RADIUS, Unit.INSTANCE); + if (NoisiumChunkManagerConfig.HANDLER.instance().loadSpawnChunks) { + overworld.getChunkManager().addTicket( + ChunkTicketType.START, overworldSpawnChunkPosition, START_TICKET_CHUNK_RADIUS, Unit.INSTANCE); + } else { + noisiumServerWorldChunkManager.getChunkAsync(overworldSpawnChunkPosition); + } - @NotNull var noisiumServerWorldChunkManager = ((ServerWorldExtension) overworld).noisiumchunkmanager$getServerWorldChunkManager(); while (!noisiumServerWorldChunkManager.isChunkLoaded(overworldSpawnChunkPosition)) { this.timeReference = Util.getMeasuringTimeMs() + 10L; this.runTasksTillTickEnd(); diff --git a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/world/ServerChunkManagerMixin.java b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/world/ServerChunkManagerMixin.java index e6e9ef8..65216b4 100644 --- a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/world/ServerChunkManagerMixin.java +++ b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/world/ServerChunkManagerMixin.java @@ -2,6 +2,7 @@ import com.mojang.datafixers.DataFixer; import io.github.steveplays28.noisiumchunkmanager.extension.world.server.ServerWorldExtension; +import io.github.steveplays28.noisiumchunkmanager.server.event.world.ticket.ServerWorldTicketEvent; import io.github.steveplays28.noisiumchunkmanager.server.world.ServerWorldChunkManager; import io.github.steveplays28.noisiumchunkmanager.server.world.chunk.event.ServerChunkEvent; import io.github.steveplays28.noisiumchunkmanager.server.world.event.ServerTickEvent; @@ -33,6 +34,7 @@ import net.minecraft.world.level.storage.LevelStorage; import net.minecraft.world.storage.NbtScannable; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -55,7 +57,7 @@ public abstract class ServerChunkManagerMixin { @Mutable @Shadow @Final - public ThreadedAnvilChunkStorage threadedAnvilChunkStorage; + public @Nullable ThreadedAnvilChunkStorage threadedAnvilChunkStorage; @Shadow public abstract @NotNull ChunkGenerator getChunkGenerator(); @@ -63,6 +65,10 @@ public abstract class ServerChunkManagerMixin { @Shadow public abstract @NotNull NoiseConfig getNoiseConfig(); + @Shadow + @Final + @NotNull ServerWorld world; + @Unique private ChunkGenerator noisiumchunkmanager$chunkGenerator; @Unique @@ -238,21 +244,14 @@ public abstract class ServerChunkManagerMixin { } @Inject(method = "addTicket", at = @At(value = "HEAD"), cancellable = true) - private void noisiumchunkmanager$loadChunksInTicketRadius(@NotNull ChunkTicketType ticketType, @NotNull ChunkPos chunkPosition, int radius, Object argument, @NotNull CallbackInfo ci) { - if (ticketType.getExpiryTicks() != 0L) { - ci.cancel(); - return; - } - - ((ServerWorldExtension) this.getWorld()).noisiumchunkmanager$getServerWorldChunkManager().getChunksInRadiusAsync( - chunkPosition, radius); + private void noisiumchunkmanager$invokeTicketCreatedEvent(@NotNull ChunkTicketType ticketType, @NotNull ChunkPos chunkPosition, int radius, Object argument, @NotNull CallbackInfo ci) { + ServerWorldTicketEvent.TICKET_CREATED.invoker().onTicketCreated(this.world, ticketType, chunkPosition, radius); ci.cancel(); } @Inject(method = "removeTicket", at = @At(value = "HEAD"), cancellable = true) - private void noisiumchunkmanager$unloadChunksInTicketRadius(@NotNull ChunkTicketType ticketType, @NotNull ChunkPos chunkPosition, int radius, Object argument, @NotNull CallbackInfo ci) { -// ((ServerWorldExtension) this.getWorld()).noisiumchunkmanager$getServerWorldChunkManager().unloadChunk( -// chunkPosition, radius); + private void noisiumchunkmanager$invokeTicketRemovedEvent(@NotNull ChunkTicketType ticketType, @NotNull ChunkPos chunkPosition, int radius, Object argument, @NotNull CallbackInfo ci) { + ServerWorldTicketEvent.TICKET_REMOVED.invoker().onTicketRemoved(this.world, chunkPosition); ci.cancel(); } } diff --git a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/world/ServerWorldMixin.java b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/world/ServerWorldMixin.java index 3880c61..fcb8af3 100644 --- a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/world/ServerWorldMixin.java +++ b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/mixin/server/world/ServerWorldMixin.java @@ -5,6 +5,7 @@ import dev.architectury.event.events.common.LifecycleEvent; import dev.architectury.event.events.common.PlayerEvent; import io.github.steveplays28.noisiumchunkmanager.server.world.ServerWorldChunkManager; +import io.github.steveplays28.noisiumchunkmanager.server.world.ticket.ServerWorldTicketTracker; import io.github.steveplays28.noisiumchunkmanager.util.world.chunk.networking.packet.PacketUtil; import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; @@ -65,6 +66,12 @@ public abstract class ServerWorldMixin implements ServerWorldExtension { */ @Unique private ServerWorldChunkManager noisiumchunkmanager$serverWorldChunkManager; + /** + * Keeps a reference to this {@link ServerWorld}'s {@link ServerWorldTicketTracker}, to make sure it doesn't get garbage collected until the object is no longer necessary. + */ + @SuppressWarnings("unused") + @Unique + private ServerWorldTicketTracker noisiumchunkmanager$serverWorldTicketTracker; /** * Keeps a reference to this {@link ServerWorld}'s {@link ServerWorldEntityTracker}, to make sure it doesn't get garbage collected until the object is no longer necessary. */ @@ -95,6 +102,10 @@ public abstract class ServerWorldMixin implements ServerWorldExtension { ); noisiumchunkmanager$serverWorldChunkManager = new ServerWorldChunkManager( serverWorld, chunkGenerator, noisiumchunkmanager$noiseConfig, session.getWorldDirectory(worldKey), dataFixer); + noisiumchunkmanager$serverWorldTicketTracker = new ServerWorldTicketTracker( + serverWorld, noisiumchunkmanager$serverWorldChunkManager::getChunksInRadiusAsync, + noisiumchunkmanager$serverWorldChunkManager::unloadChunk + ); noisiumchunkmanager$serverWorldEntityManager = new ServerWorldEntityTracker( packet -> PacketUtil.sendPacketToPlayers(serverWorld.getPlayers(), packet)); noisiumchunkmanager$serverWorldPlayerChunkLoader = new ServerWorldPlayerChunkLoader( @@ -121,6 +132,7 @@ public abstract class ServerWorldMixin implements ServerWorldExtension { LifecycleEvent.SERVER_STOPPED.register(instance -> { noisiumchunkmanager$serverWorldPlayerChunkLoader = null; noisiumchunkmanager$serverWorldEntityManager = null; + noisiumchunkmanager$serverWorldTicketTracker = null; noisiumchunkmanager$serverWorldChunkManager = null; noisiumchunkmanager$noiseConfig = null; }); diff --git a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/event/world/ticket/ServerWorldTicketEvent.java b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/event/world/ticket/ServerWorldTicketEvent.java new file mode 100644 index 0000000..763d8af --- /dev/null +++ b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/event/world/ticket/ServerWorldTicketEvent.java @@ -0,0 +1,43 @@ +package io.github.steveplays28.noisiumchunkmanager.server.event.world.ticket; + +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import net.minecraft.server.world.ChunkTicketType; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.ChunkPos; +import org.jetbrains.annotations.NotNull; + +public interface ServerWorldTicketEvent { + /** + * @see TicketCreated + */ + Event TICKET_CREATED = EventFactory.createLoop(); + /** + * @see TicketCreated + */ + Event TICKET_REMOVED = EventFactory.createLoop(); + + @FunctionalInterface + interface TicketCreated { + /** + * Invoked after a chunk ticket has been created. + * + * @param serverWorld The {@link ServerWorld}. + * @param ticketType The {@link ChunkTicketType}. + * @param chunkPosition The {@link ChunkPos}. + * @param radius The radius, in chunks. + */ + void onTicketCreated(@NotNull ServerWorld serverWorld, @NotNull ChunkTicketType ticketType, @NotNull ChunkPos chunkPosition, int radius); + } + + @FunctionalInterface + interface TicketRemoved { + /** + * Invoked after a chunk ticket has been removed. + * + * @param serverWorld The {@link ServerWorld}. + * @param chunkPosition The {@link ChunkPos}. + */ + void onTicketRemoved(@NotNull ServerWorld serverWorld, @NotNull ChunkPos chunkPosition); + } +} diff --git a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/world/ticket/ServerWorldTicket.java b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/world/ticket/ServerWorldTicket.java new file mode 100644 index 0000000..9271d22 --- /dev/null +++ b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/world/ticket/ServerWorldTicket.java @@ -0,0 +1,11 @@ +package io.github.steveplays28.noisiumchunkmanager.server.world.ticket; + +/** + * Stores specific data of a {@link net.minecraft.server.world.ChunkTicketType}. + * + * @param startTick The start tick. + * @param duration The duration, in ticks. + */ +public record ServerWorldTicket(long startTick, long duration) { + // NO-OP +} diff --git a/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/world/ticket/ServerWorldTicketTracker.java b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/world/ticket/ServerWorldTicketTracker.java new file mode 100644 index 0000000..28e3f90 --- /dev/null +++ b/common/src/main/java/io/github/steveplays28/noisiumchunkmanager/server/world/ticket/ServerWorldTicketTracker.java @@ -0,0 +1,82 @@ +package io.github.steveplays28.noisiumchunkmanager.server.world.ticket; + +import dev.architectury.event.events.common.TickEvent; +import io.github.steveplays28.noisiumchunkmanager.server.event.world.ticket.ServerWorldTicketEvent; +import net.minecraft.server.world.ChunkTicketType; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.ChunkPos; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public class ServerWorldTicketTracker { + private final @NotNull ServerWorld serverWorld; + private final @NotNull BiConsumer loadChunksInRadiusBiConsumer; + private final @NotNull Consumer unloadChunkConsumer; + private final @NotNull Map tickets; + + public ServerWorldTicketTracker(@NotNull ServerWorld serverWorld, @NotNull BiConsumer loadChunksInRadiusBiConsumer, @NotNull Consumer unloadChunkConsumer) { + this.serverWorld = serverWorld; + this.loadChunksInRadiusBiConsumer = loadChunksInRadiusBiConsumer; + this.unloadChunkConsumer = unloadChunkConsumer; + + this.tickets = new HashMap<>(); + + ServerWorldTicketEvent.TICKET_CREATED.register((ticketServerWorld, ticketType, chunkPosition, radius) -> { + if (ticketServerWorld != serverWorld) { + return; + } + + onTicketCreated(ticketServerWorld, ticketType, chunkPosition, radius); + }); + ServerWorldTicketEvent.TICKET_REMOVED.register((ticketServerWorld, chunkPosition) -> { + if (ticketServerWorld != serverWorld) { + return; + } + + onTicketRemoved(chunkPosition); + }); + TickEvent.SERVER_LEVEL_POST.register(instance -> { + if (instance != serverWorld) { + return; + } + + tick(); + }); + } + + private void onTicketCreated(@NotNull ServerWorld serverWorld, @NotNull ChunkTicketType ticketType, @NotNull ChunkPos chunkPosition, int radius) { + if (tickets.containsKey(chunkPosition)) { + return; + } + + loadChunksInRadiusBiConsumer.accept(chunkPosition, radius); + tickets.put(chunkPosition, new ServerWorldTicket(serverWorld.getTime(), ticketType.getExpiryTicks())); + } + + private void onTicketRemoved(@NotNull ChunkPos chunkPosition) { + if (!tickets.containsKey(chunkPosition)) { + return; + } + + unloadChunkConsumer.accept(chunkPosition); + tickets.remove(chunkPosition); + } + + private void tick() { + for (@NotNull var ticketEntry : tickets.entrySet()) { + @NotNull var ticket = ticketEntry.getValue(); + // TODO: Store endTick in the ServerWorldTicket instead of the startTick and duration + if (ticket.startTick() + ticket.duration() > this.serverWorld.getTime()) { + return; + } + + @NotNull var ticketChunkPosition = ticketEntry.getKey(); + unloadChunkConsumer.accept(ticketChunkPosition); + tickets.remove(ticketChunkPosition); + } + } +} diff --git a/common/src/main/resources/assets/noisiumchunkmanager/lang/en_us.json b/common/src/main/resources/assets/noisiumchunkmanager/lang/en_us.json index 7910653..941a79a 100644 --- a/common/src/main/resources/assets/noisiumchunkmanager/lang/en_us.json +++ b/common/src/main/resources/assets/noisiumchunkmanager/lang/en_us.json @@ -1,6 +1,7 @@ { "yacl3.config.noisiumchunkmanager:config.category.server": "Server", "yacl3.config.noisiumchunkmanager:config.category.server.group.serverWorldChunkManager": "Server World Chunk Manager (experimental)", + "yacl3.config.noisiumchunkmanager:config.loadSpawnChunks": "Load Spawn Chunks?", "yacl3.config.noisiumchunkmanager:config.serverWorldChunkManagerThreads": "Server World Chunk Manager Threads", "yacl3.config.noisiumchunkmanager:config.serverWorldChunkManagerThreads.desc": "The amount of threads used by a server world's chunk manager. Every world has its own chunk manager, and thus its own threads. After changing this option you MUST restart the server.", "yacl3.config.noisiumchunkmanager:config.serverWorldChunkManagerLightingThreads": "Server World Chunk Manager Lighting Threads", diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index a9316e5..1ba3f86 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -56,6 +56,7 @@ "breaks": { "c2me": "*", "vmp": "*", - "leavesbegone": "*" + "leavesbegone": "*", + "ksyxis": "*" } }