Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.network;

import java.util.List;
import io.github.notstirred.dasm.api.annotations.Dasm;
import io.github.notstirred.dasm.api.annotations.selector.MethodSig;
import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod;
import io.github.opencubicchunks.cc_core.world.level.CloPos;
import io.github.opencubicchunks.cubicchunks.CanBeCubic;
import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.ChunkToCloSet;
import io.github.opencubicchunks.cubicchunks.network.CCClientboundLevelChunkPacket;
import io.github.opencubicchunks.cubicchunks.network.CCClientboundLevelCubeWithLightPacket;
import io.github.opencubicchunks.cubicchunks.world.entity.EntityCubePosGetter;
import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo;
import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.network.protocol.game.ClientboundChunkBatchFinishedPacket;
import net.minecraft.network.protocol.game.ClientboundChunkBatchStartPacket;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.PlayerChunkSender;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.level.chunk.LevelChunk;
import net.neoforged.neoforge.network.PacketDistributor;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Dasm(ChunkToCloSet.class)
@Mixin(PlayerChunkSender.class)
public class MixinPlayerChunkSender {
@Shadow private int unacknowledgedBatches;

@Shadow private int maxUnacknowledgedBatches;

@Shadow private float desiredChunksPerTick;

@Shadow private float batchQuota;

@Shadow @Final private LongSet pendingChunks;

@Inject(method = "sendNextChunks", at = @At(value = "HEAD"), cancellable = true)
private void cc_sendNextChunks(ServerPlayer player, CallbackInfo ci) {
if(!((CanBeCubic)player).cc_isCubic()) {
return;
}

ci.cancel();

if (this.unacknowledgedBatches < this.maxUnacknowledgedBatches) {
float f = Math.max(1.0F, this.desiredChunksPerTick);
this.batchQuota = Math.min(this.batchQuota + this.desiredChunksPerTick, f);
if (!(this.batchQuota < 1.0F)) {
if (!this.pendingChunks.isEmpty()) {
ServerLevel serverlevel = player.serverLevel();
ChunkMap chunkmap = serverlevel.getChunkSource().chunkMap;
List<LevelClo> list = this.cc_collectChunksToSend(chunkmap, CloPos.cube(((EntityCubePosGetter)player).cc_cubePosition()));
if (!list.isEmpty()) {
ServerGamePacketListenerImpl servergamepacketlistenerimpl = player.connection;
++this.unacknowledgedBatches;

// This packet can remain the same because it is just for timing purposes in order to determine how many chunks (or cubes) the client should request
servergamepacketlistenerimpl.send(new ClientboundChunkBatchStartPacket());

// TODO P2 :: We need to send heightmap and lighting data, which would be contained in the Column

// We need to send the chunks first before cubes, to ensure that load order invariants are preserved
for (LevelClo levelClo : list) {
if(levelClo instanceof LevelChunk) {
cc_sendChunk(servergamepacketlistenerimpl, serverlevel, (LevelChunk) levelClo);
}
}

for (LevelClo levelClo : list) {
if(levelClo instanceof LevelCube) {
cc_sendCube(servergamepacketlistenerimpl, serverlevel, (LevelCube) levelClo);
}
}

// This packet can remain the same because it is just for timing purposes in order to determine how many chunks (or cubes) the client should request
servergamepacketlistenerimpl.send(new ClientboundChunkBatchFinishedPacket(list.size()));
this.batchQuota -= (float)list.size();
}
}
}
}
}

@Unique
private static void cc_sendCube(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelCube cube) {
PacketDistributor.PLAYER.with(packetListener.player).send(new CCClientboundLevelCubeWithLightPacket(cube));

// ChunkPos chunkpos = chunk.getPos();

// TODO :: Probably never (its for vanilla debug tools)
// DebugPackets.sendPoiPacketsForChunk(level, chunkpos);

// TODO P3 :: We need our own fireCubeSent event for this
// net.neoforged.neoforge.event.EventHooks.fireChunkSent(packetListener.player, chunk, level);
}

@Unique
private static void cc_sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) {
PacketDistributor.PLAYER.with(packetListener.player).send(new CCClientboundLevelChunkPacket(chunk.getPos()));

// ChunkPos chunkpos = chunk.getPos();

// TODO :: Probably never (its for vanilla debug tools)
// DebugPackets.sendPoiPacketsForChunk(level, chunkpos);

// TODO P3 :: We need our own fireChunkSent event for this
// net.neoforged.neoforge.event.EventHooks.fireChunkSent(packetListener.player, chunk, level);
}

@TransformFromMethod(value = @MethodSig("collectChunksToSend(Lnet/minecraft/server/level/ChunkMap;Lnet/minecraft/world/level/ChunkPos;)Ljava/util/List;"))
private native List<LevelClo> cc_collectChunksToSend(ChunkMap chunkMap, CloPos cloPos);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.network;

import javax.annotation.ParametersAreNonnullByDefault;

import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault;
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.github.opencubicchunks.cubicchunks.network;

import io.github.opencubicchunks.cubicchunks.CubicChunks;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.neoforged.neoforge.network.handling.IPlayPayloadHandler;
import net.neoforged.neoforge.network.handling.PlayPayloadContext;

public class CCClientboundLevelChunkPacket implements CustomPacketPayload {
public static final ResourceLocation ID = new ResourceLocation(CubicChunks.MODID, "level_chunk");

private final ChunkPos pos;

public CCClientboundLevelChunkPacket(ChunkPos pos) {
this.pos = pos;
}

public CCClientboundLevelChunkPacket(FriendlyByteBuf buffer) {
this.pos = new ChunkPos(buffer.readInt(), buffer.readInt());
}

@Override public void write(FriendlyByteBuf buffer) {
buffer.writeInt(pos.x);
buffer.writeInt(pos.z);
}

@Override public ResourceLocation id() {
return ID;
}

public static class Handler implements IPlayPayloadHandler<CCClientboundLevelChunkPacket>, FriendlyByteBuf.Reader<CCClientboundLevelChunkPacket> {

@Override public CCClientboundLevelChunkPacket apply(FriendlyByteBuf friendlyByteBuf) {
int x = friendlyByteBuf.readInt();
int z = friendlyByteBuf.readInt();
return new CCClientboundLevelChunkPacket(new ChunkPos(x, z));
}

@Override public void handle(CCClientboundLevelChunkPacket payload, PlayPayloadContext context) {
int x = payload.pos.x;
int z = payload.pos.z;
ChunkPos chunkPos = new ChunkPos(x, z);

// TODO P2 :: This will contain heightmap data and some other stuff
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
package io.github.opencubicchunks.cubicchunks.network;

import java.util.function.Consumer;
import io.github.opencubicchunks.cc_core.api.CubePos;
import io.github.opencubicchunks.cubicchunks.CubicChunks;
import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache;
import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import net.neoforged.neoforge.network.handling.IPlayPayloadHandler;
import net.neoforged.neoforge.network.handling.PlayPayloadContext;

// TODO (P2) the name is currently a lie; no light data :)
public class CCClientboundLevelCubeWithLightPacket implements CustomPacketPayload {
public static final ResourceLocation ID = new ResourceLocation(CubicChunks.MODID, "level_chunk");
public static final ResourceLocation ID = new ResourceLocation(CubicChunks.MODID, "level_cube_with_light");

private CubePos pos;
private final CubePos pos;
private final CCClientboundLevelCubePacketData chunkData;

public CCClientboundLevelCubeWithLightPacket(LevelCube cube) {
Expand All @@ -38,4 +45,35 @@ public CCClientboundLevelCubeWithLightPacket(final FriendlyByteBuf buffer) {
public CCClientboundLevelCubePacketData getChunkData() {
return chunkData;
}

public static class Handler implements IPlayPayloadHandler<CCClientboundLevelCubeWithLightPacket>, FriendlyByteBuf.Reader<CCClientboundLevelCubeWithLightPacket> {
@Override
public void handle(CCClientboundLevelCubeWithLightPacket payload, PlayPayloadContext context) {
int x = payload.pos.getX();
int y = payload.pos.getY();
int z = payload.pos.getZ();
this.updateLevelCube(context.level().get(), x, y, z, payload);
}

@Override
public CCClientboundLevelCubeWithLightPacket apply(FriendlyByteBuf friendlyByteBuf)
{
return new CCClientboundLevelCubeWithLightPacket(friendlyByteBuf);
}

private void updateLevelCube(Level level, int x, int y, int z, CCClientboundLevelCubeWithLightPacket payload) {
// TODO P2 :: The empty compound tag should become a heightmap
CompoundTag heightmap = new CompoundTag();

// TODO P2 :: No block entity tags consumer
Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> entityTagConsumer = (a) -> {};

((ClientCubeCache)(level
.getChunkSource()))
.cc_replaceWithPacketData(
x, y, z, payload.chunkData.getReadBuffer(), heightmap, entityTagConsumer);

// TODO P2 :: Vanilla does light updates at this point
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.github.opencubicchunks.cubicchunks.network;

import io.github.opencubicchunks.cubicchunks.CubicChunks;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlerEvent;
import net.neoforged.neoforge.network.registration.IPayloadRegistrar;


// !!! Please minimize the amount of packets that we create until we move to 1.21 !!!
// This is because NeoForge's network is significantly different (and way better!) in 1.21 and beyond

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
public class CCNetworkHandler {
@SubscribeEvent
public static void register(final RegisterPayloadHandlerEvent event) {
// Sets the current network version
final IPayloadRegistrar registrar = event.registrar(CubicChunks.MODID);

registrar.play(CCClientboundLevelCubeWithLightPacket.ID, new CCClientboundLevelCubeWithLightPacket.Handler(), new CCClientboundLevelCubeWithLightPacket.Handler());
registrar.play(CCClientboundLevelChunkPacket.ID, new CCClientboundLevelChunkPacket.Handler(), new CCClientboundLevelChunkPacket.Handler());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package io.github.opencubicchunks.cubicchunks.mixin.test.client.multiplayer;

import javax.annotation.ParametersAreNonnullByDefault;

import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.opencubicchunks.cubicchunks.test.network;

import static io.github.opencubicchunks.cubicchunks.testutils.Misc.assertDeepEquals;

import io.github.opencubicchunks.cubicchunks.network.CCClientboundLevelChunkPacket;
import io.github.opencubicchunks.cubicchunks.testutils.BaseTest;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.level.ChunkPos;
import org.junit.jupiter.api.Test;

public class TestCCClientboundLevelChunkPacket extends BaseTest {

@Test
public void serdeTest() {
var packet = new CCClientboundLevelChunkPacket(new ChunkPos(2, 4));
var buf = new FriendlyByteBuf(Unpooled.buffer());
packet.write(buf);
assertDeepEquals(new CCClientboundLevelChunkPacket(buf), packet);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,35 @@

import static io.github.opencubicchunks.cubicchunks.testutils.Misc.assertDeepEquals;
import static io.github.opencubicchunks.cubicchunks.testutils.Misc.generateRandomLevelCube;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Optional;
import java.util.Random;

import io.github.opencubicchunks.cc_core.api.CubePos;
import io.github.opencubicchunks.cc_core.utils.Coords;
import io.github.opencubicchunks.cubicchunks.CanBeCubic;
import io.github.opencubicchunks.cubicchunks.client.multiplayer.ClientCubeCache;
import io.github.opencubicchunks.cubicchunks.network.CCClientboundLevelChunkPacket;
import io.github.opencubicchunks.cubicchunks.network.CCClientboundLevelCubeWithLightPacket;
import io.github.opencubicchunks.cubicchunks.testutils.BaseTest;
import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube;
import io.netty.buffer.Unpooled;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.level.ChunkPos;
import net.neoforged.neoforge.network.handling.PlayPayloadContext;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

public class TestCCClientboundLevelCubeWithLightPacket extends BaseTest {

@Test
public void serdeTest() {
ClientLevel clientLevelMock = mock(Mockito.RETURNS_DEEP_STUBS);
Expand All @@ -42,4 +54,29 @@ public void serdeTest() {
assertDeepEquals(new CCClientboundLevelCubeWithLightPacket(buf1), packet1);
assertDeepEquals(new CCClientboundLevelCubeWithLightPacket(buf2), packet2);
}


@Test
public void handlerTest() {
PlayPayloadContext payloadContextMock = mock();
ClientLevel clientLevelMock = mock(Mockito.RETURNS_DEEP_STUBS);
ClientChunkCache clientChunkCacheMock = mock(ClientChunkCache.class);
when(clientLevelMock.getChunkSource()).thenReturn(clientChunkCacheMock);
when(((CanBeCubic) clientLevelMock).cc_isCubic()).thenReturn(true);
when(clientLevelMock.getHeight()).thenReturn(384);
when(clientLevelMock.getSectionsCount()).thenReturn(24);

when(payloadContextMock.level()).thenReturn(Optional.of(clientLevelMock));

var pos = CubePos.of(10, -2, 4);
var cube = generateRandomLevelCube(clientLevelMock, pos, new Random(3333));

var packet = new CCClientboundLevelCubeWithLightPacket(cube);

var handler = new CCClientboundLevelCubeWithLightPacket.Handler();

handler.handle(packet, payloadContextMock);

((ClientCubeCache) verify(clientChunkCacheMock, times(1))).cc_replaceWithPacketData(eq(pos.getX()), eq(pos.getY()), eq(pos.getZ()), any(), any(), any());
}
}