feat(scripting): add field model editing and defaults support
This commit is contained in:
@@ -85,6 +85,42 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool TryGetClassFieldMetadata(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
std::vector<ScriptFieldMetadata>& outFields) const override {
|
||||
(void)assemblyName;
|
||||
(void)namespaceName;
|
||||
(void)className;
|
||||
outFields.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TrySetManagedFieldValue(
|
||||
const ScriptRuntimeContext& context,
|
||||
const std::string& fieldName,
|
||||
const ScriptFieldValue& value) override {
|
||||
(void)context;
|
||||
(void)fieldName;
|
||||
(void)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryGetManagedFieldValue(
|
||||
const ScriptRuntimeContext& context,
|
||||
const std::string& fieldName,
|
||||
ScriptFieldValue& outValue) const override {
|
||||
(void)context;
|
||||
(void)fieldName;
|
||||
(void)outValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
void SyncManagedFieldsToStorage(const ScriptRuntimeContext& context) override {
|
||||
(void)context;
|
||||
}
|
||||
|
||||
bool CreateScriptInstance(const ScriptRuntimeContext& context) override {
|
||||
if (m_events) {
|
||||
m_events->push_back("Create:" + Describe(context));
|
||||
|
||||
@@ -100,6 +100,48 @@ TEST_F(MonoScriptRuntimeTest, InitializesAndDiscoversConcreteMonoBehaviourClasse
|
||||
EXPECT_EQ(std::find(classNames.begin(), classNames.end(), "Gameplay.UtilityHelper"), classNames.end());
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ClassFieldMetadataListsSupportedPublicInstanceFields) {
|
||||
std::vector<ScriptFieldMetadata> fields;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetClassFieldMetadata("GameScripts", "Gameplay", "FieldMetadataProbe", fields));
|
||||
|
||||
const std::vector<ScriptFieldMetadata> expected = {
|
||||
{"Health", ScriptFieldType::Int32},
|
||||
{"Label", ScriptFieldType::String},
|
||||
{"SpawnPoint", ScriptFieldType::Vector3},
|
||||
{"Speed", ScriptFieldType::Float},
|
||||
{"Target", ScriptFieldType::GameObject},
|
||||
};
|
||||
|
||||
EXPECT_EQ(fields, expected);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ClassFieldMetadataQueryFailsForUnknownClass) {
|
||||
std::vector<ScriptFieldMetadata> fields = {
|
||||
{"Sentinel", ScriptFieldType::Bool},
|
||||
};
|
||||
|
||||
EXPECT_FALSE(runtime->TryGetClassFieldMetadata("GameScripts", "Gameplay", "MissingProbe", fields));
|
||||
EXPECT_TRUE(fields.empty());
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ClassFieldDefaultValueQueryReturnsManagedInitializers) {
|
||||
std::vector<ScriptFieldDefaultValue> fields;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetClassFieldDefaultValues("GameScripts", "Gameplay", "RuntimeGameObjectProbe", fields));
|
||||
|
||||
const auto fieldIt = std::find_if(
|
||||
fields.begin(),
|
||||
fields.end(),
|
||||
[](const ScriptFieldDefaultValue& field) {
|
||||
return field.fieldName == "ObservedRootChildCountAfterDestroy";
|
||||
});
|
||||
|
||||
ASSERT_NE(fieldIt, fields.end());
|
||||
EXPECT_EQ(fieldIt->type, ScriptFieldType::Int32);
|
||||
EXPECT_EQ(std::get<int32_t>(fieldIt->value), -1);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineAppliesStoredFieldsAndInvokesLifecycleMethods) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
@@ -234,6 +276,746 @@ TEST_F(MonoScriptRuntimeTest, ScriptEngineAppliesStoredFieldsAndInvokesLifecycle
|
||||
EXPECT_FLOAT_EQ(localRotation.w, 0.8660254f);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, DeserializedSceneRebindsManagedScriptsAndRestoresStoredFields) {
|
||||
Scene originalScene("SerializedMonoScene");
|
||||
GameObject* hostA = originalScene.CreateGameObject("HostA");
|
||||
GameObject* hostB = originalScene.CreateGameObject("HostB");
|
||||
GameObject* targetA = originalScene.CreateGameObject("TargetA");
|
||||
GameObject* targetB = originalScene.CreateGameObject("TargetB");
|
||||
|
||||
ScriptComponent* originalComponentA = AddScript(hostA, "Gameplay", "LifecycleProbe");
|
||||
ScriptComponent* originalComponentB = AddScript(hostB, "Gameplay", "LifecycleProbe");
|
||||
|
||||
originalComponentA->GetFieldStorage().SetFieldValue("Speed", 2.5f);
|
||||
originalComponentA->GetFieldStorage().SetFieldValue("Label", "Alpha");
|
||||
originalComponentA->GetFieldStorage().SetFieldValue("Target", GameObjectReference{targetA->GetUUID()});
|
||||
originalComponentA->GetFieldStorage().SetFieldValue("SpawnPoint", XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f));
|
||||
|
||||
originalComponentB->GetFieldStorage().SetFieldValue("Speed", 9.0f);
|
||||
originalComponentB->GetFieldStorage().SetFieldValue("Label", "Beta");
|
||||
originalComponentB->GetFieldStorage().SetFieldValue("Target", GameObjectReference{targetB->GetUUID()});
|
||||
originalComponentB->GetFieldStorage().SetFieldValue("SpawnPoint", XCEngine::Math::Vector3(4.0f, 5.0f, 6.0f));
|
||||
|
||||
const uint64_t originalUUIDA = originalComponentA->GetScriptComponentUUID();
|
||||
const uint64_t originalUUIDB = originalComponentB->GetScriptComponentUUID();
|
||||
const std::string serializedScene = originalScene.SerializeToString();
|
||||
|
||||
scene = std::make_unique<Scene>("LoadedMonoScene");
|
||||
scene->DeserializeFromString(serializedScene);
|
||||
Scene* runtimeScene = scene.get();
|
||||
|
||||
GameObject* loadedHostA = runtimeScene->Find("HostA");
|
||||
GameObject* loadedHostB = runtimeScene->Find("HostB");
|
||||
GameObject* loadedTargetA = runtimeScene->Find("TargetA");
|
||||
GameObject* loadedTargetB = runtimeScene->Find("TargetB");
|
||||
ASSERT_NE(loadedHostA, nullptr);
|
||||
ASSERT_NE(loadedHostB, nullptr);
|
||||
ASSERT_NE(loadedTargetA, nullptr);
|
||||
ASSERT_NE(loadedTargetB, nullptr);
|
||||
|
||||
ScriptComponent* loadedComponentA = FindScriptComponentByClass(loadedHostA, "Gameplay", "LifecycleProbe");
|
||||
ScriptComponent* loadedComponentB = FindScriptComponentByClass(loadedHostB, "Gameplay", "LifecycleProbe");
|
||||
ASSERT_NE(loadedComponentA, nullptr);
|
||||
ASSERT_NE(loadedComponentB, nullptr);
|
||||
|
||||
EXPECT_EQ(loadedComponentA->GetScriptComponentUUID(), originalUUIDA);
|
||||
EXPECT_EQ(loadedComponentB->GetScriptComponentUUID(), originalUUIDB);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
engine->OnFixedUpdate(0.02f);
|
||||
engine->OnUpdate(0.016f);
|
||||
engine->OnLateUpdate(0.016f);
|
||||
|
||||
EXPECT_TRUE(runtime->HasManagedInstance(loadedComponentA));
|
||||
EXPECT_TRUE(runtime->HasManagedInstance(loadedComponentB));
|
||||
EXPECT_EQ(runtime->GetManagedInstanceCount(), 2u);
|
||||
|
||||
int32_t awakeCountA = 0;
|
||||
int32_t startCountA = 0;
|
||||
int32_t awakeCountB = 0;
|
||||
int32_t startCountB = 0;
|
||||
bool targetResolvedA = false;
|
||||
bool targetResolvedB = false;
|
||||
float speedA = 0.0f;
|
||||
float speedB = 0.0f;
|
||||
std::string labelA;
|
||||
std::string labelB;
|
||||
std::string observedTargetNameA;
|
||||
std::string observedTargetNameB;
|
||||
GameObjectReference targetReferenceA;
|
||||
GameObjectReference targetReferenceB;
|
||||
GameObjectReference selfReferenceA;
|
||||
GameObjectReference selfReferenceB;
|
||||
XCEngine::Math::Vector3 spawnPointA;
|
||||
XCEngine::Math::Vector3 spawnPointB;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentA, "AwakeCount", awakeCountA));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentA, "StartCount", startCountA));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentA, "TargetResolved", targetResolvedA));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentA, "Speed", speedA));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentA, "Label", labelA));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentA, "ObservedTargetName", observedTargetNameA));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentA, "Target", targetReferenceA));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentA, "SelfReference", selfReferenceA));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentA, "SpawnPoint", spawnPointA));
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentB, "AwakeCount", awakeCountB));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentB, "StartCount", startCountB));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentB, "TargetResolved", targetResolvedB));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentB, "Speed", speedB));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentB, "Label", labelB));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentB, "ObservedTargetName", observedTargetNameB));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentB, "Target", targetReferenceB));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentB, "SelfReference", selfReferenceB));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponentB, "SpawnPoint", spawnPointB));
|
||||
|
||||
EXPECT_EQ(awakeCountA, 1);
|
||||
EXPECT_EQ(startCountA, 1);
|
||||
EXPECT_TRUE(targetResolvedA);
|
||||
EXPECT_FLOAT_EQ(speedA, 3.5f);
|
||||
EXPECT_EQ(labelA, "Alpha|Awake");
|
||||
EXPECT_EQ(observedTargetNameA, "TargetA");
|
||||
EXPECT_EQ(targetReferenceA, GameObjectReference{loadedTargetA->GetUUID()});
|
||||
EXPECT_EQ(selfReferenceA, GameObjectReference{loadedHostA->GetUUID()});
|
||||
EXPECT_EQ(spawnPointA, XCEngine::Math::Vector3(2.0f, 2.0f, 3.0f));
|
||||
|
||||
EXPECT_EQ(awakeCountB, 1);
|
||||
EXPECT_EQ(startCountB, 1);
|
||||
EXPECT_TRUE(targetResolvedB);
|
||||
EXPECT_FLOAT_EQ(speedB, 10.0f);
|
||||
EXPECT_EQ(labelB, "Beta|Awake");
|
||||
EXPECT_EQ(observedTargetNameB, "TargetB");
|
||||
EXPECT_EQ(targetReferenceB, GameObjectReference{loadedTargetB->GetUUID()});
|
||||
EXPECT_EQ(selfReferenceB, GameObjectReference{loadedHostB->GetUUID()});
|
||||
EXPECT_EQ(spawnPointB, XCEngine::Math::Vector3(5.0f, 5.0f, 6.0f));
|
||||
|
||||
EXPECT_EQ(loadedHostA->GetName(), "HostA_Managed");
|
||||
EXPECT_EQ(loadedHostB->GetName(), "HostB_Managed");
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ManagedFieldChangesWriteBackToStoredCacheAndPersistAcrossSceneRoundTrip) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
GameObject* target = runtimeScene->CreateGameObject("Target");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Speed", 5.0f);
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Configured");
|
||||
component->GetFieldStorage().SetFieldValue("Target", GameObjectReference{target->GetUUID()});
|
||||
component->GetFieldStorage().SetFieldValue("SpawnPoint", XCEngine::Math::Vector3(2.0f, 4.0f, 6.0f));
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
engine->OnFixedUpdate(0.02f);
|
||||
engine->OnUpdate(0.016f);
|
||||
engine->OnLateUpdate(0.016f);
|
||||
|
||||
float storedSpeed = 0.0f;
|
||||
std::string storedLabel;
|
||||
GameObjectReference storedTarget;
|
||||
XCEngine::Math::Vector3 storedSpawnPoint;
|
||||
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Speed", storedSpeed));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", storedLabel));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Target", storedTarget));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("SpawnPoint", storedSpawnPoint));
|
||||
|
||||
EXPECT_FLOAT_EQ(storedSpeed, 6.0f);
|
||||
EXPECT_EQ(storedLabel, "Configured|Awake");
|
||||
EXPECT_EQ(storedTarget, GameObjectReference{target->GetUUID()});
|
||||
EXPECT_EQ(storedSpawnPoint, XCEngine::Math::Vector3(3.0f, 4.0f, 6.0f));
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("AwakeCount"));
|
||||
|
||||
const std::string persistedHostName = host->GetName();
|
||||
const std::string serializedScene = runtimeScene->SerializeToString();
|
||||
|
||||
engine->OnRuntimeStop();
|
||||
|
||||
scene = std::make_unique<Scene>("ReloadedMonoScene");
|
||||
scene->DeserializeFromString(serializedScene);
|
||||
Scene* reloadedScene = scene.get();
|
||||
|
||||
GameObject* loadedHost = reloadedScene->Find(persistedHostName);
|
||||
GameObject* loadedTarget = reloadedScene->Find("Target");
|
||||
ASSERT_NE(loadedHost, nullptr);
|
||||
ASSERT_NE(loadedTarget, nullptr);
|
||||
|
||||
ScriptComponent* loadedComponent = FindScriptComponentByClass(loadedHost, "Gameplay", "LifecycleProbe");
|
||||
ASSERT_NE(loadedComponent, nullptr);
|
||||
|
||||
float loadedStoredSpeed = 0.0f;
|
||||
std::string loadedStoredLabel;
|
||||
GameObjectReference loadedStoredTarget;
|
||||
XCEngine::Math::Vector3 loadedStoredSpawnPoint;
|
||||
|
||||
EXPECT_TRUE(loadedComponent->GetFieldStorage().TryGetFieldValue("Speed", loadedStoredSpeed));
|
||||
EXPECT_TRUE(loadedComponent->GetFieldStorage().TryGetFieldValue("Label", loadedStoredLabel));
|
||||
EXPECT_TRUE(loadedComponent->GetFieldStorage().TryGetFieldValue("Target", loadedStoredTarget));
|
||||
EXPECT_TRUE(loadedComponent->GetFieldStorage().TryGetFieldValue("SpawnPoint", loadedStoredSpawnPoint));
|
||||
|
||||
EXPECT_FLOAT_EQ(loadedStoredSpeed, 6.0f);
|
||||
EXPECT_EQ(loadedStoredLabel, "Configured|Awake");
|
||||
EXPECT_EQ(loadedStoredTarget, GameObjectReference{loadedTarget->GetUUID()});
|
||||
EXPECT_EQ(loadedStoredSpawnPoint, XCEngine::Math::Vector3(3.0f, 4.0f, 6.0f));
|
||||
EXPECT_FALSE(loadedComponent->GetFieldStorage().Contains("AwakeCount"));
|
||||
|
||||
engine->OnRuntimeStart(reloadedScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
int32_t awakeCount = 0;
|
||||
int32_t startCount = 0;
|
||||
int32_t updateCount = 0;
|
||||
float runtimeSpeed = 0.0f;
|
||||
std::string runtimeLabel;
|
||||
std::string observedTargetName;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "AwakeCount", awakeCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "StartCount", startCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "UpdateCount", updateCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "Speed", runtimeSpeed));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "Label", runtimeLabel));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(loadedComponent, "ObservedTargetName", observedTargetName));
|
||||
|
||||
EXPECT_EQ(awakeCount, 1);
|
||||
EXPECT_EQ(startCount, 1);
|
||||
EXPECT_EQ(updateCount, 1);
|
||||
EXPECT_FLOAT_EQ(runtimeSpeed, 7.0f);
|
||||
EXPECT_EQ(runtimeLabel, "Configured|Awake|Awake");
|
||||
EXPECT_EQ(observedTargetName, "Target");
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldApiUpdatesLiveManagedInstanceAndStoredCache) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
GameObject* target = runtimeScene->CreateGameObject("Target");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "Speed", 41.0f));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "Label", "Edited"));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "Target", GameObjectReference{target->GetUUID()}));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "SpawnPoint", XCEngine::Math::Vector3(10.0f, 20.0f, 30.0f)));
|
||||
|
||||
EXPECT_FALSE(engine->TrySetScriptFieldValue(component, "DoesNotExist", int32_t(1)));
|
||||
EXPECT_FALSE(engine->TrySetScriptFieldValue(component, "Speed", std::string("wrong")));
|
||||
|
||||
float storedSpeedBeforeUpdate = 0.0f;
|
||||
std::string storedLabelBeforeUpdate;
|
||||
GameObjectReference storedTargetBeforeUpdate;
|
||||
XCEngine::Math::Vector3 storedSpawnPointBeforeUpdate;
|
||||
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Speed", storedSpeedBeforeUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", storedLabelBeforeUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Target", storedTargetBeforeUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("SpawnPoint", storedSpawnPointBeforeUpdate));
|
||||
|
||||
EXPECT_FLOAT_EQ(storedSpeedBeforeUpdate, 41.0f);
|
||||
EXPECT_EQ(storedLabelBeforeUpdate, "Edited");
|
||||
EXPECT_EQ(storedTargetBeforeUpdate, GameObjectReference{target->GetUUID()});
|
||||
EXPECT_EQ(storedSpawnPointBeforeUpdate, XCEngine::Math::Vector3(10.0f, 20.0f, 30.0f));
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("DoesNotExist"));
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
engine->OnLateUpdate(0.016f);
|
||||
|
||||
int32_t startCount = 0;
|
||||
bool targetResolved = false;
|
||||
float runtimeSpeed = 0.0f;
|
||||
std::string runtimeLabel;
|
||||
std::string observedTargetName;
|
||||
XCEngine::Math::Vector3 runtimeSpawnPoint;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "StartCount", startCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "TargetResolved", targetResolved));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "Speed", runtimeSpeed));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "Label", runtimeLabel));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedTargetName", observedTargetName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "SpawnPoint", runtimeSpawnPoint));
|
||||
|
||||
EXPECT_EQ(startCount, 1);
|
||||
EXPECT_TRUE(targetResolved);
|
||||
EXPECT_FLOAT_EQ(runtimeSpeed, 42.0f);
|
||||
EXPECT_EQ(runtimeLabel, "Edited");
|
||||
EXPECT_EQ(observedTargetName, "Target");
|
||||
EXPECT_EQ(runtimeSpawnPoint, XCEngine::Math::Vector3(11.0f, 20.0f, 30.0f));
|
||||
|
||||
float storedSpeedAfterUpdate = 0.0f;
|
||||
std::string storedLabelAfterUpdate;
|
||||
GameObjectReference storedTargetAfterUpdate;
|
||||
XCEngine::Math::Vector3 storedSpawnPointAfterUpdate;
|
||||
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Speed", storedSpeedAfterUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", storedLabelAfterUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Target", storedTargetAfterUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("SpawnPoint", storedSpawnPointAfterUpdate));
|
||||
|
||||
EXPECT_FLOAT_EQ(storedSpeedAfterUpdate, 42.0f);
|
||||
EXPECT_EQ(storedLabelAfterUpdate, "Edited");
|
||||
EXPECT_EQ(storedTargetAfterUpdate, GameObjectReference{target->GetUUID()});
|
||||
EXPECT_EQ(storedSpawnPointAfterUpdate, XCEngine::Math::Vector3(11.0f, 20.0f, 30.0f));
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldApiCachesValuesBeforeRuntimeStartAndAppliesThemOnFirstInstance) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
GameObject* target = runtimeScene->CreateGameObject("Target");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe");
|
||||
|
||||
EXPECT_FALSE(runtime->HasManagedInstance(component));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "Speed", 9.0f));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "Label", "PreStart"));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "Target", GameObjectReference{target->GetUUID()}));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "SpawnPoint", XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f)));
|
||||
|
||||
float storedSpeedBeforeRuntime = 0.0f;
|
||||
std::string storedLabelBeforeRuntime;
|
||||
GameObjectReference storedTargetBeforeRuntime;
|
||||
XCEngine::Math::Vector3 storedSpawnPointBeforeRuntime;
|
||||
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Speed", storedSpeedBeforeRuntime));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", storedLabelBeforeRuntime));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Target", storedTargetBeforeRuntime));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("SpawnPoint", storedSpawnPointBeforeRuntime));
|
||||
|
||||
EXPECT_FLOAT_EQ(storedSpeedBeforeRuntime, 9.0f);
|
||||
EXPECT_EQ(storedLabelBeforeRuntime, "PreStart");
|
||||
EXPECT_EQ(storedTargetBeforeRuntime, GameObjectReference{target->GetUUID()});
|
||||
EXPECT_EQ(storedSpawnPointBeforeRuntime, XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f));
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
EXPECT_TRUE(runtime->HasManagedInstance(component));
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
engine->OnLateUpdate(0.016f);
|
||||
|
||||
int32_t awakeCount = 0;
|
||||
int32_t startCount = 0;
|
||||
bool targetResolved = false;
|
||||
float runtimeSpeed = 0.0f;
|
||||
std::string runtimeLabel;
|
||||
std::string observedTargetName;
|
||||
XCEngine::Math::Vector3 runtimeSpawnPoint;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "AwakeCount", awakeCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "StartCount", startCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "TargetResolved", targetResolved));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "Speed", runtimeSpeed));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "Label", runtimeLabel));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedTargetName", observedTargetName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "SpawnPoint", runtimeSpawnPoint));
|
||||
|
||||
EXPECT_EQ(awakeCount, 1);
|
||||
EXPECT_EQ(startCount, 1);
|
||||
EXPECT_TRUE(targetResolved);
|
||||
EXPECT_FLOAT_EQ(runtimeSpeed, 10.0f);
|
||||
EXPECT_EQ(runtimeLabel, "PreStart|Awake");
|
||||
EXPECT_EQ(observedTargetName, "Target");
|
||||
EXPECT_EQ(runtimeSpawnPoint, XCEngine::Math::Vector3(4.0f, 4.0f, 5.0f));
|
||||
|
||||
float storedSpeedAfterUpdate = 0.0f;
|
||||
std::string storedLabelAfterUpdate;
|
||||
GameObjectReference storedTargetAfterUpdate;
|
||||
XCEngine::Math::Vector3 storedSpawnPointAfterUpdate;
|
||||
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Speed", storedSpeedAfterUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", storedLabelAfterUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Target", storedTargetAfterUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("SpawnPoint", storedSpawnPointAfterUpdate));
|
||||
|
||||
EXPECT_FLOAT_EQ(storedSpeedAfterUpdate, 10.0f);
|
||||
EXPECT_EQ(storedLabelAfterUpdate, "PreStart|Awake");
|
||||
EXPECT_EQ(storedTargetAfterUpdate, GameObjectReference{target->GetUUID()});
|
||||
EXPECT_EQ(storedSpawnPointAfterUpdate, XCEngine::Math::Vector3(4.0f, 4.0f, 5.0f));
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldApiCachesValuesForInactiveRuntimeScriptUntilActivation) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
GameObject* target = runtimeScene->CreateGameObject("Target");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe");
|
||||
host->SetActive(false);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
EXPECT_FALSE(runtime->HasManagedInstance(component));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "Speed", 12.0f));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "Label", "Dormant"));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "Target", GameObjectReference{target->GetUUID()}));
|
||||
EXPECT_TRUE(engine->TrySetScriptFieldValue(component, "SpawnPoint", XCEngine::Math::Vector3(5.0f, 6.0f, 7.0f)));
|
||||
|
||||
float storedSpeedBeforeActivation = 0.0f;
|
||||
std::string storedLabelBeforeActivation;
|
||||
GameObjectReference storedTargetBeforeActivation;
|
||||
XCEngine::Math::Vector3 storedSpawnPointBeforeActivation;
|
||||
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Speed", storedSpeedBeforeActivation));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", storedLabelBeforeActivation));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Target", storedTargetBeforeActivation));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("SpawnPoint", storedSpawnPointBeforeActivation));
|
||||
|
||||
EXPECT_FLOAT_EQ(storedSpeedBeforeActivation, 12.0f);
|
||||
EXPECT_EQ(storedLabelBeforeActivation, "Dormant");
|
||||
EXPECT_EQ(storedTargetBeforeActivation, GameObjectReference{target->GetUUID()});
|
||||
EXPECT_EQ(storedSpawnPointBeforeActivation, XCEngine::Math::Vector3(5.0f, 6.0f, 7.0f));
|
||||
|
||||
host->SetActive(true);
|
||||
engine->OnUpdate(0.016f);
|
||||
engine->OnLateUpdate(0.016f);
|
||||
|
||||
EXPECT_TRUE(runtime->HasManagedInstance(component));
|
||||
|
||||
int32_t awakeCount = 0;
|
||||
int32_t startCount = 0;
|
||||
bool targetResolved = false;
|
||||
float runtimeSpeed = 0.0f;
|
||||
std::string runtimeLabel;
|
||||
std::string observedTargetName;
|
||||
XCEngine::Math::Vector3 runtimeSpawnPoint;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "AwakeCount", awakeCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "StartCount", startCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "TargetResolved", targetResolved));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "Speed", runtimeSpeed));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "Label", runtimeLabel));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedTargetName", observedTargetName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "SpawnPoint", runtimeSpawnPoint));
|
||||
|
||||
EXPECT_EQ(awakeCount, 1);
|
||||
EXPECT_EQ(startCount, 1);
|
||||
EXPECT_TRUE(targetResolved);
|
||||
EXPECT_FLOAT_EQ(runtimeSpeed, 13.0f);
|
||||
EXPECT_EQ(runtimeLabel, "Dormant|Awake");
|
||||
EXPECT_EQ(observedTargetName, "Target");
|
||||
EXPECT_EQ(runtimeSpawnPoint, XCEngine::Math::Vector3(6.0f, 6.0f, 7.0f));
|
||||
|
||||
float storedSpeedAfterUpdate = 0.0f;
|
||||
std::string storedLabelAfterUpdate;
|
||||
GameObjectReference storedTargetAfterUpdate;
|
||||
XCEngine::Math::Vector3 storedSpawnPointAfterUpdate;
|
||||
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Speed", storedSpeedAfterUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", storedLabelAfterUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Target", storedTargetAfterUpdate));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("SpawnPoint", storedSpawnPointAfterUpdate));
|
||||
|
||||
EXPECT_FLOAT_EQ(storedSpeedAfterUpdate, 13.0f);
|
||||
EXPECT_EQ(storedLabelAfterUpdate, "Dormant|Awake");
|
||||
EXPECT_EQ(storedTargetAfterUpdate, GameObjectReference{target->GetUUID()});
|
||||
EXPECT_EQ(storedSpawnPointAfterUpdate, XCEngine::Math::Vector3(6.0f, 6.0f, 7.0f));
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldBatchApiUpdatesLiveManagedInstanceAndReportsPerFieldStatus) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
GameObject* target = runtimeScene->CreateGameObject("Target");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("LegacyOnly", uint64_t(99));
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
const std::vector<ScriptFieldWriteRequest> requests = {
|
||||
{"Speed", ScriptFieldType::Float, ScriptFieldValue(41.0f)},
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(std::string("BatchEdited"))},
|
||||
{"Target", ScriptFieldType::GameObject, ScriptFieldValue(GameObjectReference{target->GetUUID()})},
|
||||
{"SpawnPoint", ScriptFieldType::Vector3, ScriptFieldValue(XCEngine::Math::Vector3(10.0f, 20.0f, 30.0f))},
|
||||
{"LegacyOnly", ScriptFieldType::UInt64, ScriptFieldValue(uint64_t(100))},
|
||||
{"DoesNotExist", ScriptFieldType::Int32, ScriptFieldValue(int32_t(1))},
|
||||
{"Speed", ScriptFieldType::String, ScriptFieldValue(std::string("wrong"))},
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(int32_t(5))}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldWriteResult> results;
|
||||
EXPECT_FALSE(engine->ApplyScriptFieldWrites(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldWriteStatus::Applied);
|
||||
EXPECT_EQ(results[1].status, ScriptFieldWriteStatus::Applied);
|
||||
EXPECT_EQ(results[2].status, ScriptFieldWriteStatus::Applied);
|
||||
EXPECT_EQ(results[3].status, ScriptFieldWriteStatus::Applied);
|
||||
EXPECT_EQ(results[4].status, ScriptFieldWriteStatus::StoredOnlyField);
|
||||
EXPECT_EQ(results[5].status, ScriptFieldWriteStatus::UnknownField);
|
||||
EXPECT_EQ(results[6].status, ScriptFieldWriteStatus::TypeMismatch);
|
||||
EXPECT_EQ(results[7].status, ScriptFieldWriteStatus::InvalidValue);
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
engine->OnLateUpdate(0.016f);
|
||||
|
||||
int32_t startCount = 0;
|
||||
bool targetResolved = false;
|
||||
float runtimeSpeed = 0.0f;
|
||||
std::string runtimeLabel;
|
||||
std::string observedTargetName;
|
||||
XCEngine::Math::Vector3 runtimeSpawnPoint;
|
||||
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "StartCount", startCount));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "TargetResolved", targetResolved));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "Speed", runtimeSpeed));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "Label", runtimeLabel));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedTargetName", observedTargetName));
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "SpawnPoint", runtimeSpawnPoint));
|
||||
|
||||
EXPECT_EQ(startCount, 1);
|
||||
EXPECT_TRUE(targetResolved);
|
||||
EXPECT_FLOAT_EQ(runtimeSpeed, 42.0f);
|
||||
EXPECT_EQ(runtimeLabel, "BatchEdited");
|
||||
EXPECT_EQ(observedTargetName, "Target");
|
||||
EXPECT_EQ(runtimeSpawnPoint, XCEngine::Math::Vector3(11.0f, 20.0f, 30.0f));
|
||||
|
||||
float storedSpeed = 0.0f;
|
||||
std::string storedLabel;
|
||||
GameObjectReference storedTarget;
|
||||
XCEngine::Math::Vector3 storedSpawnPoint;
|
||||
uint64_t storedLegacyOnly = 0;
|
||||
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Speed", storedSpeed));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", storedLabel));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Target", storedTarget));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("SpawnPoint", storedSpawnPoint));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("LegacyOnly", storedLegacyOnly));
|
||||
|
||||
EXPECT_FLOAT_EQ(storedSpeed, 42.0f);
|
||||
EXPECT_EQ(storedLabel, "BatchEdited");
|
||||
EXPECT_EQ(storedTarget, GameObjectReference{target->GetUUID()});
|
||||
EXPECT_EQ(storedSpawnPoint, XCEngine::Math::Vector3(11.0f, 20.0f, 30.0f));
|
||||
EXPECT_EQ(storedLegacyOnly, 99u);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldModelUsesManagedClassDefaultValuesBeforeRuntimeStart) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "RuntimeGameObjectProbe");
|
||||
|
||||
ScriptFieldModel model;
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
EXPECT_EQ(model.classStatus, ScriptFieldClassStatus::Available);
|
||||
|
||||
const auto fieldIt = std::find_if(
|
||||
model.fields.begin(),
|
||||
model.fields.end(),
|
||||
[](const ScriptFieldSnapshot& field) {
|
||||
return field.metadata.name == "ObservedRootChildCountAfterDestroy";
|
||||
});
|
||||
|
||||
ASSERT_NE(fieldIt, model.fields.end());
|
||||
EXPECT_TRUE(fieldIt->declaredInClass);
|
||||
EXPECT_TRUE(fieldIt->hasDefaultValue);
|
||||
EXPECT_FALSE(fieldIt->hasValue);
|
||||
EXPECT_EQ(fieldIt->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(std::get<int32_t>(fieldIt->defaultValue), -1);
|
||||
EXPECT_EQ(std::get<int32_t>(fieldIt->value), -1);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldClearApiRestoresManagedClassDefaultValuesAndRemovesStoredOverrides) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "RuntimeGameObjectProbe");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("ObservedRootChildCountAfterDestroy", int32_t(7));
|
||||
component->GetFieldStorage().SetFieldValue("LegacyOnly", uint64_t(99));
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
const std::vector<ScriptFieldClearRequest> requests = {
|
||||
{"ObservedRootChildCountAfterDestroy"},
|
||||
{"LegacyOnly"},
|
||||
{"DoesNotExist"},
|
||||
{""}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldClearResult> results;
|
||||
EXPECT_FALSE(engine->ClearScriptFieldOverrides(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldClearStatus::Applied);
|
||||
EXPECT_EQ(results[1].status, ScriptFieldClearStatus::Applied);
|
||||
EXPECT_EQ(results[2].status, ScriptFieldClearStatus::UnknownField);
|
||||
EXPECT_EQ(results[3].status, ScriptFieldClearStatus::EmptyFieldName);
|
||||
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("ObservedRootChildCountAfterDestroy"));
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("LegacyOnly"));
|
||||
|
||||
int32_t runtimeObservedCountAfterDestroy = 0;
|
||||
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRootChildCountAfterDestroy", runtimeObservedCountAfterDestroy));
|
||||
EXPECT_EQ(runtimeObservedCountAfterDestroy, -1);
|
||||
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("ObservedRootChildCountAfterDestroy"));
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("LegacyOnly"));
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldReadApiReturnsManagedOnlyRuntimeValues) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe");
|
||||
|
||||
float speed = 0.0f;
|
||||
int32_t awakeCount = 0;
|
||||
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("Speed"));
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("AwakeCount"));
|
||||
EXPECT_FALSE(engine->TryGetScriptFieldValue(component, "Speed", speed));
|
||||
EXPECT_FALSE(engine->TryGetScriptFieldValue(component, "AwakeCount", awakeCount));
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
EXPECT_TRUE(engine->TryGetScriptFieldValue(component, "AwakeCount", awakeCount));
|
||||
EXPECT_EQ(awakeCount, 1);
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("AwakeCount"));
|
||||
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
EXPECT_TRUE(engine->TryGetScriptFieldValue(component, "Speed", speed));
|
||||
EXPECT_FLOAT_EQ(speed, 1.0f);
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("Speed"));
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldSnapshotApiReportsMetadataAndCurrentValues) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("AwakeCount", uint64_t(7));
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
component->GetFieldStorage().SetFieldValue("LegacyOnly", uint64_t(99));
|
||||
|
||||
ScriptFieldModel model;
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
EXPECT_EQ(model.classStatus, ScriptFieldClassStatus::Available);
|
||||
|
||||
std::vector<ScriptFieldSnapshot>& snapshots = model.fields;
|
||||
|
||||
const auto findSnapshot = [&snapshots](const std::string& fieldName) -> const ScriptFieldSnapshot* {
|
||||
const auto it = std::find_if(
|
||||
snapshots.begin(),
|
||||
snapshots.end(),
|
||||
[&fieldName](const ScriptFieldSnapshot& snapshot) {
|
||||
return snapshot.metadata.name == fieldName;
|
||||
});
|
||||
return it != snapshots.end() ? &(*it) : nullptr;
|
||||
};
|
||||
|
||||
const ScriptFieldSnapshot* awakeSnapshot = findSnapshot("AwakeCount");
|
||||
const ScriptFieldSnapshot* labelSnapshot = findSnapshot("Label");
|
||||
const ScriptFieldSnapshot* speedSnapshot = findSnapshot("Speed");
|
||||
const ScriptFieldSnapshot* legacySnapshot = findSnapshot("LegacyOnly");
|
||||
|
||||
ASSERT_NE(awakeSnapshot, nullptr);
|
||||
ASSERT_NE(labelSnapshot, nullptr);
|
||||
ASSERT_NE(speedSnapshot, nullptr);
|
||||
ASSERT_NE(legacySnapshot, nullptr);
|
||||
|
||||
EXPECT_EQ(awakeSnapshot->metadata.type, ScriptFieldType::Int32);
|
||||
EXPECT_TRUE(awakeSnapshot->declaredInClass);
|
||||
EXPECT_TRUE(awakeSnapshot->hasDefaultValue);
|
||||
EXPECT_FALSE(awakeSnapshot->hasValue);
|
||||
EXPECT_EQ(awakeSnapshot->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(awakeSnapshot->issue, ScriptFieldIssue::TypeMismatch);
|
||||
EXPECT_TRUE(awakeSnapshot->hasStoredValue);
|
||||
EXPECT_EQ(awakeSnapshot->storedType, ScriptFieldType::UInt64);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->defaultValue), 0);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->value), 0);
|
||||
EXPECT_EQ(std::get<uint64_t>(awakeSnapshot->storedValue), 7u);
|
||||
|
||||
EXPECT_EQ(labelSnapshot->metadata.type, ScriptFieldType::String);
|
||||
EXPECT_TRUE(labelSnapshot->declaredInClass);
|
||||
EXPECT_TRUE(labelSnapshot->hasDefaultValue);
|
||||
EXPECT_TRUE(labelSnapshot->hasValue);
|
||||
EXPECT_EQ(labelSnapshot->valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(labelSnapshot->issue, ScriptFieldIssue::None);
|
||||
EXPECT_TRUE(labelSnapshot->hasStoredValue);
|
||||
EXPECT_EQ(labelSnapshot->storedType, ScriptFieldType::String);
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->defaultValue), "");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->value), "Stored");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->storedValue), "Stored");
|
||||
|
||||
EXPECT_EQ(speedSnapshot->metadata.type, ScriptFieldType::Float);
|
||||
EXPECT_TRUE(speedSnapshot->declaredInClass);
|
||||
EXPECT_TRUE(speedSnapshot->hasDefaultValue);
|
||||
EXPECT_FALSE(speedSnapshot->hasValue);
|
||||
EXPECT_EQ(speedSnapshot->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(speedSnapshot->issue, ScriptFieldIssue::None);
|
||||
EXPECT_FLOAT_EQ(std::get<float>(speedSnapshot->defaultValue), 0.0f);
|
||||
EXPECT_FLOAT_EQ(std::get<float>(speedSnapshot->value), 0.0f);
|
||||
|
||||
EXPECT_EQ(legacySnapshot->metadata.type, ScriptFieldType::UInt64);
|
||||
EXPECT_FALSE(legacySnapshot->declaredInClass);
|
||||
EXPECT_FALSE(legacySnapshot->hasDefaultValue);
|
||||
EXPECT_TRUE(legacySnapshot->hasValue);
|
||||
EXPECT_EQ(legacySnapshot->valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(legacySnapshot->issue, ScriptFieldIssue::StoredOnly);
|
||||
EXPECT_TRUE(legacySnapshot->hasStoredValue);
|
||||
EXPECT_EQ(legacySnapshot->storedType, ScriptFieldType::UInt64);
|
||||
EXPECT_EQ(std::get<uint64_t>(legacySnapshot->value), 99u);
|
||||
EXPECT_EQ(std::get<uint64_t>(legacySnapshot->storedValue), 99u);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
engine->OnUpdate(0.016f);
|
||||
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
EXPECT_EQ(model.classStatus, ScriptFieldClassStatus::Available);
|
||||
awakeSnapshot = findSnapshot("AwakeCount");
|
||||
labelSnapshot = findSnapshot("Label");
|
||||
speedSnapshot = findSnapshot("Speed");
|
||||
legacySnapshot = findSnapshot("LegacyOnly");
|
||||
|
||||
ASSERT_NE(awakeSnapshot, nullptr);
|
||||
ASSERT_NE(labelSnapshot, nullptr);
|
||||
ASSERT_NE(speedSnapshot, nullptr);
|
||||
ASSERT_NE(legacySnapshot, nullptr);
|
||||
|
||||
EXPECT_TRUE(awakeSnapshot->hasValue);
|
||||
EXPECT_TRUE(awakeSnapshot->hasDefaultValue);
|
||||
EXPECT_EQ(awakeSnapshot->valueSource, ScriptFieldValueSource::ManagedValue);
|
||||
EXPECT_EQ(awakeSnapshot->issue, ScriptFieldIssue::TypeMismatch);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->defaultValue), 0);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->value), 1);
|
||||
EXPECT_TRUE(labelSnapshot->hasValue);
|
||||
EXPECT_TRUE(labelSnapshot->hasDefaultValue);
|
||||
EXPECT_EQ(labelSnapshot->valueSource, ScriptFieldValueSource::ManagedValue);
|
||||
EXPECT_EQ(labelSnapshot->issue, ScriptFieldIssue::None);
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->defaultValue), "");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->value), "Stored|Awake");
|
||||
EXPECT_TRUE(speedSnapshot->hasValue);
|
||||
EXPECT_TRUE(speedSnapshot->hasDefaultValue);
|
||||
EXPECT_EQ(speedSnapshot->valueSource, ScriptFieldValueSource::ManagedValue);
|
||||
EXPECT_EQ(speedSnapshot->issue, ScriptFieldIssue::None);
|
||||
EXPECT_FLOAT_EQ(std::get<float>(speedSnapshot->defaultValue), 0.0f);
|
||||
EXPECT_FLOAT_EQ(std::get<float>(speedSnapshot->value), 1.0f);
|
||||
EXPECT_TRUE(legacySnapshot->hasValue);
|
||||
EXPECT_FALSE(legacySnapshot->hasDefaultValue);
|
||||
EXPECT_EQ(legacySnapshot->valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(legacySnapshot->issue, ScriptFieldIssue::StoredOnly);
|
||||
EXPECT_EQ(std::get<uint64_t>(legacySnapshot->value), 99u);
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, ScriptEngineFieldModelReportsMissingScriptClassAndStoredFields) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScript(host, "Gameplay", "MissingLifecycleProbe");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
component->GetFieldStorage().SetFieldValue("LegacyOnly", uint64_t(9));
|
||||
|
||||
ScriptFieldModel model;
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
|
||||
EXPECT_EQ(model.classStatus, ScriptFieldClassStatus::Missing);
|
||||
ASSERT_EQ(model.fields.size(), 2u);
|
||||
EXPECT_EQ(model.fields[0].metadata.name, "Label");
|
||||
EXPECT_EQ(model.fields[1].metadata.name, "LegacyOnly");
|
||||
|
||||
for (const ScriptFieldSnapshot& field : model.fields) {
|
||||
EXPECT_FALSE(field.declaredInClass);
|
||||
EXPECT_FALSE(field.hasDefaultValue);
|
||||
EXPECT_TRUE(field.hasValue);
|
||||
EXPECT_EQ(field.valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(field.issue, ScriptFieldIssue::StoredOnly);
|
||||
EXPECT_TRUE(field.hasStoredValue);
|
||||
EXPECT_EQ(field.metadata.type, field.storedType);
|
||||
EXPECT_EQ(field.value, field.storedValue);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MonoScriptRuntimeTest, GameObjectComponentApiResolvesTransformAndRejectsUnsupportedManagedTypes) {
|
||||
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
#include <XCEngine/Scripting/ScriptComponent.h>
|
||||
#include <XCEngine/Scripting/ScriptEngine.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -40,6 +42,59 @@ public:
|
||||
events.push_back("RuntimeStop:" + (scene ? scene->GetName() : std::string("null")));
|
||||
}
|
||||
|
||||
bool TryGetClassFieldMetadata(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
std::vector<ScriptFieldMetadata>& outFields) const override {
|
||||
(void)assemblyName;
|
||||
(void)namespaceName;
|
||||
(void)className;
|
||||
outFields = fieldMetadata;
|
||||
return !outFields.empty();
|
||||
}
|
||||
|
||||
bool TryGetClassFieldDefaultValues(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
std::vector<ScriptFieldDefaultValue>& outFields) const override {
|
||||
(void)assemblyName;
|
||||
(void)namespaceName;
|
||||
(void)className;
|
||||
outFields = fieldDefaultValues;
|
||||
return !outFields.empty();
|
||||
}
|
||||
|
||||
bool TrySetManagedFieldValue(
|
||||
const ScriptRuntimeContext& context,
|
||||
const std::string& fieldName,
|
||||
const ScriptFieldValue& value) override {
|
||||
(void)context;
|
||||
managedFieldValues[fieldName] = value;
|
||||
managedSetFieldNames.push_back(fieldName);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryGetManagedFieldValue(
|
||||
const ScriptRuntimeContext& context,
|
||||
const std::string& fieldName,
|
||||
ScriptFieldValue& outValue) const override {
|
||||
(void)context;
|
||||
|
||||
const auto it = managedFieldValues.find(fieldName);
|
||||
if (it == managedFieldValues.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SyncManagedFieldsToStorage(const ScriptRuntimeContext& context) override {
|
||||
(void)context;
|
||||
}
|
||||
|
||||
bool CreateScriptInstance(const ScriptRuntimeContext& context) override {
|
||||
events.push_back("Create:" + Describe(context));
|
||||
return context.component != nullptr;
|
||||
@@ -62,6 +117,10 @@ public:
|
||||
}
|
||||
|
||||
std::vector<std::string> events;
|
||||
std::vector<ScriptFieldMetadata> fieldMetadata;
|
||||
std::vector<ScriptFieldDefaultValue> fieldDefaultValues;
|
||||
std::unordered_map<std::string, ScriptFieldValue> managedFieldValues;
|
||||
std::vector<std::string> managedSetFieldNames;
|
||||
|
||||
private:
|
||||
static std::string Describe(const ScriptRuntimeContext& context) {
|
||||
@@ -266,4 +325,389 @@ TEST_F(ScriptEngineTest, RuntimeCreatedScriptComponentIsTrackedImmediatelyAndSta
|
||||
EXPECT_EQ(runtime.events, expectedAfterUpdate);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldReadApiPrefersLiveManagedValueAndFallsBackToStoredValue) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeReadback");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
|
||||
std::string label;
|
||||
int32_t awakeCount = 0;
|
||||
|
||||
EXPECT_TRUE(engine->TryGetScriptFieldValue(component, "Label", label));
|
||||
EXPECT_EQ(label, "Stored");
|
||||
EXPECT_FALSE(engine->TryGetScriptFieldValue(component, "AwakeCount", awakeCount));
|
||||
|
||||
runtime.managedFieldValues["Label"] = std::string("Live");
|
||||
runtime.managedFieldValues["AwakeCount"] = int32_t(1);
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
EXPECT_TRUE(engine->TryGetScriptFieldValue(component, "Label", label));
|
||||
EXPECT_EQ(label, "Live");
|
||||
EXPECT_TRUE(engine->TryGetScriptFieldValue(component, "AwakeCount", awakeCount));
|
||||
EXPECT_EQ(awakeCount, 1);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldSnapshotApiCombinesMetadataStoredValuesAndLiveManagedValues) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeSnapshot");
|
||||
|
||||
runtime.fieldMetadata = {
|
||||
{"AwakeCount", ScriptFieldType::Int32},
|
||||
{"Label", ScriptFieldType::String}
|
||||
};
|
||||
component->GetFieldStorage().SetFieldValue("AwakeCount", uint64_t(7));
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
component->GetFieldStorage().SetFieldValue("LegacyOnly", uint64_t(99));
|
||||
|
||||
ScriptFieldModel model;
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
EXPECT_EQ(model.classStatus, ScriptFieldClassStatus::Available);
|
||||
|
||||
std::vector<ScriptFieldSnapshot>& snapshots = model.fields;
|
||||
ASSERT_EQ(snapshots.size(), 3u);
|
||||
|
||||
const auto findSnapshot = [&snapshots](const std::string& fieldName) -> const ScriptFieldSnapshot* {
|
||||
const auto it = std::find_if(
|
||||
snapshots.begin(),
|
||||
snapshots.end(),
|
||||
[&fieldName](const ScriptFieldSnapshot& snapshot) {
|
||||
return snapshot.metadata.name == fieldName;
|
||||
});
|
||||
return it != snapshots.end() ? &(*it) : nullptr;
|
||||
};
|
||||
|
||||
const ScriptFieldSnapshot* awakeSnapshot = findSnapshot("AwakeCount");
|
||||
const ScriptFieldSnapshot* labelSnapshot = findSnapshot("Label");
|
||||
const ScriptFieldSnapshot* legacySnapshot = findSnapshot("LegacyOnly");
|
||||
|
||||
ASSERT_NE(awakeSnapshot, nullptr);
|
||||
ASSERT_NE(labelSnapshot, nullptr);
|
||||
ASSERT_NE(legacySnapshot, nullptr);
|
||||
|
||||
EXPECT_EQ(awakeSnapshot->metadata.type, ScriptFieldType::Int32);
|
||||
EXPECT_TRUE(awakeSnapshot->declaredInClass);
|
||||
EXPECT_TRUE(awakeSnapshot->hasDefaultValue);
|
||||
EXPECT_FALSE(awakeSnapshot->hasValue);
|
||||
EXPECT_EQ(awakeSnapshot->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(awakeSnapshot->issue, ScriptFieldIssue::TypeMismatch);
|
||||
EXPECT_TRUE(awakeSnapshot->hasStoredValue);
|
||||
EXPECT_EQ(awakeSnapshot->storedType, ScriptFieldType::UInt64);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->defaultValue), 0);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->value), 0);
|
||||
EXPECT_EQ(std::get<uint64_t>(awakeSnapshot->storedValue), 7u);
|
||||
|
||||
EXPECT_EQ(labelSnapshot->metadata.type, ScriptFieldType::String);
|
||||
EXPECT_TRUE(labelSnapshot->declaredInClass);
|
||||
EXPECT_TRUE(labelSnapshot->hasDefaultValue);
|
||||
EXPECT_TRUE(labelSnapshot->hasValue);
|
||||
EXPECT_EQ(labelSnapshot->valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(labelSnapshot->issue, ScriptFieldIssue::None);
|
||||
EXPECT_TRUE(labelSnapshot->hasStoredValue);
|
||||
EXPECT_EQ(labelSnapshot->storedType, ScriptFieldType::String);
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->defaultValue), "");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->value), "Stored");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->storedValue), "Stored");
|
||||
|
||||
EXPECT_EQ(legacySnapshot->metadata.type, ScriptFieldType::UInt64);
|
||||
EXPECT_FALSE(legacySnapshot->declaredInClass);
|
||||
EXPECT_FALSE(legacySnapshot->hasDefaultValue);
|
||||
EXPECT_TRUE(legacySnapshot->hasValue);
|
||||
EXPECT_EQ(legacySnapshot->valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(legacySnapshot->issue, ScriptFieldIssue::StoredOnly);
|
||||
EXPECT_TRUE(legacySnapshot->hasStoredValue);
|
||||
EXPECT_EQ(legacySnapshot->storedType, ScriptFieldType::UInt64);
|
||||
EXPECT_EQ(std::get<uint64_t>(legacySnapshot->value), 99u);
|
||||
EXPECT_EQ(std::get<uint64_t>(legacySnapshot->storedValue), 99u);
|
||||
|
||||
runtime.managedFieldValues["AwakeCount"] = int32_t(1);
|
||||
runtime.managedFieldValues["Label"] = std::string("Live");
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
EXPECT_EQ(model.classStatus, ScriptFieldClassStatus::Available);
|
||||
awakeSnapshot = findSnapshot("AwakeCount");
|
||||
labelSnapshot = findSnapshot("Label");
|
||||
legacySnapshot = findSnapshot("LegacyOnly");
|
||||
|
||||
ASSERT_NE(awakeSnapshot, nullptr);
|
||||
ASSERT_NE(labelSnapshot, nullptr);
|
||||
ASSERT_NE(legacySnapshot, nullptr);
|
||||
|
||||
EXPECT_TRUE(awakeSnapshot->hasValue);
|
||||
EXPECT_TRUE(awakeSnapshot->hasDefaultValue);
|
||||
EXPECT_EQ(awakeSnapshot->valueSource, ScriptFieldValueSource::ManagedValue);
|
||||
EXPECT_EQ(awakeSnapshot->issue, ScriptFieldIssue::TypeMismatch);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->defaultValue), 0);
|
||||
EXPECT_EQ(std::get<int32_t>(awakeSnapshot->value), 1);
|
||||
EXPECT_TRUE(labelSnapshot->hasValue);
|
||||
EXPECT_TRUE(labelSnapshot->hasDefaultValue);
|
||||
EXPECT_EQ(labelSnapshot->valueSource, ScriptFieldValueSource::ManagedValue);
|
||||
EXPECT_EQ(labelSnapshot->issue, ScriptFieldIssue::None);
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->defaultValue), "");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->value), "Live");
|
||||
EXPECT_TRUE(legacySnapshot->hasValue);
|
||||
EXPECT_FALSE(legacySnapshot->hasDefaultValue);
|
||||
EXPECT_EQ(legacySnapshot->valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(legacySnapshot->issue, ScriptFieldIssue::StoredOnly);
|
||||
EXPECT_EQ(std::get<uint64_t>(legacySnapshot->value), 99u);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldModelReportsMissingScriptClassAndPreservesStoredFields) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "MissingScript");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
|
||||
ScriptFieldModel model;
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
|
||||
EXPECT_EQ(model.classStatus, ScriptFieldClassStatus::Missing);
|
||||
ASSERT_EQ(model.fields.size(), 1u);
|
||||
|
||||
const ScriptFieldSnapshot& field = model.fields[0];
|
||||
EXPECT_EQ(field.metadata.name, "Label");
|
||||
EXPECT_EQ(field.metadata.type, ScriptFieldType::String);
|
||||
EXPECT_FALSE(field.declaredInClass);
|
||||
EXPECT_FALSE(field.hasDefaultValue);
|
||||
EXPECT_TRUE(field.hasValue);
|
||||
EXPECT_EQ(field.valueSource, ScriptFieldValueSource::StoredValue);
|
||||
EXPECT_EQ(field.issue, ScriptFieldIssue::StoredOnly);
|
||||
EXPECT_TRUE(field.hasStoredValue);
|
||||
EXPECT_EQ(field.storedType, ScriptFieldType::String);
|
||||
EXPECT_EQ(std::get<std::string>(field.value), "Stored");
|
||||
EXPECT_EQ(std::get<std::string>(field.storedValue), "Stored");
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldModelUsesRuntimeClassDefaultValuesWhenAvailable) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeDefaults");
|
||||
|
||||
runtime.fieldMetadata = {
|
||||
{"Health", ScriptFieldType::Int32},
|
||||
{"Label", ScriptFieldType::String},
|
||||
{"SpawnPoint", ScriptFieldType::Vector3}
|
||||
};
|
||||
runtime.fieldDefaultValues = {
|
||||
{"Health", ScriptFieldType::Int32, ScriptFieldValue(int32_t(17))},
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(std::string("Seeded"))},
|
||||
{"SpawnPoint", ScriptFieldType::Vector3, ScriptFieldValue(XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f))}
|
||||
};
|
||||
|
||||
ScriptFieldModel model;
|
||||
ASSERT_TRUE(engine->TryGetScriptFieldModel(component, model));
|
||||
ASSERT_EQ(model.fields.size(), 3u);
|
||||
|
||||
const auto findSnapshot = [&model](const std::string& fieldName) -> const ScriptFieldSnapshot* {
|
||||
const auto it = std::find_if(
|
||||
model.fields.begin(),
|
||||
model.fields.end(),
|
||||
[&fieldName](const ScriptFieldSnapshot& snapshot) {
|
||||
return snapshot.metadata.name == fieldName;
|
||||
});
|
||||
return it != model.fields.end() ? &(*it) : nullptr;
|
||||
};
|
||||
|
||||
const ScriptFieldSnapshot* healthSnapshot = findSnapshot("Health");
|
||||
const ScriptFieldSnapshot* labelSnapshot = findSnapshot("Label");
|
||||
const ScriptFieldSnapshot* spawnPointSnapshot = findSnapshot("SpawnPoint");
|
||||
|
||||
ASSERT_NE(healthSnapshot, nullptr);
|
||||
ASSERT_NE(labelSnapshot, nullptr);
|
||||
ASSERT_NE(spawnPointSnapshot, nullptr);
|
||||
|
||||
EXPECT_TRUE(healthSnapshot->hasDefaultValue);
|
||||
EXPECT_FALSE(healthSnapshot->hasValue);
|
||||
EXPECT_EQ(healthSnapshot->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(std::get<int32_t>(healthSnapshot->defaultValue), 17);
|
||||
EXPECT_EQ(std::get<int32_t>(healthSnapshot->value), 17);
|
||||
|
||||
EXPECT_TRUE(labelSnapshot->hasDefaultValue);
|
||||
EXPECT_FALSE(labelSnapshot->hasValue);
|
||||
EXPECT_EQ(labelSnapshot->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->defaultValue), "Seeded");
|
||||
EXPECT_EQ(std::get<std::string>(labelSnapshot->value), "Seeded");
|
||||
|
||||
EXPECT_TRUE(spawnPointSnapshot->hasDefaultValue);
|
||||
EXPECT_FALSE(spawnPointSnapshot->hasValue);
|
||||
EXPECT_EQ(spawnPointSnapshot->valueSource, ScriptFieldValueSource::DefaultValue);
|
||||
EXPECT_EQ(std::get<XCEngine::Math::Vector3>(spawnPointSnapshot->defaultValue), XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f));
|
||||
EXPECT_EQ(std::get<XCEngine::Math::Vector3>(spawnPointSnapshot->value), XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f));
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldWriteBatchApiReportsPerItemStatusAndAppliesValidValues) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeBatchWrite");
|
||||
|
||||
runtime.fieldMetadata = {
|
||||
{"Speed", ScriptFieldType::Float},
|
||||
{"Label", ScriptFieldType::String}
|
||||
};
|
||||
component->GetFieldStorage().SetFieldValue("Speed", uint64_t(7));
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
component->GetFieldStorage().SetFieldValue("LegacyOnly", uint64_t(99));
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
const std::vector<ScriptFieldWriteRequest> requests = {
|
||||
{"Speed", ScriptFieldType::Float, ScriptFieldValue(5.0f)},
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(std::string("Edited"))},
|
||||
{"LegacyOnly", ScriptFieldType::UInt64, ScriptFieldValue(uint64_t(100))},
|
||||
{"Missing", ScriptFieldType::Int32, ScriptFieldValue(int32_t(1))},
|
||||
{"Speed", ScriptFieldType::String, ScriptFieldValue(std::string("wrong"))},
|
||||
{"", ScriptFieldType::Int32, ScriptFieldValue(int32_t(2))},
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(int32_t(3))}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldWriteResult> results;
|
||||
EXPECT_FALSE(engine->ApplyScriptFieldWrites(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldWriteStatus::Applied);
|
||||
EXPECT_EQ(results[1].status, ScriptFieldWriteStatus::Applied);
|
||||
EXPECT_EQ(results[2].status, ScriptFieldWriteStatus::StoredOnlyField);
|
||||
EXPECT_EQ(results[3].status, ScriptFieldWriteStatus::UnknownField);
|
||||
EXPECT_EQ(results[4].status, ScriptFieldWriteStatus::TypeMismatch);
|
||||
EXPECT_EQ(results[5].status, ScriptFieldWriteStatus::EmptyFieldName);
|
||||
EXPECT_EQ(results[6].status, ScriptFieldWriteStatus::InvalidValue);
|
||||
|
||||
float speed = 0.0f;
|
||||
std::string label;
|
||||
uint64_t legacyOnly = 0;
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Speed", speed));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", label));
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("LegacyOnly", legacyOnly));
|
||||
EXPECT_FLOAT_EQ(speed, 5.0f);
|
||||
EXPECT_EQ(label, "Edited");
|
||||
EXPECT_EQ(legacyOnly, 99u);
|
||||
|
||||
ASSERT_EQ(runtime.managedSetFieldNames.size(), 2u);
|
||||
EXPECT_EQ(runtime.managedSetFieldNames[0], "Speed");
|
||||
EXPECT_EQ(runtime.managedSetFieldNames[1], "Label");
|
||||
EXPECT_EQ(std::get<float>(runtime.managedFieldValues["Speed"]), 5.0f);
|
||||
EXPECT_EQ(std::get<std::string>(runtime.managedFieldValues["Label"]), "Edited");
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldWriteBatchApiAllowsStoredFieldsWhenScriptClassMetadataIsMissing) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "MissingScript");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
|
||||
const std::vector<ScriptFieldWriteRequest> requests = {
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(std::string("Retained"))},
|
||||
{"Missing", ScriptFieldType::Int32, ScriptFieldValue(int32_t(1))}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldWriteResult> results;
|
||||
EXPECT_FALSE(engine->ApplyScriptFieldWrites(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldWriteStatus::Applied);
|
||||
EXPECT_EQ(results[1].status, ScriptFieldWriteStatus::UnknownField);
|
||||
|
||||
std::string label;
|
||||
EXPECT_TRUE(component->GetFieldStorage().TryGetFieldValue("Label", label));
|
||||
EXPECT_EQ(label, "Retained");
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldClearApiReportsPerItemStatusAndClearsStoredAndLiveValues) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeFieldClear");
|
||||
|
||||
runtime.fieldMetadata = {
|
||||
{"Label", ScriptFieldType::String},
|
||||
{"Speed", ScriptFieldType::Float}
|
||||
};
|
||||
runtime.fieldDefaultValues = {
|
||||
{"Label", ScriptFieldType::String, ScriptFieldValue(std::string("Seeded"))},
|
||||
{"Speed", ScriptFieldType::Float, ScriptFieldValue(6.5f)}
|
||||
};
|
||||
runtime.managedFieldValues["Speed"] = ScriptFieldValue(41.0f);
|
||||
runtime.managedFieldValues["Label"] = ScriptFieldValue(std::string("LiveValue"));
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Speed", 5.0f);
|
||||
component->GetFieldStorage().SetFieldValue("LegacyOnly", uint64_t(99));
|
||||
|
||||
engine->OnRuntimeStart(runtimeScene);
|
||||
|
||||
const std::vector<ScriptFieldClearRequest> requests = {
|
||||
{"Speed"},
|
||||
{"Label"},
|
||||
{"LegacyOnly"},
|
||||
{"Missing"},
|
||||
{""}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldClearResult> results;
|
||||
EXPECT_FALSE(engine->ClearScriptFieldOverrides(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldClearStatus::Applied);
|
||||
EXPECT_EQ(results[1].status, ScriptFieldClearStatus::Applied);
|
||||
EXPECT_EQ(results[2].status, ScriptFieldClearStatus::Applied);
|
||||
EXPECT_EQ(results[3].status, ScriptFieldClearStatus::UnknownField);
|
||||
EXPECT_EQ(results[4].status, ScriptFieldClearStatus::EmptyFieldName);
|
||||
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("Speed"));
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("Label"));
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("LegacyOnly"));
|
||||
|
||||
ASSERT_EQ(runtime.managedSetFieldNames.size(), 2u);
|
||||
EXPECT_EQ(runtime.managedSetFieldNames[0], "Speed");
|
||||
EXPECT_EQ(runtime.managedSetFieldNames[1], "Label");
|
||||
EXPECT_FLOAT_EQ(std::get<float>(runtime.managedFieldValues["Speed"]), 6.5f);
|
||||
EXPECT_EQ(std::get<std::string>(runtime.managedFieldValues["Label"]), "Seeded");
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldClearApiReportsNoValueToClearForDeclaredFieldWithoutStoredOrLiveValue) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "RuntimeFieldClear");
|
||||
|
||||
runtime.fieldMetadata = {
|
||||
{"Speed", ScriptFieldType::Float}
|
||||
};
|
||||
|
||||
const std::vector<ScriptFieldClearRequest> requests = {
|
||||
{"Speed"}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldClearResult> results;
|
||||
EXPECT_FALSE(engine->ClearScriptFieldOverrides(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldClearStatus::NoValueToClear);
|
||||
}
|
||||
|
||||
TEST_F(ScriptEngineTest, FieldClearApiAllowsRemovingStoredFieldsWhenScriptClassMetadataIsMissing) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
ScriptComponent* component = AddScriptComponent(host, "Gameplay", "MissingScript");
|
||||
|
||||
component->GetFieldStorage().SetFieldValue("Label", "Stored");
|
||||
|
||||
const std::vector<ScriptFieldClearRequest> requests = {
|
||||
{"Label"},
|
||||
{"Missing"}
|
||||
};
|
||||
|
||||
std::vector<ScriptFieldClearResult> results;
|
||||
EXPECT_FALSE(engine->ClearScriptFieldOverrides(component, requests, results));
|
||||
ASSERT_EQ(results.size(), requests.size());
|
||||
|
||||
EXPECT_EQ(results[0].status, ScriptFieldClearStatus::Applied);
|
||||
EXPECT_EQ(results[1].status, ScriptFieldClearStatus::UnknownField);
|
||||
EXPECT_FALSE(component->GetFieldStorage().Contains("Label"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user