feat(scripting): support managed script component api

This commit is contained in:
2026-03-28 00:43:13 +08:00
parent 6bde199393
commit e522bad582
8 changed files with 349 additions and 37 deletions

View File

@@ -53,6 +53,8 @@ public:
bool HasManagedInstance(const ScriptComponent* component) const;
size_t GetManagedInstanceCount() const { return m_instances.size(); }
MonoObject* GetManagedInstanceObject(const ScriptComponent* component) const;
MonoObject* CreateManagedComponentWrapper(MonoClass* componentClass, uint64_t gameObjectUUID);
bool TryGetFieldValue(
const ScriptComponent* component,

View File

@@ -9,6 +9,7 @@
#include "Debug/Logger.h"
#include "Scene/Scene.h"
#include "Scripting/ScriptComponent.h"
#include "Scripting/ScriptEngine.h"
#include <mono/jit/jit.h>
#include <mono/metadata/appdomain.h>
@@ -38,6 +39,7 @@ struct MonoRootState {
enum class ManagedComponentKind {
Unknown,
Script,
Transform,
Camera,
Light,
@@ -45,6 +47,14 @@ enum class ManagedComponentKind {
MeshRenderer,
};
struct ManagedComponentTypeInfo {
ManagedComponentKind kind = ManagedComponentKind::Unknown;
MonoClass* monoClass = nullptr;
std::string assemblyName;
std::string namespaceName;
std::string className;
};
MonoRootState& GetMonoRootState() {
static MonoRootState state;
return state;
@@ -94,40 +104,62 @@ std::string MonoStringToUtf8(MonoString* stringObject) {
return result;
}
ManagedComponentKind ResolveManagedComponentKind(MonoReflectionType* reflectionType) {
MonoScriptRuntime* GetActiveMonoScriptRuntime() {
return dynamic_cast<MonoScriptRuntime*>(ScriptEngine::Get().GetRuntime());
}
ManagedComponentTypeInfo ResolveManagedComponentTypeInfo(MonoReflectionType* reflectionType) {
ManagedComponentTypeInfo typeInfo;
if (!reflectionType) {
return ManagedComponentKind::Unknown;
return typeInfo;
}
MonoType* monoType = mono_reflection_type_get_type(reflectionType);
if (!monoType) {
return ManagedComponentKind::Unknown;
return typeInfo;
}
MonoClass* monoClass = mono_class_from_mono_type(monoType);
if (!monoClass) {
return ManagedComponentKind::Unknown;
return typeInfo;
}
const std::string namespaceName = SafeString(mono_class_get_namespace(monoClass));
const std::string className = SafeString(mono_class_get_name(monoClass));
if (namespaceName == "XCEngine" && className == "Transform") {
return ManagedComponentKind::Transform;
typeInfo.monoClass = monoClass;
typeInfo.namespaceName = SafeString(mono_class_get_namespace(monoClass));
typeInfo.className = SafeString(mono_class_get_name(monoClass));
if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "Transform") {
typeInfo.kind = ManagedComponentKind::Transform;
return typeInfo;
}
if (namespaceName == "XCEngine" && className == "Camera") {
return ManagedComponentKind::Camera;
if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "Camera") {
typeInfo.kind = ManagedComponentKind::Camera;
return typeInfo;
}
if (namespaceName == "XCEngine" && className == "Light") {
return ManagedComponentKind::Light;
if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "Light") {
typeInfo.kind = ManagedComponentKind::Light;
return typeInfo;
}
if (namespaceName == "XCEngine" && className == "MeshFilter") {
return ManagedComponentKind::MeshFilter;
if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "MeshFilter") {
typeInfo.kind = ManagedComponentKind::MeshFilter;
return typeInfo;
}
if (namespaceName == "XCEngine" && className == "MeshRenderer") {
return ManagedComponentKind::MeshRenderer;
if (typeInfo.namespaceName == "XCEngine" && typeInfo.className == "MeshRenderer") {
typeInfo.kind = ManagedComponentKind::MeshRenderer;
return typeInfo;
}
return ManagedComponentKind::Unknown;
MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime();
if (runtime
&& runtime->IsClassAvailable(
runtime->GetSettings().appAssemblyName,
typeInfo.namespaceName,
typeInfo.className)) {
typeInfo.kind = ManagedComponentKind::Script;
typeInfo.assemblyName = runtime->GetSettings().appAssemblyName;
}
return typeInfo;
}
Components::GameObject* FindGameObjectByUUIDRecursive(Components::GameObject* gameObject, uint64_t uuid) {
@@ -214,6 +246,7 @@ bool HasNativeComponent(Components::GameObject* gameObject, ManagedComponentKind
return gameObject->GetComponent<Components::MeshFilterComponent>() != nullptr;
case ManagedComponentKind::MeshRenderer:
return gameObject->GetComponent<Components::MeshRendererComponent>() != nullptr;
case ManagedComponentKind::Script:
case ManagedComponentKind::Unknown:
return false;
}
@@ -245,6 +278,7 @@ Components::Component* AddOrGetNativeComponent(Components::GameObject* gameObjec
return gameObject->GetComponent<Components::MeshRendererComponent>()
? static_cast<Components::Component*>(gameObject->GetComponent<Components::MeshRendererComponent>())
: static_cast<Components::Component*>(gameObject->AddComponent<Components::MeshRendererComponent>());
case ManagedComponentKind::Script:
case ManagedComponentKind::Unknown:
return nullptr;
}
@@ -252,6 +286,28 @@ Components::Component* AddOrGetNativeComponent(Components::GameObject* gameObjec
return nullptr;
}
ScriptComponent* FindMatchingScriptComponent(
Components::GameObject* gameObject,
const ManagedComponentTypeInfo& typeInfo) {
if (!gameObject || typeInfo.kind != ManagedComponentKind::Script) {
return nullptr;
}
for (ScriptComponent* component : gameObject->GetComponents<ScriptComponent>()) {
if (!component || !component->HasScriptClass()) {
continue;
}
if (component->GetAssemblyName() == typeInfo.assemblyName
&& component->GetNamespaceName() == typeInfo.namespaceName
&& component->GetClassName() == typeInfo.className) {
return component;
}
}
return nullptr;
}
Components::CameraComponent* FindCameraComponent(uint64_t gameObjectUUID) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
return gameObject ? gameObject->GetComponent<Components::CameraComponent>() : nullptr;
@@ -337,21 +393,69 @@ void InternalCall_GameObject_SetActive(uint64_t gameObjectUUID, mono_bool active
mono_bool InternalCall_GameObject_HasComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
return HasNativeComponent(gameObject, ResolveManagedComponentKind(componentType)) ? 1 : 0;
}
uint64_t InternalCall_GameObject_GetComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
if (!HasNativeComponent(gameObject, ResolveManagedComponentKind(componentType))) {
return 0;
const ManagedComponentTypeInfo typeInfo = ResolveManagedComponentTypeInfo(componentType);
if (typeInfo.kind == ManagedComponentKind::Script) {
return FindMatchingScriptComponent(gameObject, typeInfo) ? 1 : 0;
}
return gameObjectUUID;
return HasNativeComponent(gameObject, typeInfo.kind) ? 1 : 0;
}
uint64_t InternalCall_GameObject_AddComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) {
MonoObject* InternalCall_GameObject_GetComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
return AddOrGetNativeComponent(gameObject, ResolveManagedComponentKind(componentType)) ? gameObjectUUID : 0;
if (!gameObject) {
return nullptr;
}
MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime();
if (!runtime) {
return nullptr;
}
const ManagedComponentTypeInfo typeInfo = ResolveManagedComponentTypeInfo(componentType);
if (typeInfo.kind == ManagedComponentKind::Script) {
ScriptComponent* component = FindMatchingScriptComponent(gameObject, typeInfo);
if (!component) {
return nullptr;
}
if (!runtime->HasManagedInstance(component)) {
ScriptEngine::Get().OnScriptComponentEnabled(component);
}
return runtime->GetManagedInstanceObject(component);
}
if (!HasNativeComponent(gameObject, typeInfo.kind) || !typeInfo.monoClass) {
return nullptr;
}
return runtime->CreateManagedComponentWrapper(typeInfo.monoClass, gameObjectUUID);
}
MonoObject* InternalCall_GameObject_AddComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
if (!gameObject) {
return nullptr;
}
MonoScriptRuntime* runtime = GetActiveMonoScriptRuntime();
if (!runtime) {
return nullptr;
}
const ManagedComponentTypeInfo typeInfo = ResolveManagedComponentTypeInfo(componentType);
if (typeInfo.kind == ManagedComponentKind::Script) {
ScriptComponent* component = gameObject->AddComponent<ScriptComponent>();
component->SetScriptClass(typeInfo.assemblyName, typeInfo.namespaceName, typeInfo.className);
return runtime->GetManagedInstanceObject(component);
}
if (!AddOrGetNativeComponent(gameObject, typeInfo.kind) || !typeInfo.monoClass) {
return nullptr;
}
return runtime->CreateManagedComponentWrapper(typeInfo.monoClass, gameObjectUUID);
}
uint64_t InternalCall_GameObject_Find(MonoString* name) {
@@ -1199,6 +1303,11 @@ bool MonoScriptRuntime::HasManagedInstance(const ScriptComponent* component) con
return instanceData != nullptr && GetManagedObject(*instanceData) != nullptr;
}
MonoObject* MonoScriptRuntime::GetManagedInstanceObject(const ScriptComponent* component) const {
const InstanceData* instanceData = FindInstance(component);
return instanceData ? GetManagedObject(*instanceData) : nullptr;
}
bool MonoScriptRuntime::TryGetFieldValue(
const ScriptComponent* component,
const std::string& fieldName,
@@ -1766,6 +1875,37 @@ bool MonoScriptRuntime::ApplyStoredFields(
return true;
}
MonoObject* MonoScriptRuntime::CreateManagedComponentWrapper(MonoClass* componentClass, uint64_t gameObjectUUID) {
if (!m_initialized || !componentClass || gameObjectUUID == 0) {
return nullptr;
}
SetCurrentDomain();
MonoMethod* constructor = mono_class_get_method_from_name(componentClass, ".ctor", 1);
if (!constructor) {
return nullptr;
}
MonoObject* managedObject = mono_object_new(m_appDomain, componentClass);
if (!managedObject) {
return nullptr;
}
void* args[1];
uint64_t uuidArgument = gameObjectUUID;
args[0] = &uuidArgument;
MonoObject* exception = nullptr;
mono_runtime_invoke(constructor, managedObject, args, &exception);
if (exception) {
RecordException(exception);
return nullptr;
}
return managedObject;
}
MonoObject* MonoScriptRuntime::CreateManagedGameObject(uint64_t gameObjectUUID) {
if (gameObjectUUID == 0 || !m_gameObjectClass || !m_gameObjectConstructor) {
return nullptr;

View File

@@ -37,7 +37,8 @@ void ScriptEngine::OnRuntimeStart(Components::Scene* scene) {
CollectScriptComponents(root);
}
for (const ScriptInstanceKey& key : m_scriptOrder) {
const std::vector<ScriptInstanceKey> startupKeys = m_scriptOrder;
for (const ScriptInstanceKey& key : startupKeys) {
auto it = m_scriptStates.find(key);
if (it == m_scriptStates.end()) {
continue;
@@ -87,7 +88,8 @@ void ScriptEngine::OnFixedUpdate(float fixedDeltaTime) {
return;
}
for (const ScriptInstanceKey& key : m_scriptOrder) {
const std::vector<ScriptInstanceKey> updateKeys = m_scriptOrder;
for (const ScriptInstanceKey& key : updateKeys) {
auto it = m_scriptStates.find(key);
if (it == m_scriptStates.end()) {
continue;
@@ -107,7 +109,8 @@ void ScriptEngine::OnUpdate(float deltaTime) {
return;
}
for (const ScriptInstanceKey& key : m_scriptOrder) {
const std::vector<ScriptInstanceKey> updateKeys = m_scriptOrder;
for (const ScriptInstanceKey& key : updateKeys) {
auto it = m_scriptStates.find(key);
if (it == m_scriptStates.end()) {
continue;
@@ -133,7 +136,8 @@ void ScriptEngine::OnLateUpdate(float deltaTime) {
return;
}
for (const ScriptInstanceKey& key : m_scriptOrder) {
const std::vector<ScriptInstanceKey> updateKeys = m_scriptOrder;
for (const ScriptInstanceKey& key : updateKeys) {
auto it = m_scriptStates.find(key);
if (it == m_scriptStates.end()) {
continue;