Revamped AzFramework::Scene (#332)
Updated AzFramework::Scene to allow it to serve as the one-stop location for localized singletons. Localized singletons in this case are instance that can only occur once in an environment but multiple times within an application. As an example, this allows settings up a single camera per viewport for instance. Highlights of changes: Replaced the original ebuses with interfaces and events for easy of use and performance. Removed the Entity Context specific code and moved that to new locations within the Entity Context itself. Allowed basic inheritance. If a subsystem isn't found in a scene the parent can optionally be searched. Scenes can enter a zombie state and avoid immediately being deleted. This is needed for situations where subsystems can't be destroyed until async calls have been completed.
This commit is contained in:
+90
-118
@@ -130,6 +130,8 @@ namespace SceneUnitTest
|
||||
m_systemEntity->CreateComponent<AZ::JobManagerComponent>();
|
||||
m_systemEntity->CreateComponent<AZ::StreamerComponent>();
|
||||
m_systemEntity->Activate();
|
||||
|
||||
m_sceneSystem = AzFramework::SceneSystemInterface::Get();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
@@ -146,167 +148,119 @@ namespace SceneUnitTest
|
||||
AZ::IO::FileIOBase* m_prevFileIO;
|
||||
AZ::ComponentApplication m_app;
|
||||
AZ::Entity* m_systemEntity = nullptr;
|
||||
AzFramework::ISceneSystem* m_sceneSystem = nullptr;
|
||||
|
||||
};
|
||||
|
||||
TEST_F(SceneTest, CreateScene)
|
||||
{
|
||||
Scene* scene = nullptr;
|
||||
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
|
||||
|
||||
{
|
||||
// A scene should be able to be created with a given name.
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, "TestScene");
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene("TestScene");
|
||||
EXPECT_TRUE(createSceneOutcome.IsSuccess()) << "Unable to create a scene.";
|
||||
|
||||
// The scene pointer returned should be valid
|
||||
scene = createSceneOutcome.GetValue();
|
||||
EXPECT_TRUE(scene != nullptr) << "Scene creation reported success, but no scene actually was actually returned.";
|
||||
AZStd::shared_ptr<Scene> scene = createSceneOutcome.TakeValue();
|
||||
EXPECT_NE(scene, nullptr) << "Scene creation reported success, but no scene actually was actually returned.";
|
||||
|
||||
// Attempting to create another scene with the same name should fail.
|
||||
createSceneOutcome = AZ::Failure<AZStd::string>("");
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, "TestScene");
|
||||
createSceneOutcome = m_sceneSystem->CreateScene("TestScene");
|
||||
EXPECT_TRUE(!createSceneOutcome.IsSuccess()) << "Should not be able to create two scenes with the same name.";
|
||||
|
||||
}
|
||||
|
||||
TEST_F(SceneTest, GetScene)
|
||||
{
|
||||
Scene* createdScene = nullptr;
|
||||
Scene* retrievedScene = nullptr;
|
||||
Scene* nullScene = nullptr;
|
||||
const static AZStd::string_view s_sceneName = "TestScene";
|
||||
constexpr AZStd::string_view sceneName = "TestScene";
|
||||
|
||||
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, s_sceneName);
|
||||
createdScene = createSceneOutcome.GetValue();
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene(sceneName);
|
||||
AZStd::shared_ptr<Scene> createdScene = createSceneOutcome.TakeValue();
|
||||
|
||||
// Should be able to get a scene by name, and it should match the scene that was created.
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(retrievedScene, &AzFramework::SceneSystemRequestBus::Events::GetScene, s_sceneName);
|
||||
EXPECT_TRUE(retrievedScene != nullptr) << "Attempting to get scene by name resulted in nullptr.";
|
||||
EXPECT_TRUE(retrievedScene == createdScene) << "Retrieved scene does not match created scene.";
|
||||
AZStd::shared_ptr<Scene> retrievedScene = m_sceneSystem->GetScene(sceneName);
|
||||
EXPECT_NE(retrievedScene, nullptr) << "Attempting to get scene by name resulted in nullptr.";
|
||||
EXPECT_EQ(retrievedScene, createdScene) << "Retrieved scene does not match created scene.";
|
||||
|
||||
// An invalid name should return a null scene.
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(nullScene, &AzFramework::SceneSystemRequestBus::Events::GetScene, "non-existant scene");
|
||||
EXPECT_TRUE(nullScene == nullptr) << "Should not be able to retrieve a scene that wasn't created.";
|
||||
AZStd::shared_ptr<Scene> nullScene = m_sceneSystem->GetScene("non-existant scene");
|
||||
EXPECT_EQ(nullScene, nullptr) << "Should not be able to retrieve a scene that wasn't created.";
|
||||
}
|
||||
|
||||
TEST_F(SceneTest, RemoveScene)
|
||||
{
|
||||
Scene* createdScene = nullptr;
|
||||
const static AZStd::string_view s_sceneName = "TestScene";
|
||||
constexpr AZStd::string_view sceneName = "TestScene";
|
||||
|
||||
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, s_sceneName);
|
||||
createdScene = createSceneOutcome.GetValue();
|
||||
|
||||
bool success = false;
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::RemoveScene, s_sceneName);
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene(sceneName);
|
||||
bool success = m_sceneSystem->RemoveScene(sceneName);
|
||||
EXPECT_TRUE(success) << "Failed to remove the scene that was just created.";
|
||||
|
||||
success = true;
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::RemoveScene, "non-existant scene");
|
||||
success = m_sceneSystem->RemoveScene("non-existant scene");
|
||||
EXPECT_FALSE(success) << "Remove scene returned success for a non-existant scene.";
|
||||
}
|
||||
|
||||
TEST_F(SceneTest, GetAllScenes)
|
||||
TEST_F(SceneTest, IterateActiveScenes)
|
||||
{
|
||||
constexpr size_t NumScenes = 5;
|
||||
|
||||
Scene* scenes[NumScenes] = { nullptr };
|
||||
AZStd::shared_ptr<Scene> scenes[NumScenes] = {nullptr};
|
||||
|
||||
for (size_t i = 0; i < NumScenes; ++i)
|
||||
{
|
||||
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
|
||||
|
||||
AZStd::string sceneName = AZStd::string::format("scene %zu", i);
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, sceneName);
|
||||
scenes[i] = createSceneOutcome.GetValue();
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene(sceneName);
|
||||
scenes[i] = createSceneOutcome.TakeValue();
|
||||
}
|
||||
|
||||
AZStd::vector<Scene*> retrievedScenes;
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(retrievedScenes, &AzFramework::SceneSystemRequestBus::Events::GetAllScenes);
|
||||
|
||||
EXPECT_EQ(NumScenes, retrievedScenes.size()) << "GetAllScenes() returned a different number of scenes than those created.";
|
||||
|
||||
for (size_t i = 0; i < NumScenes; ++i)
|
||||
{
|
||||
EXPECT_EQ(scenes[i], retrievedScenes.at(i)) << "GetAllScenes() returned scenes in a different order than they were created.";
|
||||
}
|
||||
size_t index = 0;
|
||||
m_sceneSystem->IterateActiveScenes([&index, &scenes](const AZStd::shared_ptr<Scene>& scene)
|
||||
{
|
||||
EXPECT_EQ(scenes[index++], scene);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SceneTest, EntityContextSceneMapping)
|
||||
TEST_F(SceneTest, IterateZombieScenes)
|
||||
{
|
||||
AZStd::unique_ptr<SliceEntityOwnershipService> m_entityOwnershipService =
|
||||
AZStd::make_unique<AzFramework::SliceEntityOwnershipService>(AZ::Uuid::CreateNull(), m_app.GetSerializeContext());
|
||||
// Create the entity context, entity, and component
|
||||
EntityContext* testEntityContext = new EntityContext(AZ::Uuid::CreateRandom(), AZStd::move(m_entityOwnershipService));
|
||||
testEntityContext->InitContext();
|
||||
EntityContextId testEntityContextId = testEntityContext->GetContextId();
|
||||
AZ::Entity* testEntity = testEntityContext->CreateEntity("TestEntity");
|
||||
TestComponent* testComponent = testEntity->CreateComponent<TestComponent>();
|
||||
constexpr size_t NumScenes = 5;
|
||||
|
||||
// Try to activate an entity and get the scene before a scene has been set. This should fail.
|
||||
TestComponentConfig failConfig;
|
||||
failConfig.m_activateFunction = [](TestComponent* component)
|
||||
AZStd::shared_ptr<Scene> scenes[NumScenes] = {nullptr};
|
||||
|
||||
// Create zombies.
|
||||
for (size_t i = 0; i < NumScenes; ++i)
|
||||
{
|
||||
(void)component;
|
||||
Scene* scene = nullptr;
|
||||
EntityContextId entityContextId = EntityContextId::CreateNull();
|
||||
AZStd::string sceneName = AZStd::string::format("scene %zu", i);
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene(sceneName);
|
||||
scenes[i] = createSceneOutcome.TakeValue();
|
||||
m_sceneSystem->RemoveScene(sceneName);
|
||||
}
|
||||
|
||||
AzFramework::EntityIdContextQueryBus::BroadcastResult(entityContextId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
|
||||
// Check to make sure there are no more active scenes.
|
||||
size_t index = 0;
|
||||
m_sceneSystem->IterateActiveScenes([&index, &scenes](const AZStd::shared_ptr<Scene>&)
|
||||
{
|
||||
index++;
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ(0, index);
|
||||
|
||||
// A null scene should be returned since a scene has not been set for this entity context.
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(scene, &AzFramework::SceneSystemRequestBus::Events::GetSceneFromEntityContextId, entityContextId);
|
||||
EXPECT_TRUE(scene == nullptr) << "Found a scene when one shouldn't exist.";
|
||||
};
|
||||
|
||||
testComponent->SetConfiguration(failConfig);
|
||||
testComponent->Activate();
|
||||
testComponent->Deactivate();
|
||||
// Check that the scenes are still returned as zombies.
|
||||
index = 0;
|
||||
m_sceneSystem->IterateZombieScenes([&index, &scenes](Scene& scene)
|
||||
{
|
||||
EXPECT_EQ(scenes[index++].get(), &scene);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Create the scene
|
||||
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, "TestScene");
|
||||
Scene* scene = createSceneOutcome.GetValue();
|
||||
|
||||
// Map the Entity context to the scene
|
||||
bool success = false;
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::SetSceneForEntityContextId, testEntityContextId, scene);
|
||||
EXPECT_TRUE(success) << "Unable to associate an entity context with a scene.";
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::SetSceneForEntityContextId, testEntityContextId, scene);
|
||||
EXPECT_FALSE(success) << "Attempting to map an entity context to a scene that's already mapped, this should not work.";
|
||||
|
||||
// Now it should be possible to get the scene from the entity context within an Entity's Activate()
|
||||
TestComponentConfig successConfig;
|
||||
successConfig.m_activateFunction = [](TestComponent* component)
|
||||
// Check that all scenes are removed when there are no more handles.
|
||||
for (size_t i = 0; i < NumScenes; ++i)
|
||||
{
|
||||
(void)component;
|
||||
Scene* scene = nullptr;
|
||||
EntityContextId entityContextId = EntityContextId::CreateNull();
|
||||
|
||||
AzFramework::EntityIdContextQueryBus::BroadcastResult(entityContextId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
|
||||
|
||||
// A scene should be returned since a scene has been set for this entity context.
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(scene, &AzFramework::SceneSystemRequestBus::Events::GetSceneFromEntityContextId, entityContextId);
|
||||
EXPECT_TRUE(scene != nullptr) << "Could not find a scene for the entity context.";
|
||||
};
|
||||
|
||||
testComponent->SetConfiguration(successConfig);
|
||||
testComponent->Activate();
|
||||
testComponent->Deactivate();
|
||||
|
||||
// Now remove the entity context / scene association and make sure things fail again.
|
||||
success = false;
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::RemoveSceneForEntityContextId, testEntityContextId, nullptr);
|
||||
EXPECT_FALSE(success) << "Should not be able to remove an entity context from a scene it's not associated with.";
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequestBus::Events::RemoveSceneForEntityContextId, testEntityContextId, scene);
|
||||
EXPECT_TRUE(success) << "Was not able to remove an entity context from a scene it's associated with.";
|
||||
|
||||
testComponent->SetConfiguration(failConfig);
|
||||
testComponent->Activate();
|
||||
testComponent->Deactivate();
|
||||
|
||||
delete testEntityContext; // This should also clean up owned entities / components.
|
||||
scenes[i].reset();
|
||||
}
|
||||
index = 0;
|
||||
m_sceneSystem->IterateZombieScenes([&index, &scenes](Scene&) {
|
||||
index++;
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ(0, index);
|
||||
}
|
||||
|
||||
// Test classes for use in the SceneSystem test. These can't be defined in the test itself due to some functions created by AZ_RTTI not having a body which breaks VS2015.
|
||||
@@ -324,30 +278,48 @@ namespace SceneUnitTest
|
||||
TEST_F(SceneTest, SceneSystem)
|
||||
{
|
||||
// Create the scene
|
||||
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("");
|
||||
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequestBus::Events::CreateScene, "TestScene");
|
||||
AzFramework::Scene* scene = createSceneOutcome.GetValue();
|
||||
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome = m_sceneSystem->CreateScene("TestScene");
|
||||
EXPECT_TRUE(createSceneOutcome.IsSuccess());
|
||||
AZStd::shared_ptr<Scene> scene = createSceneOutcome.TakeValue();
|
||||
|
||||
// Set a class on the Scene
|
||||
Foo1* foo1a = new Foo1();
|
||||
EXPECT_TRUE(scene->SetSubsystem(foo1a));
|
||||
|
||||
// Get that class back from the Scene
|
||||
EXPECT_EQ(foo1a, scene->GetSubsystem<Foo1>());
|
||||
EXPECT_EQ(foo1a, *scene->FindSubsystem<Foo1*>());
|
||||
|
||||
// Try to set the same class type twice, this should fail.
|
||||
Foo1* foo1b = new Foo1();
|
||||
EXPECT_FALSE(scene->SetSubsystem(foo1b));
|
||||
delete foo1b;
|
||||
|
||||
// Add a child scene
|
||||
createSceneOutcome = m_sceneSystem->CreateSceneWithParent("ChildScene", scene);
|
||||
EXPECT_TRUE(createSceneOutcome.IsSuccess());
|
||||
AZStd::shared_ptr<Scene> childScene = createSceneOutcome.TakeValue();
|
||||
|
||||
// Get class back from parent scene.
|
||||
EXPECT_EQ(foo1a, *childScene->FindSubsystem<Foo1*>());
|
||||
|
||||
// Find overloaded version of class on child scene.
|
||||
Foo1* foo1c = new Foo1();
|
||||
EXPECT_TRUE(childScene->SetSubsystem(foo1c));
|
||||
EXPECT_EQ(foo1c, *childScene->FindSubsystem<Foo1*>());
|
||||
|
||||
// Unset system on child scene, using alternative unset function.
|
||||
EXPECT_TRUE(childScene->UnsetSubsystem(foo1c));
|
||||
delete foo1c;
|
||||
|
||||
// Try to un-set a class that was never set, this should fail.
|
||||
EXPECT_FALSE(scene->UnsetSubsystem<Foo2>());
|
||||
|
||||
// Unset the class that was previously set
|
||||
EXPECT_TRUE(scene->UnsetSubsystem<Foo1>());
|
||||
delete foo1a;
|
||||
|
||||
// Make sure that the previsouly set class was really removed.
|
||||
EXPECT_EQ(nullptr, scene->GetSubsystem<Foo1>());
|
||||
// Make sure that the previously set class was really removed.
|
||||
EXPECT_EQ(nullptr, scene->FindSubsystem<Foo1*>());
|
||||
}
|
||||
} // UnitTest
|
||||
|
||||
|
||||
Reference in New Issue
Block a user