Skip to content

Commit

Permalink
feat: Add serverside entity tracking
Browse files Browse the repository at this point in the history
Haven't fully replaced vanilla's `ServerEntityManager` yet, might be interfering with movement ticking, since cows currently move at double speed.
  • Loading branch information
Steveplays28 committed Apr 16, 2024
1 parent 1c5f1a6 commit f55d72e
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.github.steveplays28.noisium;

import io.github.steveplays28.noisium.server.world.NoisiumServerInitialiser;
import io.github.steveplays28.noisium.server.NoisiumServerInitialiser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.steveplays28.noisium.mixin.server.world;

import io.github.steveplays28.noisium.extension.world.server.NoisiumServerWorldExtension;
import net.minecraft.entity.Entity;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.world.ServerChunkManager;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.World;
Expand Down Expand Up @@ -35,4 +37,43 @@ public abstract class ServerChunkManagerMixin {
((NoisiumServerWorldExtension) this.getWorld()).noisium$getServerWorldChunkManager().getChunkAsync(
new ChunkPos(chunkX, chunkZ)).whenComplete((worldChunk, throwable) -> cir.setReturnValue(worldChunk));
}

// TODO: Don't send this packet to players out of range, to save on bandwidth
@SuppressWarnings("ForLoopReplaceableByForEach")
@Inject(method = "sendToNearbyPlayers", at = @At(value = "HEAD"), cancellable = true)
private void noisium$sendToNearbyPlayersViaNoisiumServerWorldChunkManager(Entity entity, Packet<?> packet, CallbackInfo ci) {
var server = entity.getServer();
if (server == null) {
return;
}

var players = server.getPlayerManager().getPlayerList();
for (int i = 0; i < players.size(); i++) {
players.get(i).networkHandler.sendPacket(packet);
}

ci.cancel();
}

// TODO: Don't send this packet to players out of range, to save on bandwidth
@SuppressWarnings("ForLoopReplaceableByForEach")
@Inject(method = "sendToOtherNearbyPlayers", at = @At(value = "HEAD"), cancellable = true)
private void noisium$sendToOtherNearbyPlayersViaNoisiumServerWorldChunkManager(Entity entity, Packet<?> packet, CallbackInfo ci) {
var server = entity.getServer();
if (server == null) {
return;
}

var players = server.getPlayerManager().getPlayerList();
for (int i = 0; i < players.size(); i++) {
var player = players.get(i);
if (player.equals(entity)) {
continue;
}

player.networkHandler.sendPacket(packet);
}

ci.cancel();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.datafixers.DataFixer;
import dev.architectury.event.EventResult;
import dev.architectury.event.events.common.EntityEvent;
import dev.architectury.event.events.common.PlayerEvent;
import io.github.steveplays28.noisium.extension.world.server.NoisiumServerWorldExtension;
import io.github.steveplays28.noisium.server.world.NoisiumServerWorldChunkManager;
import io.github.steveplays28.noisium.server.world.entity.NoisiumServerWorldEntityTracker;
import io.github.steveplays28.noisium.util.world.chunk.networking.packet.PacketUtil;
import net.minecraft.entity.Entity;
import net.minecraft.registry.RegistryKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.WorldGenerationProgressListener;
import net.minecraft.server.world.ChunkLevelType;
import net.minecraft.server.world.ServerEntityManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.random.RandomSequencesState;
Expand All @@ -30,20 +35,32 @@
@Mixin(ServerWorld.class)
public abstract class ServerWorldMixin implements NoisiumServerWorldExtension {
@Shadow
public abstract ServerWorld toServerWorld();

@Shadow @Final private ServerEntityManager<Entity> entityManager;
@Final
private ServerEntityManager<Entity> entityManager;
@Unique
private NoisiumServerWorldChunkManager noisium$serverWorldChunkManager;
@Unique
private NoisiumServerWorldEntityTracker noisium$serverWorldEntityManager;

@Inject(method = "<init>", at = @At(value = "TAIL"))
private void noisium$constructorCreateServerWorldChunkManager(MinecraftServer server, Executor workerExecutor, LevelStorage.Session session, ServerWorldProperties properties, RegistryKey<World> worldKey, DimensionOptions dimensionOptions, WorldGenerationProgressListener worldGenerationProgressListener, boolean debugWorld, long seed, List<?> spawners, boolean shouldTickTime, RandomSequencesState randomSequencesState, CallbackInfo ci, @Local DataFixer dataFixer) {
var serverWorld = ((ServerWorld) (Object) this);

this.noisium$serverWorldChunkManager = new NoisiumServerWorldChunkManager(
this.toServerWorld(), dimensionOptions.chunkGenerator(), session.getWorldDirectory(worldKey), dataFixer);
serverWorld, dimensionOptions.chunkGenerator(), session.getWorldDirectory(worldKey), dataFixer);
this.noisium$serverWorldEntityManager = new NoisiumServerWorldEntityTracker(
packet -> PacketUtil.sendPacketToPlayers(serverWorld.getPlayers(), packet));

// TODO: Redo the server entity manager entirely, in an event-based way
// Also remove this line when that's done, since this doesn't belong here
PlayerEvent.PLAYER_JOIN.register(player -> this.entityManager.addEntity(player));

// TODO: Move this event listener registration to ServerEntityManagerMixin
// More efficient methods can be used when registering the event listener directly in the server entity manager
EntityEvent.ADD.register((entity, world) -> {
this.entityManager.updateTrackingStatus(entity.getChunkPos(), ChunkLevelType.ENTITY_TICKING);
return EventResult.pass();
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.github.steveplays28.noisium.server;

import dev.architectury.event.events.common.LifecycleEvent;
import io.github.steveplays28.noisium.server.player.NoisiumServerPlayerBlockUpdater;
import io.github.steveplays28.noisium.server.player.NoisiumServerPlayerChunkLoader;

public class NoisiumServerInitialiser {
/**
* Keeps a reference to the {@link NoisiumServerPlayerChunkLoader}, to make sure it doesn't get garbage collected until the object is no longer necessary.
*/
@SuppressWarnings("unused")
private static NoisiumServerPlayerChunkLoader serverPlayerChunkLoader;
/**
* Keeps a reference to the {@link NoisiumServerPlayerBlockUpdater}, to make sure it doesn't get garbage collected until the object is no longer necessary.
*/
@SuppressWarnings("unused")
private static NoisiumServerPlayerBlockUpdater serverPlayerBlockUpdater;

public static void initialise() {
LifecycleEvent.SERVER_STARTED.register(instance -> {
serverPlayerChunkLoader = new NoisiumServerPlayerChunkLoader();
serverPlayerBlockUpdater = new NoisiumServerPlayerBlockUpdater();
});
LifecycleEvent.SERVER_STOPPING.register(instance -> {
serverPlayerChunkLoader = null;
serverPlayerBlockUpdater = null;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.github.steveplays28.noisium.server.player;

import dev.architectury.event.EventResult;
import dev.architectury.event.events.common.BlockEvent;
import io.github.steveplays28.noisium.util.world.chunk.ChunkUtil;
import net.minecraft.block.Blocks;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.jetbrains.annotations.NotNull;

public class NoisiumServerPlayerBlockUpdater {
public NoisiumServerPlayerBlockUpdater() {
// TODO: Replace this event with an event that runs when ServerChunkManager#markForUpdate is called
// The mixin for that event should cancel the existing method to improve performance (it'll do nothing anyway)
BlockEvent.BREAK.register((world, blockPos, blockState, player, xp) -> onBlockBreak(world, blockPos));
}

private EventResult onBlockBreak(@NotNull World world, BlockPos blockPos) {
if (world.isClient()) {
return EventResult.pass();
}

ChunkUtil.sendBlockUpdateToPlayers(((ServerWorld) world).getPlayers(), blockPos, Blocks.AIR.getDefaultState());
return EventResult.pass();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.github.steveplays28.noisium.Noisium;
import io.github.steveplays28.noisium.mixin.accessor.NoiseChunkGeneratorAccessor;
import io.github.steveplays28.noisium.server.world.chunk.ServerChunkData;
import io.github.steveplays28.noisium.server.world.chunk.event.NoisiumServerChunkEvent;
import io.github.steveplays28.noisium.util.world.chunk.ChunkUtil;
import net.minecraft.entity.EntityType;
import net.minecraft.nbt.NbtCompound;
Expand Down Expand Up @@ -35,6 +36,8 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

// TODO: Fix canTickBlockEntities() check
// The check needs to be changed to point to the server world's isChunkLoaded() method
public class NoisiumServerWorldChunkManager implements ChunkProvider {
private final ServerWorld serverWorld;
private final ChunkGenerator chunkGenerator;
Expand Down Expand Up @@ -131,7 +134,9 @@ public void onLightUpdate(LightType lightType, ChunkSectionPos chunkSectionPosit
}

fetchedWorldChunk.addChunkTickSchedulers(serverWorld);
fetchedWorldChunk.loadEntities();
loadedWorldChunks.put(chunkPos, new ServerChunkData(fetchedWorldChunk, (short) 0, new BitSet(), new BitSet()));
NoisiumServerChunkEvent.WORLD_CHUNK_GENERATED.invoker().onWorldChunkGenerated(fetchedWorldChunk);
});
}

Expand All @@ -152,6 +157,7 @@ public void onLightUpdate(LightType lightType, ChunkSectionPos chunkSectionPosit
// TODO: Schedule ProtoChunk worldgen and update loadedWorldChunks incrementally during worldgen steps
var fetchedWorldChunk = new WorldChunk(serverWorld, generateChunk(chunkPos), null);
loadedWorldChunks.put(chunkPos, new ServerChunkData(fetchedWorldChunk, (short) 0, new BitSet(), new BitSet()));
NoisiumServerChunkEvent.WORLD_CHUNK_GENERATED.invoker().onWorldChunkGenerated(fetchedWorldChunk);
return fetchedWorldChunk;
}

Expand All @@ -160,8 +166,10 @@ public void onLightUpdate(LightType lightType, ChunkSectionPos chunkSectionPosit
chunkToAddEntitiesTo -> serverWorld.addEntities(EntityType.streamFromNbt(fetchedChunk.getEntities(), serverWorld))
);
fetchedWorldChunk.addChunkTickSchedulers(serverWorld);
fetchedWorldChunk.loadEntities();

loadedWorldChunks.put(chunkPos, new ServerChunkData(fetchedWorldChunk, (short) 0, new BitSet(), new BitSet()));
NoisiumServerChunkEvent.WORLD_CHUNK_GENERATED.invoker().onWorldChunkGenerated(fetchedWorldChunk);
return fetchedWorldChunk;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.github.steveplays28.noisium.server.world.chunk.event;

import dev.architectury.event.Event;
import dev.architectury.event.EventFactory;
import net.minecraft.world.chunk.WorldChunk;

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

@FunctionalInterface
interface WorldChunkGenerated {
/**
* Invoked after a {@link WorldChunk} has been generated by {@link io.github.steveplays28.noisium.server.world.NoisiumServerWorldChunkManager}.
*
* @param worldChunk The generated {@link WorldChunk}.
*/
void onWorldChunkGenerated(WorldChunk worldChunk);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.github.steveplays28.noisium.server.world.entity;

import dev.architectury.event.EventResult;
import dev.architectury.event.events.common.EntityEvent;
import dev.architectury.event.events.common.TickEvent;
import net.minecraft.entity.Entity;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.network.EntityTrackerEntry;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.world.World;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

// TODO: Reimplement the rest of ServerEntityManager and replace it
public class NoisiumServerWorldEntityTracker {
private final Map<Integer, EntityTrackerEntry> entityTrackerEntries;
private final Consumer<Packet<?>> packetSendConsumer;

public NoisiumServerWorldEntityTracker(Consumer<Packet<?>> packetSendConsumer) {
this.entityTrackerEntries = new HashMap<>();
this.packetSendConsumer = packetSendConsumer;

EntityEvent.ADD.register(this::onEntityAdded);
TickEvent.SERVER_LEVEL_PRE.register(this::onTick);
}

@SuppressWarnings("ForLoopReplaceableByForEach")
private EventResult onEntityAdded(@NotNull Entity entity, @NotNull World world) {
if (world.isClient()) {
return EventResult.pass();
}

var entityType = entity.getType();
entityTrackerEntries.put(
entity.getId(),
new EntityTrackerEntry(
(ServerWorld) world, entity, entityType.getTrackTickInterval(), entityType.alwaysUpdateVelocity(),
packetSendConsumer
)
);

var players = ((ServerWorld) world).getPlayers();
for (int i = 0; i < players.size(); i++) {
entityTrackerEntries.get(entity.getId()).startTracking(players.get(i));
}

return EventResult.pass();
}

@SuppressWarnings("ForLoopReplaceableByForEach")
private EventResult onEntityRemoved(@NotNull Entity entity, @NotNull World world) {
var players = ((ServerWorld) world).getPlayers();
for (int i = 0; i < players.size(); i++) {
entityTrackerEntries.get(entity.getId()).stopTracking(players.get(i));
}
entityTrackerEntries.remove(entity.getId());
return EventResult.pass();
}

private void onTick(ServerWorld serverWorld) {
for (var entityTrackerEntry : entityTrackerEntries.values()) {
entityTrackerEntry.tick();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.github.steveplays28.noisium.util.world.chunk;

import net.minecraft.block.BlockState;
import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket;
import net.minecraft.network.packet.s2c.play.LightUpdateS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.chunk.light.LightingProvider;
Expand Down Expand Up @@ -73,4 +76,18 @@ public static void sendLightUpdateToPlayers(@NotNull List<ServerPlayerEntity> pl
players.get(i).networkHandler.sendPacket(new LightUpdateS2CPacket(chunkPos, lightingProvider, skyLightBits, blockLightBits));
}
}

/**
* Sends a block update to a {@link List} of players.
*
* @param players The {@link List} of players.
* @param blockPos The {@link BlockPos} of the block update that should be sent to the {@link List} of players.
* @param blockState The {@link BlockState} at the specified {@link BlockPos} of the block update that should be sent to the {@link List} of players.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
public static void sendBlockUpdateToPlayers(@NotNull List<ServerPlayerEntity> players, @NotNull BlockPos blockPos, BlockState blockState) {
for (int i = 0; i < players.size(); i++) {
players.get(i).networkHandler.sendPacket(new BlockUpdateS2CPacket(blockPos, blockState));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.github.steveplays28.noisium.util.world.chunk.networking.packet;

import net.minecraft.network.packet.Packet;
import net.minecraft.server.network.ServerPlayerEntity;
import org.jetbrains.annotations.NotNull;

import java.util.List;

public class PacketUtil {
/**
* Sends a packet to a {@link List} of players.
*
* @param players The {@link List} of players.
* @param packet The {@link Packet} that should be sent to the {@link List} of players.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
public static void sendPacketToPlayers(@NotNull List<ServerPlayerEntity> players, @NotNull Packet<?> packet) {
for (int i = 0; i < players.size(); i++) {
players.get(i).networkHandler.sendPacket(packet);
}
}
}

0 comments on commit f55d72e

Please sign in to comment.