✨ 运行时 SDL2/SDL3 后端切换 - #147
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces runtime switching between the SDL2 and SDL3 backends. It adds shadow jar relocation to the SDL3 backend to prevent class conflicts, refactors the desktop launcher to dynamically load the selected backend via reflection, and adds a setting slider in the UI to allow users to switch backends. The review feedback highlights several critical improvements: correcting a potential compilation error by using Core.settings instead of settings, resetting the active SDL version system property to "sdl2" upon fallback, using try-with-resources to prevent resource leaks when reading the settings file, and filtering out static/final fields during reflection copying to avoid access exceptions.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| + if(settings.has("sdlversion")){ | ||
| + Object val = settings.get("sdlversion", null); | ||
| + if(val instanceof String){ | ||
| + settings.put("sdlversion", val.equals("sdl3") ? 1 : 0); | ||
| + } | ||
| + } |
There was a problem hiding this comment.
在 SettingsMenuDialog.java 中,直接使用 settings 变量可能会导致编译错误。在 Mindustry/Arc 框架中,通常需要通过 Core.settings 来访问全局设置。建议将其修改为 Core.settings 以确保代码能够正确编译。
+ if(Core.settings.has("sdlversion")){
+ Object val = Core.settings.get("sdlversion", null);
+ if(val instanceof String){
+ Core.settings.put("sdlversion", val.equals("sdl3") ? 1 : 0);
+ }
+ }
| + }catch(Throwable e){ | ||
| + Log.warn("SDL3 backend failed to load, falling back to SDL2", e); |
There was a problem hiding this comment.
| + DataInputStream stream; | ||
| + boolean compressed = bytes.length >= 2 && bytes[0] == (byte)0x78 | ||
| + && (bytes[1] == (byte)0x01 || bytes[1] == (byte)0x5E || bytes[1] == (byte)0x9c || bytes[1] == (byte)0xda); | ||
| + if(compressed){ | ||
| + stream = new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(bytes))); | ||
| + }else{ | ||
| + stream = new DataInputStream(new ByteArrayInputStream(bytes)); | ||
| + } | ||
| + | ||
| + int count = stream.readInt(); | ||
| + for(int i = 0; i < count; i++){ | ||
| + String key = stream.readUTF(); | ||
| + byte type = stream.readByte(); | ||
| + String value = null; | ||
| + switch(type){ | ||
| + case 0: stream.readBoolean(); break; | ||
| + case 1: value = stream.readInt() == 0 ? "sdl2" : "sdl3"; break; | ||
| + case 2: stream.readLong(); break; | ||
| + case 3: stream.readFloat(); break; | ||
| + case 4: value = stream.readUTF(); break; | ||
| + case 5: stream.skipBytes(stream.readInt()); break; | ||
| + } | ||
| + if(key.equals("sdlversion") && value != null){ | ||
| + return value; | ||
| + } | ||
| + } | ||
| + }catch(Exception ignored){} |
There was a problem hiding this comment.
在 readSdlFromSettingsFile 方法中,创建的 DataInputStream(以及可能包装的 InflaterInputStream)在使用后没有被关闭,这可能会导致文件句柄或内存资源泄漏。建议使用 Java 的 try-with-resources 语句来确保流被安全、自动地关闭。
+ byte[] bytes = sf.readBytes();
+ boolean compressed = bytes.length >= 2 && bytes[0] == (byte)0x78
+ && (bytes[1] == (byte)0x01 || bytes[1] == (byte)0x5E || bytes[1] == (byte)0x9c || bytes[1] == (byte)0xda);
+ try(DataInputStream stream = compressed ?
+ new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(bytes))) :
+ new DataInputStream(new ByteArrayInputStream(bytes))){
+
+ int count = stream.readInt();
+ for(int i = 0; i < count; i++){
+ String key = stream.readUTF();
+ byte type = stream.readByte();
+ String value = null;
+ switch(type){
+ case 0: stream.readBoolean(); break;
+ case 1: value = stream.readInt() == 0 ? "sdl2" : "sdl3"; break;
+ case 2: stream.readLong(); break;
+ case 3: stream.readFloat(); break;
+ case 4: value = stream.readUTF(); break;
+ case 5: stream.skipBytes(stream.readInt()); break;
+ }
+ if(key.equals("sdlversion") && value != null){
+ return value;
+ }
+ }
+ }
| + private static void copySdlConfig(SdlConfig src, Object dst, Class<?> cfgClass) throws Exception{ | ||
| + for(Field f : SdlConfig.class.getFields()){ | ||
| + try{ | ||
| + Field dstField = cfgClass.getField(f.getName()); | ||
| + dstField.set(dst, f.get(src)); | ||
| + }catch(NoSuchFieldException ignored){ | ||
| + } | ||
| + } |
There was a problem hiding this comment.
在 copySdlConfig 方法中,使用反射遍历并拷贝 SdlConfig 的所有字段时,可能会遇到 static 或 final 字段。在较新的 Java 版本(如 Java 17+)中,尝试通过反射修改 final 字段或访问不当的字段可能会抛出 IllegalAccessException。建议在拷贝时过滤掉 static 和 final 字段,以提高反射操作的安全性和兼容性。
+ private static void copySdlConfig(SdlConfig src, Object dst, Class<?> cfgClass) throws Exception{
+ for(Field f : SdlConfig.class.getFields()){
+ if(Modifier.isStatic(f.getModifiers()) || Modifier.isFinal(f.getModifiers())) continue;
+ try{
+ Field dstField = cfgClass.getField(f.getName());
+ if(!Modifier.isFinal(dstField.getModifiers())){
+ dstField.set(dst, f.get(src));
+ }
+ }catch(NoSuchFieldException ignored){
+ }
+ }
|
看起来可行。有些问题:
|
* SDL3 运行时动态加载,支持环境变量(MINDUSTRY_SDL)和设置文件切换 * readSdlFromSettingsFile 使用内置 Settings.loadValues * src/mindustryX/bundles/: SDL 后端选项文案 * Loader 模式下不显示 SDL 切换选项
Q1: SDL 应该初始化比较早,是否能用 Core.settings?不能。
Q2: Loader 版本不应该支持 SDL3,因为已经初始化好,无法变更。已处理。 |
|
(要不要顺便加强制 Wayland) |
|
以及 Android 支持(? |
SDL3 路径下 X UI 语言错误的调查症状SDL3 后端( 根因分析1. 初始化顺序差异SDL3 的 cfgClass.getField("appVersion").set(dst, Version.combined());
"\nMindustryX " + VarsX.version;这使得 SDL2 路径的 2. 各修复方案及其限制方案 A:
|
|
初始化过程不应该依赖VarsX.version,据我所知,就算是原版的version,这时候也没正确初始化。 |
SDL3 应用元数据里面有版本号来着 |
|
不填吧,影响比较小 |
摘要
为 MindustryX 添加运行时 SDL2/SDL3 后端动态切换能力,支持通过环境变量、启动参数或游戏内设置即时切换,无需重新编译。
动机
SDL3 后端提供更好的现代图形 API 支持和潜在性能提升。但 SDL2 仍是默认兼容性最好的后端。本 PR 允许用户在两种后端之间自由选择,并支持 SDL3 加载失败时自动回退到 SDL2。
架构设计
启动流程链
类名隔离方案
SDL3 后端源码位于
arc.backend.sdl包内(与 SDL2 相同),通过 Shadow plugin 在构建时 relocate 到arc.backend.sdl3避免类加载冲突:运行时通过
Class.forName("arc.backend.sdl3.SdlApplication")反射调用,不干扰 SDL2 的类加载。设置持久化
0=SDL2,1=SDL3)"sdl2"/"sdl3") 并迁移mindustry.sdlversion.active记录本次实际加载的后端,供 UI 展示变更详情
Arc:
backends/backend-sdl3/build.gradlecom.gradleup.shadow,relocate 到arc.backend.sdl3apiElements/runtimeElements指向 shadowJarWork:
desktop/src/mindustry/desktop/DesktopLauncher.javabuildConfig()复用 SdlConfig 构建逻辑startSdl3()反射加载 SDL3 后端getSdlVersion()链式回退检测readSdlFromSettingsFile()直接解析 settings.bincopySdlConfig()反射拷贝配置到 SDL3 的 SdlConfigSystem.setProperty("mindustry.sdlversion.active", ...)Work:
core/src/mindustry/ui/dialogs/SettingsMenuDialog.javasliderPref滑块 (0=SDL2, 1=SDL3)instanceof String检测旧格式并自动转换Work:
build.gradle:desktop添加backend-sdl3依赖Work:
core/assets/bundles/bundle*.propertiessetting.sdlversion.namesetting.sdlversion.descriptionsetting.sdlversion.sdl2setting.sdlversion.sdl3setting.sdlversion.active使用方法
环境变量(优先级最高):
游戏内设置:
设置 → 图形 → SDL 后端滑块切换,重启后生效。测试指南
MINDUSTRY_SDL=3启动,验证 SDL3 后端加载MINDUSTRY_SDL=3但 SDL3 加载失败应自动回退到 SDL2gradle desktop:dist server:dist android:assembleRelease均通过回退注意事项
"sdl2"/"sdl3") 自动迁移文件清单