Skip to content

Commit

Permalink
Add disallowed-blocks, log-large-block-buffer-changes and block-buffe…
Browse files Browse the repository at this point in the history
…r-rate-limit config options
  • Loading branch information
Moulberry committed Dec 26, 2023
1 parent 313dd87 commit 13df370
Show file tree
Hide file tree
Showing 12 changed files with 433 additions and 228 deletions.
6 changes: 3 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins {
`java-library`
id("io.papermc.paperweight.userdev") version "1.5.8"
id("xyz.jpenilla.run-paper") version "2.2.0" // Adds runServer and runMojangMappedServer tasks for testing
id("io.papermc.paperweight.userdev") version "1.5.11"
id("xyz.jpenilla.run-paper") version "2.2.2" // Adds runServer and runMojangMappedServer tasks for testing

// Shades and relocates dependencies into our plugin jar. See https://imperceptiblethoughts.com/shadow/introduction/
id("com.github.johnrengelman.shadow") version "8.1.1"
Expand All @@ -20,8 +20,8 @@ repositories {
mavenCentral()
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
maven("https://jitpack.io")
maven("https://maven.enginehub.org/repo/")
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://maven.enginehub.org/repo/")
}

dependencies {
Expand Down
40 changes: 35 additions & 5 deletions src/main/java/com/moulberry/axiom/AxiomPaper.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.moulberry.axiom;

import com.google.common.util.concurrent.RateLimiter;
import com.mojang.brigadier.StringReader;
import com.moulberry.axiom.buffer.BlockBuffer;
import com.moulberry.axiom.buffer.CompressedBlockEntity;
import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent;
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
Expand All @@ -12,13 +15,21 @@
import io.papermc.paper.network.ChannelInitializeListener;
import io.papermc.paper.network.ChannelInitializeListenerHolder;
import net.kyori.adventure.key.Key;
import net.minecraft.commands.arguments.blocks.BlockPredicateArgument;
import net.minecraft.commands.arguments.blocks.BlockStateArgument;
import net.minecraft.commands.arguments.blocks.BlockStateParser;
import net.minecraft.core.IdMapper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.Connection;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.*;
import org.bukkit.configuration.Configuration;
import org.bukkit.entity.Player;
Expand All @@ -32,14 +43,20 @@

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

public class AxiomPaper extends JavaPlugin implements Listener {

public static AxiomPaper PLUGIN; // tsk tsk tsk

public final Set<UUID> activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final Map<UUID, RateLimiter> playerBlockBufferRateLimiters = new ConcurrentHashMap<>();
public Configuration configuration;

public IdMapper<BlockState> allowedBlockRegistry = null;
private boolean logLargeBlockBufferChanges = false;

@Override
public void onEnable() {
PLUGIN = this;
Expand All @@ -55,6 +72,11 @@ public void onEnable() {
this.getLogger().warning("Invalid value for unsupported-axiom-version, expected 'kick', 'warn' or 'ignore'");
}

this.logLargeBlockBufferChanges = this.configuration.getBoolean("log-large-block-buffer-changes");

List<String> disallowedBlocks = this.configuration.getStringList("disallowed-blocks");
this.allowedBlockRegistry = DisallowedBlocks.createAllowedBlockRegistry(disallowedBlocks);

Bukkit.getPluginManager().registerEvents(this, this);
// Bukkit.getPluginManager().registerEvents(new WorldPropertiesExample(), this);
CompressedBlockEntity.initialize(this);
Expand All @@ -70,7 +92,7 @@ public void onEnable() {
msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties");

if (configuration.getBoolean("packet-handlers.hello")) {
msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this, activeAxiomPlayers));
msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this));
}
if (configuration.getBoolean("packet-handlers.set-gamemode")) {
msg.registerIncomingPluginChannel(this, "axiom:set_gamemode", new SetGamemodePacketListener(this));
Expand Down Expand Up @@ -129,7 +151,7 @@ public void afterInitChannel(@NonNull Channel channel) {
}

Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
HashSet<UUID> newActiveAxiomPlayers = new HashSet<>();
HashSet<UUID> stillActiveAxiomPlayers = new HashSet<>();

for (Player player : Bukkit.getServer().getOnlinePlayers()) {
if (activeAxiomPlayers.contains(player.getUniqueId())) {
Expand All @@ -140,13 +162,13 @@ public void afterInitChannel(@NonNull Channel channel) {
buf.getBytes(0, bytes);
player.sendPluginMessage(this, "axiom:enable", bytes);
} else {
newActiveAxiomPlayers.add(player.getUniqueId());
stillActiveAxiomPlayers.add(player.getUniqueId());
}
}
}

activeAxiomPlayers.clear();
activeAxiomPlayers.addAll(newActiveAxiomPlayers);
activeAxiomPlayers.retainAll(stillActiveAxiomPlayers);
playerBlockBufferRateLimiters.keySet().retainAll(stillActiveAxiomPlayers);
}, 20, 20);

int maxChunkRelightsPerTick = configuration.getInt("max-chunk-relights-per-tick");
Expand All @@ -157,10 +179,18 @@ public void afterInitChannel(@NonNull Channel channel) {
}, 1, 1);
}

public boolean logLargeBlockBufferChanges() {
return this.logLargeBlockBufferChanges;
}

public boolean canUseAxiom(Player player) {
return player.hasPermission("axiom.*") && activeAxiomPlayers.contains(player.getUniqueId());
}

public @Nullable RateLimiter getBlockBufferRateLimiter(UUID uuid) {
return this.playerBlockBufferRateLimiters.get(uuid);
}

private final WeakHashMap<World, ServerWorldPropertiesRegistry> worldProperties = new WeakHashMap<>();

public @Nullable ServerWorldPropertiesRegistry getWorldPropertiesIfPresent(World world) {
Expand Down
92 changes: 92 additions & 0 deletions src/main/java/com/moulberry/axiom/DisallowedBlocks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.moulberry.axiom;

import com.mojang.brigadier.StringReader;
import com.mojang.datafixers.util.Either;
import com.moulberry.axiom.buffer.BlockBuffer;
import net.minecraft.commands.arguments.blocks.BlockPredicateArgument;
import net.minecraft.commands.arguments.blocks.BlockStateParser;
import net.minecraft.core.IdMapper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

public class DisallowedBlocks {

public static IdMapper<BlockState> createAllowedBlockRegistry(List<String> disallowedBlocks) {
List<Predicate<BlockState>> disallowedPredicates = new ArrayList<>();

for (String disallowedBlock : disallowedBlocks) {
try {
var parsed = BlockStateParser.parseForTesting(BuiltInRegistries.BLOCK.asLookup(), new StringReader(disallowedBlock), false);

parsed.left().ifPresent(result -> {
disallowedPredicates.add(blockState -> {
if (!blockState.is(result.blockState().getBlock())) {
return false;
} else {
for (Property<?> property : result.properties().keySet()) {
if (blockState.getValue(property) != result.blockState().getValue(property)) {
return false;
}
}
return true;
}
});
});

parsed.right().ifPresent(result -> {
disallowedPredicates.add(blockState -> {
if (!blockState.is(result.tag())) {
return false;
} else {
for(Map.Entry<String, String> entry : result.vagueProperties().entrySet()) {
Property<?> property = blockState.getBlock().getStateDefinition().getProperty(entry.getKey());
if (property == null) {
return false;
}

Comparable<?> comparable = property.getValue(entry.getValue()).orElse(null);
if (comparable == null) {
return false;
}

if (blockState.getValue(property) != comparable) {
return false;
}
}

return true;
}
});
});
} catch (Exception ignored) {}
}

IdMapper<BlockState> allowedBlockRegistry = new IdMapper<>();

// Create allowedBlockRegistry
blocks:
for (BlockState blockState : Block.BLOCK_STATE_REGISTRY) {
for (Predicate<BlockState> disallowedPredicate : disallowedPredicates) {
if (disallowedPredicate.test(blockState)) {
allowedBlockRegistry.add(BlockBuffer.EMPTY_STATE);
continue blocks;
}
}

allowedBlockRegistry.add(blockState);
}
allowedBlockRegistry.addMapping(BlockBuffer.EMPTY_STATE, Block.BLOCK_STATE_REGISTRY.getId(BlockBuffer.EMPTY_STATE));
return allowedBlockRegistry;
}

}
12 changes: 10 additions & 2 deletions src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.moulberry.axiom.buffer;

import com.google.common.util.concurrent.RateLimiter;
import it.unimi.dsi.fastutil.objects.Object2ByteMap;
import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import org.jetbrains.annotations.Nullable;

import java.util.concurrent.atomic.AtomicBoolean;

public class BiomeBuffer {

Expand All @@ -27,6 +31,10 @@ private BiomeBuffer(Position2ByteMap map, ResourceKey<Biome>[] palette, Object2B
this.paletteSize = this.paletteReverse.size();
}

public int size() {
return this.map.size();
}

public void save(FriendlyByteBuf friendlyByteBuf) {
friendlyByteBuf.writeByte(this.paletteSize);
for (int i = 0; i < this.paletteSize; i++) {
Expand All @@ -35,7 +43,7 @@ public void save(FriendlyByteBuf friendlyByteBuf) {
this.map.save(friendlyByteBuf);
}

public static BiomeBuffer load(FriendlyByteBuf friendlyByteBuf) {
public static BiomeBuffer load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit) {
int paletteSize = friendlyByteBuf.readByte();
ResourceKey<Biome>[] palette = new ResourceKey[255];
Object2ByteMap<ResourceKey<Biome>> paletteReverse = new Object2ByteOpenHashMap<>();
Expand All @@ -44,7 +52,7 @@ public static BiomeBuffer load(FriendlyByteBuf friendlyByteBuf) {
palette[i] = key;
paletteReverse.put(key, (byte)(i+1));
}
Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf);
Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf, rateLimiter, reachedRateLimit);
return new BiomeBuffer(map, palette, paletteReverse);

}
Expand Down
54 changes: 31 additions & 23 deletions src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.moulberry.axiom.buffer;

import com.google.common.util.concurrent.RateLimiter;
import com.moulberry.axiom.AxiomConstants;
import com.moulberry.axiom.AxiomPaper;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
Expand All @@ -15,6 +16,8 @@
import net.minecraft.world.level.chunk.PalettedContainer;
import org.jetbrains.annotations.Nullable;

import java.util.concurrent.atomic.AtomicBoolean;

public class BlockBuffer {

public static final BlockState EMPTY_STATE = Blocks.STRUCTURE_VOID.defaultBlockState();
Expand All @@ -24,6 +27,8 @@ public class BlockBuffer {
private PalettedContainer<BlockState> last = null;
private long lastId = AxiomConstants.MIN_POSITION_LONG;
private final Long2ObjectMap<Short2ObjectMap<CompressedBlockEntity>> blockEntities = new Long2ObjectOpenHashMap<>();
private long totalBlockEntities = 0;
private long totalBlockEntityBytes = 0;

public BlockBuffer() {
this.values = new Long2ObjectOpenHashMap<>();
Expand Down Expand Up @@ -53,54 +58,57 @@ public void save(FriendlyByteBuf friendlyByteBuf) {
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
}

public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf) {
public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit) {
BlockBuffer buffer = new BlockBuffer();

long totalBlockEntities = 0;
long totalBlockEntityBytes = 0;

while (true) {
long index = friendlyByteBuf.readLong();
if (index == AxiomConstants.MIN_POSITION_LONG) break;

if (rateLimiter != null) {
if (!rateLimiter.tryAcquire()) {
reachedRateLimit.set(true);
buffer.totalBlockEntities = totalBlockEntities;
buffer.totalBlockEntityBytes = totalBlockEntityBytes;
return buffer;
}
}

PalettedContainer<BlockState> palettedContainer = buffer.getOrCreateSection(index);
palettedContainer.read(friendlyByteBuf);

int blockEntitySize = Math.min(4096, friendlyByteBuf.readVarInt());
if (blockEntitySize > 0) {
Short2ObjectMap<CompressedBlockEntity> map = new Short2ObjectOpenHashMap<>(blockEntitySize);

int startIndex = friendlyByteBuf.readerIndex();

for (int i = 0; i < blockEntitySize; i++) {
short offset = friendlyByteBuf.readShort();
CompressedBlockEntity blockEntity = CompressedBlockEntity.read(friendlyByteBuf);
map.put(offset, blockEntity);
}

buffer.blockEntities.put(index, map);
totalBlockEntities += blockEntitySize;
totalBlockEntityBytes += friendlyByteBuf.readerIndex() - startIndex;
}
}

buffer.totalBlockEntities = totalBlockEntities;
buffer.totalBlockEntityBytes = totalBlockEntityBytes;
return buffer;
}

public void clear() {
this.last = null;
this.lastId = AxiomConstants.MIN_POSITION_LONG;
this.values.clear();
}

public void putBlockEntity(int x, int y, int z, CompressedBlockEntity blockEntity) {
long cpos = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
Short2ObjectMap<CompressedBlockEntity> chunkMap = this.blockEntities.computeIfAbsent(cpos, k -> new Short2ObjectOpenHashMap<>());

int key = (x & 0xF) | ((y & 0xF) << 4) | ((z & 0xF) << 8);
chunkMap.put((short)key, blockEntity);
public long getTotalBlockEntities() {
return this.totalBlockEntities;
}

@Nullable
public CompressedBlockEntity getBlockEntity(int x, int y, int z) {
long cpos = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
Short2ObjectMap<CompressedBlockEntity> chunkMap = this.blockEntities.get(cpos);

if (chunkMap == null) return null;

int key = (x & 0xF) | ((y & 0xF) << 4) | ((z & 0xF) << 8);
return chunkMap.get((short)key);
public long getTotalBlockEntityBytes() {
return this.totalBlockEntityBytes;
}

@Nullable
Expand Down Expand Up @@ -170,7 +178,7 @@ public PalettedContainer<BlockState> getOrCreateSectionForCoord(int x, int y, in
public PalettedContainer<BlockState> getOrCreateSection(long id) {
if (this.last == null || id != this.lastId) {
this.lastId = id;
this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY,
this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(AxiomPaper.PLUGIN.allowedBlockRegistry,
EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES));
}

Expand Down
Loading

0 comments on commit 13df370

Please sign in to comment.