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-cpp 替换手写 C ABI 引导并保留执行协议 采用 MSVC 与 CMake 构建,校验依赖归档和源码缓存 修复动态卸载时的实例绑定与单例包装生命周期 补充构建缓存测试、实机验收结果及分发文档
160 lines
5.7 KiB
C++
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();
|
|
}
|