Add SerializeField private field support
This commit is contained in:
@@ -271,4 +271,9 @@ C# 脚本模块已经完成了第一阶段的核心闭环,不再是“从 0
|
||||
- `MonoScriptRuntime` 现已支持把常见整数底层的 C# `enum` 字段纳入脚本字段模型,并按 `Int32` 进入默认值、存储值、运行时值三层同步链路。
|
||||
- 已新增 `FieldMetadataProbeState` / `EnumFieldProbeState` 探针,覆盖枚举字段发现、默认值提取、运行时写回与场景 roundtrip。
|
||||
- 已通过新增定向测试,以及完整 `ScriptFieldStorage_Test.*:MonoScriptRuntimeTest.*:ProjectScriptAssemblyTest.*` 整组验证;输出全绿,`exit code 3` 仍为既有历史现象。
|
||||
- 字段系统下一步建议切到:`[SerializeField] private` 字段支持
|
||||
- 已完成字段系统第二小项:`[SerializeField] private` 字段支持
|
||||
- 新增 `XCEngine.SerializeField` attribute,命名与 Unity 对齐;runtime 字段发现现已支持“public 字段”与“带 `[SerializeField]` 的 private 字段”双通路。
|
||||
- 字段筛选已同步排除 `static` / `const` / `readonly`,并新增 `SerializeFieldProbe`、`FieldMetadataProbe` 扩展与对应 C++ 回归测试,覆盖默认值提取、存储覆盖、运行时写回与场景 roundtrip。
|
||||
- `ProjectScriptAssemblyTest.*` 现改为使用独立的测试项目程序集输出目录,不再依赖真实 `project/Library/ScriptAssemblies`,避免与 editor / Mono 持有的文件锁互相干扰。
|
||||
- 已通过完整 `ScriptFieldStorage_Test.*:MonoScriptRuntimeTest.*:ProjectScriptAssemblyTest.*` 验证;输出全绿,`exit code 3` 仍为既有历史现象。
|
||||
- 字段系统下一步建议切到:组件引用字段支持
|
||||
|
||||
@@ -173,6 +173,7 @@ private:
|
||||
bool ApplyContextFields(const ScriptRuntimeContext& context, MonoObject* instance);
|
||||
bool ApplyStoredFields(const ScriptRuntimeContext& context, const ClassMetadata& metadata, MonoObject* instance);
|
||||
MonoObject* CreateManagedGameObject(uint64_t gameObjectUUID);
|
||||
bool HasSerializeFieldAttribute(MonoClassField* field) 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;
|
||||
@@ -198,6 +199,7 @@ private:
|
||||
MonoClass* m_behaviourClass = nullptr;
|
||||
MonoClass* m_gameObjectClass = nullptr;
|
||||
MonoClass* m_monoBehaviourClass = nullptr;
|
||||
MonoClass* m_serializeFieldAttributeClass = nullptr;
|
||||
MonoMethod* m_gameObjectConstructor = nullptr;
|
||||
MonoClassField* m_managedGameObjectUUIDField = nullptr;
|
||||
MonoClassField* m_gameObjectUUIDField = nullptr;
|
||||
|
||||
@@ -1665,6 +1665,7 @@ void MonoScriptRuntime::Shutdown() {
|
||||
m_behaviourClass = nullptr;
|
||||
m_gameObjectClass = nullptr;
|
||||
m_monoBehaviourClass = nullptr;
|
||||
m_serializeFieldAttributeClass = nullptr;
|
||||
m_gameObjectConstructor = nullptr;
|
||||
m_managedGameObjectUUIDField = nullptr;
|
||||
m_gameObjectUUIDField = nullptr;
|
||||
@@ -2219,6 +2220,15 @@ bool MonoScriptRuntime::DiscoverScriptClasses() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_serializeFieldAttributeClass = mono_class_from_name(
|
||||
m_coreImage,
|
||||
m_settings.baseNamespace.c_str(),
|
||||
"SerializeField");
|
||||
if (!m_serializeFieldAttributeClass) {
|
||||
SetError("Failed to locate the managed SerializeField attribute type.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_gameObjectUUIDField = mono_class_get_field_from_name(m_componentClass, "m_gameObjectUUID");
|
||||
m_scriptComponentUUIDField = mono_class_get_field_from_name(m_behaviourClass, "m_scriptComponentUUID");
|
||||
if (!m_gameObjectUUIDField || !m_scriptComponentUUIDField) {
|
||||
@@ -2264,7 +2274,14 @@ void MonoScriptRuntime::DiscoverScriptClassesInImage(const std::string& assembly
|
||||
void* fieldIterator = nullptr;
|
||||
while (MonoClassField* field = mono_class_get_fields(monoClass, &fieldIterator)) {
|
||||
const uint32_t fieldFlags = mono_field_get_flags(field);
|
||||
if ((fieldFlags & MONO_FIELD_ATTR_PUBLIC) == 0 || (fieldFlags & MONO_FIELD_ATTR_STATIC) != 0) {
|
||||
if ((fieldFlags & MONO_FIELD_ATTR_STATIC) != 0
|
||||
|| (fieldFlags & MONO_FIELD_ATTR_LITERAL) != 0
|
||||
|| (fieldFlags & MONO_FIELD_ATTR_INIT_ONLY) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool isPublicField = (fieldFlags & MONO_FIELD_ATTR_PUBLIC) != 0;
|
||||
if (!isPublicField && !HasSerializeFieldAttribute(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2299,6 +2316,26 @@ bool MonoScriptRuntime::IsMonoBehaviourSubclass(MonoClass* monoClass) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::HasSerializeFieldAttribute(MonoClassField* field) const {
|
||||
if (!field || !m_serializeFieldAttributeClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoClass* ownerClass = mono_field_get_parent(field);
|
||||
if (!ownerClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoCustomAttrInfo* attributes = mono_custom_attrs_from_field(ownerClass, field);
|
||||
if (!attributes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const mono_bool hasAttribute = mono_custom_attrs_has_attr(attributes, m_serializeFieldAttributeClass);
|
||||
mono_custom_attrs_free(attributes);
|
||||
return hasAttribute != 0;
|
||||
}
|
||||
|
||||
MonoScriptRuntime::FieldMetadata MonoScriptRuntime::BuildFieldMetadata(MonoClassField* field) const {
|
||||
FieldMetadata metadata;
|
||||
metadata.field = field;
|
||||
|
||||
@@ -62,6 +62,30 @@ set(
|
||||
"${XCENGINE_PROJECT_MANAGED_OUTPUT_DIR}/mscorlib.dll"
|
||||
CACHE FILEPATH
|
||||
"Mono corlib copied into the project script assembly directory")
|
||||
set(
|
||||
XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR
|
||||
"${CMAKE_BINARY_DIR}/managed/ProjectScriptAssemblies"
|
||||
CACHE PATH
|
||||
"Output directory for scripting tests that validate project asset assemblies"
|
||||
)
|
||||
set(
|
||||
XCENGINE_SCRIPTING_TEST_PROJECT_SCRIPT_CORE_DLL
|
||||
"${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR}/XCEngine.ScriptCore.dll"
|
||||
CACHE FILEPATH
|
||||
"ScriptCore assembly used by scripting tests for project asset assembly validation"
|
||||
)
|
||||
set(
|
||||
XCENGINE_SCRIPTING_TEST_PROJECT_GAME_SCRIPTS_DLL
|
||||
"${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR}/GameScripts.dll"
|
||||
CACHE FILEPATH
|
||||
"GameScripts assembly used by scripting tests for project asset assembly validation"
|
||||
)
|
||||
set(
|
||||
XCENGINE_SCRIPTING_TEST_PROJECT_MONO_MSCORLIB_PATH
|
||||
"${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR}/mscorlib.dll"
|
||||
CACHE FILEPATH
|
||||
"Mono corlib copied into the scripting test project assembly directory"
|
||||
)
|
||||
|
||||
foreach(XCENGINE_REQUIRED_PATH
|
||||
"${XCENGINE_CSC_DLL}"
|
||||
@@ -89,6 +113,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/MonoBehaviour.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Object.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Quaternion.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/SerializeField.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Space.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Time.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Transform.cs
|
||||
@@ -111,6 +136,7 @@ set(XCENGINE_GAME_SCRIPT_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/MeshComponentProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/MeshRendererEdgeCaseProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/ObjectApiProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/SerializeFieldProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TagLayerProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TickLogProbe.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformConversionProbe.cs
|
||||
@@ -190,6 +216,50 @@ add_custom_target(
|
||||
${XCENGINE_MANAGED_OUTPUT_DIR}/mscorlib.dll
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${XCENGINE_SCRIPTING_TEST_PROJECT_SCRIPT_CORE_DLL}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${XCENGINE_SCRIPT_CORE_DLL}
|
||||
${XCENGINE_SCRIPTING_TEST_PROJECT_SCRIPT_CORE_DLL}
|
||||
DEPENDS ${XCENGINE_SCRIPT_CORE_DLL}
|
||||
VERBATIM
|
||||
COMMENT "Copying XCEngine.ScriptCore.dll into the scripting test project assembly directory")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${XCENGINE_SCRIPTING_TEST_PROJECT_GAME_SCRIPTS_DLL}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR}
|
||||
COMMAND ${XCENGINE_DOTNET_EXECUTABLE} ${XCENGINE_CSC_DLL}
|
||||
/nologo
|
||||
/target:library
|
||||
/langversion:latest
|
||||
/nostdlib+
|
||||
/out:${XCENGINE_SCRIPTING_TEST_PROJECT_GAME_SCRIPTS_DLL}
|
||||
${XCENGINE_MANAGED_FRAMEWORK_REFERENCES}
|
||||
/reference:${XCENGINE_SCRIPTING_TEST_PROJECT_SCRIPT_CORE_DLL}
|
||||
${XCENGINE_PROJECT_GAME_SCRIPT_SOURCES}
|
||||
DEPENDS ${XCENGINE_PROJECT_GAME_SCRIPT_SOURCES} ${XCENGINE_SCRIPTING_TEST_PROJECT_SCRIPT_CORE_DLL}
|
||||
VERBATIM
|
||||
COMMENT "Building scripting test GameScripts.dll from project asset scripts")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${XCENGINE_SCRIPTING_TEST_PROJECT_MONO_MSCORLIB_PATH}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${XCENGINE_MONO_MSCORLIB_PATH}
|
||||
${XCENGINE_SCRIPTING_TEST_PROJECT_MONO_MSCORLIB_PATH}
|
||||
DEPENDS ${XCENGINE_MONO_MSCORLIB_PATH}
|
||||
VERBATIM
|
||||
COMMENT "Copying mscorlib.dll into the scripting test project assembly directory")
|
||||
|
||||
add_custom_target(
|
||||
xcengine_test_project_managed_assemblies
|
||||
DEPENDS
|
||||
${XCENGINE_SCRIPTING_TEST_PROJECT_SCRIPT_CORE_DLL}
|
||||
${XCENGINE_SCRIPTING_TEST_PROJECT_GAME_SCRIPTS_DLL}
|
||||
${XCENGINE_SCRIPTING_TEST_PROJECT_MONO_MSCORLIB_PATH}
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${XCENGINE_PROJECT_SCRIPT_CORE_DLL}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${XCENGINE_PROJECT_MANAGED_OUTPUT_DIR}
|
||||
|
||||
@@ -15,10 +15,12 @@ namespace Gameplay
|
||||
public string Label = string.Empty;
|
||||
public Vector3 SpawnPoint;
|
||||
public FieldMetadataProbeState State = FieldMetadataProbeState.Running;
|
||||
[SerializeField] private bool HiddenFlag = true;
|
||||
public GameObject Target;
|
||||
public Quaternion UnsupportedRotation;
|
||||
public static int SharedCounter;
|
||||
private bool HiddenFlag = true;
|
||||
private int IgnoredPrivateCounter = 7;
|
||||
public bool HiddenFlagMirror => HiddenFlag;
|
||||
public int IgnoredPrivateCounterMirror => IgnoredPrivateCounter;
|
||||
}
|
||||
}
|
||||
|
||||
32
managed/GameScripts/SerializeFieldProbe.cs
Normal file
32
managed/GameScripts/SerializeFieldProbe.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using XCEngine;
|
||||
|
||||
namespace Gameplay
|
||||
{
|
||||
public sealed class SerializeFieldProbe : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private int HiddenCounter = 7;
|
||||
[SerializeField] private bool HiddenEnabled = true;
|
||||
private int IgnoredPrivateCounter = 99;
|
||||
|
||||
public int ObservedInitialHiddenCounter = -1;
|
||||
public bool ObservedInitialHiddenEnabled;
|
||||
public bool ObservedStoredValuesApplied;
|
||||
public int ObservedUpdatedHiddenCounter = -1;
|
||||
public bool ObservedUpdatedHiddenEnabled;
|
||||
public bool ObservedIgnoredPrivateCounterUntouched;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
ObservedInitialHiddenCounter = HiddenCounter;
|
||||
ObservedInitialHiddenEnabled = HiddenEnabled;
|
||||
ObservedStoredValuesApplied = HiddenCounter == 42 && !HiddenEnabled;
|
||||
ObservedIgnoredPrivateCounterUntouched = IgnoredPrivateCounter == 99;
|
||||
|
||||
HiddenCounter += 1;
|
||||
HiddenEnabled = !HiddenEnabled;
|
||||
|
||||
ObservedUpdatedHiddenCounter = HiddenCounter;
|
||||
ObservedUpdatedHiddenEnabled = HiddenEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
managed/XCEngine.ScriptCore/SerializeField.cs
Normal file
9
managed/XCEngine.ScriptCore/SerializeField.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace XCEngine
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = true)]
|
||||
public sealed class SerializeField : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ if(XCENGINE_ENABLE_MONO_SCRIPTING)
|
||||
test_mono_script_runtime.cpp
|
||||
)
|
||||
|
||||
if(TARGET xcengine_project_managed_assemblies)
|
||||
if(TARGET xcengine_test_project_managed_assemblies)
|
||||
list(APPEND SCRIPTING_TEST_SOURCES
|
||||
test_project_script_assembly.cpp
|
||||
)
|
||||
@@ -52,12 +52,21 @@ if(TARGET xcengine_managed_assemblies)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(TARGET xcengine_project_managed_assemblies)
|
||||
add_dependencies(scripting_tests xcengine_project_managed_assemblies)
|
||||
if(TARGET xcengine_test_project_managed_assemblies)
|
||||
add_dependencies(scripting_tests xcengine_test_project_managed_assemblies)
|
||||
|
||||
file(TO_CMAKE_PATH "${XCENGINE_PROJECT_MANAGED_OUTPUT_DIR}" XCENGINE_PROJECT_MANAGED_OUTPUT_DIR_CMAKE)
|
||||
file(TO_CMAKE_PATH "${XCENGINE_PROJECT_SCRIPT_CORE_DLL}" XCENGINE_PROJECT_SCRIPT_CORE_DLL_CMAKE)
|
||||
file(TO_CMAKE_PATH "${XCENGINE_PROJECT_GAME_SCRIPTS_DLL}" XCENGINE_PROJECT_GAME_SCRIPTS_DLL_CMAKE)
|
||||
file(
|
||||
TO_CMAKE_PATH
|
||||
"${XCENGINE_SCRIPTING_TEST_PROJECT_MANAGED_OUTPUT_DIR}"
|
||||
XCENGINE_PROJECT_MANAGED_OUTPUT_DIR_CMAKE)
|
||||
file(
|
||||
TO_CMAKE_PATH
|
||||
"${XCENGINE_SCRIPTING_TEST_PROJECT_SCRIPT_CORE_DLL}"
|
||||
XCENGINE_PROJECT_SCRIPT_CORE_DLL_CMAKE)
|
||||
file(
|
||||
TO_CMAKE_PATH
|
||||
"${XCENGINE_SCRIPTING_TEST_PROJECT_GAME_SCRIPTS_DLL}"
|
||||
XCENGINE_PROJECT_GAME_SCRIPTS_DLL_CMAKE)
|
||||
|
||||
target_compile_definitions(scripting_tests PRIVATE
|
||||
XCENGINE_TEST_PROJECT_MANAGED_OUTPUT_DIR=\"${XCENGINE_PROJECT_MANAGED_OUTPUT_DIR_CMAKE}\"
|
||||
|
||||
@@ -177,6 +177,7 @@ TEST_F(MonoScriptRuntimeTest, ClassFieldMetadataListsSupportedPublicInstanceFiel
|
||||
|
||||
const std::vector<ScriptFieldMetadata> expected = {
|
||||
{"Health", ScriptFieldType::Int32},
|
||||
{"HiddenFlag", ScriptFieldType::Bool},
|
||||
{"Label", ScriptFieldType::String},
|
||||
{"SpawnPoint", ScriptFieldType::Vector3},
|
||||
{"Speed", ScriptFieldType::Float},
|
||||
@@ -230,6 +231,23 @@ TEST_F(MonoScriptRuntimeTest, ClassFieldDefaultValueQueryReturnsEnumInitializers
|
||||
EXPECT_EQ(std::get<int32_t>(fieldIt->value), 2);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ClassFieldDefaultValueQueryReturnsSerializeFieldPrivateInitializers) {
|
||||
std::vector<ScriptFieldDefaultValue> fields;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetClassFieldDefaultValues("GameScripts", "Gameplay", "FieldMetadataProbe", fields));
|
||||
|
||||
const auto fieldIt = std::find_if(
|
||||
fields.begin(),
|
||||
fields.end(),
|
||||
[](const ScriptFieldDefaultValue& field) {
|
||||
return field.fieldName == "HiddenFlag";
|
||||
});
|
||||
|
||||
ASSERT_NE(fieldIt, fields.end());
|
||||
EXPECT_EQ(fieldIt->type, ScriptFieldType::Bool);
|
||||
EXPECT_TRUE(std::get<bool>(fieldIt->value));
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineAppliesStoredFieldsAndInvokesLifecycleMethods) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
@@ -864,6 +882,92 @@ TEST_F(MonoScriptRuntimeTest, EnumScriptFieldsApplyStoredValuesAndPersistAcrossS
|
||||
EXPECT_EQ(loadedRuntimeState, 9);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, SerializeFieldPrivateFieldsApplyStoredValuesAndPersistAcrossSceneRoundTrip) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "SerializeFieldProbe");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("HiddenCounter", int32_t(42));
|
||||
component->GetFieldStorage().SetFieldValue("HiddenEnabled", false);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
int32_t observedInitialHiddenCounter = 0;
|
||||
bool observedInitialHiddenEnabled = true;
|
||||
bool observedStoredValuesApplied = false;
|
||||
int32_t observedUpdatedHiddenCounter = 0;
|
||||
bool observedUpdatedHiddenEnabled = false;
|
||||
bool observedIgnoredPrivateCounterUntouched = false;
|
||||
int32_t runtimeHiddenCounter = 0;
|
||||
bool runtimeHiddenEnabled = false;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialHiddenCounter", observedInitialHiddenCounter));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialHiddenEnabled", observedInitialHiddenEnabled));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedStoredValuesApplied", observedStoredValuesApplied));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedHiddenCounter", observedUpdatedHiddenCounter));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedHiddenEnabled", observedUpdatedHiddenEnabled));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedIgnoredPrivateCounterUntouched", observedIgnoredPrivateCounterUntouched));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "HiddenCounter", runtimeHiddenCounter));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "HiddenEnabled", runtimeHiddenEnabled));
|
||||
|
||||
EXPECT_EQ(observedInitialHiddenCounter, 42);
|
||||
EXPECT_FALSE(observedInitialHiddenEnabled);
|
||||
EXPECT_TRUE(observedStoredValuesApplied);
|
||||
EXPECT_EQ(observedUpdatedHiddenCounter, 43);
|
||||
EXPECT_TRUE(observedUpdatedHiddenEnabled);
|
||||
EXPECT_TRUE(observedIgnoredPrivateCounterUntouched);
|
||||
EXPECT_EQ(runtimeHiddenCounter, 43);
|
||||
EXPECT_TRUE(runtimeHiddenEnabled);
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("IgnoredPrivateCounter"));
|
||||
|
||||
int32_t storedHiddenCounter = 0;
|
||||
bool storedHiddenEnabled = false;
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("HiddenCounter", storedHiddenCounter));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("HiddenEnabled", storedHiddenEnabled));
|
||||
EXPECT_EQ(storedHiddenCounter, 43);
|
||||
EXPECT_TRUE(storedHiddenEnabled);
|
||||
|
||||
const std::string serializedScene = runtimeScene->SerializeToString();
|
||||
|
||||
engine->OnRuntimeStop();
|
||||
|
||||
scene = std::make_unique<Scene>("ReloadedSerializeFieldScene");
|
||||
scene->DeserializeFromString(serializedScene);
|
||||
Scene* reloadedScene = scene.get();
|
||||
|
||||
GameObject* loadedHost = reloadedScene->Find("Host");
|
||||
ASSERT_NE(loadedHost, nullptr);
|
||||
|
||||
ScriptComponent* loadedComponent = FindScriptComponentByClass(loadedHost, "Gameplay", "SerializeFieldProbe");
|
||||
ASSERT_NE(loadedComponent, nullptr);
|
||||
|
||||
int32_t loadedStoredHiddenCounter = 0;
|
||||
bool loadedStoredHiddenEnabled = false;
|
||||
EXPECT_TRUE(loadedComponent->GetFieldStorage().TryGetFieldValue("HiddenCounter", loadedStoredHiddenCounter));
|
||||
EXPECT_TRUE(loadedComponent->GetFieldStorage().TryGetFieldValue("HiddenEnabled", loadedStoredHiddenEnabled));
|
||||
EXPECT_EQ(loadedStoredHiddenCounter, 43);
|
||||
EXPECT_TRUE(loadedStoredHiddenEnabled);
|
||||
|
||||
engine->OnRuntimeStart(reloadedScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
int32_t loadedObservedInitialHiddenCounter = 0;
|
||||
bool loadedObservedInitialHiddenEnabled = false;
|
||||
int32_t loadedRuntimeHiddenCounter = 0;
|
||||
bool loadedRuntimeHiddenEnabled = false;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "ObservedInitialHiddenCounter", loadedObservedInitialHiddenCounter));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "ObservedInitialHiddenEnabled", loadedObservedInitialHiddenEnabled));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "HiddenCounter", loadedRuntimeHiddenCounter));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "HiddenEnabled", loadedRuntimeHiddenEnabled));
|
||||
|
||||
EXPECT_EQ(loadedObservedInitialHiddenCounter, 43);
|
||||
EXPECT_TRUE(loadedObservedInitialHiddenEnabled);
|
||||
EXPECT_EQ(loadedRuntimeHiddenCounter, 44);
|
||||
EXPECT_FALSE(loadedRuntimeHiddenEnabled);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldApiUpdatesLiveManagedInstanceAndStoredCache) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
|
||||
@@ -8,24 +8,70 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
using namespace XCEngine::Scripting;
|
||||
|
||||
namespace {
|
||||
|
||||
std::filesystem::path GetExecutableDirectory() {
|
||||
#ifdef _WIN32
|
||||
std::wstring buffer(MAX_PATH, L'\0');
|
||||
const DWORD length = GetModuleFileNameW(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()));
|
||||
if (length == 0 || length >= buffer.size()) {
|
||||
return std::filesystem::current_path();
|
||||
}
|
||||
|
||||
buffer.resize(length);
|
||||
return std::filesystem::path(buffer).parent_path();
|
||||
#else
|
||||
return std::filesystem::current_path();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveProjectManagedOutputDirectory() {
|
||||
constexpr const char* configuredDirectory = XCENGINE_TEST_PROJECT_MANAGED_OUTPUT_DIR;
|
||||
if (configuredDirectory[0] != '\0') {
|
||||
return std::filesystem::path(configuredDirectory);
|
||||
}
|
||||
|
||||
return (GetExecutableDirectory() / ".." / ".." / "managed" / "ProjectScriptAssemblies").lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveProjectScriptCoreDllPath() {
|
||||
constexpr const char* configuredPath = XCENGINE_TEST_PROJECT_SCRIPT_CORE_DLL;
|
||||
if (configuredPath[0] != '\0') {
|
||||
return std::filesystem::path(configuredPath);
|
||||
}
|
||||
|
||||
return ResolveProjectManagedOutputDirectory() / "XCEngine.ScriptCore.dll";
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveProjectGameScriptsDllPath() {
|
||||
constexpr const char* configuredPath = XCENGINE_TEST_PROJECT_GAME_SCRIPTS_DLL;
|
||||
if (configuredPath[0] != '\0') {
|
||||
return std::filesystem::path(configuredPath);
|
||||
}
|
||||
|
||||
return ResolveProjectManagedOutputDirectory() / "GameScripts.dll";
|
||||
}
|
||||
|
||||
MonoScriptRuntime::Settings CreateProjectMonoSettings() {
|
||||
MonoScriptRuntime::Settings settings;
|
||||
settings.assemblyDirectory = XCENGINE_TEST_PROJECT_MANAGED_OUTPUT_DIR;
|
||||
settings.corlibDirectory = XCENGINE_TEST_PROJECT_MANAGED_OUTPUT_DIR;
|
||||
settings.coreAssemblyPath = XCENGINE_TEST_PROJECT_SCRIPT_CORE_DLL;
|
||||
settings.appAssemblyPath = XCENGINE_TEST_PROJECT_GAME_SCRIPTS_DLL;
|
||||
settings.assemblyDirectory = ResolveProjectManagedOutputDirectory();
|
||||
settings.corlibDirectory = settings.assemblyDirectory;
|
||||
settings.coreAssemblyPath = ResolveProjectScriptCoreDllPath();
|
||||
settings.appAssemblyPath = ResolveProjectGameScriptsDllPath();
|
||||
return settings;
|
||||
}
|
||||
|
||||
class ProjectScriptAssemblyTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(std::filesystem::exists(XCENGINE_TEST_PROJECT_SCRIPT_CORE_DLL));
|
||||
ASSERT_TRUE(std::filesystem::exists(XCENGINE_TEST_PROJECT_GAME_SCRIPTS_DLL));
|
||||
ASSERT_TRUE(std::filesystem::exists(ResolveProjectScriptCoreDllPath()));
|
||||
ASSERT_TRUE(std::filesystem::exists(ResolveProjectGameScriptsDllPath()));
|
||||
|
||||
runtime = std::make_unique<MonoScriptRuntime>(CreateProjectMonoSettings());
|
||||
ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError();
|
||||
@@ -36,8 +82,8 @@ protected:
|
||||
|
||||
TEST_F(ProjectScriptAssemblyTest, InitializesFromProjectScriptAssemblyDirectory) {
|
||||
EXPECT_TRUE(runtime->IsInitialized());
|
||||
EXPECT_EQ(runtime->GetSettings().assemblyDirectory, std::filesystem::path(XCENGINE_TEST_PROJECT_MANAGED_OUTPUT_DIR));
|
||||
EXPECT_EQ(runtime->GetSettings().appAssemblyPath, std::filesystem::path(XCENGINE_TEST_PROJECT_GAME_SCRIPTS_DLL));
|
||||
EXPECT_EQ(runtime->GetSettings().assemblyDirectory, ResolveProjectManagedOutputDirectory());
|
||||
EXPECT_EQ(runtime->GetSettings().appAssemblyPath, ResolveProjectGameScriptsDllPath());
|
||||
}
|
||||
|
||||
TEST_F(ProjectScriptAssemblyTest, DiscoversProjectAssetMonoBehaviourClassesAndFieldMetadata) {
|
||||
|
||||
Reference in New Issue
Block a user