feat(python): 在 app 进程里起 40250 脚本层(2P step 4,macOS)

移植 UserInterface/RunMainScript 到 platform/ScriptLib/PythonBoot:40250 把
CPythonLauncher 放在 Main() 的栈上,这里进程归 Godot,launcher 必须活过创建它的
那次调用,所以拆成 Start / RunMainScript / RunLine / Stop。37 个模块初始化函数里
只调已移植的 initpack(),system.py 因此仍停在第一个 import。

脚本报错能传回 GDScript,靠的是 platform/EterBase/Debug.cpp 实现 LogBox/LogBoxf:
ScriptLib 的 Traceback() 在 PyErr_Fetch 之后只剩这一条路,LastLogBoxMessage()
(platform/EterBase/LogBox.h)是 traceback 唯一幸存的地方。

GDScript 侧新增 Metin2PythonHost(python_host_node.{h,cpp})——唯一同时认识
godot-cpp 和 port 树的单元,PythonBoot 保持 godot-free。检查脚本
python_host_check.gd 分两段:解释器 + 标准库那段不需要 40250 的 pack,哪个平台都
能跑;system.py 那段需要 pack,没有就跳过。桌面入口 python_host_test.gd,真机入口
MT_TEST_MODE=python(导出模板不接受 --script)。

验证(macOS):标准库可 import、codec 注册表可用、sys.path 非空,system.py 停在和
ctest port.python_launcher 完全相同的 ImportError: No module named app;
ctest 27/27。Android/iOS 进程内未做,正式启动路径仍不起解释器(2V0)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 10:43:10 +09:00
co-authored by Claude Opus 5
parent e926759ff5
commit 0a1cdf3d3e
16 changed files with 638 additions and 76 deletions
@@ -0,0 +1,150 @@
// See PythonBoot.h. The sequence is 40250 UserInterface.cpp:241-352 (RunMainScript) and 419-434 (Main),
// split so the launcher can outlive the call that creates it.
#include "ScriptLib/StdAfx.h"
#include "ScriptLib/PythonLauncher.h"
#include "UserInterface/StdAfx.h" // initpack and the rest of the module initializer list
#include "PythonBoot.h"
#include "PythonHost.h"
#include "../EterBase/LogBox.h"
#include <memory>
namespace
{
// 40250 has this on Main()'s stack. CPythonLauncher is a CSingleton, so there is still only ever one.
std::unique_ptr<CPythonLauncher> g_launcher;
bool fail(std::string* error, const std::string& text)
{
if (error)
*error = text;
return false;
}
}
namespace PythonBoot
{
bool IsRunning()
{
return g_launcher != nullptr;
}
bool Start(const char* stdlib_path, std::string* error)
{
if (g_launcher)
return true;
const std::string stdlib = (stdlib_path && *stdlib_path) ? stdlib_path : PythonHost::DefaultStdLibPath();
if (stdlib.empty())
return fail(error, "no Python standard library path (Metin2Python.stdlib_path / $MT_PYTHON_STDLIB)");
// The flags have to be set before the interpreter starts, i.e. before the constructor below.
PythonHost::Configure();
g_launcher = std::make_unique<CPythonLauncher>(); // Py_Initialize
if (!g_launcher->Create())
{
Stop();
return fail(error, "CPythonLauncher::Create failed");
}
std::string stdlib_error;
if (!PythonHost::InstallStdLib(stdlib.c_str(), &stdlib_error))
{
Stop();
return fail(error, "InstallStdLib(" + stdlib + "): " + stdlib_error);
}
return true;
}
// 40250: RunMainScript
bool RunMainScript(const char* lpCmdLine, std::string* error)
{
if (!g_launcher)
return fail(error, "PythonBoot::Start has not run");
CPythonLauncher& pyLauncher = *g_launcher;
// The module initializers, in 40250's order. Everything after initpack (initdbg, initime, initgrp,
// initgrpImage, initgrpText, initwndMgr, initudp, initapp, ... initServerStateChecker) is a 2V slice:
// system.py stops at the first import of one of them, today `app`.
initpack();
ClearLogBoxMessage();
NANOBEGIN
// RegisterDebugFlag
{
std::string stRegisterDebugFlag;
#ifdef _DISTRIBUTE
stRegisterDebugFlag ="__DEBUG__ = 0";
#else
stRegisterDebugFlag ="__DEBUG__ = 1";
#endif
if (!pyLauncher.RunLine(stRegisterDebugFlag.c_str()))
{
TraceError("RegisterDebugFlag Error");
return fail(error, "RegisterDebugFlag Error: " + LastLogBoxMessage());
}
}
// RegisterCommandLine.
// PORT: the "-cs"/"-ncs" auto-login argument is passed through unchanged. Decoding it needs
// EterBase/Utils.cpp SplitLine and EterLib/Util.cpp base64_decode, which are declared but not ported
// yet; they belong to the login track (批次 2V1) and nothing in this host passes -cs today.
{
std::string stRegisterCmdLine;
std::string stCmdLine = lpCmdLine ? lpCmdLine : "";
stRegisterCmdLine ="__COMMAND_LINE__ = ";
stRegisterCmdLine+='"';
stRegisterCmdLine+=stCmdLine;
stRegisterCmdLine+='"';
const CHAR* c_szRegisterCmdLine=stRegisterCmdLine.c_str();
if (!pyLauncher.RunLine(c_szRegisterCmdLine))
{
TraceError("RegisterCommandLine Error");
return fail(error, "RegisterCommandLine Error: " + LastLogBoxMessage());
}
}
// PORT: 40250 also honours "--pause-before-create-window" here with system("pause") and the XTrap
// init check. Neither exists on this platform (no console to pause, no XTrap), so only the RunFile
// is kept.
{
if (!pyLauncher.RunFile("system.py"))
{
TraceError("RunMain Error");
return fail(error, "RunMain Error: " + LastLogBoxMessage());
}
}
NANOEND
return true;
}
bool RunLine(const char* source, std::string* error)
{
if (!g_launcher)
return fail(error, "PythonBoot::Start has not run");
ClearLogBoxMessage();
if (!g_launcher->RunLine(source ? source : ""))
return fail(error, LastLogBoxMessage());
return true;
}
void Stop()
{
if (!g_launcher)
return;
g_launcher->Clear(); // Main() calls Clear() explicitly before the launcher leaves scope
g_launcher.reset();
}
}