Add concrete component script field support
This commit is contained in:
@@ -277,3 +277,11 @@ C# 脚本模块已经完成了第一阶段的核心闭环,不再是“从 0
|
||||
- `ProjectScriptAssemblyTest.*` 现改为使用独立的测试项目程序集输出目录,不再依赖真实 `project/Library/ScriptAssemblies`,避免与 editor / Mono 持有的文件锁互相干扰。
|
||||
- 已通过完整 `ScriptFieldStorage_Test.*:MonoScriptRuntimeTest.*:ProjectScriptAssemblyTest.*` 验证;输出全绿,`exit code 3` 仍为既有历史现象。
|
||||
- 字段系统下一步建议切到:组件引用字段支持
|
||||
- 已完成字段系统第三小项:具体组件引用字段支持
|
||||
- `MonoScriptRuntime` 现已支持把常用具体组件字段纳入脚本字段模型,包括 `Transform`、`Camera`、`Light`、`MeshFilter`、`MeshRenderer`,以及具体脚本类字段(`MonoBehaviour` 子类)。
|
||||
- 字段存储层已新增 `ScriptFieldType::Component` 与 `ComponentReference`,序列化格式同时保存 `gameObjectUUID` 与 `scriptComponentUUID`,从而能区分内建组件引用与脚本组件引用。
|
||||
- 当前刻意暂不支持抽象基类字段:`Component`、`Behaviour`、`MonoBehaviour`;这样先保证字段定位与场景 roundtrip 语义稳定,不把抽象匹配和 editor 复杂度提前拉进主线。
|
||||
- 已新增 `ComponentFieldMetadataProbe` / `ComponentFieldProbe` 与对应 C++ 回归测试,覆盖字段发现、默认空引用、存储值应用、运行时写回,以及场景 roundtrip 恢复。
|
||||
- 本步定向测试 `ScriptFieldStorage_Test.*:ScriptComponent_Test.*:MonoScriptRuntimeTest.ClassFieldMetadataListsConcreteComponentReferenceFields:MonoScriptRuntimeTest.ClassFieldDefaultValueQueryReturnsNullComponentReferences:MonoScriptRuntimeTest.ComponentReferenceFieldsApplyStoredValuesAndPersistAcrossSceneRoundTrip` 全部通过。
|
||||
- 扩大回归 `ScriptFieldStorage_Test.*:MonoScriptRuntimeTest.*:ProjectScriptAssemblyTest.*` 日志层面全绿;当前测试进程仍存在历史性的异常退出码现象,需要后续单独跟踪,不是本步引入的问题。
|
||||
- 字段系统下一步建议切到:资源引用字段,或等 editor Inspector 重构稳定后,再补组件字段的可视化选择器。
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -125,6 +125,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES
|
||||
set(XCENGINE_GAME_SCRIPT_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/BuiltinComponentProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/AddComponentProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/ComponentReferenceProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/EnumFieldProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/FieldMetadataProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/GetComponentsProbe.cs
|
||||
|
||||
70
managed/GameScripts/ComponentReferenceProbe.cs
Normal file
70
managed/GameScripts/ComponentReferenceProbe.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using XCEngine;
|
||||
|
||||
namespace Gameplay
|
||||
{
|
||||
public sealed class ComponentFieldMetadataProbe : MonoBehaviour
|
||||
{
|
||||
public Transform Pivot;
|
||||
public Camera SceneCamera;
|
||||
public ScriptComponentTargetProbe ScriptTarget;
|
||||
public Component UnsupportedComponent;
|
||||
public Behaviour UnsupportedBehaviour;
|
||||
public MonoBehaviour UnsupportedMonoBehaviour;
|
||||
}
|
||||
|
||||
public sealed class ComponentFieldProbe : MonoBehaviour
|
||||
{
|
||||
public Transform Pivot;
|
||||
public Camera SceneCamera;
|
||||
public ScriptComponentTargetProbe ScriptTarget;
|
||||
|
||||
public bool ObservedStoredPivotApplied;
|
||||
public bool ObservedStoredCameraApplied;
|
||||
public bool ObservedStoredScriptApplied;
|
||||
public string ObservedPivotName = string.Empty;
|
||||
public string ObservedCameraName = string.Empty;
|
||||
public string ObservedScriptName = string.Empty;
|
||||
public int ObservedScriptAwakeCount = -1;
|
||||
public int ObservedScriptHostCallCount = -1;
|
||||
|
||||
public bool ObservedUpdatedPivotAssigned;
|
||||
public bool ObservedUpdatedCameraAssigned;
|
||||
public bool ObservedUpdatedScriptAssigned;
|
||||
public string ObservedUpdatedPivotName = string.Empty;
|
||||
public string ObservedUpdatedCameraName = string.Empty;
|
||||
public string ObservedUpdatedScriptName = string.Empty;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
ObservedStoredPivotApplied = Pivot != null;
|
||||
ObservedStoredCameraApplied = SceneCamera != null;
|
||||
ObservedStoredScriptApplied = ScriptTarget != null;
|
||||
|
||||
ObservedPivotName = Pivot != null ? Pivot.gameObject.name : string.Empty;
|
||||
ObservedCameraName = SceneCamera != null ? SceneCamera.gameObject.name : string.Empty;
|
||||
ObservedScriptName = ScriptTarget != null ? ScriptTarget.gameObject.name : string.Empty;
|
||||
|
||||
if (ScriptTarget != null)
|
||||
{
|
||||
ObservedScriptAwakeCount = ScriptTarget.AwakeCount;
|
||||
ScriptTarget.IncrementFromHost();
|
||||
ObservedScriptHostCallCount = ScriptTarget.HostCallCount;
|
||||
}
|
||||
|
||||
Pivot = transform;
|
||||
SceneCamera = GetComponent<Camera>();
|
||||
ScriptTarget = GetComponent<ScriptComponentTargetProbe>();
|
||||
if (ScriptTarget == null)
|
||||
{
|
||||
ScriptTarget = AddComponent<ScriptComponentTargetProbe>();
|
||||
}
|
||||
|
||||
ObservedUpdatedPivotAssigned = Pivot != null;
|
||||
ObservedUpdatedCameraAssigned = SceneCamera != null;
|
||||
ObservedUpdatedScriptAssigned = ScriptTarget != null;
|
||||
ObservedUpdatedPivotName = Pivot != null ? Pivot.gameObject.name : string.Empty;
|
||||
ObservedUpdatedCameraName = SceneCamera != null ? SceneCamera.gameObject.name : string.Empty;
|
||||
ObservedUpdatedScriptName = ScriptTarget != null ? ScriptTarget.gameObject.name : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,6 +248,43 @@ TEST_F(MonoScriptRuntimeTest, ClassFieldDefaultValueQueryReturnsSerializeFieldPr
|
||||
EXPECT_TRUE(std::get<bool>(fieldIt->value));
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ClassFieldMetadataListsConcreteComponentReferenceFields) {
|
||||
std::vector<ScriptFieldMetadata> fields;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetClassFieldMetadata("GameScripts", "Gameplay", "ComponentFieldMetadataProbe", fields));
|
||||
|
||||
const std::vector<ScriptFieldMetadata> expected = {
|
||||
{"Pivot", ScriptFieldType::Component},
|
||||
{"SceneCamera", ScriptFieldType::Component},
|
||||
{"ScriptTarget", ScriptFieldType::Component},
|
||||
};
|
||||
|
||||
EXPECT_EQ(fields, expected);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ClassFieldDefaultValueQueryReturnsNullComponentReferences) {
|
||||
std::vector<ScriptFieldDefaultValue> fields;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetClassFieldDefaultValues("GameScripts", "Gameplay", "ComponentFieldMetadataProbe", fields));
|
||||
|
||||
const auto expectNullComponentField = [&](const char* fieldName) {
|
||||
const auto fieldIt = std::find_if(
|
||||
fields.begin(),
|
||||
fields.end(),
|
||||
[fieldName](const ScriptFieldDefaultValue& field) {
|
||||
return field.fieldName == fieldName;
|
||||
});
|
||||
|
||||
ASSERT_NE(fieldIt, fields.end());
|
||||
EXPECT_EQ(fieldIt->type, ScriptFieldType::Component);
|
||||
EXPECT_EQ(std::get<ComponentReference>(fieldIt->value), (ComponentReference{}));
|
||||
};
|
||||
|
||||
expectNullComponentField("Pivot");
|
||||
expectNullComponentField("SceneCamera");
|
||||
expectNullComponentField("ScriptTarget");
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineAppliesStoredFieldsAndInvokesLifecycleMethods) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
@@ -968,6 +1005,163 @@ TEST_F(MonoScriptRuntimeTest, SerializeFieldPrivateFieldsApplyStoredValuesAndPer
|
||||
EXPECT_FALSE(loadedRuntimeHiddenEnabled);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ComponentReferenceFieldsApplyStoredValuesAndPersistAcrossSceneRoundTrip) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
host->AddComponent<CameraComponent>();
|
||||
|
||||
GameObject* pivotTarget = runtimeScene->CreateGameObject("PivotTarget");
|
||||
GameObject* cameraTarget = runtimeScene->CreateGameObject("CameraTarget");
|
||||
cameraTarget->AddComponent<CameraComponent>();
|
||||
GameObject* scriptTargetHost = runtimeScene->CreateGameObject("ScriptTarget");
|
||||
|
||||
ScriptComponent* referencedScript = AddScript(scriptTargetHost, "Gameplay", "ScriptComponentTargetProbe");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "ComponentFieldProbe");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Pivot", ComponentReference{pivotTarget->GetUUID(), 0});
|
||||
component->GetFieldStorage().SetFieldValue("SceneCamera", ComponentReference{cameraTarget->GetUUID(), 0});
|
||||
component->GetFieldStorage().SetFieldValue(
|
||||
"ScriptTarget",
|
||||
ComponentReference{scriptTargetHost->GetUUID(), referencedScript->GetScriptComponentUUID()});
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
bool observedStoredPivotApplied = false;
|
||||
bool observedStoredCameraApplied = false;
|
||||
bool observedStoredScriptApplied = false;
|
||||
std::string observedPivotName;
|
||||
std::string observedCameraName;
|
||||
std::string observedScriptName;
|
||||
int32_t observedScriptAwakeCount = -1;
|
||||
int32_t observedScriptHostCallCount = -1;
|
||||
bool observedUpdatedPivotAssigned = false;
|
||||
bool observedUpdatedCameraAssigned = false;
|
||||
bool observedUpdatedScriptAssigned = false;
|
||||
std::string observedUpdatedPivotName;
|
||||
std::string observedUpdatedCameraName;
|
||||
std::string observedUpdatedScriptName;
|
||||
ComponentReference runtimePivot;
|
||||
ComponentReference runtimeCamera;
|
||||
ComponentReference runtimeScript;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedStoredPivotApplied", observedStoredPivotApplied));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedStoredCameraApplied", observedStoredCameraApplied));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedStoredScriptApplied", observedStoredScriptApplied));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedPivotName", observedPivotName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedCameraName", observedCameraName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedScriptName", observedScriptName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedScriptAwakeCount", observedScriptAwakeCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedScriptHostCallCount", observedScriptHostCallCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedPivotAssigned", observedUpdatedPivotAssigned));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedCameraAssigned", observedUpdatedCameraAssigned));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedScriptAssigned", observedUpdatedScriptAssigned));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedPivotName", observedUpdatedPivotName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedCameraName", observedUpdatedCameraName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedScriptName", observedUpdatedScriptName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "Pivot", runtimePivot));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "SceneCamera", runtimeCamera));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ScriptTarget", runtimeScript));
|
||||
|
||||
EXPECT_TRUE(observedStoredPivotApplied);
|
||||
EXPECT_TRUE(observedStoredCameraApplied);
|
||||
EXPECT_TRUE(observedStoredScriptApplied);
|
||||
EXPECT_EQ(observedPivotName, "PivotTarget");
|
||||
EXPECT_EQ(observedCameraName, "CameraTarget");
|
||||
EXPECT_EQ(observedScriptName, "ScriptTarget");
|
||||
EXPECT_EQ(observedScriptAwakeCount, 1);
|
||||
EXPECT_EQ(observedScriptHostCallCount, 1);
|
||||
EXPECT_TRUE(observedUpdatedPivotAssigned);
|
||||
EXPECT_TRUE(observedUpdatedCameraAssigned);
|
||||
EXPECT_TRUE(observedUpdatedScriptAssigned);
|
||||
EXPECT_EQ(observedUpdatedPivotName, "Host");
|
||||
EXPECT_EQ(observedUpdatedCameraName, "Host");
|
||||
EXPECT_EQ(observedUpdatedScriptName, "Host");
|
||||
|
||||
ScriptComponent* assignedHostScript = FindScriptComponentByClass(host, "Gameplay", "ScriptComponentTargetProbe");
|
||||
ASSERT_NE(assignedHostScript, nullptr);
|
||||
|
||||
EXPECT_EQ(runtimePivot, (ComponentReference{host->GetUUID(), 0}));
|
||||
EXPECT_EQ(runtimeCamera, (ComponentReference{host->GetUUID(), 0}));
|
||||
EXPECT_EQ(
|
||||
runtimeScript,
|
||||
(ComponentReference{host->GetUUID(), assignedHostScript->GetScriptComponentUUID()}));
|
||||
|
||||
ComponentReference storedPivot;
|
||||
ComponentReference storedCamera;
|
||||
ComponentReference storedScript;
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Pivot", storedPivot));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("SceneCamera", storedCamera));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("ScriptTarget", storedScript));
|
||||
EXPECT_EQ(storedPivot, runtimePivot);
|
||||
EXPECT_EQ(storedCamera, runtimeCamera);
|
||||
EXPECT_EQ(storedScript, runtimeScript);
|
||||
|
||||
const std::string serializedScene = runtimeScene->SerializeToString();
|
||||
|
||||
engine->OnRuntimeStop();
|
||||
|
||||
scene = std::make_unique<Scene>("ReloadedComponentFieldScene");
|
||||
scene->DeserializeFromString(serializedScene);
|
||||
Scene* reloadedScene = scene.get();
|
||||
|
||||
GameObject* loadedHost = reloadedScene->Find("Host");
|
||||
ASSERT_NE(loadedHost, nullptr);
|
||||
|
||||
ScriptComponent* loadedComponent = FindScriptComponentByClass(loadedHost, "Gameplay", "ComponentFieldProbe");
|
||||
ScriptComponent* loadedAssignedHostScript =
|
||||
FindScriptComponentByClass(loadedHost, "Gameplay", "ScriptComponentTargetProbe");
|
||||
ASSERT_NE(loadedComponent, nullptr);
|
||||
ASSERT_NE(loadedAssignedHostScript, nullptr);
|
||||
|
||||
ComponentReference loadedStoredPivot;
|
||||
ComponentReference loadedStoredCamera;
|
||||
ComponentReference loadedStoredScript;
|
||||
EXPECT_TRUE(loadedComponent->GetFieldStorage().TryGetFieldValue("Pivot", loadedStoredPivot));
|
||||
EXPECT_TRUE(loadedComponent->GetFieldStorage().TryGetFieldValue("SceneCamera", loadedStoredCamera));
|
||||
EXPECT_TRUE(loadedComponent->GetFieldStorage().TryGetFieldValue("ScriptTarget", loadedStoredScript));
|
||||
EXPECT_EQ(loadedStoredPivot, (ComponentReference{loadedHost->GetUUID(), 0}));
|
||||
EXPECT_EQ(loadedStoredCamera, (ComponentReference{loadedHost->GetUUID(), 0}));
|
||||
EXPECT_EQ(
|
||||
loadedStoredScript,
|
||||
(ComponentReference{loadedHost->GetUUID(), loadedAssignedHostScript->GetScriptComponentUUID()}));
|
||||
|
||||
engine->OnRuntimeStart(reloadedScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
bool loadedObservedStoredPivotApplied = false;
|
||||
bool loadedObservedStoredCameraApplied = false;
|
||||
bool loadedObservedStoredScriptApplied = false;
|
||||
std::string loadedObservedPivotName;
|
||||
std::string loadedObservedCameraName;
|
||||
std::string loadedObservedScriptName;
|
||||
ComponentReference loadedRuntimePivot;
|
||||
ComponentReference loadedRuntimeCamera;
|
||||
ComponentReference loadedRuntimeScript;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "ObservedStoredPivotApplied", loadedObservedStoredPivotApplied));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "ObservedStoredCameraApplied", loadedObservedStoredCameraApplied));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "ObservedStoredScriptApplied", loadedObservedStoredScriptApplied));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "ObservedPivotName", loadedObservedPivotName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "ObservedCameraName", loadedObservedCameraName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "ObservedScriptName", loadedObservedScriptName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "Pivot", loadedRuntimePivot));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "SceneCamera", loadedRuntimeCamera));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "ScriptTarget", loadedRuntimeScript));
|
||||
|
||||
EXPECT_TRUE(loadedObservedStoredPivotApplied);
|
||||
EXPECT_TRUE(loadedObservedStoredCameraApplied);
|
||||
EXPECT_TRUE(loadedObservedStoredScriptApplied);
|
||||
EXPECT_EQ(loadedObservedPivotName, "Host");
|
||||
EXPECT_EQ(loadedObservedCameraName, "Host");
|
||||
EXPECT_EQ(loadedObservedScriptName, "Host");
|
||||
EXPECT_EQ(loadedRuntimePivot, (ComponentReference{loadedHost->GetUUID(), 0}));
|
||||
EXPECT_EQ(loadedRuntimeCamera, (ComponentReference{loadedHost->GetUUID(), 0}));
|
||||
EXPECT_EQ(
|
||||
loadedRuntimeScript,
|
||||
(ComponentReference{loadedHost->GetUUID(), loadedAssignedHostScript->GetScriptComponentUUID()}));
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldApiUpdatesLiveManagedInstanceAndStoredCache) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
|
||||
@@ -41,6 +41,7 @@ TEST(ScriptComponent_Test, SerializeRoundTripPreservesMetadataAndFields) {
|
||||
ASSERT_TRUE(original.GetFieldStorage().SetFieldValue("DisplayName", std::string("Hero=One;Ready|100%")));
|
||||
ASSERT_TRUE(original.GetFieldStorage().SetFieldValue("MoveSpeed", 6.25f));
|
||||
ASSERT_TRUE(original.GetFieldStorage().SetFieldValue("Target", GameObjectReference{9988}));
|
||||
ASSERT_TRUE(original.GetFieldStorage().SetFieldValue("TargetCamera", ComponentReference{9988, 0}));
|
||||
|
||||
std::ostringstream stream;
|
||||
original.Serialize(stream);
|
||||
@@ -52,6 +53,7 @@ TEST(ScriptComponent_Test, SerializeRoundTripPreservesMetadataAndFields) {
|
||||
std::string displayName;
|
||||
float moveSpeed = 0.0f;
|
||||
GameObjectReference target;
|
||||
ComponentReference targetCamera;
|
||||
|
||||
EXPECT_EQ(restored.GetScriptComponentUUID(), original.GetScriptComponentUUID());
|
||||
EXPECT_EQ(restored.GetAssemblyName(), "GameScripts.Runtime");
|
||||
@@ -61,9 +63,11 @@ TEST(ScriptComponent_Test, SerializeRoundTripPreservesMetadataAndFields) {
|
||||
EXPECT_TRUE(restored.GetFieldStorage().TryGetFieldValue("DisplayName", displayName));
|
||||
EXPECT_TRUE(restored.GetFieldStorage().TryGetFieldValue("MoveSpeed", moveSpeed));
|
||||
EXPECT_TRUE(restored.GetFieldStorage().TryGetFieldValue("Target", target));
|
||||
EXPECT_TRUE(restored.GetFieldStorage().TryGetFieldValue("TargetCamera", targetCamera));
|
||||
EXPECT_EQ(displayName, "Hero=One;Ready|100%");
|
||||
EXPECT_FLOAT_EQ(moveSpeed, 6.25f);
|
||||
EXPECT_EQ(target, GameObjectReference{9988});
|
||||
EXPECT_EQ(targetCamera, (ComponentReference{9988, 0}));
|
||||
}
|
||||
|
||||
TEST(ScriptComponent_Test, ComponentFactoryRegistryCreatesScriptComponents) {
|
||||
|
||||
@@ -20,6 +20,7 @@ TEST(ScriptFieldStorage_Test, StoresRetrievesAndRemovesSupportedValues) {
|
||||
EXPECT_TRUE(storage.SetFieldValue("Velocity", Vector3(3.0f, 4.0f, 5.0f)));
|
||||
EXPECT_TRUE(storage.SetFieldValue("Tint", Vector4(0.1f, 0.2f, 0.3f, 1.0f)));
|
||||
EXPECT_TRUE(storage.SetFieldValue("Target", GameObjectReference{42}));
|
||||
EXPECT_TRUE(storage.SetFieldValue("TargetCamera", ComponentReference{42, 0}));
|
||||
|
||||
float speed = 0.0f;
|
||||
double accuracy = 0.0;
|
||||
@@ -31,8 +32,9 @@ TEST(ScriptFieldStorage_Test, StoresRetrievesAndRemovesSupportedValues) {
|
||||
Vector3 velocity;
|
||||
Vector4 tint;
|
||||
GameObjectReference target;
|
||||
ComponentReference targetCamera;
|
||||
|
||||
EXPECT_EQ(storage.GetFieldCount(), 10u);
|
||||
EXPECT_EQ(storage.GetFieldCount(), 11u);
|
||||
EXPECT_TRUE(storage.Contains("Velocity"));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Speed", speed));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Accuracy", accuracy));
|
||||
@@ -44,6 +46,7 @@ TEST(ScriptFieldStorage_Test, StoresRetrievesAndRemovesSupportedValues) {
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Velocity", velocity));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Tint", tint));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("Target", target));
|
||||
EXPECT_TRUE(storage.TryGetFieldValue("TargetCamera", targetCamera));
|
||||
|
||||
EXPECT_FLOAT_EQ(speed, 3.5f);
|
||||
EXPECT_DOUBLE_EQ(accuracy, 0.875);
|
||||
@@ -55,10 +58,11 @@ TEST(ScriptFieldStorage_Test, StoresRetrievesAndRemovesSupportedValues) {
|
||||
EXPECT_EQ(velocity, Vector3(3.0f, 4.0f, 5.0f));
|
||||
EXPECT_EQ(tint, Vector4(0.1f, 0.2f, 0.3f, 1.0f));
|
||||
EXPECT_EQ(target, GameObjectReference{42});
|
||||
EXPECT_EQ(targetCamera, (ComponentReference{42, 0}));
|
||||
|
||||
EXPECT_TRUE(storage.Remove("IsAlive"));
|
||||
EXPECT_FALSE(storage.Contains("IsAlive"));
|
||||
EXPECT_EQ(storage.GetFieldCount(), 9u);
|
||||
EXPECT_EQ(storage.GetFieldCount(), 10u);
|
||||
}
|
||||
|
||||
TEST(ScriptFieldStorage_Test, SerializeRoundTripPreservesFieldValues) {
|
||||
@@ -66,6 +70,7 @@ TEST(ScriptFieldStorage_Test, SerializeRoundTripPreservesFieldValues) {
|
||||
ASSERT_TRUE(original.SetFieldValue("Message", std::string("Ready;Set=Go|100%\nLine2")));
|
||||
ASSERT_TRUE(original.SetFieldValue("Scale", Vector3(2.0f, 3.0f, 4.0f)));
|
||||
ASSERT_TRUE(original.SetFieldValue("Owner", GameObjectReference{778899}));
|
||||
ASSERT_TRUE(original.SetFieldValue("View", ComponentReference{778899, 123456}));
|
||||
ASSERT_TRUE(original.SetFieldValue("Counter", int32_t(-17)));
|
||||
|
||||
const std::string serialized = original.SerializeToString();
|
||||
@@ -76,16 +81,19 @@ TEST(ScriptFieldStorage_Test, SerializeRoundTripPreservesFieldValues) {
|
||||
std::string message;
|
||||
Vector3 scale;
|
||||
GameObjectReference owner;
|
||||
ComponentReference view;
|
||||
int32_t counter = 0;
|
||||
|
||||
EXPECT_TRUE(restored.TryGetFieldValue("Message", message));
|
||||
EXPECT_TRUE(restored.TryGetFieldValue("Scale", scale));
|
||||
EXPECT_TRUE(restored.TryGetFieldValue("Owner", owner));
|
||||
EXPECT_TRUE(restored.TryGetFieldValue("View", view));
|
||||
EXPECT_TRUE(restored.TryGetFieldValue("Counter", counter));
|
||||
|
||||
EXPECT_EQ(message, "Ready;Set=Go|100%\nLine2");
|
||||
EXPECT_EQ(scale, Vector3(2.0f, 3.0f, 4.0f));
|
||||
EXPECT_EQ(owner, GameObjectReference{778899});
|
||||
EXPECT_EQ(view, (ComponentReference{778899, 123456}));
|
||||
EXPECT_EQ(counter, -17);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user