Add concrete component script field support
This commit is contained in:
@@ -111,6 +111,7 @@ private:
|
||||
struct FieldMetadata {
|
||||
ScriptFieldType type = ScriptFieldType::None;
|
||||
MonoClassField* field = nullptr;
|
||||
MonoClass* componentClass = nullptr;
|
||||
bool isEnum = false;
|
||||
int32_t enumUnderlyingType = 0;
|
||||
};
|
||||
@@ -154,6 +155,8 @@ private:
|
||||
bool DiscoverScriptClasses();
|
||||
void DiscoverScriptClassesInImage(const std::string& assemblyName, MonoImage* image);
|
||||
bool IsMonoBehaviourSubclass(MonoClass* monoClass) const;
|
||||
bool IsSupportedComponentFieldClass(MonoClass* monoClass) const;
|
||||
bool IsScriptComponentFieldClass(MonoClass* monoClass) const;
|
||||
|
||||
FieldMetadata BuildFieldMetadata(MonoClassField* field) const;
|
||||
|
||||
@@ -174,6 +177,7 @@ private:
|
||||
bool ApplyStoredFields(const ScriptRuntimeContext& context, const ClassMetadata& metadata, MonoObject* instance);
|
||||
MonoObject* CreateManagedGameObject(uint64_t gameObjectUUID);
|
||||
bool HasSerializeFieldAttribute(MonoClassField* field) const;
|
||||
bool TryExtractComponentReference(MonoObject* managedObject, ComponentReference& outReference) const;
|
||||
bool TryExtractGameObjectReference(MonoObject* managedObject, GameObjectReference& outReference) const;
|
||||
bool TrySetFieldValue(MonoObject* instance, const FieldMetadata& fieldMetadata, const ScriptFieldValue& value);
|
||||
bool TryReadFieldValue(MonoObject* instance, const FieldMetadata& fieldMetadata, ScriptFieldValue& outValue) const;
|
||||
|
||||
@@ -23,7 +23,8 @@ enum class ScriptFieldType {
|
||||
Vector2,
|
||||
Vector3,
|
||||
Vector4,
|
||||
GameObject
|
||||
GameObject,
|
||||
Component
|
||||
};
|
||||
|
||||
struct GameObjectReference {
|
||||
@@ -38,6 +39,20 @@ struct GameObjectReference {
|
||||
}
|
||||
};
|
||||
|
||||
struct ComponentReference {
|
||||
uint64_t gameObjectUUID = 0;
|
||||
uint64_t scriptComponentUUID = 0;
|
||||
|
||||
bool operator==(const ComponentReference& other) const {
|
||||
return gameObjectUUID == other.gameObjectUUID
|
||||
&& scriptComponentUUID == other.scriptComponentUUID;
|
||||
}
|
||||
|
||||
bool operator!=(const ComponentReference& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
struct ScriptFieldMetadata {
|
||||
std::string name;
|
||||
ScriptFieldType type = ScriptFieldType::None;
|
||||
@@ -99,7 +114,8 @@ using ScriptFieldValue = std::variant<
|
||||
Math::Vector2,
|
||||
Math::Vector3,
|
||||
Math::Vector4,
|
||||
GameObjectReference>;
|
||||
GameObjectReference,
|
||||
ComponentReference>;
|
||||
|
||||
struct ScriptFieldDefaultValue {
|
||||
std::string fieldName;
|
||||
|
||||
@@ -70,6 +70,11 @@ struct ScriptFieldTypeResolver<GameObjectReference> {
|
||||
static constexpr ScriptFieldType value = ScriptFieldType::GameObject;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ScriptFieldTypeResolver<ComponentReference> {
|
||||
static constexpr ScriptFieldType value = ScriptFieldType::Component;
|
||||
};
|
||||
|
||||
class ScriptFieldStorage {
|
||||
public:
|
||||
template<typename T>
|
||||
|
||||
@@ -92,6 +92,15 @@ std::string SafeString(const char* value) {
|
||||
return value ? std::string(value) : std::string();
|
||||
}
|
||||
|
||||
bool IsMonoClassOrSubclass(MonoClass* monoClass, MonoClass* potentialBaseClass) {
|
||||
if (!monoClass || !potentialBaseClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return monoClass == potentialBaseClass
|
||||
|| mono_class_is_subclass_of(monoClass, potentialBaseClass, 0) != 0;
|
||||
}
|
||||
|
||||
std::string MonoStringToUtf8(MonoString* stringObject) {
|
||||
if (!stringObject) {
|
||||
return std::string();
|
||||
@@ -2316,6 +2325,40 @@ bool MonoScriptRuntime::IsMonoBehaviourSubclass(MonoClass* monoClass) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::IsSupportedComponentFieldClass(MonoClass* monoClass) const {
|
||||
if (!IsMonoClassOrSubclass(monoClass, m_componentClass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (monoClass == m_componentClass || monoClass == m_behaviourClass || monoClass == m_monoBehaviourClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsScriptComponentFieldClass(monoClass)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string namespaceName = SafeString(mono_class_get_namespace(monoClass));
|
||||
const std::string className = SafeString(mono_class_get_name(monoClass));
|
||||
if (namespaceName != m_settings.baseNamespace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return className == "Transform"
|
||||
|| className == "Camera"
|
||||
|| className == "Light"
|
||||
|| className == "MeshFilter"
|
||||
|| className == "MeshRenderer";
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::IsScriptComponentFieldClass(MonoClass* monoClass) const {
|
||||
if (!monoClass || monoClass == m_monoBehaviourClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsMonoClassOrSubclass(monoClass, m_monoBehaviourClass);
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::HasSerializeFieldAttribute(MonoClassField* field) const {
|
||||
if (!field || !m_serializeFieldAttributeClass) {
|
||||
return false;
|
||||
@@ -2377,6 +2420,12 @@ MonoScriptRuntime::FieldMetadata MonoScriptRuntime::BuildFieldMetadata(MonoClass
|
||||
const std::string className = SafeString(mono_class_get_name(referenceClass));
|
||||
if (namespaceName == m_settings.baseNamespace && className == "GameObject") {
|
||||
metadata.type = ScriptFieldType::GameObject;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
if (IsSupportedComponentFieldClass(referenceClass)) {
|
||||
metadata.type = ScriptFieldType::Component;
|
||||
metadata.componentClass = referenceClass;
|
||||
}
|
||||
|
||||
return metadata;
|
||||
@@ -2593,6 +2642,40 @@ MonoObject* MonoScriptRuntime::CreateManagedGameObject(uint64_t gameObjectUUID)
|
||||
return managedObject;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::TryExtractComponentReference(
|
||||
MonoObject* managedObject,
|
||||
ComponentReference& outReference) const {
|
||||
outReference = ComponentReference{};
|
||||
if (!managedObject) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_componentClass || !m_gameObjectUUIDField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetCurrentDomain();
|
||||
|
||||
MonoClass* monoClass = mono_object_get_class(managedObject);
|
||||
if (!IsMonoClassOrSubclass(monoClass, m_componentClass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t gameObjectUUID = 0;
|
||||
mono_field_get_value(managedObject, m_gameObjectUUIDField, &gameObjectUUID);
|
||||
if (gameObjectUUID == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t scriptComponentUUID = 0;
|
||||
if (m_scriptComponentUUIDField && IsMonoClassOrSubclass(monoClass, m_behaviourClass)) {
|
||||
mono_field_get_value(managedObject, m_scriptComponentUUIDField, &scriptComponentUUID);
|
||||
}
|
||||
|
||||
outReference = ComponentReference{gameObjectUUID, scriptComponentUUID};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::DestroyManagedObject(MonoObject* managedObject) {
|
||||
if (!m_initialized || !managedObject) {
|
||||
return false;
|
||||
@@ -2811,6 +2894,85 @@ bool MonoScriptRuntime::TrySetFieldValue(
|
||||
mono_field_set_value(instance, fieldMetadata.field, managedGameObject);
|
||||
return true;
|
||||
}
|
||||
case ScriptFieldType::Component: {
|
||||
const ComponentReference reference = std::get<ComponentReference>(value);
|
||||
if (reference.gameObjectUUID == 0 && reference.scriptComponentUUID == 0) {
|
||||
mono_field_set_value(instance, fieldMetadata.field, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!fieldMetadata.componentClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoObject* managedComponent = nullptr;
|
||||
if (IsScriptComponentFieldClass(fieldMetadata.componentClass)) {
|
||||
if (reference.gameObjectUUID == 0 || reference.scriptComponentUUID == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScriptComponent* component = FindScriptComponentByUUID(reference.scriptComponentUUID);
|
||||
if (!component
|
||||
|| !component->GetGameObject()
|
||||
|| component->GetGameObject()->GetUUID() != reference.gameObjectUUID
|
||||
|| component->GetAssemblyName() != m_settings.appAssemblyName
|
||||
|| component->GetNamespaceName() != SafeString(mono_class_get_namespace(fieldMetadata.componentClass))
|
||||
|| component->GetClassName() != SafeString(mono_class_get_name(fieldMetadata.componentClass))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HasManagedInstance(component)) {
|
||||
ScriptEngine::Get().OnScriptComponentEnabled(component);
|
||||
}
|
||||
|
||||
managedComponent = GetManagedInstanceObject(component);
|
||||
if (!managedComponent) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (reference.gameObjectUUID == 0 || reference.scriptComponentUUID != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Components::GameObject* targetObject = FindGameObjectByUUID(reference.gameObjectUUID);
|
||||
if (!targetObject) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string namespaceName = SafeString(mono_class_get_namespace(fieldMetadata.componentClass));
|
||||
const std::string className = SafeString(mono_class_get_name(fieldMetadata.componentClass));
|
||||
if (namespaceName != m_settings.baseNamespace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasComponent = false;
|
||||
if (className == "Transform") {
|
||||
hasComponent = targetObject->GetTransform() != nullptr;
|
||||
} else if (className == "Camera") {
|
||||
hasComponent = targetObject->GetComponent<Components::CameraComponent>() != nullptr;
|
||||
} else if (className == "Light") {
|
||||
hasComponent = targetObject->GetComponent<Components::LightComponent>() != nullptr;
|
||||
} else if (className == "MeshFilter") {
|
||||
hasComponent = targetObject->GetComponent<Components::MeshFilterComponent>() != nullptr;
|
||||
} else if (className == "MeshRenderer") {
|
||||
hasComponent = targetObject->GetComponent<Components::MeshRendererComponent>() != nullptr;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasComponent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
managedComponent = CreateManagedComponentWrapper(fieldMetadata.componentClass, reference.gameObjectUUID);
|
||||
if (!managedComponent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mono_field_set_value(instance, fieldMetadata.field, managedComponent);
|
||||
return true;
|
||||
}
|
||||
case ScriptFieldType::None:
|
||||
return false;
|
||||
}
|
||||
@@ -2947,6 +3109,16 @@ bool MonoScriptRuntime::TryReadFieldValue(
|
||||
outValue = reference;
|
||||
return true;
|
||||
}
|
||||
case ScriptFieldType::Component: {
|
||||
MonoObject* managedObject = mono_field_get_value_object(m_appDomain, fieldMetadata.field, instance);
|
||||
ComponentReference reference;
|
||||
if (!TryExtractComponentReference(managedObject, reference)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = reference;
|
||||
return true;
|
||||
}
|
||||
case ScriptFieldType::None:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ std::string ScriptFieldTypeToString(ScriptFieldType type) {
|
||||
case ScriptFieldType::Vector3: return "Vector3";
|
||||
case ScriptFieldType::Vector4: return "Vector4";
|
||||
case ScriptFieldType::GameObject: return "GameObject";
|
||||
case ScriptFieldType::Component: return "Component";
|
||||
}
|
||||
|
||||
return "None";
|
||||
@@ -103,6 +104,8 @@ bool TryParseScriptFieldType(const std::string& value, ScriptFieldType& outType)
|
||||
outType = ScriptFieldType::Vector4;
|
||||
} else if (value == "GameObject") {
|
||||
outType = ScriptFieldType::GameObject;
|
||||
} else if (value == "Component") {
|
||||
outType = ScriptFieldType::Component;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -123,6 +126,7 @@ bool IsScriptFieldValueCompatible(ScriptFieldType type, const ScriptFieldValue&
|
||||
case ScriptFieldType::Vector3: return std::holds_alternative<Math::Vector3>(value);
|
||||
case ScriptFieldType::Vector4: return std::holds_alternative<Math::Vector4>(value);
|
||||
case ScriptFieldType::GameObject: return std::holds_alternative<GameObjectReference>(value);
|
||||
case ScriptFieldType::Component: return std::holds_alternative<ComponentReference>(value);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -141,6 +145,7 @@ ScriptFieldValue CreateDefaultScriptFieldValue(ScriptFieldType type) {
|
||||
case ScriptFieldType::Vector3: return Math::Vector3::Zero();
|
||||
case ScriptFieldType::Vector4: return Math::Vector4::Zero();
|
||||
case ScriptFieldType::GameObject: return GameObjectReference{};
|
||||
case ScriptFieldType::Component: return ComponentReference{};
|
||||
}
|
||||
|
||||
return std::monostate{};
|
||||
@@ -180,6 +185,10 @@ std::string SerializeScriptFieldValue(ScriptFieldType type, const ScriptFieldVal
|
||||
}
|
||||
case ScriptFieldType::GameObject:
|
||||
return std::to_string(std::get<GameObjectReference>(value).gameObjectUUID);
|
||||
case ScriptFieldType::Component: {
|
||||
const ComponentReference reference = std::get<ComponentReference>(value);
|
||||
return std::to_string(reference.gameObjectUUID) + "," + std::to_string(reference.scriptComponentUUID);
|
||||
}
|
||||
}
|
||||
|
||||
return std::string();
|
||||
@@ -236,6 +245,18 @@ bool TryDeserializeScriptFieldValue(ScriptFieldType type, const std::string& val
|
||||
case ScriptFieldType::GameObject:
|
||||
outValue = GameObjectReference{static_cast<uint64_t>(std::stoull(value))};
|
||||
return true;
|
||||
case ScriptFieldType::Component: {
|
||||
const size_t separator = value.find(',');
|
||||
if (separator == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = ComponentReference{
|
||||
static_cast<uint64_t>(std::stoull(value.substr(0, separator))),
|
||||
static_cast<uint64_t>(std::stoull(value.substr(separator + 1)))
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user