Files
Genarrative/plugins/agc-godot-editor/native/gdextension/src/native.cpp
T
kdletters fdc48aa573
Project CI / AI game creator shell Rust shard 1/4 (push) Failing after 17s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 18s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 18s
Project CI / AI game creator shell Rust shard 2/4 (push) Failing after 18s
Project CI / Backend tests (push) Failing after 17s
Project CI / AI game creator shell Rust smoke (push) Failing after 19s
Project CI / Native shell tests (push) Failing after 18s
Project CI / AI game creator shell Rust crates (push) Failing after 18s
Project CI / Frontend tests (push) Failing after 6s
Project CI / AI game creator shell web tests (push) Failing after 11s
Project CI / Repository checks (push) Failing after 11s
将 Godot 原生引导迁移到官方 C++ 绑定
使用固定版本 godot-cpp 替换手写 C ABI 引导并保留执行协议
采用 MSVC 与 CMake 构建,校验依赖归档和源码缓存
修复动态卸载时的实例绑定与单例包装生命周期
补充构建缓存测试、实机验收结果及分发文档
2026-09-20 22:00:44 +08:00

160 lines
5.7 KiB
C++

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <godot_cpp/classes/gd_script.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/object.hpp>
#include <godot_cpp/godot.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <string>
#include "embedded_bridge.h"
namespace {
using namespace godot;
// Godot 值的析构必须发生在官方绑定终止之前,不能依赖 DLL 静态析构顺序。
struct BridgeState {
Ref<GDScript> script;
uint64_t node_id = 0;
};
BridgeState *bridge = nullptr;
bool plain_directory(const std::wstring &path) {
const DWORD attributes = GetFileAttributesW(path.c_str());
return attributes != INVALID_FILE_ATTRIBUTES &&
(attributes & FILE_ATTRIBUTE_DIRECTORY) &&
!(attributes & FILE_ATTRIBUTE_REPARSE_POINT);
}
bool append_cache_directory(std::wstring &path, const wchar_t *part) {
if (path.back() != L'\\')
path += L'\\';
path += part;
if (!CreateDirectoryW(path.c_str(), nullptr) &&
GetLastError() != ERROR_ALREADY_EXISTS)
return false;
return plain_directory(path);
}
String session_cache_path() {
const String project_root =
ProjectSettings::get_singleton()->globalize_path("res://");
const Char16String utf16 = project_root.utf16();
static_assert(sizeof(wchar_t) == sizeof(char16_t),
"Windows UTF-16 path required");
std::wstring path(reinterpret_cast<const wchar_t *>(utf16.get_data()),
utf16.length());
if (path.size() < 3 || path.size() > 32000 || path[1] != L':')
return {};
for (wchar_t &character : path)
if (character == L'/')
character = L'\\';
// 项目根及其所有祖先都须为普通本地目录,不经过链接或 junction。
for (size_t index = 3; index <= path.size(); ++index) {
if (index == path.size() || path[index] == L'\\') {
if (!plain_directory(path.substr(0, index)))
return {};
}
}
if (!append_cache_directory(path, L".godot") ||
!append_cache_directory(path, L"agc"))
return {};
path +=
L"\\editor-bridge-" + std::to_wstring(GetCurrentProcessId()) + L".json";
const DWORD attributes = GetFileAttributesW(path.c_str());
if (attributes != INVALID_FILE_ATTRIBUTES &&
(attributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)))
return {};
return String::utf16(reinterpret_cast<const char16_t *>(path.c_str()));
}
bool schedule_bridge() {
FILETIME creation, exit_time, kernel, user;
if (!GetProcessTimes(GetCurrentProcess(), &creation, &exit_time, &kernel,
&user))
return false;
ULARGE_INTEGER timestamp;
timestamp.LowPart = creation.dwLowDateTime;
timestamp.HighPart = creation.dwHighDateTime;
const String started = String::num_uint64(timestamp.QuadPart);
const String cache = session_cache_path();
if (cache.is_empty())
return false;
bridge->script.instantiate();
bridge->script->set_source_code(
String::utf8(reinterpret_cast<const char *>(AGC_EMBEDDED_BRIDGE)));
if (bridge->script->reload() != OK)
return false;
const Variant instance = bridge->script->call("new");
Node *node = Object::cast_to<Node>(static_cast<Object *>(instance));
if (!node)
return false;
bridge->node_id = node->get_instance_id();
node->call_deferred("bootstrap", AGC_BUILD_ID, started, cache);
return true;
}
void initialize_bridge(ModuleInitializationLevel level) {
if (level != MODULE_INITIALIZATION_LEVEL_EDITOR || bridge)
return;
bridge = new BridgeState;
if (!schedule_bridge()) {
UtilityFunctions::push_warning(
"AGC Godot editor bridge initialization failed");
delete bridge;
bridge = nullptr;
}
}
void deinitialize_bridge(ModuleInitializationLevel level) {
if (level != MODULE_INITIALIZATION_LEVEL_EDITOR)
return;
if (bridge) {
// bootstrap 失败或编辑器退出时 Node 可能先被销毁,不能保留悬空指针。
if (Object *node = ObjectDB::get_instance(bridge->node_id)) {
const GDExtensionObjectPtr owner = node->_owner;
node->call("native_deinitialize");
// GDScript 的 queue_free 晚于 DLL 卸载;只移除 C++ 包装的回调,不销毁引擎
// Node。
internal::gdextension_interface_object_free_instance_binding(
owner, internal::token);
}
// 脚本仍可能被当前 GDScript 调用栈引用。用 Variant 保活引擎对象,先释放 C++
// Ref, 再解除 DLL 内的包装回调;保活值在本回调返回前销毁。
const Variant script_lifetime = bridge->script;
const GDExtensionObjectPtr script_owner =
bridge->script.is_valid() ? bridge->script->_owner : nullptr;
bridge->script.unref();
if (script_owner)
internal::gdextension_interface_object_free_instance_binding(
script_owner, internal::token);
delete bridge;
bridge = nullptr;
}
// 晚加载扩展只收到 EDITOR 生命周期,官方单例包装清理原本位于 CORE 终止阶段。
ClassDB::deinitialize(GDEXTENSION_INITIALIZATION_CORE);
}
} // namespace
extern "C" GDExtensionBool GDE_EXPORT
agc_godot_editor_init(GDExtensionInterfaceGetProcAddress get_proc_address,
GDExtensionClassLibraryPtr library,
GDExtensionInitialization *initialization) {
godot::GDExtensionBinding::InitObject init(get_proc_address, library,
initialization);
init.register_initializer(initialize_bridge);
init.register_terminator(deinitialize_bridge);
init.set_minimum_library_initialization_level(
godot::MODULE_INITIALIZATION_LEVEL_EDITOR);
return init.init();
}