Merge branch 'main' into hultonha_LYN-2315_camera-phase-2

This commit is contained in:
hultonha
2021-04-30 10:42:36 +01:00
121 changed files with 7813 additions and 1286 deletions
@@ -19,7 +19,6 @@
#include <AzCore/Jobs/JobManagerComponent.h>
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/NativeUI/NativeUISystemComponent.h>
#include <AzCore/Script/ScriptSystemComponent.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/Slice/SliceSystemComponent.h>
@@ -43,7 +42,6 @@ namespace AZ
AssetManagerComponent::CreateDescriptor(),
UserSettingsComponent::CreateDescriptor(),
Debug::FrameProfilerComponent::CreateDescriptor(),
NativeUI::NativeUISystemComponent::CreateDescriptor(),
SliceComponent::CreateDescriptor(),
SliceSystemComponent::CreateDescriptor(),
SliceMetadataInfoComponent::CreateDescriptor(),
@@ -28,6 +28,8 @@
#include <AzCore/Memory/AllocatorManager.h>
#include <AzCore/Memory/MallocSchema.h>
#include <AzCore/NativeUI/NativeUIRequests.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/Utils.h>
@@ -424,7 +426,7 @@ namespace AZ
// Now that the Allocators are initialized, the Command Line parameters can be parsed
m_commandLine.Parse(m_argC, m_argV);
ParseCommandLine(m_commandLine);
SettingsRegistryMergeUtils::ParseCommandLine(m_commandLine);
// Create the settings registry and register it with the AZ interface system
// This is done after the AppRoot has been calculated so that the Bootstrap.cfg
@@ -527,10 +529,42 @@ namespace AZ
DestroyAllocator();
}
void ReportBadEngineRoot()
{
AZStd::string errorMessage = {"Unable to determine a valid path to the engine.\n"
"Check parameters such as --project-path and --engine-path and make sure they are valid.\n"};
if (auto registry = AZ::SettingsRegistry::Get(); registry != nullptr)
{
AZ::SettingsRegistryInterface::FixedValueString filePathErrorStr;
if (registry->Get(filePathErrorStr, AZ::SettingsRegistryMergeUtils::FilePathKey_ErrorText); !filePathErrorStr.empty())
{
errorMessage += "Additional Info:\n";
errorMessage += filePathErrorStr.c_str();
}
}
if (auto nativeUI = AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get(); nativeUI != nullptr)
{
nativeUI->DisplayOkDialog("O3DE Fatal Error", errorMessage.c_str(), false);
}
else
{
AZ_Error("ComponentApplication", false, "O3DE Fatal Error: %s\n", errorMessage.c_str());
}
}
Entity* ComponentApplication::Create(const Descriptor& descriptor, const StartupParameters& startupParameters)
{
AZ_Assert(!m_isStarted, "Component application already started!");
if (m_engineRoot.empty())
{
ReportBadEngineRoot();
return nullptr;
}
m_startupParameters = startupParameters;
m_descriptor = descriptor;
@@ -871,46 +905,6 @@ namespace AZ
}
}
void ComponentApplication::ParseCommandLine(const AZ::CommandLine& commandLine)
{
struct OptionKeyToRegsetKey
{
AZStd::string_view m_optionKey;
AZStd::string m_regsetKey;
};
// Provide overrides for the engine root, the project root and the project cache root
AZStd::array commandOptions = {
OptionKeyToRegsetKey{ "engine-path", AZStd::string::format("%s/engine_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) },
OptionKeyToRegsetKey{ "project-path", AZStd::string::format("%s/project_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) },
OptionKeyToRegsetKey{ "project-cache-path", AZStd::string::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) }
};
AZStd::fixed_vector<AZStd::string, commandOptions.size()> overrideArgs;
for (auto&& [optionKey, regsetKey] : commandOptions)
{
if (size_t optionCount = commandLine.GetNumSwitchValues(optionKey); optionCount > 0)
{
// Use the last supplied command option value to override previous values
auto overrideArg = AZStd::string::format(R"(--regset="%s=%s")", regsetKey.c_str(),
commandLine.GetSwitchValue(optionKey, optionCount - 1).c_str());
overrideArgs.emplace_back(AZStd::move(overrideArg));
}
}
if (!overrideArgs.empty())
{
// Dump the input command line, add the additional option overrides
// and Parse the new command line into the Component Application command line
AZ::CommandLine::ParamContainer commandLineArgs;
commandLine.Dump(commandLineArgs);
commandLineArgs.insert(commandLineArgs.end(), AZStd::make_move_iterator(overrideArgs.begin()),
AZStd::make_move_iterator(overrideArgs.end()));
m_commandLine.Parse(commandLineArgs);
}
}
void ComponentApplication::MergeSettingsToRegistry(SettingsRegistryInterface& registry)
{
SettingsRegistryInterface::Specializations specializations;
@@ -328,9 +328,6 @@ namespace AZ
/// Create the drillers
void CreateDrillers();
/// Parse ComponentApplication specific command line arguments
void ParseCommandLine(const AZ::CommandLine& commandLine);
virtual void MergeSettingsToRegistry(SettingsRegistryInterface& registry);
//! Sets the specializations that will be used when loading the Settings Registry. Extend this in derived
@@ -15,45 +15,49 @@
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h>
namespace AZ
namespace AZ::NativeUI
{
namespace NativeUI
enum AssertAction
{
enum AssertAction
{
IGNORE_ASSERT = 0,
IGNORE_ALL_ASSERTS,
BREAK,
NONE,
};
IGNORE_ASSERT = 0,
IGNORE_ALL_ASSERTS,
BREAK,
NONE,
};
class NativeUIRequests
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
using MutexType = AZStd::recursive_mutex;
class NativeUIRequests
{
public:
AZ_RTTI(NativeUIRequests, "{48361EE6-C1E7-4965-A13A-7425B2691817}");
virtual ~NativeUIRequests() = default;
// Waits for user to select an option before execution continues
// Returns the option string selected by the user
virtual AZStd::string DisplayBlockingDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, const AZStd::vector<AZStd::string>& /*options*/) const { return ""; };
// Waits for user to select an option before execution continues
// Returns the option string selected by the user
virtual AZStd::string DisplayBlockingDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, const AZStd::vector<AZStd::string>& /*options*/) const { return ""; };
// Waits for user to select an option ('Ok' or optionally 'Cancel') before execution continues
// Returns the option string selected by the user
virtual AZStd::string DisplayOkDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, bool /*showCancel*/) const { return ""; };
// Waits for user to select an option ('Ok' or optionally 'Cancel') before execution continues
// Returns the option string selected by the user
virtual AZStd::string DisplayOkDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, bool /*showCancel*/) const { return ""; };
// Waits for user to select an option ('Yes', 'No' or optionally 'Cancel') before execution continues
// Returns the option string selected by the user
virtual AZStd::string DisplayYesNoDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, bool /*showCancel*/) const { return ""; };
// Waits for user to select an option ('Yes', 'No' or optionally 'Cancel') before execution continues
// Returns the option string selected by the user
virtual AZStd::string DisplayYesNoDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, bool /*showCancel*/) const { return ""; };
// Displays an assert dialog box
// Returns the action selected by the user
virtual AssertAction DisplayAssertDialog(const AZStd::string& /*message*/) const { return AssertAction::NONE; };
};
// Displays an assert dialog box
// Returns the action selected by the user
virtual AssertAction DisplayAssertDialog(const AZStd::string& /*message*/) const { return AssertAction::NONE; };
};
using NativeUIRequestBus = AZ::EBus<NativeUIRequests>;
}
}
class NativeUIEBusTraits
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
using MutexType = AZStd::recursive_mutex;
};
using NativeUIRequestBus = AZ::EBus<NativeUIRequests, NativeUIEBusTraits>;
} // namespace AZ::NativeUI
@@ -15,50 +15,19 @@
#include <AzCore/NativeUI/NativeUISystemComponent.h>
namespace AZ
namespace AZ::NativeUI
{
using namespace AZ::NativeUI;
void NativeUISystemComponent::Reflect(AZ::ReflectContext* context)
NativeUISystem::NativeUISystem()
{
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<NativeUISystemComponent, AZ::Component>()
->Version(0)
;
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<NativeUISystemComponent>("NativeUI", "Adds basic support for native (platform specific) UI dialog boxes")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
NativeUIRequestBus::Handler::BusConnect();
}
void NativeUISystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
NativeUISystem::~NativeUISystem()
{
provided.push_back(AZ_CRC("NativeUIService", 0x8ec25f87));
NativeUIRequestBus::Handler::BusDisconnect();
}
void NativeUISystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("NativeUIService", 0x8ec25f87));
}
void NativeUISystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
(void)required;
}
void NativeUISystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
(void)dependent;
}
AssertAction NativeUISystemComponent::DisplayAssertDialog(const AZStd::string& message) const
AssertAction NativeUISystem::DisplayAssertDialog(const AZStd::string& message) const
{
static const char* buttonNames[3] = { "Ignore", "Ignore All", "Break" };
AZStd::vector<AZStd::string> options;
@@ -80,7 +49,7 @@ namespace AZ
return AssertAction::NONE;
}
AZStd::string NativeUISystemComponent::DisplayOkDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const
AZStd::string NativeUISystem::DisplayOkDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const
{
AZStd::vector<AZStd::string> options;
@@ -93,7 +62,7 @@ namespace AZ
return DisplayBlockingDialog(title, message, options);
}
AZStd::string NativeUISystemComponent::DisplayYesNoDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const
AZStd::string NativeUISystem::DisplayYesNoDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const
{
AZStd::vector<AZStd::string> options;
@@ -106,18 +75,4 @@ namespace AZ
return DisplayBlockingDialog(title, message, options);
}
void NativeUISystemComponent::Init()
{
}
void NativeUISystemComponent::Activate()
{
NativeUIRequestBus::Handler::BusConnect();
}
void NativeUISystemComponent::Deactivate()
{
NativeUIRequestBus::Handler::BusDisconnect();
}
}
} // namespace AZ::NativeUI
@@ -15,40 +15,24 @@
#include <AzCore/Component/Component.h>
#include <AzCore/NativeUI/NativeUIRequests.h>
namespace AZ
namespace AZ::NativeUI
{
namespace NativeUI
class NativeUISystem
: public NativeUIRequestBus::Handler
{
class NativeUISystemComponent
: public AZ::Component
, public NativeUIRequestBus::Handler
{
public:
AZ_COMPONENT(NativeUISystemComponent, "{E996C058-4AFE-4C8C-816F-98D864D8576D}");
public:
AZ_RTTI(NativeUISystem, "{FF534B2C-11BE-4DEA-A5B7-A4FA96FE1EDE}", NativeUIRequests);
AZ_CLASS_ALLOCATOR(NativeUISystem, AZ::OSAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
NativeUISystem();
~NativeUISystem() override;
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
////////////////////////////////////////////////////////////////////////
// NativeUIRequestBus interface implementation
AZStd::string DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const override;
AZStd::string DisplayOkDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const override;
AZStd::string DisplayYesNoDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const override;
AssertAction DisplayAssertDialog(const AZStd::string& message) const override;
////////////////////////////////////////////////////////////////////////
protected:
////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Init() override;
void Activate() override;
void Deactivate() override;
////////////////////////////////////////////////////////////////////////
};
}
}
////////////////////////////////////////////////////////////////////////
// NativeUIRequestBus interface implementation
AZStd::string DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const override;
AZStd::string DisplayOkDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const override;
AZStd::string DisplayYesNoDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const override;
AssertAction DisplayAssertDialog(const AZStd::string& message) const override;
////////////////////////////////////////////////////////////////////////
};
} // namespace AZ::NativeUI
@@ -699,6 +699,10 @@ Data::AssetHandler::LoadResult ScriptSystemComponent::LoadAssetData(
script->m_scriptBuffer.resize(scriptDataLength);
stream->Read(scriptDataLength, script->m_scriptBuffer.data());
// Clear cached references in the event of a successful load. This function has to be queued on
// AssetBus where NotifyAssetReloaded is also queued, to ensure its execution before NotifyAssetReloaded
Data::AssetBus::QueueFunction(&ScriptSystemComponent::ClearAssetReferences, this, asset.GetId());
return Data::AssetHandler::LoadResult::LoadComplete;
}
@@ -256,7 +256,7 @@ namespace AZ
//! Remove the value at the provided path
//! @param path The path to a value that should be removed
//! @return Whether or not the value was stored at the provided path. An invalid path will return false;
//! @return Whether or not the path was found and removed. An invalid path will return false;
virtual bool Remove(AZStd::string_view path) = 0;
//! Structure which contains configuration settings for how to parse a single command line argument
@@ -32,17 +32,12 @@
namespace AZ::Internal
{
AZ::SettingsRegistryInterface::FixedValueString GetEngineMonikerForProject(
SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath)
SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectJsonPath)
{
// projectPath needs to be an absolute path here.
using namespace AZ::SettingsRegistryMergeUtils;
bool projectJsonMerged = false;
auto projectJsonPath = projectPath / "project.json";
if (AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
{
projectJsonMerged = settingsRegistry.MergeSettingsFile(
projectJsonPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ProjectSettingsRootKey);
}
bool projectJsonMerged = settingsRegistry.MergeSettingsFile(
projectJsonPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ProjectSettingsRootKey);
AZ::SettingsRegistryInterface::FixedValueString engineMoniker;
if (projectJsonMerged)
@@ -105,12 +100,12 @@ namespace AZ::Internal
const auto engineMonikerKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engine_name", EngineSettingsRootKey);
AZStd::set<AZ::IO::FixedMaxPath> projectPathsNotFound;
for (EngineInfo& engineInfo : pathVisitor.m_enginePaths)
{
AZ::IO::FixedMaxPath engineSettingsPath{engineInfo.m_path};
engineSettingsPath /= "engine.json";
if (AZ::IO::SystemFile::Exists(engineSettingsPath.c_str()))
if (auto engineSettingsPath = AZ::IO::FixedMaxPath{engineInfo.m_path} / "engine.json";
AZ::IO::SystemFile::Exists(engineSettingsPath.c_str()))
{
if (settingsRegistry.MergeSettingsFile(
engineSettingsPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, EngineSettingsRootKey))
@@ -119,12 +114,61 @@ namespace AZ::Internal
}
}
auto engineMoniker = Internal::GetEngineMonikerForProject(settingsRegistry, engineInfo.m_path / projectPath);
if (!engineMoniker.empty() && engineMoniker == engineInfo.m_moniker)
if (auto projectJsonPath = (engineInfo.m_path / projectPath / "project.json").LexicallyNormal();
AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
{
engineRoot = engineInfo.m_path;
break;
if (auto engineMoniker = Internal::GetEngineMonikerForProject(settingsRegistry, projectJsonPath);
!engineMoniker.empty() && engineMoniker == engineInfo.m_moniker)
{
engineRoot = engineInfo.m_path;
break;
}
}
else
{
projectPathsNotFound.insert(projectJsonPath);
}
// Continue looking for candidates, remove the previous engine and project settings that were merged above.
settingsRegistry.Remove(ProjectSettingsRootKey);
settingsRegistry.Remove(EngineSettingsRootKey);
}
if (engineRoot.empty())
{
AZStd::string errorStr;
if (!projectPathsNotFound.empty())
{
// This case is usually encountered when a project path is given as a relative path,
// which is assumed to be relative to an engine root.
// When no project.json files are found this way, dump this error message about
// which project paths were checked.
AZStd::string projectPathsTested;
for (const auto& path : projectPathsNotFound)
{
projectPathsTested.append(AZStd::string::format(" %s\n", path.c_str()));
}
errorStr = AZStd::string::format("No valid project was found at these locations:\n%s"
"Please supply a valid --project-path to the application.",
projectPathsTested.c_str());
}
else
{
// The other case is that a project.json was found, but after checking all the registered engines
// none of them matched the engine moniker.
AZStd::string enginePathsChecked;
for (const auto& engineInfo : pathVisitor.m_enginePaths)
{
enginePathsChecked.append(AZStd::string::format(" %s (%s)\n", engineInfo.m_path.c_str(), engineInfo.m_moniker.c_str()));
}
errorStr = AZStd::string::format(
"No engine was found in o3de_manifest.json with a name that matches the one set in the project.json.\n"
"Engines that were checked:\n%s"
"Please check that your engine and project have both been registered with scripts/o3de.py.", enginePathsChecked.c_str()
);
}
settingsRegistry.Set(FilePathKey_ErrorText, errorStr.c_str());
}
}
@@ -158,7 +202,7 @@ namespace AZ::Internal
return {};
}
void InjectSettingToCommandLineFront(AZ::SettingsRegistryInterface& settingsRegistry,
void InjectSettingToCommandLineBack(AZ::SettingsRegistryInterface& settingsRegistry,
AZStd::string_view path, AZStd::string_view value)
{
AZ::CommandLine commandLine;
@@ -168,7 +212,7 @@ namespace AZ::Internal
auto projectPathOverride = AZStd::string::format(R"(--regset="%.*s=%.*s")",
aznumeric_cast<int>(path.size()), path.data(), aznumeric_cast<int>(value.size()), value.data());
paramContainer.emplace(paramContainer.begin(), AZStd::move(projectPathOverride));
paramContainer.emplace(paramContainer.end(), AZStd::move(projectPathOverride));
commandLine.Parse(paramContainer);
AZ::SettingsRegistryMergeUtils::StoreCommandLineToRegistry(settingsRegistry, commandLine);
}
@@ -197,8 +241,8 @@ namespace AZ::SettingsRegistryMergeUtils
if (!engineRoot.empty())
{
settingsRegistry.Set(engineRootKey, engineRoot.Native());
// Inject the engine root into the front of the command line settings
Internal::InjectSettingToCommandLineFront(settingsRegistry, engineRootKey, engineRoot.Native());
// Inject the engine root at the end of the command line settings
Internal::InjectSettingToCommandLineBack(settingsRegistry, engineRootKey, engineRoot.Native());
return engineRoot;
}
}
@@ -244,8 +288,8 @@ namespace AZ::SettingsRegistryMergeUtils
if (!projectRoot.empty())
{
settingsRegistry.Set(projectRootKey, projectRoot.c_str());
// Inject the project root into the front of the command line settings
Internal::InjectSettingToCommandLineFront(settingsRegistry, projectRootKey, projectRoot.Native());
// Inject the project root at the end of the command line settings
Internal::InjectSettingToCommandLineBack(settingsRegistry, projectRootKey, projectRoot.Native());
return projectRoot;
}
}
@@ -874,6 +918,49 @@ namespace AZ::SettingsRegistryMergeUtils
return true;
}
void ParseCommandLine(AZ::CommandLine& commandLine)
{
struct OptionKeyToRegsetKey
{
AZStd::string_view m_optionKey;
AZStd::string m_regsetKey;
};
// Provide overrides for the engine root, the project root and the project cache root
AZStd::array commandOptions = {
OptionKeyToRegsetKey{
"engine-path", AZStd::string::format("%s/engine_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)},
OptionKeyToRegsetKey{
"project-path", AZStd::string::format("%s/project_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)},
OptionKeyToRegsetKey{
"project-cache-path",
AZStd::string::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)}};
AZStd::fixed_vector<AZStd::string, commandOptions.size()> overrideArgs;
for (auto&& [optionKey, regsetKey] : commandOptions)
{
if (size_t optionCount = commandLine.GetNumSwitchValues(optionKey); optionCount > 0)
{
// Use the last supplied command option value to override previous values
auto overrideArg = AZStd::string::format(
R"(--regset="%s=%s")", regsetKey.c_str(), commandLine.GetSwitchValue(optionKey, optionCount - 1).c_str());
overrideArgs.emplace_back(AZStd::move(overrideArg));
}
}
if (!overrideArgs.empty())
{
// Dump the input command line, add the additional option overrides
// and Parse the new command line args (write back) into the input command line.
AZ::CommandLine::ParamContainer commandLineArgs;
commandLine.Dump(commandLineArgs);
commandLineArgs.insert(
commandLineArgs.end(), AZStd::make_move_iterator(overrideArgs.begin()), AZStd::make_move_iterator(overrideArgs.end()));
commandLine.Parse(commandLineArgs);
}
}
bool DumpSettingsRegistryToStream(SettingsRegistryInterface& registry, AZStd::string_view key,
AZ::IO::GenericStream& stream, const DumperSettings& dumperSettings)
{
@@ -55,6 +55,9 @@ namespace AZ::SettingsRegistryMergeUtils
//! Development write storage path may be considered temporary or cache storage on some platforms
inline static constexpr char FilePathKey_DevWriteStorage[] = "/Amazon/AzCore/Runtime/FilePaths/DevWriteStorage";
//! Stores error text regarding engine boot sequence when engine and project roots cannot be determined
inline static constexpr char FilePathKey_ErrorText[] = "/Amazon/AzCore/Runtime/FilePaths/ErrorText";
//! Root key for where command line are stored at within the settings registry
inline static constexpr char CommandLineRootKey[] = "/Amazon/AzCore/Runtime/CommandLine";
//! Key set to trigger a notification that the CommandLine has been stored within the settings registry
@@ -219,6 +222,9 @@ namespace AZ::SettingsRegistryMergeUtils
//! into the AZ::CommandLine instance
bool GetCommandLineFromRegistry(SettingsRegistryInterface& registry, AZ::CommandLine& commandLine);
//! Parse a CommandLine and transform certain options into formal "regset" options
void ParseCommandLine(AZ::CommandLine& commandLine);
//! Structure for configuring how values should be dumped from the Settings Registry
struct DumperSettings
{
@@ -22,7 +22,7 @@ namespace AZ
{
namespace NativeUI
{
AZStd::string NativeUISystemComponent::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
{
AZ::Android::JNI::Object object("com/amazon/lumberyard/NativeUI/LumberyardNativeUI");
object.RegisterStaticMethod("DisplayDialog", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
@@ -12,16 +12,11 @@
#include <AzCore/NativeUI/NativeUISystemComponent.h>
namespace AZ
namespace AZ::NativeUI
{
namespace NativeUI
AZStd::string NativeUISystem::DisplayBlockingDialog([[maybe_unused]] const AZStd::string& title, [[maybe_unused]] const AZStd::string& message,
[[maybe_unused]] const AZStd::vector<AZStd::string>& options) const
{
AZStd::string NativeUISystemComponent::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
{
AZ_UNUSED(title);
AZ_UNUSED(message);
AZ_UNUSED(options);
return "";
}
return {};
}
}
@@ -26,7 +26,7 @@ namespace AZ
{
namespace NativeUI
{
AZStd::string NativeUISystemComponent::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
{
__block NSModalResponse response = -1;
@@ -245,7 +245,7 @@ namespace AZ
{
namespace NativeUI
{
AZStd::string NativeUISystemComponent::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
{
if (options.size() >= MAX_ITEMS)
{
@@ -18,7 +18,7 @@ namespace AZ
{
namespace NativeUI
{
AZStd::string NativeUISystemComponent::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
{
__block AZStd::string userSelection = "";
@@ -175,7 +175,7 @@ namespace AzFramework
}
// Initializes the IArchive for reading archive(.pak) files
if (auto archive = AZ::Interface<AZ::IO::IArchive>::Get(); !archive)
if (auto archive = AZ::Interface<AZ::IO::IArchive>::Get(); archive == nullptr)
{
m_archive = AZStd::make_unique<AZ::IO::Archive>();
AZ::Interface<AZ::IO::IArchive>::Register(m_archive.get());
@@ -189,6 +189,12 @@ namespace AzFramework
SetFileIOAliases();
}
if (auto nativeUI = AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get(); nativeUI == nullptr)
{
m_nativeUI = AZStd::make_unique<AZ::NativeUI::NativeUISystem>();
AZ::Interface<AZ::NativeUI::NativeUIRequests>::Register(m_nativeUI.get());
}
ApplicationRequests::Bus::Handler::BusConnect();
AZ::UserSettingsFileLocatorBus::Handler::BusConnect();
NetSystemRequestBus::Handler::BusConnect();
@@ -205,12 +211,17 @@ namespace AzFramework
AZ::UserSettingsFileLocatorBus::Handler::BusDisconnect();
ApplicationRequests::Bus::Handler::BusDisconnect();
if (AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get() == m_nativeUI.get())
{
AZ::Interface<AZ::NativeUI::NativeUIRequests>::Unregister(m_nativeUI.get());
}
m_nativeUI.reset();
// Unset the Archive file IO if it is set as the direct instance
if (AZ::IO::FileIOBase::GetInstance() == m_archiveFileIO.get())
{
AZ::IO::FileIOBase::SetInstance(nullptr);
}
m_archiveFileIO.reset();
// Destroy the IArchive instance
@@ -303,7 +314,6 @@ namespace AzFramework
azrtti_typeid<AZ::AssetManagerComponent>(),
azrtti_typeid<AZ::UserSettingsComponent>(),
azrtti_typeid<AZ::Debug::FrameProfilerComponent>(),
azrtti_typeid<AZ::NativeUI::NativeUISystemComponent>(),
azrtti_typeid<AZ::SliceComponent>(),
azrtti_typeid<AZ::SliceSystemComponent>(),
@@ -372,7 +382,6 @@ namespace AzFramework
azrtti_typeid<AZ::UserSettingsComponent>(),
azrtti_typeid<AZ::ScriptSystemComponent>(),
azrtti_typeid<AZ::JobManagerComponent>(),
azrtti_typeid<AZ::NativeUI::NativeUISystemComponent>(),
azrtti_typeid<AZ::SliceSystemComponent>(),
azrtti_typeid<AzFramework::AssetCatalogComponent>(),
@@ -16,6 +16,7 @@
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/UserSettings/UserSettings.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/NativeUI/NativeUIRequests.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/fixed_string.h>
@@ -187,6 +188,7 @@ namespace AzFramework
AZStd::unique_ptr<AZ::IO::FileIOBase> m_archiveFileIO; ///> The Default file IO instance is a ArchiveFileIO.
AZStd::unique_ptr<AZ::IO::Archive> m_archive; ///> The AZ::IO::Instance
AZStd::unique_ptr<Implementation> m_pimpl;
AZStd::unique_ptr<AZ::NativeUI::NativeUIRequests> m_nativeUI;
bool m_ownsConsole = false;
bool m_exitMainLoopRequested = false;
@@ -220,7 +220,7 @@ namespace AzFramework
{
// Read the wait for connection boolean from the Settings Registry
AZ::s64 waitForConnect64{};
if (!AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, waitForConnect64, AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::WaitForConnect))
if (AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, waitForConnect64, AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::WaitForConnect))
{
outputConnectionSettings.m_waitForConnect = waitForConnect64 != 0;
}
@@ -17,7 +17,7 @@
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Debug/Trace.h>
#include <AzFramework/Scene/Scene.h>
#include <AzFramework/Scene/SceneSystemBus.h>
#include <AzFramework/Scene/SceneSystemInterface.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
namespace AzFramework
@@ -50,36 +50,34 @@ namespace AzFramework
void AzFrameworkConfigurationSystemComponent::Activate()
{
// Create the defaults scene and associate the GameEntityContext with it.
AZ::Outcome<Scene*, AZStd::string> createSceneOutcome = AZ::Failure<AZStd::string>("SceneSystemRequests bus not responding.");
SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequests::CreateScene, "default");
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> createSceneOutcome =
SceneSystemInterface::Get()->CreateScene(Scene::MainSceneName);
if (createSceneOutcome)
{
Scene* scene = createSceneOutcome.GetValue();
bool success = false;
EntityContextId gameEntityContextId = EntityContextId::CreateNull();
GameEntityContextRequestBus::BroadcastResult(gameEntityContextId, &GameEntityContextRequests::GetGameEntityContextId);
if (!gameEntityContextId.IsNull())
AZStd::shared_ptr<Scene> scene = createSceneOutcome.TakeValue();
EntityContext* gameEntityContext = nullptr;
GameEntityContextRequestBus::BroadcastResult(gameEntityContext, &GameEntityContextRequests::GetGameEntityContextInstance);
if (gameEntityContext != nullptr)
{
SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequests::SetSceneForEntityContextId, gameEntityContextId, scene);
[[maybe_unused]] bool result = scene->SetSubsystem<EntityContext::SceneStorageType&>(gameEntityContext);
AZ_Assert(result, "Unable to register main entity context with the main scene.");
}
else
{
AZ_Assert(false, "Unable to retrieve the game entity context instance.");
}
AZ_Assert(success, "The application was unable to setup a scene for the game entity context, this should always work");
}
else
{
AZ_Assert(false, "%s", createSceneOutcome.GetError().data());
AZ_Assert(false, "Unable to create main scene due to: %s", createSceneOutcome.GetError().c_str());
}
}
void AzFrameworkConfigurationSystemComponent::Deactivate()
{
bool success = false;
SceneSystemRequestBus::BroadcastResult(
success, &AzFramework::SceneSystemRequestBus::Events::RemoveScene, "default");
AZ_Assert(success, "\"default\" scene was not removed");
[[maybe_unused]] bool success = SceneSystemInterface::Get()->RemoveScene(Scene::MainSceneName);
AZ_Assert(success, "Unable to remove the main scene.");
}
void AzFrameworkConfigurationSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
@@ -18,6 +18,8 @@
#include <AzCore/Component/TickBus.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/std/containers/stack.h>
#include <AzFramework/Scene/Scene.h>
#include <AzFramework/Scene/SceneSystemInterface.h>
#include "EntityContext.h"
@@ -37,6 +39,30 @@ namespace AzFramework
}
}
AZStd::shared_ptr<Scene> EntityContext::FindContainingScene(const EntityContextId& contextId)
{
auto sceneSystem = SceneSystemInterface::Get();
AZ_Assert(sceneSystem, "Attempting to retrieve the scene containing a entity context before the scene system is available.");
AZStd::shared_ptr<Scene> result;
sceneSystem->IterateActiveScenes([&result, &contextId](const AZStd::shared_ptr<Scene>& scene)
{
EntityContext** entityContext = scene->FindSubsystemInScene<EntityContext::SceneStorageType>();
if (entityContext && (*entityContext)->GetContextId() == contextId)
{
result = scene;
// Result found, returning.
return false;
}
else
{
// No match, continuing to search for containing scene.
return true;
}
});
return result;
}
//=========================================================================
// EntityContext ctor
//=========================================================================
@@ -17,6 +17,7 @@
#include <AzCore/Component/EntityBus.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/IdUtils.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Entity/SliceEntityOwnershipService.h>
@@ -27,7 +28,7 @@ namespace AZ
namespace AzFramework
{
class EntityContext;
class Scene;
/**
* Provides services for a group of entities under the umbrella of a given context.
@@ -47,9 +48,11 @@ namespace AzFramework
, public EntityOwnershipServiceNotificationBus::Handler
{
public:
AZ_TYPE_INFO(EntityContext, "{4F98A6B9-C7B5-450E-8A8A-30EEFC411EF5}");
/// The type used to store entity in AzFramework::Scene.
using SceneStorageType = EntityContext*;
EntityContext(AZ::SerializeContext* serializeContext = nullptr);
EntityContext(const EntityContextId& contextId, AZ::SerializeContext* serializeContext = nullptr);
EntityContext(const EntityContextId& contextId, AZStd::unique_ptr<EntityOwnershipService> entityOwnershipService,
@@ -75,6 +78,7 @@ namespace AzFramework
//////////////////////////////////////////////////////////////////////////
static void Reflect(AZ::ReflectContext* context);
static AZStd::shared_ptr<Scene> FindContainingScene(const EntityContextId& contextId);
protected:
@@ -66,6 +66,8 @@ namespace AzFramework
*/
virtual EntityContextId GetGameEntityContextId() = 0;
virtual EntityContext* GetGameEntityContextInstance() = 0;
/**
* Creates an entity in the game context.
* @param name A name for the new entity.
@@ -51,6 +51,7 @@ namespace AzFramework
//////////////////////////////////////////////////////////////////////////
// GameEntityContextRequestBus
AZ::Uuid GetGameEntityContextId() override { return GetContextId(); }
EntityContext* GetGameEntityContextInstance() override { return this; }
void ResetGameContext() override;
AZ::Entity* CreateGameEntity(const char* name) override;
BehaviorEntity CreateGameEntityForBehaviorContext(const char* name) override;
@@ -41,9 +41,10 @@ namespace AzFramework::ProjectManager
// at the end of the function
AZ::CommandLine commandLine;
commandLine.Parse(argc, argv);
AZ::SettingsRegistryImpl settingsRegistry;
// Store the Command line to the Setting Registry
AZ::SettingsRegistryMergeUtils::ParseCommandLine(commandLine);
// Store the Command line to the Setting Registry
AZ::SettingsRegistryImpl settingsRegistry;
AZ::SettingsRegistryMergeUtils::StoreCommandLineToRegistry(settingsRegistry, commandLine);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(settingsRegistry);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});
@@ -68,7 +69,14 @@ namespace AzFramework::ProjectManager
// If we were able to locate a path to a project, we're done
if (!projectRootPath.empty())
{
return ProjectPathCheckResult::ProjectPathFound;
AZ::IO::FixedMaxPath projectJsonPath = engineRootPath / projectRootPath / "project.json";
if (AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
{
return ProjectPathCheckResult::ProjectPathFound;
}
AZ_TracePrintf(
"ProjectManager", "Did not find a project file at location '%s', launching the Project Manager...",
projectJsonPath.c_str());
}
if (LaunchProjectManager(engineRootPath))
@@ -14,9 +14,9 @@
#include <AzCore/std/algorithm.h>
#include <AzCore/std/sort.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzFramework/Render/Intersector.h>
#include <AzFramework/Scene/Scene.h>
#include <AzFramework/Scene/SceneSystemBus.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <algorithm>
@@ -30,11 +30,11 @@ namespace AzFramework
{
IntersectorBus::Handler::BusConnect(m_contextId);
IntersectionNotificationBus::Handler::BusConnect(m_contextId);
Scene* scene = nullptr;
SceneSystemRequestBus::BroadcastResult(scene, &AzFramework::SceneSystemRequestBus::Events::GetSceneFromEntityContextId, m_contextId);
AZStd::shared_ptr<Scene> scene = EntityContext::FindContainingScene(m_contextId);
if (scene)
{
scene->SetSubsystem<IntersectorInterface>(this);
scene->SetSubsystem(this);
}
}
@@ -42,11 +42,12 @@ namespace AzFramework
{
IntersectorBus::Handler::BusDisconnect();
IntersectionNotificationBus::Handler::BusDisconnect();
Scene* scene = nullptr;
SceneSystemRequestBus::BroadcastResult(scene, &AzFramework::SceneSystemRequestBus::Events::GetSceneFromEntityContextId, m_contextId);
AZStd::shared_ptr<Scene> scene = EntityContext::FindContainingScene(m_contextId);
if (scene)
{
scene->UnsetSubsystem<IntersectorInterface>();
[[maybe_unused]] bool result = scene->UnsetSubsystem(this);
AZ_Assert(result, "Failed to unregister Intersector with scene");
}
}
@@ -33,6 +33,8 @@ namespace AzFramework
, protected IntersectionNotificationBus::Handler
{
public:
AZ_TYPE_INFO(AzFramework::RenderGeometry::Intersector, "{4CCA7971-CD83-4856-ADEA-89CEB41FB197}");
Intersector(AzFramework::EntityContextId contextId);
~Intersector();
@@ -14,13 +14,102 @@
namespace AzFramework
{
Scene::Scene(AZStd::string_view name)
: m_name(name)
Scene::Scene(AZStd::string name)
: m_name(AZStd::move(name))
{
}
const AZStd::string& Scene::GetName()
Scene::Scene(AZStd::string name, AZStd::shared_ptr<Scene> parent)
: m_name(AZStd::move(name))
, m_parent(AZStd::move(parent))
{
}
Scene::~Scene()
{
m_removalEvent.Signal(*this, RemovalEventType::Destroyed);
}
const AZStd::string& Scene::GetName() const
{
return m_name;
}
const AZStd::shared_ptr<Scene>& Scene::GetParent()
{
return m_parent;
}
AZStd::shared_ptr<const Scene> Scene::GetParent() const
{
return m_parent;
}
bool Scene::IsAlive() const
{
return m_isAlive;
}
void Scene::ConnectToEvents(RemovalEvent::Handler& handler)
{
handler.Connect(m_removalEvent);
}
void Scene::ConnectToEvents(SubsystemEvent::Handler& handler)
{
handler.Connect(m_subsystemEvent);
}
AZStd::any* Scene::FindSubsystem(const AZ::TypeId& typeId)
{
AZStd::any* result = FindSubsystemInScene(typeId);
return (!result && m_parent) ? m_parent->FindSubsystem(typeId) : result;
}
const AZStd::any* Scene::FindSubsystem(const AZ::TypeId& typeId) const
{
return const_cast<Scene*>(this)->FindSubsystem(typeId);
}
AZStd::any* Scene::FindSubsystemInScene(const AZ::TypeId& typeId)
{
// Spot check that the internal arrays remain consistent.
AZ_Assert(
m_systemKeys.size() == m_systemObjects.size(), "Key and object list in AzFramework::Scene '%s' have gone out of sync.",
m_name.c_str());
const size_t m_systemKeysCount = m_systemKeys.size();
for (size_t i = 0; i < m_systemKeysCount; ++i)
{
if (m_systemKeys[i] != typeId)
{
continue;
}
else
{
return &m_systemObjects[i];
}
}
return nullptr;
}
const AZStd::any* Scene::FindSubsystemInScene(const AZ::TypeId& typeId) const
{
return const_cast<Scene*>(this)->FindSubsystemInScene(typeId);
}
void Scene::MarkForDestruction()
{
m_isAlive = false;
m_removalEvent.Signal(*this, RemovalEventType::Zombified);
}
void Scene::RemoveSubsystem(size_t index, const AZ::TypeId& subsystemType)
{
m_systemKeys[index] = m_systemKeys.back();
m_systemObjects[index] = AZStd::move(m_systemObjects.back());
m_systemKeys.pop_back();
m_systemObjects.pop_back();
m_subsystemEvent.Signal(*this, SubsystemEventType::Removed, subsystemType);
}
}
@@ -11,10 +11,14 @@
*/
#pragma once
#include <AzCore/EBus/Event.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/any.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/string_view.h>
namespace AzFramework
{
@@ -24,71 +28,100 @@ namespace AzFramework
AZ_TYPE_INFO(Scene, "{DB449BB3-7A95-434D-BC61-47ACBB1F3436}");
AZ_CLASS_ALLOCATOR(Scene, AZ::SystemAllocator, 0);
explicit Scene(AZStd::string_view name);
friend class ISceneSystem;
const AZStd::string& GetName();
constexpr static AZStd::string_view MainSceneName = "Main";
constexpr static AZStd::string_view EditorMainSceneName = "Editor";
enum class RemovalEventType
{
Zombified, // The scene has be marked for destruction and is no longer visible in the scene system.
Destroyed, // The scene has been destroyed.
};
using RemovalEvent = AZ::Event<Scene&, RemovalEventType>;
enum class SubsystemEventType
{
Added,
Removed
};
using SubsystemEvent = AZ::Event<Scene&, SubsystemEventType, const AZ::TypeId&>;
explicit Scene(AZStd::string name);
Scene(AZStd::string name, AZStd::shared_ptr<Scene> parent);
~Scene();
[[nodiscard]] const AZStd::string& GetName() const;
[[nodiscard]] const AZStd::shared_ptr<Scene>& GetParent();
[[nodiscard]] AZStd::shared_ptr<const Scene> GetParent() const;
[[nodiscard]] bool IsAlive() const;
void ConnectToEvents(RemovalEvent::Handler& handler);
void ConnectToEvents(SubsystemEvent::Handler& handler);
// Set the instance of a subsystem associated with this scene.
template <typename T>
bool SetSubsystem(T* system);
bool SetSubsystem(T&& system);
// Unset the instance of a subsystem associated with this scene.
template <typename T>
bool UnsetSubsystem();
// Get the instance of a subsystem associated with this scene.
// Unset the instance of the exact system associated with this scene.
// Use this to make sure the expected instance is removed or to make sure type deduction is done in the same way as during setting.
template<typename T>
bool UnsetSubsystem(const T& system);
// Get the instance of a subsystem associated with this scene. This call will also look in parent scenes if not found on the target
// scene. Returns a pointer to the subsystem if found, otherwise returns a nullptr.
[[nodiscard]] AZStd::any* FindSubsystem(const AZ::TypeId& typeId);
// Get the instance of a subsystem associated with this scene. This call will also look in parent scenes if not found on the target
// scene. Returns a pointer to the subsystem if found, otherwise returns a nullptr.
[[nodiscard]] const AZStd::any* FindSubsystem(const AZ::TypeId& typeId) const;
// Get the instance of a subsystem associated with this scene. This call will also look in parent scenes if not found on the target
// scene. Returns a pointer to the subsystem if found, otherwise returns a nullptr.
template <typename T>
T* GetSubsystem();
[[nodiscard]] T* FindSubsystem();
// Get the instance of a subsystem associated with this scene. This call will also look in parent scenes if not found on the target
// scene. Returns a pointer to the subsystem if found, otherwise returns a nullptr.
template<typename T>
[[nodiscard]] const T* FindSubsystem() const;
// Get the instance of a subsystem associated with this scene. This call will only look in the selected scene. Returns a pointer to
// the subsystem if found, otherwise returns a nullptr.
[[nodiscard]] AZStd::any* FindSubsystemInScene(const AZ::TypeId& typeId);
// Get the instance of a subsystem associated with this scene. This call will only look in the selected scene. Returns a pointer to
// the subsystem if found, otherwise returns a nullptr.
[[nodiscard]] const AZStd::any* FindSubsystemInScene(const AZ::TypeId& typeId) const;
// Get the instance of a subsystem associated with this scene. This call will only look in the selected scene. Returns a pointer to
// the subsystem if found, otherwise returns a nullptr.
template<typename T>
[[nodiscard]] T* FindSubsystemInScene();
// Get the instance of a subsystem associated with this scene. This call will only look in the selected scene. Returns a pointer to
// the subsystem if found, otherwise returns a nullptr.
template<typename T>
[[nodiscard]] const T* FindSubsystemInScene() const;
private:
void MarkForDestruction();
void RemoveSubsystem(size_t index, const AZ::TypeId& subsystemType);
AZStd::string m_name;
RemovalEvent m_removalEvent;
SubsystemEvent m_subsystemEvent;
// Storing keys separate from data to optimize for fast key search.
AZStd::vector<AZ::TypeId> m_systemKeys;
AZStd::vector<void*> m_systemPointers;
AZStd::vector<AZStd::any> m_systemObjects;
// Name that identifies the scene.
AZStd::string m_name;
// Parent to this scene. Any subsystems are inherited from the parent but can be overwritten locally.
AZStd::shared_ptr<Scene> m_parent;
// If false, the scene has been removed from scene system and can no longer be found. As soon as all handles to the scene are
// released it will be destroyed.
bool m_isAlive{ true };
};
template <typename T>
bool Scene::SetSubsystem(T* system)
{
if (GetSubsystem<T>() != nullptr)
{
return false;
}
m_systemKeys.push_back(T::RTTI_Type());
m_systemPointers.push_back(system);
return true;
}
template <typename T>
bool Scene::UnsetSubsystem()
{
for (size_t i = 0; i < m_systemKeys.size(); ++i)
{
if (m_systemKeys.at(i) == T::RTTI_Type())
{
m_systemKeys.at(i) = m_systemKeys.back();
m_systemKeys.pop_back();
m_systemPointers.at(i) = m_systemPointers.back();
m_systemPointers.pop_back();
return true;
}
}
return false;
}
template <typename T>
T* Scene::GetSubsystem()
{
for (size_t i = 0; i < m_systemKeys.size(); ++i)
{
if (m_systemKeys.at(i) == T::RTTI_Type())
{
return reinterpret_cast<T*>(m_systemPointers.at(i));
}
}
return nullptr;
}
} // AzFramework
#include <AzFramework/Scene/Scene.inl>
@@ -0,0 +1,108 @@
/*
* 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.
*
*/
namespace AzFramework
{
template<typename T>
bool Scene::SetSubsystem(T&& system)
{
const AZ::TypeId& targetType = azrtti_typeid<T>();
for (const AZ::TypeId& key : m_systemKeys)
{
if (key == targetType)
{
return false;
}
}
m_systemKeys.push_back(targetType);
m_systemObjects.emplace_back(AZStd::forward<T>(system));
m_subsystemEvent.Signal(*this, SubsystemEventType::Added, targetType);
return true;
}
template<typename T>
bool Scene::UnsetSubsystem()
{
const AZ::TypeId& targetType = azrtti_typeid<T>();
const size_t m_systemKeysCount = m_systemKeys.size();
for (size_t i = 0; i < m_systemKeysCount; ++i)
{
if (m_systemKeys[i] != targetType)
{
continue;
}
else
{
RemoveSubsystem(i, targetType);
return true;
}
}
return false;
}
template<typename T>
bool Scene::UnsetSubsystem(const T& system)
{
const AZ::TypeId& targetType = azrtti_typeid<T>();
const size_t systemKeysCount = m_systemKeys.size();
for (size_t i = 0; i < systemKeysCount; ++i)
{
if (m_systemKeys[i] != targetType)
{
continue;
}
else
{
[[maybe_unused]] T* instance = AZStd::any_cast<T>(&m_systemObjects[i]);
AZ_Assert(
instance && *instance == system,
"Subsystem being released matched type, but wasn't pointing to the same system that was stored.");
RemoveSubsystem(i, targetType);
return true;
}
}
return false;
}
template<typename T>
T* Scene::FindSubsystem()
{
const AZ::TypeId& targetType = azrtti_typeid<T>();
AZStd::any* subSystem = FindSubsystem(targetType);
return subSystem ? AZStd::any_cast<T>(subSystem) : nullptr;
}
template<typename T>
const T* Scene::FindSubsystem() const
{
const AZ::TypeId& targetType = azrtti_typeid<T>();
const AZStd::any* subSystem = FindSubsystem(targetType);
return subSystem ? AZStd::any_cast<const T>(subSystem) : nullptr;
}
template<typename T>
T* Scene::FindSubsystemInScene()
{
const AZ::TypeId& targetType = azrtti_typeid<T>();
AZStd::any* subSystem = FindSubsystemInScene(targetType);
return subSystem ? AZStd::any_cast<T>(subSystem) : nullptr;
}
template<typename T>
const T* Scene::FindSubsystemInScene() const
{
const AZ::TypeId& targetType = azrtti_typeid<T>();
const AZStd::any* subSystem = FindSubsystemInScene(targetType);
return subSystem ? AZStd::any_cast<const T>(subSystem) : nullptr;
}
} // namespace AzFramework
@@ -1,114 +0,0 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzFramework/Entity/EntityContext.h>
namespace AzFramework
{
// Forward declarations
class Scene;
//! Interface used to create, get, or destroy scenes.
class SceneSystemRequests
: public AZ::EBusTraits
{
public:
virtual ~SceneSystemRequests() = default;
//! Single handler policy since there should only be one instance of this system component.
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Creates a scene with a given name.
//! - If there is already a scene with the provided name this will return AZ::Failure().
//! - If isDefault is set to true and there is already a default scene, the default scene will be switched to this one.
virtual AZ::Outcome<Scene*, AZStd::string> CreateScene(AZStd::string_view name) = 0;
//! Gets a scene with a given name
//! - If a scene does not exist with the given name, nullptr is returned.
virtual Scene* GetScene(AZStd::string_view name) = 0;
//! Gets all the scenes that currently exist.
virtual AZStd::vector<Scene*> GetAllScenes() = 0;
//! Remove a scene with a given name and return if the operation was successful.
//! - If the removed scene is the default scene, there will no longer be a default scene.
virtual bool RemoveScene(AZStd::string_view name) = 0;
//! Add a mapping from the provided EntityContextId to a Scene
//! - If a scene is already associated with this EntityContextId, nothing is changed and false is returned.
virtual bool SetSceneForEntityContextId(EntityContextId entityContextId, Scene* scene) = 0;
//! Remove a mapping from the provided EntityContextId to a Scene
//! - If no scene is found from the provided EntityContextId, false is returned.
virtual bool RemoveSceneForEntityContextId(EntityContextId entityContextId, Scene* scene) = 0;
//! Get the scene associated with an EntityContextId
//! - If no scene is found for the provided EntityContextId, nullptr is returned.
virtual Scene* GetSceneFromEntityContextId(EntityContextId entityContextId) = 0;
};
using SceneSystemRequestBus = AZ::EBus<SceneSystemRequests>;
//! Interface used for notifications from the scene system
class SceneSystemNotifications
: public AZ::EBusTraits
{
public:
virtual ~SceneSystemNotifications() = default;
//! There can be multiple listeners to changes in the scene system.
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
//! Called when a scene has been created.
virtual void SceneCreated(Scene& /*scene*/) {};
//! Called just before a scene is removed.
virtual void SceneAboutToBeRemoved(Scene& /*scene*/) {};
};
using SceneSystemNotificationBus = AZ::EBus<SceneSystemNotifications>;
//! Interface used for notifications about individual scenes
class SceneNotifications
: public AZ::EBusTraits
{
public:
virtual ~SceneNotifications() = default;
//! There can be multiple listeners to changes in the scene system.
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
//! Bus is listened to using the pointer of the scene
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
//! Specifies that events are addressed by the pointer to the scene
using BusIdType = Scene*;
//! Called just before a scene is removed.
virtual void SceneAboutToBeRemoved() {};
//! Called when an entity context is mapped to this scene.
virtual void EntityContextMapped(EntityContextId /*entityContextId*/) {};
//! Called when an entity context is unmapped from this scene.
virtual void EntityContextUnmapped(EntityContextId /*entityContextId*/) {};
};
using SceneNotificationBus = AZ::EBus<SceneNotifications>;
} // AzFramework
@@ -14,7 +14,7 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Scene/Scene.h>
#include <AzCore/std/smart_ptr/make_shared.h>
namespace AzFramework
{
@@ -41,14 +41,10 @@ namespace AzFramework
void SceneSystemComponent::Activate()
{
// Connect busses
SceneSystemRequestBus::Handler::BusConnect();
}
void SceneSystemComponent::Deactivate()
{
// Disconnect Busses
SceneSystemRequestBus::Handler::BusDisconnect();
}
void SceneSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
@@ -61,140 +57,105 @@ namespace AzFramework
incompatible.push_back(AZ_CRC("SceneSystemComponentService", 0xd8975435));
}
AZ::Outcome<Scene*, AZStd::string> SceneSystemComponent::CreateScene(AZStd::string_view name)
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> SceneSystemComponent::CreateScene(AZStd::string_view name)
{
Scene* existingScene = GetScene(name);
return CreateSceneWithParent(name, nullptr);
}
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> SceneSystemComponent::CreateSceneWithParent(
AZStd::string_view name, AZStd::shared_ptr<Scene> parent)
{
const AZStd::shared_ptr<Scene>& existingScene = GetScene(name);
if (existingScene)
{
return AZ::Failure<AZStd::string>("A scene already exists with this name.");
}
auto newScene = AZStd::make_unique<Scene>(name);
Scene* scenePointer = newScene.get();
m_scenes.push_back(AZStd::move(newScene));
SceneSystemNotificationBus::Broadcast(&SceneSystemNotificationBus::Events::SceneCreated, *scenePointer);
return AZ::Success(scenePointer);
auto newScene = AZStd::make_shared<Scene>(name, AZStd::move(parent));
m_activeScenes.push_back(newScene);
{
AZStd::lock_guard lock(m_eventMutex);
m_events.Signal(EventType::SceneCreated, newScene);
}
return AZ::Success(AZStd::move(newScene));
}
Scene* SceneSystemComponent::GetScene(AZStd::string_view name)
AZStd::shared_ptr<Scene> SceneSystemComponent::GetScene(AZStd::string_view name)
{
auto sceneIterator = AZStd::find_if(m_scenes.begin(), m_scenes.end(),
auto sceneIterator = AZStd::find_if(m_activeScenes.begin(), m_activeScenes.end(),
[name](auto& scene) -> bool
{
return scene->GetName() == name;
}
);
return sceneIterator == m_scenes.end() ? nullptr : sceneIterator->get();
return sceneIterator == m_activeScenes.end() ? nullptr : *sceneIterator;
}
AZStd::vector<Scene*> SceneSystemComponent::GetAllScenes()
void SceneSystemComponent::IterateActiveScenes(const ActiveIterationCallback& callback)
{
AZStd::vector<Scene*> scenes;
scenes.resize_no_construct(m_scenes.size());
for (size_t i = 0; i < m_scenes.size(); ++i)
bool keepGoing = true;
auto end = m_activeScenes.end();
for (auto it = m_activeScenes.begin(); it != end && keepGoing; ++it)
{
scenes.at(i) = m_scenes.at(i).get();
keepGoing = callback(*it);
}
}
void SceneSystemComponent::IterateZombieScenes(const ZombieIterationCallback& callback)
{
bool keepGoing = true;
auto end = m_zombieScenes.end();
for (auto it = m_zombieScenes.begin(); it != end && keepGoing;)
{
if (!it->expired())
{
keepGoing = callback(*(it->lock()));
++it;
}
else
{
*it = m_zombieScenes.back();
m_zombieScenes.pop_back();
end = m_zombieScenes.end();
}
}
return scenes;
}
bool SceneSystemComponent::RemoveScene(AZStd::string_view name)
{
for (size_t i = 0; i < m_scenes.size(); ++i)
for (AZStd::shared_ptr<Scene>& scene : m_activeScenes)
{
auto& scenePtr = m_scenes.at(i);
if (scenePtr->GetName() == name)
if (scene->GetName() == name)
{
// Remove any entityContext mappings.
Scene* scene = scenePtr.get();
for (auto entityContextScenePairIt = m_entityContextToScenes.begin(); entityContextScenePairIt != m_entityContextToScenes.end();)
MarkSceneForDestruction(*scene);
{
AZStd::pair<EntityContextId, Scene*>& pair = *entityContextScenePairIt;
if (pair.second == scene)
{
// swap and pop back.
*entityContextScenePairIt = m_entityContextToScenes.back();
m_entityContextToScenes.pop_back();
}
else
{
++entityContextScenePairIt;
}
AZStd::lock_guard lock(m_eventMutex);
m_events.Signal(EventType::ScenePendingRemoval, scene);
}
SceneSystemNotificationBus::Broadcast(&SceneSystemNotificationBus::Events::SceneAboutToBeRemoved, *scene);
SceneNotificationBus::Event(scene, &SceneNotificationBus::Events::SceneAboutToBeRemoved);
m_scenes.erase(&scenePtr);
// Zombies are weak pointers that are kept around for situations where there's a delay in deleting the scene. This can happen
// if there are outstanding calls like in-progress async calls or resources locked by hardware. A weak_ptr of the original
// scene is kept so the zombie scene can still be found through iteration as it may require additional calls such as Tick calls.
m_zombieScenes.push_back(scene);
scene = AZStd::move(m_activeScenes.back());
m_activeScenes.pop_back();
// The scene may not be held onto anymore, so check here to see if the previously added zombie can be released.
if (m_zombieScenes.back().expired())
{
m_zombieScenes.pop_back();
}
return true;
}
}
AZ_Warning("SceneSystemComponent", false, "Attempting to remove scene name \"%.*s\", but that scene was not found.", static_cast<int>(name.size()), name.data());
AZ_Warning("SceneSystemComponent", false, R"(Attempting to remove scene name "%.*s", but that scene was not found.)", AZ_STRING_ARG(name));
return false;
}
bool SceneSystemComponent::SetSceneForEntityContextId(EntityContextId entityContextId, Scene* scene)
void SceneSystemComponent::ConnectToEvents(SceneEvent::Handler& handler)
{
Scene* existingSceneForEntityContext = GetSceneFromEntityContextId(entityContextId);
if (existingSceneForEntityContext)
{
// This entity context is already mapped and must be unmapped explictely before it can be changed.
char entityContextIdString[EntityContextId::MaxStringBuffer];
entityContextId.ToString(entityContextIdString, sizeof(entityContextIdString));
AZ_Warning("SceneSystemComponent", false, "Failed to set a scene for entity context %s, scene is already set for that entity context.", entityContextIdString);
return false;
}
m_entityContextToScenes.emplace_back(entityContextId, scene);
SceneNotificationBus::Event(scene, &SceneNotificationBus::Events::EntityContextMapped, entityContextId);
return true;
AZStd::lock_guard lock(m_eventMutex);
handler.Connect(m_events);
}
bool SceneSystemComponent::RemoveSceneForEntityContextId(EntityContextId entityContextId, Scene* scene)
{
if (!scene || entityContextId.IsNull())
{
return false;
}
for (auto entityContextScenePairIt = m_entityContextToScenes.begin(); entityContextScenePairIt != m_entityContextToScenes.end();)
{
AZStd::pair<EntityContextId, Scene*>& pair = *entityContextScenePairIt;
if (!(pair.first == entityContextId && pair.second == scene))
{
++entityContextScenePairIt;
}
else
{
// swap and pop back.
*entityContextScenePairIt = m_entityContextToScenes.back();
m_entityContextToScenes.pop_back();
SceneNotificationBus::Event(scene, &SceneNotificationBus::Events::EntityContextUnmapped, entityContextId);
return true;
}
}
char entityContextIdString[EntityContextId::MaxStringBuffer];
entityContextId.ToString(entityContextIdString, sizeof(entityContextIdString));
AZ_Warning("SceneSystemComponent", false, "Failed to remove scene \"%.*s\" for entity context %s, entity context is not currently mapped to that scene.", static_cast<int>(scene->GetName().size()), scene->GetName().data(), entityContextIdString);
return false;
}
Scene* SceneSystemComponent::GetSceneFromEntityContextId(EntityContextId entityContextId)
{
for (AZStd::pair<EntityContextId, Scene*>& pair : m_entityContextToScenes)
{
if (pair.first == entityContextId)
{
return pair.second;
}
}
return nullptr;
}
} // AzFramework
} // namespace AzFramework
@@ -13,18 +13,18 @@
#include <AzCore/Component/Component.h>
#include <AzCore/std/containers/map.h>
#include <AzFramework/Scene/SceneSystemBus.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzFramework/Scene/SceneSystemInterface.h>
#include <AzFramework/Entity/EntityContext.h>
namespace AzFramework
{
class SceneSystemComponent
: public AZ::Component
, public SceneSystemRequestBus::Handler
, public SceneSystemInterface::Registrar
{
public:
AZ_COMPONENT(SceneSystemComponent, "{7AC53AF0-BE1A-437C-BE3E-4D6A998DA945}", AZ::Component);
AZ_COMPONENT(SceneSystemComponent, "{7AC53AF0-BE1A-437C-BE3E-4D6A998DA945}", AZ::Component, ISceneSystem);
SceneSystemComponent();
~SceneSystemComponent() override;
@@ -41,24 +41,26 @@ namespace AzFramework
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
//////////////////////////////////////////////////////////////////////////
// SceneSystemRequestsBus::Handler
// SceneSystemInterface overrides
//////////////////////////////////////////////////////////////////////////
AZ::Outcome<Scene*, AZStd::string> CreateScene(AZStd::string_view name) override;
Scene* GetScene(AZStd::string_view name) override;
AZStd::vector<Scene*> GetAllScenes() override;
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> CreateScene(AZStd::string_view name) override;
AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> CreateSceneWithParent(
AZStd::string_view name, AZStd::shared_ptr<Scene> parent) override;
[[nodiscard]] AZStd::shared_ptr<Scene> GetScene(AZStd::string_view name) override;
void IterateActiveScenes(const ActiveIterationCallback& callback) override;
void IterateZombieScenes(const ZombieIterationCallback& callback) override;
bool RemoveScene(AZStd::string_view name) override;
bool SetSceneForEntityContextId(EntityContextId entityContextId, Scene* scene) override;
bool RemoveSceneForEntityContextId(EntityContextId entityContextId, Scene* scene) override;
Scene* GetSceneFromEntityContextId(EntityContextId entityContextId) override;
void ConnectToEvents(SceneEvent::Handler& handler) override;
private:
AZ_DISABLE_COPY_MOVE(SceneSystemComponent);
AZ_DISABLE_COPY(SceneSystemComponent);
// Container of scene in order of creation
AZStd::vector<AZStd::unique_ptr<Scene>> m_scenes;
// Map of entity context Ids to scenes. Using a vector because lookups will be common, but the size will be small.
AZStd::vector<AZStd::pair<EntityContextId, Scene*>> m_entityContextToScenes;
AZStd::vector<AZStd::shared_ptr<Scene>> m_activeScenes;
AZStd::vector<AZStd::weak_ptr<Scene>> m_zombieScenes;
// Using a mutex around the events as other threads may respond to a new/deleted scene by making
// local updates and unregistering themselves. Since Scene is single threaded, no updates (other
// then unregistering an event) should be done from other threads though.
AZStd::recursive_mutex m_eventMutex;
SceneEvent m_events;
};
}
@@ -0,0 +1,88 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/EBus/Event.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/functional.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzFramework/Scene/Scene.h>
namespace AzFramework
{
//! Interface used to create, get, or destroy scenes.
//! This interface is single thread and is intended to be called from a single thread, commonly the main thread. The exception
//! is connecting events, which is thread safe.
class ISceneSystem
{
public:
AZ_RTTI(AzFramework::ISceneSystem, "{DAE482A8-88AE-4BD3-8A5B-52D19A96E15F}");
AZ_DISABLE_COPY_MOVE(ISceneSystem);
enum class EventType
{
SceneCreated,
ScenePendingRemoval
};
using SceneEvent = AZ::Event<EventType, const AZStd::shared_ptr<Scene>&>;
ISceneSystem() = default;
virtual ~ISceneSystem() = default;
using ActiveIterationCallback = AZStd::function<bool(const AZStd::shared_ptr<Scene>& scene)>;
using ZombieIterationCallback = AZStd::function<bool(Scene& scene)>;
//! Creates a scene with a given name.
//! - If there is already a scene with the provided name this will return AZ::Failure().
virtual AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> CreateScene(AZStd::string_view name) = 0;
//! Creates a scene with a given name and a parent.
//! - If there is already a scene with the provided name this will return AZ::Failure().
virtual AZ::Outcome<AZStd::shared_ptr<Scene>, AZStd::string> CreateSceneWithParent(
AZStd::string_view name, AZStd::shared_ptr<Scene> parent) = 0;
//! Gets a scene with a given name
//! - If a scene does not exist with the given name, nullptr is returned.
[[nodiscard]] virtual AZStd::shared_ptr<Scene> GetScene(AZStd::string_view name) = 0;
//! Iterates over all scenes that are in active use. Iteration stops if the callback returns false or all scenes have been listed.
virtual void IterateActiveScenes(const ActiveIterationCallback& callback) = 0;
//! Iterates over all zombie scenes. Zombie scenes are scenes that have been removed but still have references held on to. This can
//! happen because scenes hold on to subsystems that can't immediately be deleted. These subsystems may still require being called
//! such as a periodic tick. Iteration stops if the callback returns false or all scenes have been listed.
virtual void IterateZombieScenes(const ZombieIterationCallback& callback) = 0;
//! Remove a scene with a given name and return if the operation was successful.
virtual bool RemoveScene(AZStd::string_view name) = 0;
//! Connects the provided handler to the events that are called after scenes are created or before they get removed.
virtual void ConnectToEvents(SceneEvent::Handler& handler) = 0;
protected:
// Strictly a forwarding function to call private functions on the scene.
void MarkSceneForDestruction(Scene& scene) { scene.MarkForDestruction(); }
};
using SceneSystemInterface = AZ::Interface<ISceneSystem>;
// EBus wrapper for ScriptCanvas
class ISceneSystemRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using ILoggerRequestBus = AZ::EBus<ISceneSystem, ISceneSystemRequests>;
} // AzFramework
@@ -194,10 +194,11 @@ set(FILES
Logging/MissingAssetLogger.h
Logging/MissingAssetNotificationBus.h
Scene/Scene.h
Scene/Scene.inl
Scene/Scene.cpp
Scene/SceneSystemBus.h
Scene/SceneSystemComponent.h
Scene/SceneSystemComponent.cpp
Scene/SceneSystemInterface.h
Script/ScriptComponent.h
Script/ScriptComponent.cpp
Script/ScriptDebugAgentBus.h
@@ -284,6 +284,10 @@ namespace AzToolsFramework
void ToolsApplication::Start(const Descriptor& descriptor, const StartupParameters& startupParameters/* = StartupParameters()*/)
{
Application::Start(descriptor, startupParameters);
if (!m_isStarted)
{
return;
}
m_editorEntityManager.Start();
@@ -49,6 +49,8 @@ namespace AzToolsFramework
/// Retrieve the Id of the editor entity context.
virtual AzFramework::EntityContextId GetEditorEntityContextId() = 0;
virtual AzFramework::EntityContext* GetEditorEntityContextInstance() = 0;
/// Creates an entity in the editor context.
/// \return the EntityId for the created Entity
virtual AZ::EntityId CreateNewEditorEntity(const char* name) = 0;
@@ -77,6 +77,7 @@ namespace AzToolsFramework
//////////////////////////////////////////////////////////////////////////
// EditorEntityContextRequestBus
AzFramework::EntityContextId GetEditorEntityContextId() override { return GetContextId(); }
AzFramework::EntityContext* GetEditorEntityContextInstance() override { return this; }
void ResetEditorContext() override;
AZ::EntityId CreateNewEditorEntity(const char* name) override;
@@ -769,12 +769,12 @@ namespace AzToolsFramework
template<typename Vertex>
void EditorVertexSelectionBase<Vertex>::DisplayViewport2d(
const AzFramework::ViewportInfo& /*viewportInfo*/,
const AzFramework::ViewportInfo& viewportInfo,
AzFramework::DebugDisplayRequests& debugDisplay)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
m_editorBoxSelect.Display2d(debugDisplay);
m_editorBoxSelect.Display2d(viewportInfo, debugDisplay);
}
template<typename Vertex>
@@ -16,7 +16,7 @@
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Scene/Scene.h>
#include <AzFramework/Scene/SceneSystemBus.h>
#include <AzFramework/Scene/SceneSystemInterface.h>
#include <AzToolsFramework/Editor/EditorSettingsAPIBus.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
@@ -56,22 +56,47 @@ namespace AzToolsFramework
void AzToolsFrameworkConfigurationSystemComponent::Activate()
{
// Associate the EditorEntityContext with the default scene.
// Create the editor specific child scene to the main scene and add the editor entity context to it.
AzFramework::EntityContextId editorEntityContextId;
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
AzFramework::Scene* defaultScene = nullptr;
AzFramework::SceneSystemRequestBus::BroadcastResult(defaultScene, &AzFramework::SceneSystemRequests::GetScene, "default");
if (!editorEntityContextId.IsNull() && defaultScene)
if (!editorEntityContextId.IsNull())
{
bool success = false;
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequests::SetSceneForEntityContextId, editorEntityContextId, defaultScene);
auto sceneSystem = AzFramework::SceneSystemInterface::Get();
AZ_Assert(sceneSystem, "Scene system not available to create the editor scene.");
AZStd::shared_ptr<AzFramework::Scene> mainScene = sceneSystem->GetScene(AzFramework::Scene::MainSceneName);
if (mainScene)
{
AZ::Outcome<AZStd::shared_ptr<AzFramework::Scene>, AZStd::string> editorScene =
sceneSystem->CreateSceneWithParent(AzFramework::Scene::EditorMainSceneName, mainScene);
if (editorScene.IsSuccess())
{
AzFramework::EntityContext* editorEntityContext = nullptr;
EditorEntityContextRequestBus::BroadcastResult(
editorEntityContext, &EditorEntityContextRequests::GetEditorEntityContextInstance);
if (editorEntityContext != nullptr)
{
[[maybe_unused]] bool contextAdded =
editorScene.GetValue()->SetSubsystem<AzFramework::EntityContext::SceneStorageType&>(editorEntityContext);
AZ_Assert(contextAdded, "Unable to add editor entity context to scene.");
}
}
else
{
AZ_Assert(false, "Failed to create editor scene because: %s", editorScene.GetError().c_str());
}
}
else
{
AZ_Assert(false, "No main scene to parent the editor scene under.");
}
}
}
void AzToolsFrameworkConfigurationSystemComponent::Deactivate()
{
[[maybe_unused]] bool success = AzFramework::SceneSystemInterface::Get()->RemoveScene(AzFramework::Scene::EditorMainSceneName);
AZ_Assert(success, "Unable to remove the main editor scene.");
}
void AzToolsFrameworkConfigurationSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
@@ -13,6 +13,7 @@
#include "EditorBoxSelect.h"
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <QApplication>
@@ -72,7 +73,7 @@ namespace AzToolsFramework
m_previousModifiers = mouseInteraction.m_mouseInteraction.m_keyboardModifiers;
}
void EditorBoxSelect::Display2d(AzFramework::DebugDisplayRequests& debugDisplay)
void EditorBoxSelect::Display2d(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
@@ -82,12 +83,15 @@ namespace AzToolsFramework
debugDisplay.SetLineWidth(s_boxSelectLineWidth);
debugDisplay.SetColor(s_boxSelectColor);
debugDisplay.DrawWireBox(
AZ::Vector3(
static_cast<float>(m_boxSelectRegion->x()), static_cast<float>(m_boxSelectRegion->y()), 0.0f),
AZ::Vector3(
static_cast<float>(m_boxSelectRegion->x()) + static_cast<float>(m_boxSelectRegion->width()),
static_cast<float>(m_boxSelectRegion->y()) + static_cast<float>(m_boxSelectRegion->height()), 0.0f));
AZ::Vector2 viewportSize = AzToolsFramework::GetCameraState(viewportInfo.m_viewportId).m_viewportSize;
debugDisplay.DrawWireQuad2d(
AZ::Vector2(
aznumeric_cast<float>(m_boxSelectRegion->x()), aznumeric_cast<float>(m_boxSelectRegion->y())) / viewportSize,
AZ::Vector2(
aznumeric_cast<float>(m_boxSelectRegion->x()) + aznumeric_cast<float>(m_boxSelectRegion->width()),
aznumeric_cast<float>(m_boxSelectRegion->y()) + aznumeric_cast<float>(m_boxSelectRegion->height())) / viewportSize,
0.f);
debugDisplay.DepthTestOn();
@@ -42,7 +42,7 @@ namespace AzToolsFramework
const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
/// Responsible for drawing the 2d box representing the selection in screen space.
void Display2d(AzFramework::DebugDisplayRequests& debugDisplay);
void Display2d(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay);
/// Custom drawing behavior to happen during a box select.
void DisplayScene(
@@ -3494,7 +3494,7 @@ namespace AzToolsFramework
DrawAxisGizmo(viewportInfo, debugDisplay);
m_boxSelect.Display2d(debugDisplay);
m_boxSelect.Display2d(viewportInfo, debugDisplay);
}
void EditorTransformComponentSelection::RefreshSelectedEntityIds()
+1
View File
@@ -42,6 +42,7 @@ namespace UnitTest
MOCK_METHOD3(InstantiateDynamicSlice, AzFramework::SliceInstantiationTicket(const AZ::Data::Asset<AZ::Data::AssetData>&, const AZ::Transform&, const AZ::IdUtils::Remapper<AZ::EntityId>::IdMapper&));
MOCK_METHOD0(GetGameEntityContextId, AzFramework::EntityContextId());
MOCK_METHOD0(GetGameEntityContextInstance, AzFramework::EntityContext*());
MOCK_METHOD1(CreateGameEntity, AZ::Entity*(const char*));
MOCK_METHOD1(AddGameEntity, void (AZ::Entity*));
MOCK_METHOD1(DestroyGameEntity, void (const AZ::EntityId&));
+90 -118
View File
@@ -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