架构重构:差异全部下移到 Java,Python 只做路由
用户口径:差异放在 Java 层,Python 相当于路由。 下移到 Java(新增 cmd=setblocks): Python 只发 RLE([[stateId,count],...]),Java 用官方 API 完成 调色板组装 / 位打包 / 官方序列化 / 入 cache / 渲染。 ★ 意外收益:走 ClientboundLevelChunkPacketData(chunk).getReadBuffer() + replaceWithPacketData(...) 这条官方路径,两代签名完全一致 ⇒ 原先的 Storage.chunks 反射注入**整段删掉**。 新增 cmd=capacity:世界尺寸(sectionsCount/minSection/defaultBiome) 由运行时回答,Python 不再硬编码 24/-4(1.16 时代是 16/0)。 Python 侧删除(防回归,见 test_import_surface): render_bridge.py(旧桥,无世代概念) render._pack/_long_array/_encode_paletted/_encode_chunk/_varint anvil.encode_block_section/encode_chunk_payload/save_chunk_to_payload 合计 454→374 行(render.py)、300→177 行(anvil.py) 保留在 Python(都是「不随版本变」的知识): RLE 压缩(rle_encode/rle_decode,具名可测) 存档读取(region/NBT) 方块名→id 的路由(查询在 Java) NDJSON 协议 测试:62 项单元 + 6 项渲染联测全绿 新测试反映新架构:RLE 边界、运行时容量、世代分派、新路径渲染 test_anvil 里原有 8 项协议位打包测试已删(那已下移到 Java, 由两代后端各自的编译验证覆盖) 新增 scaffold/ARCHITECTURE.md(分层原则 + 加新世代步骤 + 防回归清单)
This commit is contained in:
@ -37,6 +37,8 @@ import net.minecraft.world.level.LightLayer;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.DataLayer;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
@ -180,6 +182,66 @@ public class McfoldRenderServer {
|
||||
emit(id, r);
|
||||
break;
|
||||
}
|
||||
case "setblocks": {
|
||||
// ★ 世代无关:Python 只做路由,方块写入、调色板组装、入 cache
|
||||
// 全部由 Java 用官方 API 完成 —— 版本差异不落回 Python。
|
||||
// 载荷:sections[].rle = [[stateId,count],...],按 (y,z,x) 线性序覆盖该 section 4096 格。
|
||||
// 优势:协议位打包(历代改过)不再由 Python 复制;入 cache 走官方
|
||||
// ClientboundLevelChunkPacketData(LevelChunk) → getReadBuffer(),
|
||||
// 签名两代一致,**无需反射**。
|
||||
int cx = req.get("cx").getAsInt(), cz = req.get("cz").getAsInt();
|
||||
LevelChunk chunk = new LevelChunk(level, new ChunkPos(cx, cz));
|
||||
LevelChunkSection[] sections = chunk.getSections();
|
||||
int placed = 0;
|
||||
long cells = 0;
|
||||
for (JsonElement se : req.getAsJsonArray("sections")) {
|
||||
JsonObject so = se.getAsJsonObject();
|
||||
int si = so.get("index").getAsInt();
|
||||
if (si < 0 || si >= sections.length) continue;
|
||||
LevelChunkSection sec = sections[si];
|
||||
int x = 0, y = 0, z = 0;
|
||||
for (JsonElement re : so.getAsJsonArray("rle")) {
|
||||
JsonArray pair = re.getAsJsonArray();
|
||||
int stateId = pair.get(0).getAsInt();
|
||||
int count = pair.get(1).getAsInt();
|
||||
BlockState st = stateId == 0 ? null : Block.stateById(stateId);
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (st != null) { sec.setBlockState(x, y, z, st); placed++; }
|
||||
cells++;
|
||||
if (++x == 16) { x = 0; if (++z == 16) { z = 0; y++; } }
|
||||
}
|
||||
}
|
||||
}
|
||||
// 光照(无光照数据则全亮,否则地形全黑)
|
||||
var light = level.getChunkSource().getLightEngine();
|
||||
for (int sy = level.getMinSection(); sy < level.getMaxSection(); sy++) {
|
||||
DataLayer sky = new DataLayer();
|
||||
for (int lx = 0; lx < 16; lx++)
|
||||
for (int ly = 0; ly < 16; ly++)
|
||||
for (int lz = 0; lz < 16; lz++) sky.set(lx, ly, lz, 15);
|
||||
light.queueSectionData(LightLayer.SKY, net.minecraft.core.SectionPos.of(cx, sy, cz), sky);
|
||||
light.queueSectionData(LightLayer.BLOCK, net.minecraft.core.SectionPos.of(cx, sy, cz), sky);
|
||||
}
|
||||
// ★ 官方序列化 → 官方入口入 cache(两代签名一致)
|
||||
var pkt = new net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData(chunk);
|
||||
level.getChunkSource().replaceWithPacketData(cx, cz,
|
||||
pkt.getReadBuffer(), pkt.getHeightmaps(),
|
||||
new java.util.function.Consumer<
|
||||
net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData.BlockEntityTagOutput>() {
|
||||
@Override public void accept(
|
||||
net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData.BlockEntityTagOutput t) { }
|
||||
});
|
||||
light.setLightEnabled(new ChunkPos(cx, cz), true);
|
||||
light.runLightUpdates();
|
||||
JsonObject r = new JsonObject();
|
||||
r.addProperty("ok", true);
|
||||
r.addProperty("placed", placed);
|
||||
r.addProperty("cells", cells);
|
||||
r.addProperty("sections", sections.length);
|
||||
r.addProperty("loadedChunks", level.getChunkSource().getLoadedChunksCount());
|
||||
emit(id, r);
|
||||
break;
|
||||
}
|
||||
case "camera": {
|
||||
double x = req.get("x").getAsDouble(), y = req.get("y").getAsDouble(), z = req.get("z").getAsDouble();
|
||||
float yaw = req.has("yaw") ? req.get("yaw").getAsFloat() : 0f;
|
||||
@ -283,6 +345,28 @@ public class McfoldRenderServer {
|
||||
emit(id, r);
|
||||
break;
|
||||
}
|
||||
case "capacity": {
|
||||
// ★ 世界尺寸由运行时提供 —— Python 不硬编码版本相关常量。
|
||||
// 这些方法都在 LevelHeightAccessor 上(Level 实现),两代 API 一致。
|
||||
JsonObject r = new JsonObject();
|
||||
r.addProperty("ok", true);
|
||||
r.addProperty("sectionsCount", level.getSectionsCount());
|
||||
r.addProperty("minSection", level.getMinSection());
|
||||
r.addProperty("maxSection", level.getMaxSection());
|
||||
r.addProperty("minBuildHeight", level.getMinBuildHeight());
|
||||
r.addProperty("maxBuildHeight", level.getMaxBuildHeight());
|
||||
r.addProperty("height", level.getHeight());
|
||||
// 生物群系默认 id(不硬编码 1)
|
||||
try {
|
||||
var bio = mc.level.registryAccess().registryOrThrow(Registries.BIOME);
|
||||
var key = bio.getKey(bio.byId(1));
|
||||
r.addProperty("defaultBiome", key != null ? key.toString() : "minecraft:plains");
|
||||
} catch (Throwable t) {
|
||||
r.addProperty("defaultBiome", "minecraft:plains");
|
||||
}
|
||||
emit(id, r);
|
||||
break;
|
||||
}
|
||||
case "registry": {
|
||||
JsonObject r = new JsonObject();
|
||||
r.addProperty("ok", true);
|
||||
@ -317,8 +401,12 @@ public class McfoldRenderServer {
|
||||
// 而这个名字→id 的权威映射只有客户端注册表有。
|
||||
// 入参: {"cmd":"resolve","blocks":["minecraft:oak_log[axis=y]", ...]}
|
||||
JsonArray arr = req.getAsJsonArray("blocks");
|
||||
// ★ parseForBlock 要 HolderLookup(不是 Registry)
|
||||
var reg = mc.level.registryAccess().lookupOrThrow(Registries.BLOCK);
|
||||
// ★ parseForBlock 要 HolderLookup(不是 Registry)。
|
||||
// 用**静态注册表**的 asLookup():它是方块名→id 的权威来源
|
||||
// (mod 方块也注册在这里),且两代 API 一致。
|
||||
// 不用 mc.level.registryAccess() —— 1.21 上它来自 cookie,
|
||||
// 只含 worldgen 那批注册表,查 block 会报 "Registry not found"(实测)。
|
||||
var reg = BuiltInRegistries.BLOCK.asLookup();
|
||||
JsonArray out = new JsonArray();
|
||||
int unknown = 0;
|
||||
for (JsonElement el : arr) {
|
||||
|
||||
Reference in New Issue
Block a user