Skip to content
Closed
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
8 changes: 4 additions & 4 deletions .github/modules.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
{ "module": "config", "needs": [] },
{ "module": "integration", "needs": [] },
{ "module": "moveable-entity-block", "needs": [] },
{ "module": "network", "needs": [] },
{ "module": "rendering", "needs": [] },
{ "module": "space-select", "needs": [] },

{ "module": "font", "needs": ["rendering"] },
{ "module": "util", "needs": ["codec"] },
{ "module": "network", "needs": ["config"] },
{ "module": "font", "needs": ["rendering"] },
{ "module": "util", "needs": ["codec"] },
{ "module": "explosion", "needs": ["config"] },
{ "module": "rpc", "needs": ["network"] },

{ "module": "rpc", "needs": ["network"] },
{ "module": "multiblock", "needs": ["codec", "config", "network", "util"] },
{ "module": "recipe", "needs": ["codec", "config", "util"] },
{ "module": "registrum", "needs": ["util"] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"anvillib_explosion.configuration.default_remove_blocks_per_tick.tooltip": "每 tick 内可以删除的方块的默认数量",
"anvillib_explosion.configuration.max_remove_blocks_per_tick": "最大每 tick 移除的方块数",
"anvillib_explosion.configuration.max_remove_blocks_per_tick.tooltip": "每 tick 内可以删除的方块的最大数量",
"anvillib_explosion.configuration.section.anvillib.explosion.common.toml": "Anvillib Explosion 通用 配置",
"anvillib_explosion.configuration.section.anvillib.explosion.common.toml.title": "Anvillib Explosion 通用 配置",
"anvillib_explosion.configuration.title": "Anvillib Explosion 配置"
"anvillib_explosion.configuration.section.anvillib.explosion.common.toml": "AnvilLib Explosion 通用 配置",
"anvillib_explosion.configuration.section.anvillib.explosion.common.toml.title": "AnvilLib Explosion 通用 配置",
"anvillib_explosion.configuration.title": "AnvilLib Explosion 配置"
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"narration.anvillib_font.dropdown.collapsed": "折叠",
"narration.anvillib_font.dropdown.expanded": "展开",
"screen.anvillib_font.config": "铁砧库 文字渲染 配置",
"screen.anvillib_font.config": "AnvilLib 文字渲染 配置",
"screen.anvillib_font.config.family": "字体系列",
"screen.anvillib_font.config.font": "字体",
"screen.anvillib_font.config.test": "文字渲染测试"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
"anvillib_multiblock.configuration.formed_multiblock_check_interval.tooltip": "已成型的多方块的检查周期(以tick为单位)",
"anvillib_multiblock.configuration.max_checks_per_tick": "每 tick 最大检查数量",
"anvillib_multiblock.configuration.max_checks_per_tick.tooltip": "每 tick 提交多方块检查的最大数量",
"anvillib_multiblock.configuration.section.anvillib_multiblock.common.toml": "铁砧库-多方块 通用配置",
"anvillib_multiblock.configuration.section.anvillib_multiblock.common.toml.title": "铁砧库-多方块 通用配置",
"anvillib_multiblock.configuration.title": "铁砧库-多方块 配置",
"anvillib_multiblock.configuration.section.anvillib_multiblock.common.toml": "AnvilLib 多方块 通用 配置",
"anvillib_multiblock.configuration.section.anvillib_multiblock.common.toml.title": "AnvilLib 多方块 通用 配置",
"anvillib_multiblock.configuration.title": "AnvilLib 多方块 配置",
"anvillib_multiblock.configuration.unformed_multiblock_check_interval": "未成型多方块检查周期",
"anvillib_multiblock.configuration.unformed_multiblock_check_interval.tooltip": "未成型的多方块的检查周期(以tick为单位)"
}
6 changes: 6 additions & 0 deletions module.network/build.gradle
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
dependencies {
if (System.getenv("NOT_DEV") == 'true') {
jarJar(api("dev.anvilcraft.lib:anvillib-config-neoforge-26.1:latest.release"))
} else {
jarJar(implementation project(":anvillib-config-neoforge-26.1"))
}
jarJar(implementation("at.yawk.lz4:lz4-java:1.10.1"))
}
3 changes: 3 additions & 0 deletions module.network/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
mod_id=anvillib_network
mod_name=AnvilLib-Network
mod_description=Network API with direction-specific packets and automatic registry

anvillib.needRunConfig=true
anvillib.needRunConfig.data=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"anvillib_network.configuration.default_compression_algorithm": "Default Compression Algorithm",
"anvillib_network.configuration.default_compression_algorithm.tooltip": "The default compression algorithm to use for network packets.\nDefault: LZ4\n- NONE No compression\n- LZ4 Extremely fast compression and decompression speed\n- GZIP Maximum compatibility\n",
"anvillib_network.configuration.max_decompressed_size": "Max Decompressed Size",
"anvillib_network.configuration.max_decompressed_size.tooltip": "The maximum decompressed size in bytes.\nDefault: 16777216\n",
"anvillib_network.configuration.section.anvillib.network.server.toml": "Anvillib Network Server Configuration",
"anvillib_network.configuration.section.anvillib.network.server.toml.title": "Anvillib Network Server Configuration",
"anvillib_network.configuration.threshold": "Threshold",
"anvillib_network.configuration.threshold.tooltip": "The threshold for compression in bytes.\nDefault: 128\n",
"anvillib_network.configuration.title": "Anvillib Network Configuration"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dev.anvilcraft.lib.v2.network;

import dev.anvilcraft.lib.v2.config.ConfigManager;
import net.neoforged.fml.common.Mod;

@Mod(AnvilLibNetwork.MOD_ID)
public class AnvilLibNetwork {
public static final String MOD_ID = "anvillib_network";
public static final AnvilLibNetworkServerConfig CONFIG = ConfigManager.register(
AnvilLibNetwork.MOD_ID,
AnvilLibNetworkServerConfig::new
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package dev.anvilcraft.lib.v2.network;

import dev.anvilcraft.lib.v2.config.BoundedDiscrete;
import dev.anvilcraft.lib.v2.config.Comment;
import dev.anvilcraft.lib.v2.config.Config;
import dev.anvilcraft.lib.v2.network.compression.CompressionAlgorithm;
import net.neoforged.fml.config.ModConfig;

@Config(name = "anvillib_network", group = "anvillib", type = ModConfig.Type.SERVER)
public class AnvilLibNetworkServerConfig {
@Comment(
"""
The default compression algorithm to use for network packets.
Default: LZ4
- NONE No compression
- LZ4 Extremely fast compression and decompression speed
- GZIP Maximum compatibility
"""
)
public volatile CompressionAlgorithm defaultCompressionAlgorithm = CompressionAlgorithm.LZ4;
@Comment(
"""
The threshold for compression in bytes.
Default: 128
"""
)
@BoundedDiscrete(min = 1, max = 1024)
public volatile int threshold = 128;
@Comment(
"""
The maximum decompressed size in bytes.
Default: 16777216
"""
)
@BoundedDiscrete(min = 1048576, max = 268435456)
public volatile int maxDecompressedSize = 16777216;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dev.anvilcraft.lib.v2.network.compression;

/**
* Compression algorithms supported by the AnvilLib byte payload wire format.
*/
public enum CompressionAlgorithm {
NONE(0),
LZ4(1),
GZIP(2);

private final int id;

CompressionAlgorithm(int id) {
this.id = id;
}

public int id() {
return this.id;
}

static CompressionAlgorithm byId(int id) {
for (CompressionAlgorithm algorithm : values()) {
if (algorithm.id == id) return algorithm;
}
throw new IllegalArgumentException("Unknown payload compression algorithm id: " + id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package dev.anvilcraft.lib.v2.network.compression;

import dev.anvilcraft.lib.v2.network.AnvilLibNetwork;
import io.netty.buffer.ByteBuf;
import net.jpountz.lz4.LZ4Factory;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
* Shared compression envelope for opaque network payloads.
*
* <p>The wire format is an algorithm byte followed by the raw bytes for
* {@link CompressionAlgorithm#NONE}, or a VarInt uncompressed length and the
* compressed bytes for all other algorithms.</p>
*/
public final class PayloadCompression {
public static final StreamCodec<ByteBuf, byte[]> STREAM_CODEC = ByteBufCodecs.BYTE_ARRAY.map(
PayloadCompression::decode,
PayloadCompression::encode
);

private PayloadCompression() {
}

public static CompressionAlgorithm algorithm() {
return AnvilLibNetwork.CONFIG.defaultCompressionAlgorithm;
}

public static int threshold() {
return AnvilLibNetwork.CONFIG.threshold;
}

public static int maxDecompressedSize() {
return AnvilLibNetwork.CONFIG.maxDecompressedSize;
}

public static byte[] encode(byte[] data) {
Objects.requireNonNull(data, "data");
int threshold = threshold();
int maxSize = maxDecompressedSize();
CompressionAlgorithm algo = algorithm();
if (data.length > maxSize) {
throw new IllegalArgumentException("Payload exceeds maximum uncompressed size: " + data.length);
}

CompressionAlgorithm selected = data.length >= threshold ? algo : CompressionAlgorithm.NONE;
if (selected == CompressionAlgorithm.NONE) return uncompressed(data);

byte[] compressed = compress(selected, data);
int headerLength = 1 + varIntSize(data.length);
if (compressed.length + headerLength >= data.length + 1) return uncompressed(data);

ByteArrayOutputStream output = new ByteArrayOutputStream(headerLength + compressed.length);
output.write(selected.id());
writeVarInt(output, data.length);
output.writeBytes(compressed);
return output.toByteArray();
}

public static byte[] decode(byte[] envelope) {
Objects.requireNonNull(envelope, "envelope");
int maxSize = maxDecompressedSize();
if (envelope.length == 0) throw new IllegalArgumentException("Compressed payload envelope is empty");

CompressionAlgorithm selected = CompressionAlgorithm.byId(Byte.toUnsignedInt(envelope[0]));
if (selected == CompressionAlgorithm.NONE) {
int size = envelope.length - 1;
if (size > maxSize) {
throw new IllegalArgumentException("Payload exceeds maximum uncompressed size: " + size);
}
return Arrays.copyOfRange(envelope, 1, envelope.length);
}

VarInt length = readVarInt(envelope, 1);
if (length.value() < 0 || length.value() > maxSize) {
throw new IllegalArgumentException("Invalid decompressed payload size: " + length.value());
}
if (length.nextOffset() >= envelope.length) {
throw new IllegalArgumentException("Compressed payload has no body");
}

byte[] compressed = Arrays.copyOfRange(envelope, length.nextOffset(), envelope.length);
byte[] data = decompress(selected, compressed, length.value());
if (data.length != length.value()) {
throw new IllegalArgumentException("Decompressed payload size mismatch: expected " + length.value() + ", got " + data.length);
}
return data;
}

private static byte[] uncompressed(byte[] data) {
byte[] envelope = new byte[data.length + 1];
envelope[0] = (byte) CompressionAlgorithm.NONE.id();
System.arraycopy(data, 0, envelope, 1, data.length);
return envelope;
}

private static byte[] compress(CompressionAlgorithm selected, byte[] data) {
return switch (selected) {
case LZ4 -> LZ4Factory.fastestInstance().fastCompressor().compress(data);
case GZIP -> gzipCompress(data);
case NONE -> throw new IllegalArgumentException("NONE does not compress data");
};
}

private static byte[] decompress(CompressionAlgorithm selected, byte[] data, int originalLength) {
return switch (selected) {
case LZ4 -> LZ4Factory.fastestInstance().safeDecompressor().decompress(data, originalLength);
case GZIP -> gzipDecompress(data, originalLength);
case NONE -> throw new IllegalArgumentException("NONE does not decompress data");
};
}

private static byte[] gzipCompress(byte[] data) {
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(output)) {
gzip.write(data);
}
return output.toByteArray();
} catch (IOException e) {
throw new IllegalStateException("Failed to GZIP-compress payload", e);
}
}

private static byte[] gzipDecompress(byte[] data, int originalLength) {
try (GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(data))) {
ByteArrayOutputStream output = new ByteArrayOutputStream(originalLength);
byte[] buffer = new byte[8192];
int total = 0;
int read;
while ((read = gzip.read(buffer)) >= 0) {
total += read;
if (total > originalLength) {
throw new IllegalArgumentException("GZIP payload exceeds declared decompressed size");
}
output.write(buffer, 0, read);
}
return output.toByteArray();
} catch (IOException e) {
throw new IllegalArgumentException("Failed to GZIP-decompress payload", e);
}
}

private static void writeVarInt(ByteArrayOutputStream output, int value) {
while ((value & -128) != 0) {
output.write(value & 127 | 128);
value >>>= 7;
}
output.write(value);
}

private static VarInt readVarInt(byte[] data, int offset) {
int value = 0;
int position = 0;
while (position < 32) {
if (offset >= data.length) throw new IllegalArgumentException("Truncated payload length VarInt");
byte current = data[offset++];
value |= (current & 127) << position;
if ((current & 128) == 0) return new VarInt(value, offset);
position += 7;
}
throw new IllegalArgumentException("Payload length VarInt is too large");
}

private static int varIntSize(int value) {
int size = 1;
while ((value & -128) != 0) {
size++;
value >>>= 7;
}
return size;
}

private record VarInt(int value, int nextOffset) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NullMarked
package dev.anvilcraft.lib.v2.network.compression;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.anvilcraft.lib.v2.network.data;

import dev.anvilcraft.lib.v2.network.AnvilLibNetwork;
import dev.anvilcraft.lib.v2.network.data.provider.ModLanguageProvider;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.data.event.GatherDataEvent;

@EventBusSubscriber(modid = AnvilLibNetwork.MOD_ID)
public class AnvilLibDatagen {
@SubscribeEvent
public static void gatherData(GatherDataEvent.Client event) {
DataGenerator generator = event.getGenerator();
PackOutput packOutput = generator.getPackOutput();
generator.addProvider(true, new ModLanguageProvider(packOutput));
}

@SubscribeEvent
public static void gatherData(GatherDataEvent.Server event) {
DataGenerator generator = event.getGenerator();
PackOutput packOutput = generator.getPackOutput();
generator.addProvider(false, new ModLanguageProvider(packOutput));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NullMarked
package dev.anvilcraft.lib.v2.network.data;

import org.jspecify.annotations.NullMarked;
Loading
Loading