Revamped AzFramework::Scene (#332)

Updated AzFramework::Scene to allow it to serve as the one-stop location for localized singletons. Localized singletons in this case are instance that can only occur once in an environment but multiple times within an application. As an example, this allows settings up a single camera per viewport for instance.

Highlights of changes:

Replaced the original ebuses with interfaces and events for easy of use and performance.
Removed the Entity Context specific code and moved that to new locations within the Entity Context itself.
Allowed basic inheritance. If a subsystem isn't found in a scene the parent can optionally be searched.
Scenes can enter a zombie state and avoid immediately being deleted. This is needed for situations where subsystems can't be destroyed until async calls have been completed.
This commit is contained in:
AMZN-koppersr
2021-04-29 08:50:48 -07:00
committed by GitHub
parent 45fd86c2bf
commit 5e4094b258
42 changed files with 757 additions and 529 deletions
@@ -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;
@@ -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