Repository navigation
[1.21.1] Share structure previews 共用结构渲染与只读界面预览 - #283
Conversation
- 迁移隔离结构沙盒、相机拾取与原生界面浮窗 - 提供自定义界面注册和附加渲染接口,解除对手册的依赖 - 适配当前游戏版本的渲染与透明度补丁 - 补充通用翻译、接口说明及只读作用域回归检查
🌿 Roseau API Breaking Change Report
|
Gugle2308
left a comment
There was a problem hiding this comment.
结论:需要修改(尚未合并;head 57052c2e,32 个文件 +3553/−0,CI build / build ✅)。核对后确认,4 个 mixin 目标与两处关键覆写的前提都成立,真正的阻塞项集中在「渲染状态没有还原、mixin 作用面过宽、可选依赖缺少版本防护」三处;另有约 220 行代码、214 条 lang 在本 PR 内没有任何生产调用方。
一、已核对的基础事实(先确认前提成立)
| 断言 | 核对结果 |
|---|---|
| 本 PR 仍是开放状态且尚未合并 | state: open / merged: false / draft: false,1 个提交 |
| 改动范围 | API changed_files=32 / +3553 / −0,与收到的 diff 文件数一致,覆盖完整 |
GuiGraphics.setColor 存在 |
1.21.1 mappings 190:192:void setColor(float,float,float,float) -> a,并且该类只有这一个 setColor 重载 ⇒ 只写方法名而不写描述符时不会匹配到多个目标,argsOnly=true, ordinal=3 对应第 4 个 float 形参(alpha)✅ |
Screen.renderTransparentBackground(GuiGraphics) 存在 |
377:378:void renderTransparentBackground(GuiGraphics) -> b ✅ |
RecipeBookComponent.isVisibleAccordingToBookData() 存在 |
179:179:boolean isVisibleAccordingToBookData() -> k ✅ |
RenderStateShard.setupRenderState() 存在且全层级只声明一次 |
mappings 全文件仅 1 处 ⇒ 基类 TAIL 注入确实覆盖每个 shard(没有被子类覆写绕过)✅ |
SandboxLevelChunk.setBlockState 真的构成覆写 |
ChunkAccess 里该方法无行号 = abstract,LevelChunk(223:297)/EmptyLevelChunk(33:33) 各自实现 ⇒ 子类覆写成立。这条是整个预览能画出内容的前提(覆写里才更新 filledBlocks),若当初是包私有方法就整块失效 |
StructureTemplate.placeInWorld(ServerLevelAccessor,…) 签名 |
204:350 存在;settings.setIgnoreEntities(true) 与手工重建实体路径自洽 |
| 语言文件 | en_us / zh_cn 各 107 条 tooltip.*,无单向缺键 ✅ |
| CI | head sha check-runs 共 1 项,conclusion: success |
二、必须修改(合并前)
1. StructurePreviewRenderer 泄漏渲染状态,作为库会在调用方同帧内留下脏状态
StructurePreviewRenderer.java(新增 441 行)里:
render(level, cameraRig)(2 参便捷重载):RenderSystem.clear(GlConst.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX)—— 在 GUI pass 里清整屏深度,且此时没有enableScissor限定区域,前面已绘制的 GUI/物品深度被抹掉,后续依赖深度的绘制会错位。render(..., int, int, Effects):setShaderFogColor(1,1,1,0)/setShaderFogStart(0)/setShaderFogEnd(1000)/setShaderFogShape(FogShape.SPHERE)与setShaderGameTime(...)设置后没有还原;finally只popMatrix + applyModelViewMatrix + restoreProjectionMatrix + Lighting.setupFor3DItems()。PreviewLightmapAtlas.bind()把预览图集绑到纹理单元 2,同样不还原原版 lightmap。
对「机器界面/手册共用」的库来说,这三处都会作用到调用方后续的 GUI 绘制与同帧其它模组。建议:把 clear 放进 enableScissor 的预览区域内(或直接去掉,改画自己的四边形);在 finally 里把 fog/着色器时间/纹理单元 2 恢复到 GUI 期望值(1.21.1 无 fog getter,需按「GUI 默认」常量回写并在 javadoc 写明),或在 README 明确要求调用方自行 reset。
2. StructureGuiRenderStateMixin 的作用面超出了它想做的事
@Inject(method = "setupRenderState", at = @At("TAIL"))
private void anvillib$blendPreview(CallbackInfo ci) {
if (StructureGuiSession.isRendering() && StructureGuiSession.opacity() < 1) {
RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc();
}
}setupRenderState 在 RenderStateShard 里只声明一次(已核),因此在预览作用域内每个 shard 执行完都会被强行打开混合——包括显式 disableBlend() 的 NO_TRANSPARENCY、LIGHTMAP/OVERLAY 等并不需要混合的 shard,而且打开后不关闭,会带到同帧后续绘制。建议改成不依赖全局 shard 的点位:在 StructureGuiPreview.render 里包住 screen.render(...) 手工 enableBlend/defaultBlendFunc/disableBlend(作用域与生命周期都在自己手里),或至少把条件收窄到「方块/物品的 texture+transparency 组合」。
3. SodiumSpriteBridge 只挡「没装」,挡不住「装的是别的版本」
compileOnly 'maven.modrinth:sodium:mc1.21.1-0.6.13-neoforge' + ModList.get().isLoaded("sodium") 后直接 SpriteUtil.INSTANCE.markSpriteActive(sprite):Sodium 是可选依赖,玩家可能装其它 0.6.x(甚至 Embeddium 生态的替代实现),SpriteUtil 的包路径/方法一旦对不上就是预览渲染路径上的 NoSuchMethodError / NoClassDefFoundError。建议 try { … } catch (Throwable ignored) {}(这是纯粹的动画纹理提示,失败无害),或先用 ModList.getModContainerById("sodium") 校验版本区间再调用。
三、建议(不阻塞合并)
- 未接线代码量偏大:
StructureBlockEntityDetails.describe(144 行)、StructureDetailsPager(74 行)、StructureGuiSession.tint(全仓仅被PreviewRegressionTest调用)在本 PR 内没有任何生产调用方,却同时带进 107×2 条nbt.field.*翻译。要么在机器/手册界面里接上(PR 描述声称「附加模型和后处理接口」已就绪,这部分像是留给下一步),要么先删并留 TODO,否则库使用者会以为这是可用能力。 SectionOffsetVertexConsumer只平移 3 参addVertex:putBulkData(...)(两个重载)与 11 参addVertex、addVertex(Matrix4f,…)都直通不偏移。当前renderLiquid走 3 参路径没问题,但类名与注释宣称「转换为世界坐标」,任何走 bulk 的调用都会画到分段原点。SandboxRenderLevel.getFilledBlocks()返回复用的MutableBlockPos:内部getBounds()用forEach+cur.set(...)没问题、StructurePreviewPicker.pick用了pos.immutable()✅,但这是 public 方法,任何collect(toList())的调用方会拿到 N 个同一实例。建议改为返回BlockPos的不可变流,或把方法降为包私有 + 提供forEachFilled。StructureGuiProviders.register第三方面冲突直接抛IllegalStateException(客户端初始化期)⇒ 两个模组登记同一方块 id 会变成启动崩溃。库侧更稳妥的是 log + first-wins(回归测试断言「不得覆盖已有提供器」可以保留,但抛异常与「不得覆盖」是两件事)。StructureBlockEntityDetails.appendArray每次取getAsByteArray()/getAsIntArray()/getAsLongArray(),lambda 里又取一次 ⇒ 逐元素重复分配数组(MAX_LINES4096 保护了上界,但可一行提出)。DelegatingServerLevelAccessor.getLevel()抛UnsupportedOperationException:ignoreEntities(true)下不该被调到,StructureGuiPreview.open也 catch 了RuntimeException转 false ✅;但StructureSandboxFactory.create自身没有兜底,建议在那里 catch 并给一条带结构尺寸/坐标的 warn,否则用户只能看到「预览打不开」。mixins.json是"required": true+defaultRequire: 1,4 个 client mixin 全打在原版类上 ⇒ 1.21.1 的补丁级重命名(如renderTransparentBackground改名)会让整个模块加载失败。库可以考虑对「纯视觉增强」的两个注入给require = 0。
四、测试与验证缺口
PR 自述「客户端画面尚未实测」,而本次新增的 previewTest(已正确挂在 check 上)只覆盖 provider 注册/重复保护、renderOnly 嵌套与异常作用域恢复、easeOut 单调性——完全不覆盖渲染。建议补以下场景:真实容器屏(箱子/熔炉/模组存储)在 100%/150% GUI scale 下的浮窗与 scissor 裁剪;F3 隐藏 GUI;开光影(Iris)与未装 Sodium / 装 Sodium 两条路径;浮窗拖出屏幕边缘;结构含方块实体与实体(含乘客)时的重建;以及同一帧内先渲染预览、再渲染原版 GUI 深度是否被污染。
五、验证边界
未执行 Gradle 构建、runClient 与游戏内实测;结论来自静态读码 + 1.21.1 官方 client_mappings/混淆 client.jar 的成员与抽象性核对(setColor、renderTransparentBackground、isVisibleAccordingToBookData、setupRenderState、ChunkAccess#setBlockState、StructureTemplate#placeInWorld)+ head CI check-runs + 语言文件键集合比对 + 32 文件 +3553 与 API 计数对账。渲染观感、光影/Sodium 叠加表现、浮窗手感必须由实机确认,我无法静态替代。
将结构渲染、隔离沙盒、相机拾取和只读 GUI 浮窗移入砧库,让手册与机器界面共用实现,避免机器功能依赖手册模组。
验证:
:anvillib-rendering-neoforge-1.21.1:build(含previewTest)与git diff --check通过;已对照当前版本源码核查 Mixin 目标。客户端画面尚未实测。