Files
mtgodot-poc/docs/PYTHON-EMBED-EVAL.md
T
2026-09-22 03:01:57 -07:00

106 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 内嵌 Python 运行 40250 UI 的评估
日期:2026-09-22。目的:判断能否不翻译 40250 的 Python UI,而是内嵌解释器原样运行
`Client/Eternexus/root/*.py``uiscript/`,只移植它调用的 C++ 模块。
## 40250 的实际情况
| 项 | 数据 |
| --- | --- |
| 解释器 | CPython 2.7.6 内嵌(`extern/include/Python-2.7``python27.lib` |
| 启动 | `ScriptLib/PythonLauncher.cpp``CPythonLauncher::Create` 注入 `TRUE`/`FALSE``__builtin__`,然后把 `system.py` 作为 `__main__` 运行(`__main__``__builtins__` 是模块,不是 dict);`system.py` 替换 `__import__`,从 pack 读源码并 `compile` |
| 脚本 | `root/*.py` 90 个(其中 74 个 `.py` 模块),`uiscript/uiscript/*.py` 94 个 |
| 语法 | 154 个文件在 Python 2.7.18 下全部能编译;Python 3 下有 56 个编译失败;约 292 行只在 Python 2 下成立(`print` 语句、`except X, e``has_key``iteritems` 等) |
| 语义风险 | 约 185 行在整数之间用 `/`Python 2 是整除,Python 3 是浮点);字符串是 CP949 字节串 |
| C++ 暴露的模块 | 35 个(`app` `net` `player` `chr` `chrmgr` `wndMgr` `grp` `item` `skill` 等),约 1160 个函数、约 994 个常量 |
| 需要移植的 C++ | `UserInterface/*Module.cpp` + `EterPythonLib`(窗口系统)+ `ScriptLib`,共约 2.44 万行 |
| 脚本实际引用 | 1627 个不同的 `模块.属性`(含常量) |
对比:如果逐个翻译成 GDScript,需要翻译 `root` 下的 3350 个函数,外加 uiscript 数据文件;而这些 C++
模块在任何方案里都要移植(它们就是 UI 和游戏逻辑的接口)。现在仓库里 `project/ui/*.gd` 有 60 个文件、
2.39 万行,当初依据的是错误版本的脚本(m2dev `assets/root`),无论选哪条路都要重做。
## 原型验证(已完成)
`.agents/skills/metin2-40250-parity-audit/scripts/py_embed_spike.py`,在本机 Python 2.7.18 下:
-`CPythonLauncher::Create` 注入 `TRUE`/`FALSE`,所有 C++ 模块用返回 0 的桩代替,`pack` 用目录代替;
- 原样运行 40250 的 `system.py`,再通过它自己的 `__pack_import` 导入全部 74 个 root 模块。
结果:**不改任何脚本**`system.py` 引导后 `prototype.py` 完整执行了 `RunApp()`:创建 MainStream、进入 logo
阶段、加载 `UIScript/PopupDialog.py``app.Loop`、Destroy。之后逐个导入全部 74 个 root 模块,66 个成功。
剩下 8 个失败都不是解释器或脚本的问题:
- `prototype`:已经由 `system.py` 执行过,再导入时 `RunApp` 结束触发 `SystemExit`,属正常;
- `game``interfacemodule``chr.MOTION_SKILL + 121` 这类常量需要真实整数(真实模块用 `PyModule_AddIntConstant` 提供);
- `introloading``consolemodule`locale 数据路径(`app.GetLocaleServiceName` 等返回值决定走哪个 locale 目录);
- `system`:只能作为 `__main__` 运行;`rootlibcythonizer``test_affect` 是开发用脚本,40250 运行时也不加载。
引导过程中脚本调用了 33 个 C++ 模块函数(`app.Create``wndMgr.Register``net.SetHandler` 等)。
结论:在 Python 2.7 上,40250 的脚本和导入机制可以原封不动运行;工作量集中在 C++ 模块本身。
## 方案对比
| 方案 | 脚本改动 | 与 40250 一致性 | 平台风险 | 结论 |
| --- | --- | --- | --- | --- |
| A. 内嵌 CPython 2.7.18(静态链接) | 无 | 最高:解释器语义、`/` 整除、字节串、C API`Py_InitModule``PyTuple_GetInteger`)都和 40250 相同,`*Module.cpp` 可以几乎原样复制 | 需要自己为 Android/iOS 交叉编译;2.7 已停止维护 | **推荐** |
| B. 内嵌 CPython 3.13+ | 56 个文件要转换,约 185 处 `/` 需逐一判断 | 中:字符串/整除/字典顺序语义不同,转换本身会引入偏差;C API 要改写 | 3.13 起官方支持 Android/iOS | A 失败时的备选 |
| C. pocketpy 等轻量解释器 | 需要大量改动 | 低:只支持 Python 3 子集 | 小 | 不推荐 |
| D. 翻译成 GDScript | 全部重写 3350 个函数 | 取决于翻译质量,难以机械核对 | 无 | 不推荐 |
### 方案 A 的平台问题
- macOS / Linux / Windows:都需要各自编译和验证;Windows 使用 `PCbuild`/`PC/pyconfig.h`,不走 Unix configure。
- Androidarm64NDK 27):CPython 2.7 能用 NDK 编译(python-for-android、Kivy 曾长期支持),但需要补丁:
关闭 `dlopen` 扩展、把用到的标准库 C 模块静态编进去、`pyconfig.h` 按 NDK 调整。
- iOS:同样静态链接(Kivy-ios 曾支持 2.7)。App Store 允许包内自带的解释型代码(不下载代码即可)。
- 标准库只需要极少一部分:脚本用到的是 `sys` `os` `marshal` `imp` `types` `copy` `math` `traceback`
`_weakref` `__builtin__` `locale``psyco` 有 try 保护。可以冻结为内置模块,不需要带整个 `Lib/`
- 2.7 停止维护的风险:解释器只运行包内自带的脚本;服务器数据先经过 C++ 解析再以普通值交给 Python,
不会执行远端代码。
## 采用方案 A 后的结构
```
extension/src/port/ScriptLib/PythonLauncher.cpp # CPythonLauncher,几乎原样
extension/src/port/EterPythonLib/PythonWindow*.cpp # 窗口树、焦点、命中、事件:逻辑照搬
extension/src/port/UserInterface/*Module.cpp # net/player/chr/... 模块,调用已移植的 CPython* 单例
extension/src/platform/ui/ # 适配层:CGraphicImageInstance/CGraphicTextInstance
# 的绘制 -> Godot RenderingServer/CanvasItem
extension/third_party/cpython-2.7.18/ # 静态库
```
Godot 这边只剩一个宿主 Control:把输入转发给 `CPythonWindowManager`,每帧调用它的 Update/Render。
`root/*.py``uiscript/` 直接从 pack 读取,和 40250 相同。port-map 中 `Client/root` 的函数在其
参考字节、资源清单和目标平台运行证据都成立后记为 `RUN_AS_IS`,不能记为 `N_A`python 层剩下约
1540 个 C++ 函数要移植。
## Android 验证(2026-09-22,已通过)
设备:一加 13PJZ110),Android API 36arm64-v8a。复现:`tools/py_embed_android/build-and-run.sh`
- **编译**CPython 2.7.18 官方源码,用 NDK 27.2 的 `aarch64-linux-android24-clang` 交叉 configure
`make libpython2.7.a` 一次通过。只需要一处改动:关掉 `HAVE_LANGINFO_H`,因为 bionic 从 API 26 才声明 `nl_langinfo`
另外 30 个 C 模块静态编进去,包括 `posix` `math` `time` `_struct` `operator` `itertools` `_collections`
`cStringIO` `cPickle` `_locale` `binascii` `datetime` `select` `fcntl` 等。全部编译通过,库大小 13 MB(含调试信息)。
- **链接**:测试程序 `tools/py_embed_android/main.c` 链接 `libpython2.7.a -lm -ldl`,没有任何未定义符号。
- **运行**`Py_NoSiteFlag`/`Py_IgnoreEnvironmentFlag`/`Py_FrozenFlag` 启动,标准库以不压缩的 zip(8 MB)
加入 `sys.path`,然后运行 `py_embed_spike.py`:**结果与 macOS 逐项相同**,66/74 个模块加载,失败的 8 个完全相同,
引导期调用的 33 个 C++ 函数也完全相同。
- **尚未验证**
- 这次是用 `adb shell` 运行的独立可执行文件,还没有在 APK 的 app 进程里跑过;
- 标准库是从文件系统读的,还没有走 PCK 或 `asset_io`
这两点属于集成工作,不涉及解释器本身能不能跑。
## 下一步
1. 把 CPython 2.7.18 放进 `extension/third_party/`,共用源码/静态模块清单,但为 macOS、Linux、Windows、
Android、iOS 分别生成或维护 `pyconfig.h`,静态链接进 `libmtgodot`
2. `pack` 模块走 `asset_io`,继续负责 40250 的 root/uiscript。标准库沿用已验证的 `python27.zip + sys.path`
方案:桌面端放在解释器可读的真实文件系统路径,移动端首次启动从应用资源复制到应用沙盒并校验 sha256;静态启用
`zipimport`。不要依赖 `system.py` 的简单 `name + '.py'` 钩子加载带 package/dotted import 的标准库;
3. 在 APK app 进程里跑同样的引导流程(C++ 桩模块),然后分别验证 iOS、Linux 和 Windows
4.`docs/PORT-PLAN.md` 的 2V0-2V3 切片依次替换桩模块和旧运行路径。