Skip to content

Commit

Permalink
feat: Add ticket tracker
Browse files Browse the repository at this point in the history
Also fixed loading into saves and added a note about ModernFix compatibility to the readme.
  • Loading branch information
Steveplays28 committed Jul 20, 2024
1 parent f69f5b3 commit 58b2bbe
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 17 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -55,14 +57,18 @@ public abstract class ServerChunkManagerMixin {
@Mutable
@Shadow
@Final
public ThreadedAnvilChunkStorage threadedAnvilChunkStorage;
public @Nullable ThreadedAnvilChunkStorage threadedAnvilChunkStorage;

@Shadow
public abstract @NotNull ChunkGenerator getChunkGenerator();

@Shadow
public abstract @NotNull NoiseConfig getNoiseConfig();

@Shadow
@Final
@NotNull ServerWorld world;

@Unique
private ChunkGenerator noisiumchunkmanager$chunkGenerator;
@Unique
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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(
Expand All @@ -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;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TicketCreated> TICKET_CREATED = EventFactory.createLoop();
/**
* @see TicketCreated
*/
Event<TicketRemoved> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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<ChunkPos, Integer> loadChunksInRadiusBiConsumer;
private final @NotNull Consumer<ChunkPos> unloadChunkConsumer;
private final @NotNull Map<ChunkPos, ServerWorldTicket> tickets;

public ServerWorldTicketTracker(@NotNull ServerWorld serverWorld, @NotNull BiConsumer<ChunkPos, Integer> loadChunksInRadiusBiConsumer, @NotNull Consumer<ChunkPos> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
3 changes: 2 additions & 1 deletion fabric/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"breaks": {
"c2me": "*",
"vmp": "*",
"leavesbegone": "*"
"leavesbegone": "*",
"ksyxis": "*"
}
}

0 comments on commit 58b2bbe

Please sign in to comment.