Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ClientChunkCache and CCClientboundLevelCubeWithLightPacket #39

Merged
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,107 @@
package io.github.opencubicchunks.cubicchunks.client.multiplayer;

import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.function.Consumer;

import javax.annotation.Nullable;

import io.github.notstirred.dasm.api.annotations.Dasm;
import io.github.notstirred.dasm.api.annotations.selector.MethodSig;
import io.github.notstirred.dasm.api.annotations.selector.Ref;
import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod;
import io.github.opencubicchunks.cc_core.api.CubePos;
import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.CubeAccessAndDescendantsSet;
import io.github.opencubicchunks.cubicchunks.world.level.cube.CubicChunkSource;
import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;

public interface CubicClientChunkCache extends CubicChunkSource {
// TODO (P2) we might want a version of the vanilla replaceWithPacketData with a different signature for handling chunks, since we only need heightmap data with CC

void cc_drop(CubePos pChunkPos);

void cc_replaceBiomes(int pX, int pY, int pZ, FriendlyByteBuf pBuffer);

@Nullable LevelCube cc_replaceWithPacketData(
int pX,
int pY,
int pZ,
FriendlyByteBuf pBuffer,
CompoundTag pTag,
Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> pConsumer
);

void cc_updateViewCenter(int pX, int pY, int pZ);

void cc_updateViewRadius(int pViewDistance);

// Fields and methods on this are public so they can be accessed from MixinClientChunkCache and tests; they should not be used anywhere else
// (This has to be here since we can't add inner classes with mixin)
@Dasm(CubeAccessAndDescendantsSet.class)
final class Storage {
public final AtomicReferenceArray<LevelCube> chunks;
public final int cubeRadius;
private final int viewRange;
public volatile int viewCenterX;
public volatile int viewCenterY;
public volatile int viewCenterZ;
public int chunkCount;
// Field added since we can't get it off ClientChunkCache since this is no longer an inner class
final ClientLevel level;

public Storage(int pChunkRadius, ClientLevel clientLevel) {
this.cubeRadius = pChunkRadius;
this.viewRange = pChunkRadius * 2 + 1;
this.chunks = new AtomicReferenceArray<>(this.viewRange * this.viewRange * this.viewRange);
this.level = clientLevel;
}

public int getIndex(int pX, int pY, int pZ) {
return Math.floorMod(pZ, this.viewRange) * this.viewRange * this.viewRange + Math.floorMod(pY, this.viewRange) * this.viewRange + Math.floorMod(pX, this.viewRange);
}

public void replace(int pChunkIndex, @Nullable LevelCube pChunk) {
LevelCube levelchunk = this.chunks.getAndSet(pChunkIndex, pChunk);
if (levelchunk != null) {
--this.chunkCount;
// this.level.unload(levelchunk); // TODO P2
}

if (pChunk != null) {
++this.chunkCount;
}
}

public LevelCube replace(int pChunkIndex, LevelCube pChunk, @Nullable LevelCube pReplaceWith) {
if (this.chunks.compareAndSet(pChunkIndex, pChunk, pReplaceWith) && pReplaceWith == null) {
--this.chunkCount;
}

// this.level.unload(pChunk); // TODO P2
return pChunk;
}

public boolean inRange(int pX, int pY, int pZ) {
return Math.abs(pX - this.viewCenterX) <= this.cubeRadius
&& Math.abs(pY - this.viewCenterY) <= this.cubeRadius
&& Math.abs(pZ - this.viewCenterZ) <= this.cubeRadius;
}

@Nullable
@TransformFromMethod(copyFrom = @Ref(ClientChunkCache.Storage.class), value = @MethodSig("getChunk(I)Lnet/minecraft/world/level/chunk/LevelChunk;"))
public native LevelCube getChunk(int pChunkIndex);

// TODO dasm copying getChunk currently changes the access modifier from public to protected, so we need a dummy public method
@Nullable public LevelCube temp_getChunk(int index) {
return getChunk(index);
}

public void dumpChunks(String pFilePath) {
// TODO reimplement debug code
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.multiplayer;

import static io.github.opencubicchunks.cc_core.CubicChunksBase.LOGGER;
import static io.github.opencubicchunks.cc_core.utils.Coords.cubeToSection;

import java.util.function.Consumer;

import javax.annotation.Nullable;

import io.github.notstirred.dasm.api.annotations.Dasm;
import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets;
import io.github.notstirred.dasm.api.annotations.selector.FieldSig;
import io.github.notstirred.dasm.api.annotations.selector.Ref;
import io.github.opencubicchunks.cc_core.api.CubePos;
import io.github.opencubicchunks.cc_core.api.CubicConstants;
import io.github.opencubicchunks.cubicchunks.CanBeCubic;
import io.github.opencubicchunks.cubicchunks.client.multiplayer.CubicClientChunkCache;
import io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk.MixinChunkSource;
import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.CubeAccessAndDescendantsSet;
import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos;
import io.github.opencubicchunks.cubicchunks.world.level.cube.EmptyLevelCube;
import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.chunk.ChunkStatus;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

/**
* The vanilla {@link ClientChunkCache} class stores all loaded chunks on the client and has methods to update and unload them, as well as change the center and range of the chunk storage.
* This mixin adds versions of these methods for cubes, meaning that this class now stores both cubes and chunks.
*/
@Dasm(CubeAccessAndDescendantsSet.class)
@Mixin(ClientChunkCache.class)
public abstract class MixinClientChunkCache extends MixinChunkSource implements CubicClientChunkCache {
@AddFieldToSets(sets = CubeAccessAndDescendantsSet.class, owner = @Ref(ClientChunkCache.class),
field = @FieldSig(type = @Ref(ClientChunkCache.Storage.class), name = "storage"))
volatile CubicClientChunkCache.Storage cc_cubeStorage;

private LevelCube cc_emptyCube;

@Shadow @Final ClientLevel level;

/**
* Initialize cube storage and the empty cube if the level is cubic
*/
@Inject(method = "<init>", at = @At("RETURN"))
private void cc_onConstruct(ClientLevel pLevel, int pViewDistance, CallbackInfo ci) {
if (((CanBeCubic) pLevel).cc_isCubic()) {
cc_emptyCube = new EmptyLevelCube(
pLevel, CloPos.cube(0, 0, 0), pLevel.registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.PLAINS)
);
cc_cubeStorage = new CubicClientChunkCache.Storage(calculateStorageRange(pViewDistance), pLevel);
// TODO we could redirect the initial construction instead of immediately resizing. doesn't really matter
updateViewRadius(cc_calculateChunkViewDistance(pViewDistance));
}
}

private static boolean cc_isValidCube(@Nullable LevelCube pChunk, int pX, int pY, int pZ) {
if (pChunk == null) {
return false;
} else {
CubePos cubePos = pChunk.cc_getCloPos().cubePos();
return cubePos.getX() == pX && cubePos.getY() == pY && cubePos.getZ() == pZ;
}
}

@Override
public void cc_drop(CubePos pChunkPos) {
if (this.cc_cubeStorage.inRange(pChunkPos.getX(), pChunkPos.getY(), pChunkPos.getZ())) {
int i = this.cc_cubeStorage.getIndex(pChunkPos.getX(), pChunkPos.getY(), pChunkPos.getZ());
LevelCube levelCube = this.cc_cubeStorage.temp_getChunk(i);
if (cc_isValidCube(levelCube, pChunkPos.getX(), pChunkPos.getY(), pChunkPos.getZ())) {
// TODO event hook
// net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.event.level.ChunkEvent.Unload(levelCube));
this.cc_cubeStorage.replace(i, levelCube, null);
}
}
}

@Override
public @Nullable LevelCube cc_getCube(int pChunkX, int pChunkY, int pChunkZ, ChunkStatus pRequiredStatus, boolean pLoad) {
if (this.cc_cubeStorage.inRange(pChunkX, pChunkY,pChunkZ)) {
LevelCube levelCube = this.cc_cubeStorage.temp_getChunk(this.cc_cubeStorage.getIndex(pChunkX, pChunkY,pChunkZ));
if (cc_isValidCube(levelCube, pChunkX, pChunkY,pChunkZ)) {
return levelCube;
}
}

return pLoad ? this.cc_emptyCube : null;
}

@Override
public void cc_replaceBiomes(int pX, int pY, int pZ, FriendlyByteBuf pBuffer) {
if (true) throw new UnsupportedOperationException("don't remove this exception until packet integration tests are added for this method"); // TODO (P2)
if (!this.cc_cubeStorage.inRange(pX, pY, pZ)) {
LOGGER.warn("Ignoring cube since it's not in the view range: {}, {}, {}", pX, pY, pZ);
} else {
int i = this.cc_cubeStorage.getIndex(pX, pY, pZ);
LevelCube levelCube = this.cc_cubeStorage.chunks.get(i);
if (!cc_isValidCube(levelCube, pX, pY, pZ)) {
LOGGER.warn("Ignoring cube since it's not present: {}, {}, {}", pX, pY, pZ);
} else {
levelCube.replaceBiomes(pBuffer);
}
}
}

@Override
public @Nullable LevelCube cc_replaceWithPacketData(
int pX,
int pY,
int pZ,
FriendlyByteBuf pBuffer,
CompoundTag pTag,
Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> pConsumer
) {
if (!this.cc_cubeStorage.inRange(pX, pY, pZ)) {
LOGGER.warn("Ignoring cube since it's not in the view range: {}, {}, {}", pX, pY, pZ);
return null;
} else {
int i = this.cc_cubeStorage.getIndex(pX, pY, pZ);
LevelCube levelCube = this.cc_cubeStorage.chunks.get(i);
CubePos cubePos = CubePos.of(pX, pY, pZ);
if (!cc_isValidCube(levelCube, pX, pY, pZ)) {
levelCube = new LevelCube(this.level, CloPos.cube(cubePos));
levelCube.replaceWithPacketData(pBuffer, pTag, pConsumer);
this.cc_cubeStorage.replace(i, levelCube);
} else {
levelCube.replaceWithPacketData(pBuffer, pTag, pConsumer);
}

// ((CubicClientLevel) this.level).onCubeLoaded(cubePos); // TODO (P3) onCubeLoaded call
// TODO event hook
// net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.event.level.ChunkEvent.Load(levelCube, false));
return levelCube;
}
}

@Shadow public abstract void updateViewCenter(int pX, int pZ);

@Override
public void cc_updateViewCenter(int pX, int pY, int pZ) {
this.cc_cubeStorage.viewCenterX = pX;
this.cc_cubeStorage.viewCenterY = pY;
this.cc_cubeStorage.viewCenterZ = pZ;
this.updateViewCenter(cubeToSection(pX, 0), cubeToSection(pZ, 0));
}

@Shadow public abstract void updateViewRadius(int pViewDistance);

@Override
public void cc_updateViewRadius(int pViewDistance) {
int i = this.cc_cubeStorage.cubeRadius;
int j = calculateStorageRange(pViewDistance);
if (i != j) {
CubicClientChunkCache.Storage storage = new CubicClientChunkCache.Storage(j, this.level);
storage.viewCenterX = this.cc_cubeStorage.viewCenterX;
storage.viewCenterY = this.cc_cubeStorage.viewCenterY;
storage.viewCenterZ = this.cc_cubeStorage.viewCenterZ;

for(int k = 0; k < this.cc_cubeStorage.chunks.length(); ++k) {
LevelCube levelCube = this.cc_cubeStorage.chunks.get(k);
if (levelCube != null) {
CubePos cubePos = levelCube.cc_getCloPos().cubePos();
if (storage.inRange(cubePos.getX(), cubePos.getY(), cubePos.getZ())) {
storage.replace(storage.getIndex(cubePos.getX(), cubePos.getY(), cubePos.getZ()), levelCube);
}
}
}
this.cc_cubeStorage = storage;
}
updateViewRadius(cc_calculateChunkViewDistance(pViewDistance));
}

@Shadow
private static int calculateStorageRange(int pViewDistance) {
throw new IllegalStateException("mixin failed to apply");
}

private static int cc_calculateChunkViewDistance(int cubeViewDistance) {
int cubeStorageRange = calculateStorageRange(cubeViewDistance);
// TODO this radius might be larger than it needs to be? coordinate maths is difficult
int chunkStorageRange = CubicConstants.DIAMETER_IN_SECTIONS * (cubeStorageRange + 1);
return chunkStorageRange - 3; // This gives the view distance, which gets passed back into calculateStorageRange which will readd the 3
}

// TODO gatherStats (only used for debug)

@Override
public int cc_getLoadedCubeCount() {
return this.cc_cubeStorage.chunkCount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.opencubicchunks.cubicchunks.mixin.core.common.client.multiplayer;

import io.github.opencubicchunks.cubicchunks.client.multiplayer.CubicClientChunkCache;
import org.spongepowered.asm.mixin.Mixin;

// Needed for DASM to apply
@Mixin(CubicClientChunkCache.Storage.class)
public class MixinCubicClientChunkCache$Storage {
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import io.github.notstirred.dasm.api.annotations.Dasm;
import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets;
import io.github.notstirred.dasm.api.annotations.selector.MethodSig;
import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod;
import io.github.opencubicchunks.cc_core.annotation.UsedFromASM;
import io.github.opencubicchunks.cubicchunks.MarkableAsCubic;
import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GeneralSet;
import io.github.opencubicchunks.cubicchunks.mixin.dasmsets.GlobalSet;
import io.github.opencubicchunks.cubicchunks.server.level.CubicDistanceManager;
import io.github.opencubicchunks.cubicchunks.server.level.CubicTicketType;
import io.github.opencubicchunks.cubicchunks.server.level.CubicTickingTracker;
Expand Down Expand Up @@ -59,36 +61,36 @@ private void cc_onUseChunkPos(CallbackInfo ci){

@Override
@UsedFromASM
@TransformFromMethod(@MethodSig("addTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
public abstract <T> void addTicket(TicketType<T> type, CloPos pos, int level, T value);
@AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("addTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
public abstract <T> void cc_addTicket(TicketType<T> type, CloPos pos, int level, T value);

@Override
@UsedFromASM
@TransformFromMethod(@MethodSig("removeTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
public abstract <T> void removeTicket(TicketType<T> type, CloPos pos, int level, T value);
@AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("removeTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
public abstract <T> void cc_removeTicket(TicketType<T> type, CloPos pos, int level, T value);

@Override
@UsedFromASM
@TransformFromMethod(@MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
public abstract <T> void addRegionTicket(TicketType<T> type, CloPos pos, int distance, T value);
@AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
public abstract <T> void cc_addRegionTicket(TicketType<T> type, CloPos pos, int distance, T value);

@Override
@UsedFromASM
@TransformFromMethod(@MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V"))
public abstract <T> void addRegionTicket(TicketType<T> type, CloPos pos, int distance, T value, boolean forceTicks);
@AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("addRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V"))
public abstract <T> void cc_addRegionTicket(TicketType<T> type, CloPos pos, int distance, T value, boolean forceTicks);

@Override
@UsedFromASM
@TransformFromMethod(@MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
public abstract <T> void removeRegionTicket(TicketType<T> type, CloPos pos, int distance, T value);
@AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V"))
public abstract <T> void cc_removeRegionTicket(TicketType<T> type, CloPos pos, int distance, T value);

@Override
@UsedFromASM
@TransformFromMethod(@MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V"))
public abstract <T> void removeRegionTicket(TicketType<T> type, CloPos pos, int distance, T value, boolean forceTicks);
@AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("removeRegionTicket(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;Z)V"))
public abstract <T> void cc_removeRegionTicket(TicketType<T> type, CloPos pos, int distance, T value, boolean forceTicks);

@UsedFromASM
@TransformFromMethod(@MethodSig("updateChunkForced(Lnet/minecraft/world/level/ChunkPos;Z)V"))
@AddTransformToSets(GlobalSet.class) @TransformFromMethod(@MethodSig("updateChunkForced(Lnet/minecraft/world/level/ChunkPos;Z)V"))
protected abstract void updateCubeForced(CloPos pos, boolean add);

/**
Expand Down
Loading
Loading