5e4094b258
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.
327 lines
12 KiB
C++
327 lines
12 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
|
|
#include <AzCore/UnitTest/TestTypes.h>
|
|
|
|
AZ_PUSH_DISABLE_WARNING(, "-Wdelete-non-virtual-dtor")
|
|
|
|
#include <FrameworkApplicationFixture.h>
|
|
#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;
|
|
desc.m_enableDrilling = false; // the unit test framework already adds a driller
|
|
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, &scenes](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, &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.
|
|
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
|