You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/AzFramework/Tests/Scene.cpp

321 lines
11 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/UnitTest/TestTypes.h>
AZ_PUSH_DISABLE_WARNING(, "-Wdelete-non-virtual-dtor")
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/Component.h>
#include <AzFramework/Scene/SceneSystemComponent.h>
#include <AzFramework/Scene/Scene.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzCore/Jobs/JobManagerComponent.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/Slice/SliceAssetHandler.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/Slice/SliceSystemComponent.h>
using namespace AzFramework;
// Test component that allows code to be injected into activate / deactivate for testing.
namespace SceneUnitTest
{
class TestComponent;
class TestComponentConfig : public AZ::ComponentConfig
{
public:
AZ_RTTI(TestComponentConfig, "{DCD12D72-3BFE-43A9-9679-66B745814CAF}", ComponentConfig);
typedef void(*ActivateFunction)(TestComponent* component);
ActivateFunction m_activateFunction = nullptr;
typedef void(*DeactivateFunction)(TestComponent* component);
DeactivateFunction m_deactivateFunction = nullptr;
};
static const AZ::TypeId TestComponentTypeId = "{DC096267-4815-47D1-BA23-A1CDF0D72D9D}";
class TestComponent : public AZ::Component
{
public:
AZ_COMPONENT(TestComponent, TestComponentTypeId);
static void Reflect(AZ::ReflectContext*) {};
void Activate() override
{
if (m_config.m_activateFunction)
{
m_config.m_activateFunction(this);
}
}
void Deactivate() override
{
if (m_config.m_deactivateFunction)
{
m_config.m_deactivateFunction(this);
}
}
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override
{
if (auto config = azrtti_cast<const TestComponentConfig*>(baseConfig))
{
m_config = *config;
return true;
}
return false;
}
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override
{
if (auto outConfig = azrtti_cast<TestComponentConfig*>(outBaseConfig))
{
*outConfig = m_config;
return true;
}
return false;
}
TestComponentConfig m_config;
};
// Fixture that creates a bare-bones app with only the system components necesary.
class SceneTest
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
void SetUp() override
{
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
m_prevFileIO = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FileIOBase::SetInstance(&m_fileIO);
m_app.RegisterComponentDescriptor(SceneSystemComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(AZ::SliceSystemComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(AZ::AssetManagerComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(AZ::JobManagerComponent::CreateDescriptor());
m_app.RegisterComponentDescriptor(AZ::StreamerComponent::CreateDescriptor());
AZ::ComponentApplication::Descriptor desc;
m_systemEntity = m_app.Create(desc);
m_systemEntity->Init();
m_systemEntity->CreateComponent<SceneSystemComponent>();
// Asset / slice system components needed by entity contexts
m_systemEntity->CreateComponent<AZ::SliceSystemComponent>();
m_systemEntity->CreateComponent<AZ::AssetManagerComponent>();
m_systemEntity->CreateComponent<AZ::JobManagerComponent>();
m_systemEntity->CreateComponent<AZ::StreamerComponent>();
m_systemEntity->Activate();
m_sceneSystem = AzFramework::SceneSystemInterface::Get();
}
void TearDown() override
{
m_app.Destroy();
AZ::IO::FileIOBase::SetInstance(m_prevFileIO);
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
}
AZ::IO::LocalFileIO m_fileIO;
AZ::IO::FileIOBase* m_prevFileIO;
AZ::ComponentApplication m_app;
AZ::Entity* m_systemEntity = nullptr;
AzFramework::ISceneSystem* m_sceneSystem = nullptr;
};
TEST_F(SceneTest, CreateScene)
{
// A scene should be able to be created with a given name.
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
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 = m_sceneSystem->CreateScene("TestScene");
EXPECT_TRUE(!createSceneOutcome.IsSuccess()) << "Should not be able to create two scenes with the same name.";
}
TEST_F(SceneTest, GetScene)
{
constexpr AZStd::string_view sceneName = "TestScene";
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.
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.
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)
{
constexpr AZStd::string_view sceneName = "TestScene";
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 = m_sceneSystem->RemoveScene("non-existant scene");
EXPECT_FALSE(success) << "Remove scene returned success for a non-existant scene.";
}
TEST_F(SceneTest, IterateActiveScenes)
{
constexpr size_t NumScenes = 5;
AZStd::shared_ptr<Scene> scenes[NumScenes] = {nullptr};
for (size_t i = 0; i < NumScenes; ++i)
{
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();
}
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, IterateZombieScenes)
{
constexpr size_t NumScenes = 5;
AZStd::shared_ptr<Scene> scenes[NumScenes] = {nullptr};
// Create zombies.
for (size_t i = 0; i < NumScenes; ++i)
{
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);
}
// Check to make sure there are no more active scenes.
size_t index = 0;
m_sceneSystem->IterateActiveScenes([&index](const AZStd::shared_ptr<Scene>&)
{
index++;
return true;
});
EXPECT_EQ(0, index);
// 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;
});
// Check that all scenes are removed when there are no more handles.
for (size_t i = 0; i < NumScenes; ++i)
{
scenes[i].reset();
}
index = 0;
m_sceneSystem->IterateZombieScenes([&index](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.
class Foo1
{
public:
AZ_RTTI(Foo1, "{9A6AA770-E2EA-4C5E-952A-341802E2DE58}");
};
class Foo2
{
public:
AZ_RTTI(Foo2, "{916A2DB4-9C30-4B90-837E-2BC9855B474B}");
};
TEST_F(SceneTest, SceneSystem)
{
// Create the scene
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->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 previously set class was really removed.
EXPECT_EQ(nullptr, scene->FindSubsystem<Foo1*>());
}
} // UnitTest
AZ_POP_DISABLE_WARNING