Skip to content

Commit

Permalink
feat: Add chunk tickers
Browse files Browse the repository at this point in the history
Also added a world chunk unloaded event, added a method to `ServerWorldExtension` for sending packets to nearby players (currently sends packets to all players in the `ServerWorld`, fixed `FallingBlockEntity`s and fixed `WorldGenerationProgressListener`s statuses being updated by non-overworld dimensions.
  • Loading branch information
Steveplays28 committed Jul 20, 2024
1 parent def05ad commit aa77dd4
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package io.github.steveplays28.noisiumchunkmanager.extension.world.server;

import io.github.steveplays28.noisiumchunkmanager.server.world.ServerWorldChunkManager;
import net.minecraft.network.packet.Packet;
import net.minecraft.world.gen.noise.NoiseConfig;
import org.jetbrains.annotations.NotNull;

public interface ServerWorldExtension {
ServerWorldChunkManager noisiumchunkmanager$getServerWorldChunkManager();

NoiseConfig noisiumchunkmanager$getNoiseConfig();

void noisiumchunkmanager$sendPacketToNearbyPlayers(@NotNull Packet<?> packet);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import net.minecraft.client.gui.WorldGenerationProgressTracker;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.dimension.DimensionTypes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
Expand All @@ -22,6 +23,12 @@ public abstract class WorldGenerationProgressTrackerMixin {

@Inject(method = "<init>", at = @At(value = "TAIL"))
private void noisiumchunkmanager$registerEventListeners(@NotNull CallbackInfo ci) {
ServerChunkEvent.WORLD_CHUNK_GENERATED.register(worldChunk -> this.setChunkStatus(worldChunk.getPos(), worldChunk.getStatus()));
ServerChunkEvent.WORLD_CHUNK_LOADED.register((instance, worldChunk) -> {
if (!instance.getDimension().effects().equals(DimensionTypes.OVERWORLD_ID)) {
return;
}

this.setChunkStatus(worldChunk.getPos(), worldChunk.getStatus());
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.github.steveplays28.noisiumchunkmanager.mixin.entity;

import io.github.steveplays28.noisiumchunkmanager.extension.world.server.ServerWorldExtension;
import net.minecraft.entity.Entity;
import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.world.ThreadedAnvilChunkStorage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

@Mixin(FallingBlockEntity.class)
public class FallingBlockEntityMixin {
@Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ThreadedAnvilChunkStorage;sendToOtherNearbyPlayers(Lnet/minecraft/entity/Entity;Lnet/minecraft/network/packet/Packet;)V"))
private void noisiumchunkmanager$sendBlockUpdatePacketToAllPlayersInEntityWorld(@Nullable ThreadedAnvilChunkStorage instance, @NotNull Entity entity, @NotNull Packet<?> packet) {
@NotNull var world = entity.getWorld();
if (world.isClient()) {
return;
}

((ServerWorldExtension) world).noisiumchunkmanager$sendPacketToNearbyPlayers(packet);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import net.minecraft.server.WorldGenerationProgressLogger;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.dimension.DimensionTypes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
Expand All @@ -19,6 +20,12 @@ public abstract class WorldGenerationProgressLoggerMixin {

@Inject(method = "<init>", at = @At(value = "TAIL"))
private void noisiumchunkmanager$registerEventListeners(@NotNull CallbackInfo ci) {
ServerChunkEvent.WORLD_CHUNK_GENERATED.register(worldChunk -> this.setChunkStatus(worldChunk.getPos(), worldChunk.getStatus()));
ServerChunkEvent.WORLD_CHUNK_LOADED.register((instance, worldChunk) -> {
if (!instance.getDimension().effects().equals(DimensionTypes.OVERWORLD_ID)) {
return;
}

this.setChunkStatus(worldChunk.getPos(), worldChunk.getStatus());
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
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.chunk.tick.ServerWorldChunkTicker;
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;
import net.minecraft.network.packet.Packet;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.WorldGenerationProgressListener;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ChunkLevelType;
import net.minecraft.server.world.ServerChunkManager;
import net.minecraft.server.world.ServerEntityManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
Expand Down Expand Up @@ -59,6 +63,9 @@ public abstract class ServerWorldMixin implements ServerWorldExtension {
@Shadow
public abstract @NotNull MinecraftServer getServer();

@Shadow
public abstract @NotNull List<ServerPlayerEntity> getPlayers();

@Unique
private NoiseConfig noisiumchunkmanager$noiseConfig;
/**
Expand All @@ -72,6 +79,12 @@ public abstract class ServerWorldMixin implements ServerWorldExtension {
@SuppressWarnings("unused")
@Unique
private ServerWorldTicketTracker noisiumchunkmanager$serverWorldTicketTracker;
/**
* Keeps a reference to this {@link ServerWorld}'s {@link ServerWorldChunkTicker}, to make sure it doesn't get garbage collected until the object is no longer necessary.
*/
@SuppressWarnings("unused")
@Unique
private ServerWorldChunkTicker noisiumchunkmanager$serverWorldChunkTicker;
/**
* 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 @@ -101,11 +114,14 @@ public abstract class ServerWorldMixin implements ServerWorldExtension {
serverWorld.getSeed()
);
noisiumchunkmanager$serverWorldChunkManager = new ServerWorldChunkManager(
serverWorld, chunkGenerator, noisiumchunkmanager$noiseConfig, session.getWorldDirectory(worldKey), dataFixer);
serverWorld, chunkGenerator, noisiumchunkmanager$noiseConfig, this.getServer()::executeSync,
session.getWorldDirectory(worldKey), dataFixer
);
noisiumchunkmanager$serverWorldTicketTracker = new ServerWorldTicketTracker(
serverWorld, noisiumchunkmanager$serverWorldChunkManager::getChunksInRadiusAsync,
noisiumchunkmanager$serverWorldChunkManager::unloadChunk
);
noisiumchunkmanager$serverWorldChunkTicker = new ServerWorldChunkTicker(serverWorld);
noisiumchunkmanager$serverWorldEntityManager = new ServerWorldEntityTracker(
packet -> PacketUtil.sendPacketToPlayers(serverWorld.getPlayers(), packet));
noisiumchunkmanager$serverWorldPlayerChunkLoader = new ServerWorldPlayerChunkLoader(
Expand All @@ -127,11 +143,17 @@ public abstract class ServerWorldMixin implements ServerWorldExtension {
// TODO: Move this event listener registration to ServerEntityManagerMixin
// or (when it's finished and able to completely replace the vanilla class) to NoisiumServerWorldEntityTracker
// More efficient methods can be used when registering the event listener directly in the server entity manager
ServerChunkEvent.WORLD_CHUNK_GENERATED.register(worldChunk -> server.executeSync(
() -> this.entityManager.updateTrackingStatus(worldChunk.getPos(), ChunkLevelType.ENTITY_TICKING)));
ServerChunkEvent.WORLD_CHUNK_LOADED.register((instance, worldChunk) -> {
if (instance != serverWorld) {
return;
}

server.executeSync(() -> this.entityManager.updateTrackingStatus(worldChunk.getPos(), ChunkLevelType.ENTITY_TICKING));
});
LifecycleEvent.SERVER_STOPPED.register(instance -> {
noisiumchunkmanager$serverWorldPlayerChunkLoader = null;
noisiumchunkmanager$serverWorldEntityManager = null;
noisiumchunkmanager$serverWorldChunkTicker = null;
noisiumchunkmanager$serverWorldTicketTracker = null;
noisiumchunkmanager$serverWorldChunkManager = null;
noisiumchunkmanager$noiseConfig = null;
Expand Down Expand Up @@ -186,4 +208,13 @@ public abstract class ServerWorldMixin implements ServerWorldExtension {
public NoiseConfig noisiumchunkmanager$getNoiseConfig() {
return noisiumchunkmanager$noiseConfig;
}

@SuppressWarnings("ForLoopReplaceableByForEach")
@Override
public void noisiumchunkmanager$sendPacketToNearbyPlayers(@NotNull Packet<?> packet) {
@NotNull var players = this.getPlayers();
for (int i = 0; i < players.size(); i++) {
players.get(i).networkHandler.sendPacket(packet);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Function;

import static io.github.steveplays28.noisiumchunkmanager.NoisiumChunkManager.MOD_NAME;
Expand All @@ -58,23 +59,26 @@ public class ServerWorldChunkManager {
private final ServerWorld serverWorld;
private final ChunkGenerator chunkGenerator;
private final NoiseConfig noiseConfig;
private final Consumer<Runnable> syncRunnableConsumer;
private final PersistentStateManager persistentStateManager;
private final PointOfInterestStorage pointOfInterestStorage;
private final VersionedChunkStorage versionedChunkStorage;
private final Executor threadPoolExecutor;
private final Executor noisePopulationThreadPoolExecutor;
private final Executor lightingThreadPoolExecutor;
private final ConcurrentMap<ChunkPos, CompletableFuture<WorldChunk>> loadingWorldChunks;
private final Queue<ChunkPos> unloadingWorldChunks;
private final ConcurrentMap<ChunkPos, IoWorldChunk> ioWorldChunks;
private final Map<ChunkPos, WorldChunk> loadedWorldChunks;

private boolean isStopping;

@SuppressWarnings("ResultOfMethodCallIgnored")
public ServerWorldChunkManager(@NotNull ServerWorld serverWorld, @NotNull ChunkGenerator chunkGenerator, @NotNull NoiseConfig noiseConfig, @NotNull Path worldDirectoryPath, DataFixer dataFixer) {
public ServerWorldChunkManager(@NotNull ServerWorld serverWorld, @NotNull ChunkGenerator chunkGenerator, @NotNull NoiseConfig noiseConfig, @NotNull Consumer<Runnable> syncRunnableConsumer, @NotNull Path worldDirectoryPath, @NotNull DataFixer dataFixer) {
this.serverWorld = serverWorld;
this.chunkGenerator = chunkGenerator;
this.noiseConfig = noiseConfig;
this.syncRunnableConsumer = syncRunnableConsumer;

var worldDataFile = worldDirectoryPath.resolve("data").toFile();
worldDataFile.mkdirs();
Expand All @@ -94,6 +98,7 @@ public ServerWorldChunkManager(@NotNull ServerWorld serverWorld, @NotNull ChunkG
"Noisium Server World Chunk Manager Lighting " + serverWorld.getDimension().effects() + " %d").build()
);
this.loadingWorldChunks = new ConcurrentHashMap<>();
this.unloadingWorldChunks = new ConcurrentLinkedQueue<>();
this.ioWorldChunks = new ConcurrentHashMap<>();
this.loadedWorldChunks = new HashMap<>();

Expand Down Expand Up @@ -160,11 +165,18 @@ public ServerWorldChunkManager(@NotNull ServerWorld serverWorld, @NotNull ChunkG
return;
}

serverWorld.getServer().executeSync(() -> fetchedWorldChunk.addChunkTickSchedulers(serverWorld));
syncRunnableConsumer.accept(() -> fetchedWorldChunk.addChunkTickSchedulers(serverWorld));
fetchedWorldChunk.loadEntities();
loadingWorldChunks.remove(chunkPos);
loadedWorldChunks.put(chunkPos, fetchedWorldChunk);
ServerChunkEvent.WORLD_CHUNK_GENERATED.invoker().onWorldChunkGenerated(fetchedWorldChunk);

if (!unloadingWorldChunks.contains(chunkPos)) {
loadedWorldChunks.put(chunkPos, fetchedWorldChunk);
}

syncRunnableConsumer.accept(
() -> ServerChunkEvent.WORLD_CHUNK_LOADED.invoker().onWorldChunkLoaded(serverWorld, fetchedWorldChunk));
unloadingWorldChunks.remove(chunkPos);
syncRunnableConsumer.accept(() -> ServerChunkEvent.WORLD_CHUNK_UNLOADED.invoker().onWorldChunkUnloaded(serverWorld, chunkPos));
});
loadingWorldChunks.put(chunkPos, worldChunkCompletableFuture);
return worldChunkCompletableFuture;
Expand Down Expand Up @@ -194,19 +206,31 @@ public ServerWorldChunkManager(@NotNull ServerWorld serverWorld, @NotNull ChunkG
// TODO: Schedule ProtoChunk worldgen and update loadedWorldChunks incrementally during worldgen steps
var fetchedWorldChunk = new WorldChunk(
serverWorld, generateChunk(chunkPos, this::getIoWorldChunk, ioWorldChunks::remove), null);
loadedWorldChunks.put(chunkPos, fetchedWorldChunk);
ServerChunkEvent.WORLD_CHUNK_GENERATED.invoker().onWorldChunkGenerated(fetchedWorldChunk);
if (!unloadingWorldChunks.contains(chunkPos)) {
loadedWorldChunks.put(chunkPos, fetchedWorldChunk);
}

syncRunnableConsumer.accept(
() -> ServerChunkEvent.WORLD_CHUNK_LOADED.invoker().onWorldChunkLoaded(serverWorld, fetchedWorldChunk));
unloadingWorldChunks.remove(chunkPos);
syncRunnableConsumer.accept(() -> ServerChunkEvent.WORLD_CHUNK_UNLOADED.invoker().onWorldChunkUnloaded(serverWorld, chunkPos));
return fetchedWorldChunk;
}

var fetchedChunk = ChunkSerializer.deserialize(serverWorld, pointOfInterestStorage, chunkPos, fetchedNbtData);
var fetchedWorldChunk = new WorldChunk(serverWorld, fetchedChunk,
chunkToAddEntitiesTo -> serverWorld.addEntities(EntityType.streamFromNbt(fetchedChunk.getEntities(), serverWorld))
);
serverWorld.getServer().executeSync(() -> fetchedWorldChunk.addChunkTickSchedulers(serverWorld));
syncRunnableConsumer.accept(() -> fetchedWorldChunk.addChunkTickSchedulers(serverWorld));
fetchedWorldChunk.loadEntities();
loadedWorldChunks.put(chunkPos, fetchedWorldChunk);
ServerChunkEvent.WORLD_CHUNK_GENERATED.invoker().onWorldChunkGenerated(fetchedWorldChunk);

if (!unloadingWorldChunks.contains(chunkPos)) {
loadedWorldChunks.put(chunkPos, fetchedWorldChunk);
}

syncRunnableConsumer.accept(() -> ServerChunkEvent.WORLD_CHUNK_LOADED.invoker().onWorldChunkLoaded(serverWorld, fetchedWorldChunk));
unloadingWorldChunks.remove(chunkPos);
syncRunnableConsumer.accept(() -> ServerChunkEvent.WORLD_CHUNK_UNLOADED.invoker().onWorldChunkUnloaded(serverWorld, chunkPos));
return fetchedWorldChunk;
}

Expand Down Expand Up @@ -263,10 +287,12 @@ public ServerWorldChunkManager(@NotNull ServerWorld serverWorld, @NotNull ChunkG

public void unloadChunk(@NotNull ChunkPos chunkPosition) {
if (loadingWorldChunks.containsKey(chunkPosition)) {
loadingWorldChunks.get(chunkPosition).whenComplete((chunk, throwable) -> loadedWorldChunks.remove(chunkPosition));
unloadingWorldChunks.add(chunkPosition);
return;
}

loadedWorldChunks.remove(chunkPosition);
syncRunnableConsumer.accept(() -> ServerChunkEvent.WORLD_CHUNK_UNLOADED.invoker().onWorldChunkUnloaded(serverWorld, chunkPosition));
}

public boolean isChunkLoaded(ChunkPos chunkPos) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,52 @@
import dev.architectury.event.EventFactory;
import io.github.steveplays28.noisiumchunkmanager.server.world.ServerWorldChunkManager;
import net.minecraft.block.BlockState;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.world.LightType;
import net.minecraft.world.chunk.WorldChunk;
import org.jetbrains.annotations.NotNull;

public interface ServerChunkEvent {
/**
* @see WorldChunkGenerated
* @see WorldChunkLoaded
*/
Event<WorldChunkGenerated> WORLD_CHUNK_GENERATED = EventFactory.createLoop();

Event<WorldChunkLoaded> WORLD_CHUNK_LOADED = EventFactory.createLoop();
/**
* @see WorldChunkUnloaded
*/
Event<WorldChunkUnloaded> WORLD_CHUNK_UNLOADED = EventFactory.createLoop();
/**
* @see WorldChunkGenerated
* @see LightUpdate
*/
Event<LightUpdate> LIGHT_UPDATE = EventFactory.createLoop();

/**
* @see WorldChunkGenerated
* @see BlockChange
*/
Event<BlockChange> BLOCK_CHANGE = EventFactory.createLoop();

@FunctionalInterface
interface WorldChunkGenerated {
interface WorldChunkLoaded {
/**
* Invoked after a {@link WorldChunk} has been loaded by {@link ServerWorldChunkManager}, either via world generation or from save data.
*
* @param serverWorld The {@link ServerWorld} of the loaded {@link WorldChunk}.
* @param worldChunk The loaded {@link WorldChunk}.
*/
void onWorldChunkLoaded(@NotNull ServerWorld serverWorld, @NotNull WorldChunk worldChunk);
}

@FunctionalInterface
interface WorldChunkUnloaded {
/**
* Invoked after a {@link WorldChunk} has been generated by {@link ServerWorldChunkManager}.
* Invoked after a {@link WorldChunk} has been unloaded by {@link ServerWorldChunkManager}.
*
* @param worldChunk The generated {@link WorldChunk}.
* @param serverWorld The {@link ServerWorld} of the unloaded {@link WorldChunk}.
* @param worldChunkPosition The {@link ChunkPos} of the unloaded {@link WorldChunk}.
*/
void onWorldChunkGenerated(WorldChunk worldChunk);
void onWorldChunkUnloaded(@NotNull ServerWorld serverWorld, @NotNull ChunkPos worldChunkPosition);
}

@FunctionalInterface
Expand Down
Loading

0 comments on commit aa77dd4

Please sign in to comment.