more development freshening

Signed-off-by: Scott Murray <scottmur@amazon.com>
monroegm-disable-blank-issue-2
Scott Murray 4 years ago
commit 2c0e78cb0d

@ -253,73 +253,3 @@ class TestAtomEditorComponentsMain(object):
) )
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
@pytest.mark.system
class TestMaterialEditorBasicTests(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project):
def delete_files():
file_system.delete(
[
os.path.join(workspace.paths.project(), "Materials", "test_material.material"),
os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"),
os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"),
],
True,
True,
)
# Cleanup our newly created materials
delete_files()
def teardown():
# Cleanup our newly created materials
delete_files()
request.addfinalizer(teardown)
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
@pytest.mark.test_case_id("C34448113") # Creating a New Asset.
@pytest.mark.test_case_id("C34448114") # Opening an Existing Asset.
@pytest.mark.test_case_id("C34448115") # Closing Selected Material.
@pytest.mark.test_case_id("C34448116") # Closing All Materials.
@pytest.mark.test_case_id("C34448117") # Closing all but Selected Material.
@pytest.mark.test_case_id("C34448118") # Saving Material.
@pytest.mark.test_case_id("C34448119") # Saving as a New Material.
@pytest.mark.test_case_id("C34448120") # Saving as a Child Material.
@pytest.mark.test_case_id("C34448121") # Saving all Open Materials.
def test_MaterialEditorBasicTests(
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name):
expected_lines = [
"Material opened: True",
"Test asset doesn't exist initially: True",
"New asset created: True",
"New Material opened: True",
"Material closed: True",
"All documents closed: True",
"Close All Except Selected worked as expected: True",
"Actual Document saved with changes: True",
"Document saved as copy is saved with changes: True",
"Document saved as child is saved with changes: True",
"Save All worked as expected: True",
]
unexpected_lines = [
# "Trace::Assert",
# "Trace::Error",
"Traceback (most recent call last):"
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
generic_launcher,
"hydra_AtomMaterialEditor_BasicTests.py",
run_python="--runpython",
timeout=120,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
null_renderer=True,
log_file_name="MaterialEditor.log",
)

@ -70,3 +70,75 @@ class TestAtomEditorComponentsSandbox(object):
null_renderer=True, null_renderer=True,
cfg_args=cfg_args, cfg_args=cfg_args,
) )
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
@pytest.mark.system
class TestMaterialEditorBasicTests(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project):
def delete_files():
file_system.delete(
[
os.path.join(workspace.paths.project(), "Materials", "test_material.material"),
os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"),
os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"),
],
True,
True,
)
# Cleanup our newly created materials
delete_files()
def teardown():
# Cleanup our newly created materials
delete_files()
request.addfinalizer(teardown)
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
@pytest.mark.test_case_id("C34448113") # Creating a New Asset.
@pytest.mark.test_case_id("C34448114") # Opening an Existing Asset.
@pytest.mark.test_case_id("C34448115") # Closing Selected Material.
@pytest.mark.test_case_id("C34448116") # Closing All Materials.
@pytest.mark.test_case_id("C34448117") # Closing all but Selected Material.
@pytest.mark.test_case_id("C34448118") # Saving Material.
@pytest.mark.test_case_id("C34448119") # Saving as a New Material.
@pytest.mark.test_case_id("C34448120") # Saving as a Child Material.
@pytest.mark.test_case_id("C34448121") # Saving all Open Materials.
def test_MaterialEditorBasicTests(
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name):
expected_lines = [
"Material opened: True",
"Test asset doesn't exist initially: True",
"New asset created: True",
"New Material opened: True",
"Material closed: True",
"All documents closed: True",
"Close All Except Selected worked as expected: True",
"Actual Document saved with changes: True",
"Document saved as copy is saved with changes: True",
"Document saved as child is saved with changes: True",
"Save All worked as expected: True",
]
unexpected_lines = [
# "Trace::Assert",
# "Trace::Error",
"Traceback (most recent call last):"
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
generic_launcher,
"hydra_AtomMaterialEditor_BasicTests.py",
run_python="--runpython",
timeout=120,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
null_renderer=True,
log_file_name="MaterialEditor.log",
)

@ -43,7 +43,7 @@ namespace AzFramework
class IMatchmakingAsyncRequests class IMatchmakingAsyncRequests
{ {
public: public:
AZ_RTTI(ISessionAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}"); AZ_RTTI(IMatchmakingAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}");
IMatchmakingAsyncRequests() = default; IMatchmakingAsyncRequests() = default;
virtual ~IMatchmakingAsyncRequests() = default; virtual ~IMatchmakingAsyncRequests() = default;
@ -60,4 +60,31 @@ namespace AzFramework
// @param stopMatchmakingRequest The request of StopMatchmaking operation // @param stopMatchmakingRequest The request of StopMatchmaking operation
virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0;
}; };
//! MatchmakingAsyncRequestNotifications
//! The notifications correspond to matchmaking async requests
class MatchmakingAsyncRequestNotifications
: public AZ::EBusTraits
{
public:
// Safeguard handler for multi-threaded use case
using MutexType = AZStd::recursive_mutex;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
virtual void OnAcceptMatchAsyncComplete() = 0;
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
virtual void OnStopMatchmakingAsyncComplete() = 0;
};
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
} // namespace AzFramework } // namespace AzFramework

@ -13,36 +13,10 @@
namespace AzFramework namespace AzFramework
{ {
//! MatchmakingAsyncRequestNotifications
//! The notifications correspond to matchmaking async requests
class MatchmakingAsyncRequestNotifications
: public AZ::EBusTraits
{
public:
// Safeguard handler for multi-threaded use case
using MutexType = AZStd::recursive_mutex;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
virtual void OnAcceptMatchAsyncComplete() = 0;
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
virtual void OnStopMatchmakingAsyncComplete() = 0;
};
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
//! MatchmakingNotifications //! MatchmakingNotifications
//! The matchmaking notifications to listen for performing required operations //! The matchmaking notifications to listen for performing required operations
class MatchAcceptanceNotifications //! based on matchmaking ticket event
class MatchmakingNotifications
: public AZ::EBusTraits : public AZ::EBusTraits
{ {
public: public:
@ -55,8 +29,18 @@ namespace AzFramework
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// OnMatchAcceptance is fired when DescribeMatchmaking ticket status is REQUIRES_ACCEPTANCE // OnMatchAcceptance is fired when match is found and pending on acceptance
// Use this notification to accept found match
virtual void OnMatchAcceptance() = 0; virtual void OnMatchAcceptance() = 0;
// OnMatchComplete is fired when match is complete
virtual void OnMatchComplete() = 0;
// OnMatchError is fired when match is processed with error
virtual void OnMatchError() = 0;
// OnMatchFailure is fired when match is failed to complete
virtual void OnMatchFailure() = 0;
}; };
using MatchAcceptanceNotificationBus = AZ::EBus<MatchAcceptanceNotifications>; using MatchmakingNotificationBus = AZ::EBus<MatchmakingNotifications>;
} // namespace AzFramework } // namespace AzFramework

@ -53,7 +53,7 @@ namespace AzToolsFramework
AZ_Assert(m_loaderInterface != nullptr, AZ_Assert(m_loaderInterface != nullptr,
"Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work"); "Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work");
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "NewLevel.prefab")); m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "newLevel.prefab"));
m_sliceOwnershipService.BusConnect(m_entityContextId); m_sliceOwnershipService.BusConnect(m_entityContextId);
m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage; m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage;
m_editorSliceOwnershipService.BusConnect(); m_editorSliceOwnershipService.BusConnect();

@ -28,7 +28,9 @@ namespace AzToolsFramework::Prefab
"Instance Entity Mapper Interface could not be found. " "Instance Entity Mapper Interface could not be found. "
"Check that it is being correctly initialized."); "Check that it is being correctly initialized.");
EditorEntityInfoNotificationBus::Handler::BusConnect();
EditorEntityContextNotificationBus::Handler::BusConnect(); EditorEntityContextNotificationBus::Handler::BusConnect();
PrefabPublicNotificationBus::Handler::BusConnect();
AZ::Interface<PrefabFocusInterface>::Register(this); AZ::Interface<PrefabFocusInterface>::Register(this);
AZ::Interface<PrefabFocusPublicInterface>::Register(this); AZ::Interface<PrefabFocusPublicInterface>::Register(this);
} }
@ -37,10 +39,12 @@ namespace AzToolsFramework::Prefab
{ {
AZ::Interface<PrefabFocusPublicInterface>::Unregister(this); AZ::Interface<PrefabFocusPublicInterface>::Unregister(this);
AZ::Interface<PrefabFocusInterface>::Unregister(this); AZ::Interface<PrefabFocusInterface>::Unregister(this);
PrefabPublicNotificationBus::Handler::BusDisconnect();
EditorEntityContextNotificationBus::Handler::BusDisconnect(); EditorEntityContextNotificationBus::Handler::BusDisconnect();
EditorEntityInfoNotificationBus::Handler::BusDisconnect();
} }
void PrefabFocusHandler::Initialize() void PrefabFocusHandler::InitializeEditorInterfaces()
{ {
m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get(); m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
AZ_Assert( AZ_Assert(
@ -55,13 +59,6 @@ namespace AzToolsFramework::Prefab
"Prefab - PrefabFocusHandler - " "Prefab - PrefabFocusHandler - "
"Focus Mode Interface could not be found. " "Focus Mode Interface could not be found. "
"Check that it is being correctly initialized."); "Check that it is being correctly initialized.");
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
AZ_Assert(
m_instanceEntityMapperInterface,
"Prefab - PrefabFocusHandler - "
"Instance Entity Mapper Interface could not be found. "
"Check that it is being correctly initialized.");
} }
PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId) PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId)
@ -90,12 +87,12 @@ namespace AzToolsFramework::Prefab
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index) PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
{ {
if (index < 0 || index >= m_instanceFocusVector.size()) if (index < 0 || index >= m_instanceFocusHierarchy.size())
{ {
return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex.")); return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex."));
} }
InstanceOptionalReference focusedInstance = m_instanceFocusVector[index]; InstanceOptionalReference focusedInstance = m_instanceFocusHierarchy[index];
FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId()); FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId());
@ -134,41 +131,37 @@ namespace AzToolsFramework::Prefab
return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on.")); return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on."));
} }
if (!m_isInitialized) // Close all container entities in the old path.
{ CloseInstanceContainers(m_instanceFocusHierarchy);
Initialize();
}
if (!m_focusedInstance.has_value() || &m_focusedInstance->get() != &focusedInstance->get())
{
// Close all container entities in the old path
CloseInstanceContainers(m_instanceFocusVector);
m_focusedInstance = focusedInstance; m_focusedInstance = focusedInstance;
m_focusedTemplateId = focusedInstance->get().GetTemplateId(); m_focusedTemplateId = focusedInstance->get().GetTemplateId();
AZ::EntityId containerEntityId; AZ::EntityId containerEntityId;
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt) if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
{ {
containerEntityId = focusedInstance->get().GetContainerEntityId(); containerEntityId = focusedInstance->get().GetContainerEntityId();
} }
else else
{ {
containerEntityId = AZ::EntityId(); containerEntityId = AZ::EntityId();
} }
// Focus on the descendants of the container entity // Focus on the descendants of the container entity in the Editor, if the interface is initialized.
if (m_focusModeInterface)
{
m_focusModeInterface->SetFocusRoot(containerEntityId); m_focusModeInterface->SetFocusRoot(containerEntityId);
}
// Refresh path variables // Refresh path variables.
RefreshInstanceFocusList(); RefreshInstanceFocusList();
RefreshInstanceFocusPath();
// Open all container entities in the new path // Open all container entities in the new path.
OpenInstanceContainers(m_instanceFocusVector); OpenInstanceContainers(m_instanceFocusHierarchy);
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
}
return AZ::Success(); return AZ::Success();
} }
@ -220,49 +213,80 @@ namespace AzToolsFramework::Prefab
const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
{ {
return aznumeric_cast<int>(m_instanceFocusVector.size()); return aznumeric_cast<int>(m_instanceFocusHierarchy.size());
} }
void PrefabFocusHandler::OnEntityStreamLoadSuccess() void PrefabFocusHandler::OnContextReset()
{ {
if (!m_isInitialized)
{
Initialize();
}
// Clear the old focus vector // Clear the old focus vector
m_instanceFocusVector.clear(); m_instanceFocusHierarchy.clear();
// Focus on the root prefab (AZ::EntityId() will default to it) // Focus on the root prefab (AZ::EntityId() will default to it)
FocusOnPrefabInstanceOwningEntityId(AZ::EntityId()); FocusOnPrefabInstanceOwningEntityId(AZ::EntityId());
} }
void PrefabFocusHandler::OnEntityInfoUpdatedName(AZ::EntityId entityId, [[maybe_unused]]const AZStd::string& name)
{
// Determine if the entityId is the container for any of the instances in the vector
auto result = AZStd::find_if(
m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(),
[entityId](const InstanceOptionalReference& instance)
{
return (instance->get().GetContainerEntityId() == entityId);
}
);
if (result != m_instanceFocusHierarchy.end())
{
// Refresh the path and notify changes.
RefreshInstanceFocusPath();
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
}
}
void PrefabFocusHandler::OnPrefabInstancePropagationEnd()
{
// Refresh the path and notify changes in case propagation updated any container names.
RefreshInstanceFocusPath();
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
}
void PrefabFocusHandler::RefreshInstanceFocusList() void PrefabFocusHandler::RefreshInstanceFocusList()
{ {
m_instanceFocusVector.clear(); m_instanceFocusHierarchy.clear();
m_instanceFocusPath.clear();
AZStd::list<InstanceOptionalReference> instanceFocusList; AZStd::list<InstanceOptionalReference> instanceFocusList;
// Use a support list to easily push front while traversing the prefab hierarchy
InstanceOptionalReference currentInstance = m_focusedInstance; InstanceOptionalReference currentInstance = m_focusedInstance;
while (currentInstance.has_value()) while (currentInstance.has_value())
{ {
instanceFocusList.push_front(currentInstance); m_instanceFocusHierarchy.emplace_back(currentInstance);
currentInstance = currentInstance->get().GetParentInstance(); currentInstance = currentInstance->get().GetParentInstance();
} }
// Populate internals using the support list // Invert the vector, since we need the top instance to be at index 0
for (auto& instance : instanceFocusList) AZStd::reverse(m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end());
}
void PrefabFocusHandler::RefreshInstanceFocusPath()
{
m_instanceFocusPath.clear();
for (const InstanceOptionalReference& instance : m_instanceFocusHierarchy)
{ {
m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName()); m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName());
m_instanceFocusVector.emplace_back(instance);
} }
} }
void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
{ {
// If this is called outside the Editor, this interface won't be initialized.
if (!m_containerEntityInterface)
{
return;
}
for (const InstanceOptionalReference& instance : instances) for (const InstanceOptionalReference& instance : instances)
{ {
if (instance.has_value()) if (instance.has_value())
@ -274,6 +298,12 @@ namespace AzToolsFramework::Prefab
void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
{ {
// If this is called outside the Editor, this interface won't be initialized.
if (!m_containerEntityInterface)
{
return;
}
for (const InstanceOptionalReference& instance : instances) for (const InstanceOptionalReference& instance : instances)
{ {
if (instance.has_value()) if (instance.has_value())

@ -11,9 +11,11 @@
#include <AzCore/Memory/SystemAllocator.h> #include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h> #include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h> #include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h> #include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h> #include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
#include <AzToolsFramework/Prefab/Template/Template.h> #include <AzToolsFramework/Prefab/Template/Template.h>
namespace AzToolsFramework namespace AzToolsFramework
@ -30,7 +32,9 @@ namespace AzToolsFramework::Prefab
class PrefabFocusHandler final class PrefabFocusHandler final
: private PrefabFocusInterface : private PrefabFocusInterface
, private PrefabFocusPublicInterface , private PrefabFocusPublicInterface
, private PrefabPublicNotificationBus::Handler
, private EditorEntityContextNotificationBus::Handler , private EditorEntityContextNotificationBus::Handler
, private EditorEntityInfoNotificationBus::Handler
{ {
public: public:
AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0); AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0);
@ -38,9 +42,8 @@ namespace AzToolsFramework::Prefab
PrefabFocusHandler(); PrefabFocusHandler();
~PrefabFocusHandler(); ~PrefabFocusHandler();
void Initialize();
// PrefabFocusInterface overrides ... // PrefabFocusInterface overrides ...
void InitializeEditorInterfaces() override;
PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override; PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override;
TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override; TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override;
InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override; InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override;
@ -54,25 +57,34 @@ namespace AzToolsFramework::Prefab
const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override; const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override;
// EditorEntityContextNotificationBus overrides ... // EditorEntityContextNotificationBus overrides ...
void OnEntityStreamLoadSuccess() override; void OnContextReset() override;
// EditorEntityInfoNotificationBus overrides ...
void OnEntityInfoUpdatedName(AZ::EntityId entityId, const AZStd::string& name) override;
// PrefabPublicNotifications overrides ...
void OnPrefabInstancePropagationEnd();
private: private:
PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance); PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance);
void RefreshInstanceFocusList(); void RefreshInstanceFocusList();
void RefreshInstanceFocusPath();
void OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const; void OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
void CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const; void CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
//! The instance the editor is currently focusing on.
InstanceOptionalReference m_focusedInstance; InstanceOptionalReference m_focusedInstance;
//! The templateId of the focused instance.
TemplateId m_focusedTemplateId; TemplateId m_focusedTemplateId;
AZStd::vector<InstanceOptionalReference> m_instanceFocusVector; //! The list of instances going from the root (index 0) to the focused instance.
AZStd::vector<InstanceOptionalReference> m_instanceFocusHierarchy;
//! A path containing the names of the containers in the instance focus hierarchy, separated with a /.
AZ::IO::Path m_instanceFocusPath; AZ::IO::Path m_instanceFocusPath;
ContainerEntityInterface* m_containerEntityInterface = nullptr; ContainerEntityInterface* m_containerEntityInterface = nullptr;
FocusModeInterface* m_focusModeInterface = nullptr; FocusModeInterface* m_focusModeInterface = nullptr;
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr; InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
bool m_isInitialized = false;
}; };
} // namespace AzToolsFramework::Prefab } // namespace AzToolsFramework::Prefab

@ -26,6 +26,11 @@ namespace AzToolsFramework::Prefab
public: public:
AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}"); AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}");
//! Initializes the editor interfaces for Prefab Focus mode.
//! If this is not called on initialization, the Prefab Focus Mode functions will still work
//! but won't trigger the Editor APIs to visualize focus mode on the UI.
virtual void InitializeEditorInterfaces() = 0;
//! Set the focused prefab instance to the owning instance of the entityId provided. //! Set the focused prefab instance to the owning instance of the entityId provided.
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on. //! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0; virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0;

@ -27,6 +27,7 @@
#include <AzToolsFramework/Entity/EditorEntityContextBus.h> #include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h> #include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h> #include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h> #include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h> #include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h> #include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
@ -135,6 +136,10 @@ namespace AzToolsFramework
return; return;
} }
// Initialize Editor functionality for the Prefab Focus Handler
auto prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
prefabFocusInterface->InitializeEditorInterfaces();
EditorContextMenuBus::Handler::BusConnect(); EditorContextMenuBus::Handler::BusConnect();
EditorEventsBus::Handler::BusConnect(); EditorEventsBus::Handler::BusConnect();
PrefabInstanceContainerNotificationBus::Handler::BusConnect(); PrefabInstanceContainerNotificationBus::Handler::BusConnect();

@ -10,7 +10,9 @@
#include <utilities/BatchApplicationManager.h> #include <utilities/BatchApplicationManager.h>
#include <utilities/ApplicationServer.h> #include <utilities/ApplicationServer.h>
#include <AzFramework/Asset/AssetSystemComponent.h> #include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/UnitTest/TestTypes.h> #include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Utils/Utils.h>
#include <connection/connectionManager.h> #include <connection/connectionManager.h>
#include <QCoreApplication> #include <QCoreApplication>
#include <QTemporaryDir> #include <QTemporaryDir>
@ -98,10 +100,26 @@ namespace AssetProcessorMessagesTests
int argC = 0; int argC = 0;
m_batchApplicationManager = AZStd::make_unique<UnitTestBatchApplicationManager>(&argC, nullptr, nullptr); m_batchApplicationManager = AZStd::make_unique<UnitTestBatchApplicationManager>(&argC, nullptr, nullptr);
m_batchApplicationManager->BeforeRun();
// Override Game Name to be "AutomatedTesting" auto registry = AZ::SettingsRegistry::Get();
AssetUtilities::ComputeProjectName("AutomatedTesting", true); EXPECT_NE(registry, nullptr);
constexpr AZ::SettingsRegistryInterface::FixedValueString bootstrapKey{
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey
};
constexpr AZ::SettingsRegistryInterface::FixedValueString projectPathKey{ bootstrapKey + "/project_path" };
registry->Set(projectPathKey, "AutomatedTesting");
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
// Force the branch token into settings registry before starting the application manager.
// This avoids writing the asset_processor.setreg file which can cause fileIO errors.
const AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath();
constexpr AZ::SettingsRegistryInterface::FixedValueString branchTokenKey{ bootstrapKey + "/assetProcessor_branch_token" };
AZStd::string token;
AZ::StringFunc::AssetPath::CalculateBranchToken(enginePath.c_str(), token);
registry->Set(branchTokenKey, token.c_str());
auto status = m_batchApplicationManager->BeforeRun();
ASSERT_EQ(status, ApplicationManager::BeforeRunStatus::Status_Success);
m_batchApplicationManager->m_platformConfiguration = new PlatformConfiguration(); m_batchApplicationManager->m_platformConfiguration = new PlatformConfiguration();
m_batchApplicationManager->InitAssetProcessorManager(); m_batchApplicationManager->InitAssetProcessorManager();
@ -159,21 +177,25 @@ namespace AssetProcessorMessagesTests
ASSERT_TRUE(result); ASSERT_TRUE(result);
}); });
} }
void TearDown() override void TearDown() override
{ {
QEventLoop eventLoop; if (m_batchApplicationManager->m_connectionManager)
{
QEventLoop eventLoop;
QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit); QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit);
m_batchApplicationManager->m_connectionManager->QuitRequested(); m_batchApplicationManager->m_connectionManager->QuitRequested();
eventLoop.exec(); eventLoop.exec();
}
m_assetSystemComponent->Deactivate(); if (m_assetSystemComponent)
{
m_assetSystemComponent->Deactivate();
}
m_batchApplicationManager->Destroy(); m_batchApplicationManager->Destroy();
} }

@ -505,6 +505,14 @@ bool ApplicationManager::StartAZFramework()
AzFramework::Application::Descriptor appDescriptor; AzFramework::Application::Descriptor appDescriptor;
AZ::ComponentApplication::StartupParameters params; AZ::ComponentApplication::StartupParameters params;
QDir projectPath{ AssetUtilities::ComputeProjectPath() };
if (!projectPath.exists("project.json"))
{
AZStd::string errorMsg = AZStd::string::format("Path '%s' is not a valid project path.", projectPath.path().toUtf8().constData());
AssetProcessor::MessageInfoBus::Broadcast(&AssetProcessor::MessageInfoBus::Events::OnErrorMessage, errorMsg.c_str());
return false;
}
QString projectName = AssetUtilities::ComputeProjectName(); QString projectName = AssetUtilities::ComputeProjectName();
// Prevent loading of gems in the Create method of the ComponentApplication // Prevent loading of gems in the Create method of the ComponentApplication
@ -520,7 +528,6 @@ bool ApplicationManager::StartAZFramework()
//Registering all the Components //Registering all the Components
m_frameworkApp.RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor()); m_frameworkApp.RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor());
Reflect(); Reflect();
const AzFramework::CommandLine* commandLine = nullptr; const AzFramework::CommandLine* commandLine = nullptr;

@ -95,6 +95,8 @@ GUIApplicationManager::~GUIApplicationManager()
ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun() ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun()
{ {
AssetProcessor::MessageInfoBus::Handler::BusConnect();
ApplicationManager::BeforeRunStatus status = ApplicationManagerBase::BeforeRun(); ApplicationManager::BeforeRunStatus status = ApplicationManagerBase::BeforeRun();
if (status != ApplicationManager::BeforeRunStatus::Status_Success) if (status != ApplicationManager::BeforeRunStatus::Status_Success)
{ {
@ -109,7 +111,6 @@ ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun()
#if defined(EXTERNAL_CRASH_REPORTING) #if defined(EXTERNAL_CRASH_REPORTING)
CrashHandler::ToolsCrashHandler::InitCrashHandler("AssetProcessor", projectAssetRoot.absolutePath().toStdString()); CrashHandler::ToolsCrashHandler::InitCrashHandler("AssetProcessor", projectAssetRoot.absolutePath().toStdString());
#endif #endif
AssetProcessor::MessageInfoBus::Handler::BusConnect();
// we have to monitor both the cache folder and the database file and restart AP if either of them gets deleted // we have to monitor both the cache folder and the database file and restart AP if either of them gets deleted
// It is important to note that we are monitoring the parent folder and not the actual cache folder itself since // It is important to note that we are monitoring the parent folder and not the actual cache folder itself since
@ -436,98 +437,7 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message)
connection = Qt::QueuedConnection; connection = Qt::QueuedConnection;
} }
if (m_isCurrentlyLoadingGems) QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, QString(message)), Q_ARG(bool, true));
{
// if something goes wrong during gem initialization, this is a special case and we need to be extra helpful.
const char* userSettingsFile = "_WAF_/user_settings.options";
const char* defaultSettingsFile = "_WAF_/default_settings.json";
QDir engineRoot;
AssetUtilities::ComputeEngineRoot(engineRoot);
QString settingsPath = engineRoot.absoluteFilePath(userSettingsFile);
QString friendlyErrorMessage;
bool usingDefaults = false;
if (QFile::exists(settingsPath))
{
QSettings loader(settingsPath, QSettings::IniFormat);
QVariant settingValue = loader.value("Game Projects/enabled_game_projects");
QStringList compiledProjects = settingValue.toStringList();
if (compiledProjects.isEmpty())
{
QByteArray byteArray;
QFile jsonFile;
jsonFile.setFileName(engineRoot.absoluteFilePath(defaultSettingsFile));
jsonFile.open(QIODevice::ReadOnly | QIODevice::Text);
byteArray = jsonFile.readAll();
jsonFile.close();
QJsonObject settingsObject = QJsonDocument::fromJson(byteArray).object();
QJsonArray projectsArray = settingsObject["Game Projects"].toArray();
if (!projectsArray.isEmpty())
{
auto projectObject = projectsArray[0].toObject();
QString projects = projectObject["default_value"].toString();
if (!projects.isEmpty())
{
compiledProjects = projects.split(',');
usingDefaults = true;
}
}
}
for (int i = 0; i < compiledProjects.size(); ++i)
{
compiledProjects[i] = compiledProjects[i].trimmed();
}
QString enabledProject = AssetUtilities::ComputeProjectName();
if (!compiledProjects.contains(enabledProject))
{
QString projectSourceLine;
if (usingDefaults)
{
projectSourceLine = QString("The currently compiled projects according to the defaults in %1 are '%2'").arg(defaultSettingsFile);
}
else
{
projectSourceLine = QString("The currently compiled projects according to %1 are '%2'").arg(userSettingsFile);
}
projectSourceLine = projectSourceLine.arg(compiledProjects.join(", "));
friendlyErrorMessage = QString("An error occurred while loading gems.\n"
"The enabled game project is not in the list of compiled projects.\n"
"Please configure the enabled project to be compiled and rebuild or change the enabled project.\n"
"The currently enabled game project (from bootstrap.cfg or /%4 command-line parameter) is '%1'.\n"
"%2\n"
"Full error text:\n"
"%3"
).arg(enabledProject).arg(projectSourceLine).arg(message).arg(AssetUtilities::ProjectPathOverrideParameter);
}
}
if (friendlyErrorMessage.isEmpty())
{
friendlyErrorMessage = QString("An error occurred while loading gems.\n"
"This can happen when new gems are added to a project, but those gems need to be built in order to function.\n"
"This can also happen when switching to a different project, one which uses gems which are not yet built.\n"
"To continue, please build the current project before attempting to run Asset Processor again.\n\n"
"Full error text:\n"
"%1").arg(message);
}
QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, friendlyErrorMessage), Q_ARG(bool, true));
}
else
{
QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, QString(message)), Q_ARG(bool, true));
}
return true; return true;
} }

@ -5,75 +5,16 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT * SPDX-License-Identifier: Apache-2.0 OR MIT
* *
*/ */
#pragma once #pragma once
#include <AzCore/EBus/EBus.h> #include <AzCore/EBus/EBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/std/string/string.h> #include <AzCore/std/string/string.h>
#include <AzFramework/Matchmaking/IMatchmakingRequests.h> #include <AzFramework/Matchmaking/IMatchmakingRequests.h>
#include <AzFramework/Session/ISessionRequests.h>
namespace AWSGameLift namespace AWSGameLift
{ {
//! IAWSGameLiftRequests // IMatchmakingAsyncRequests EBus wrapper
//! GameLift Gem interfaces to configure client manager
class IAWSGameLiftRequests
{
public:
AZ_RTTI(IAWSGameLiftRequests, "{494167AD-1185-4AF3-8BF9-C8C37FC9C199}");
IAWSGameLiftRequests() = default;
virtual ~IAWSGameLiftRequests() = default;
//! ConfigureGameLiftClient
//! Configure GameLift client to interact with Amazon GameLift service
//! @param region Specifies the AWS region to use
//! @return True if client configuration succeeds, false otherwise
virtual bool ConfigureGameLiftClient(const AZStd::string& region) = 0;
//! CreatePlayerId
//! Create a new, random ID number for every player in every new game session.
//! @param includeBrackets Whether includes brackets in player id
//! @param includeDashes Whether includes dashes in player id
//! @return The player id to use in game session
virtual AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) = 0;
};
// IAWSGameLiftRequests EBus wrapper for scripting
class AWSGameLiftRequests
: public AZ::EBusTraits
{
public:
using MutexType = AZStd::recursive_mutex;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using AWSGameLiftRequestBus = AZ::EBus<IAWSGameLiftRequests, AWSGameLiftRequests>;
// ISessionAsyncRequests EBus wrapper for scripting
class AWSGameLiftSessionAsyncRequests
: public AZ::EBusTraits
{
public:
using MutexType = AZStd::recursive_mutex;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using AWSGameLiftSessionAsyncRequestBus = AZ::EBus<AzFramework::ISessionAsyncRequests, AWSGameLiftSessionAsyncRequests>;
// ISessionRequests EBus wrapper for scripting
class AWSGameLiftSessionRequests
: public AZ::EBusTraits
{
public:
using MutexType = AZStd::recursive_mutex;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using AWSGameLiftSessionRequestBus = AZ::EBus<AzFramework::ISessionRequests, AWSGameLiftSessionRequests>;
// IMatchmakingAsyncRequests EBus wrapper for scripting
class AWSGameLiftMatchmakingAsyncRequests class AWSGameLiftMatchmakingAsyncRequests
: public AZ::EBusTraits : public AZ::EBusTraits
{ {
@ -84,7 +25,7 @@ namespace AWSGameLift
}; };
using AWSGameLiftMatchmakingAsyncRequestBus = AZ::EBus<AzFramework::IMatchmakingAsyncRequests, AWSGameLiftMatchmakingAsyncRequests>; using AWSGameLiftMatchmakingAsyncRequestBus = AZ::EBus<AzFramework::IMatchmakingAsyncRequests, AWSGameLiftMatchmakingAsyncRequests>;
// IMatchmakingRequests EBus wrapper for scripting // IMatchmakingRequests EBus wrapper
class AWSGameLiftMatchmakingRequests class AWSGameLiftMatchmakingRequests
: public AZ::EBusTraits : public AZ::EBusTraits
{ {
@ -121,7 +62,7 @@ namespace AWSGameLift
virtual void StopPolling() = 0; virtual void StopPolling() = 0;
}; };
// IAWSGameLiftMatchmakingEventRequests EBus wrapper for scripting // IAWSGameLiftMatchmakingEventRequests EBus wrapper
class AWSGameLiftMatchmakingEventRequests class AWSGameLiftMatchmakingEventRequests
: public AZ::EBusTraits : public AZ::EBusTraits
{ {

@ -0,0 +1,51 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h>
namespace AWSGameLift
{
//! IAWSGameLiftRequests
//! GameLift Gem interfaces to configure GameLift client and other help functions,
//! like creating random GameLift player id
class IAWSGameLiftRequests
{
public:
AZ_RTTI(IAWSGameLiftRequests, "{494167AD-1185-4AF3-8BF9-C8C37FC9C199}");
IAWSGameLiftRequests() = default;
virtual ~IAWSGameLiftRequests() = default;
//! ConfigureGameLiftClient
//! Configure GameLift client to interact with Amazon GameLift service
//! @param region Specifies the AWS region to use
//! @return True if client configuration succeeds, false otherwise
virtual bool ConfigureGameLiftClient(const AZStd::string& region) = 0;
//! CreatePlayerId
//! Create a new, random ID number for every player in every new game session.
//! @param includeBrackets Whether includes brackets in player id
//! @param includeDashes Whether includes dashes in player id
//! @return The player id to use in game session
virtual AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) = 0;
};
// IAWSGameLiftRequests EBus wrapper
class AWSGameLiftRequests
: public AZ::EBusTraits
{
public:
using MutexType = AZStd::recursive_mutex;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using AWSGameLiftRequestBus = AZ::EBus<IAWSGameLiftRequests, AWSGameLiftRequests>;
} // namespace AWSGameLift

@ -0,0 +1,38 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Session/ISessionRequests.h>
namespace AWSGameLift
{
// ISessionAsyncRequests EBus wrapper
class AWSGameLiftSessionAsyncRequests
: public AZ::EBusTraits
{
public:
using MutexType = AZStd::recursive_mutex;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using AWSGameLiftSessionAsyncRequestBus = AZ::EBus<AzFramework::ISessionAsyncRequests, AWSGameLiftSessionAsyncRequests>;
// ISessionRequests EBus wrapper
class AWSGameLiftSessionRequests
: public AZ::EBusTraits
{
public:
using MutexType = AZStd::recursive_mutex;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using AWSGameLiftSessionRequestBus = AZ::EBus<AzFramework::ISessionRequests, AWSGameLiftSessionRequests>;
} // namespace AWSGameLift

@ -97,6 +97,7 @@ namespace AWSGameLift
AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName,
"Matchmaking ticket %s is complete.", ticket.GetTicketId().c_str()); "Matchmaking ticket %s is complete.", ticket.GetTicketId().c_str());
RequestPlayerJoinMatch(ticket, playerId); RequestPlayerJoinMatch(ticket, playerId);
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchComplete);
m_status = TicketTrackerStatus::Idle; m_status = TicketTrackerStatus::Idle;
return; return;
} }
@ -104,25 +105,28 @@ namespace AWSGameLift
ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::FAILED || ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::FAILED ||
ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::CANCELLED) ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::CANCELLED)
{ {
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Matchmaking ticket %s is not complete, %s", AZ_Warning(AWSGameLiftClientLocalTicketTrackerName, false, "Matchmaking ticket %s is not complete, %s",
ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str()); ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str());
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchFailure);
m_status = TicketTrackerStatus::Idle; m_status = TicketTrackerStatus::Idle;
return; return;
} }
else if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::REQUIRES_ACCEPTANCE) else if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::REQUIRES_ACCEPTANCE)
{ {
// broadcast acceptance requires to player AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is pending on acceptance, %s.",
AzFramework::MatchAcceptanceNotificationBus::Broadcast(&AzFramework::MatchAcceptanceNotifications::OnMatchAcceptance); ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str());
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchAcceptance);
} }
else else
{ {
AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is processing, %s.", AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is processing, %s.",
ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str()); ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str());
} }
} }
else else
{ {
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Unable to find expected ticket with id %s", ticketId.c_str()); AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Unable to find expected ticket with id %s", ticketId.c_str());
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError);
} }
} }
else else
@ -130,11 +134,13 @@ namespace AWSGameLift
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftErrorMessageTemplate, AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftErrorMessageTemplate,
describeMatchmakingOutcome.GetError().GetExceptionName().c_str(), describeMatchmakingOutcome.GetError().GetExceptionName().c_str(),
describeMatchmakingOutcome.GetError().GetMessage().c_str()); describeMatchmakingOutcome.GetError().GetMessage().c_str());
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError);
} }
} }
else else
{ {
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftClientMissingErrorMessage); AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftClientMissingErrorMessage);
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError);
} }
m_waitEvent.try_acquire_for(AZStd::chrono::milliseconds(m_pollingPeriodInMS)); m_waitEvent.try_acquire_for(AZStd::chrono::milliseconds(m_pollingPeriodInMS));
} }

@ -12,7 +12,7 @@
#include <AzCore/std/parallel/mutex.h> #include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/parallel/thread.h> #include <AzCore/std/parallel/thread.h>
#include <Request/IAWSGameLiftRequests.h> #include <Request/AWSGameLiftMatchmakingRequestBus.h>
#include <aws/gamelift/model/MatchmakingTicket.h> #include <aws/gamelift/model/MatchmakingTicket.h>

@ -14,6 +14,7 @@
#include <AWSCoreBus.h> #include <AWSCoreBus.h>
#include <Credential/AWSCredentialBus.h> #include <Credential/AWSCredentialBus.h>
#include <Framework/AWSApiJobConfig.h>
#include <ResourceMapping/AWSResourceMappingBus.h> #include <ResourceMapping/AWSResourceMappingBus.h>
#include <AWSGameLiftClientManager.h> #include <AWSGameLiftClientManager.h>
@ -75,7 +76,15 @@ namespace AWSGameLift
bool AWSGameLiftClientManager::ConfigureGameLiftClient(const AZStd::string& region) bool AWSGameLiftClientManager::ConfigureGameLiftClient(const AZStd::string& region)
{ {
AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(nullptr); AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(nullptr);
Aws::Client::ClientConfiguration clientConfig; Aws::Client::ClientConfiguration clientConfig;
AWSCore::AwsApiJobConfig* defaultConfig = nullptr;
AWSCore::AWSCoreRequestBus::BroadcastResult(defaultConfig, &AWSCore::AWSCoreRequests::GetDefaultConfig);
if (defaultConfig)
{
clientConfig = defaultConfig->GetClientConfiguration();
}
// Set up client endpoint or region // Set up client endpoint or region
AZStd::string localEndpoint = ""; AZStd::string localEndpoint = "";
#if defined(AWSGAMELIFT_DEV) #if defined(AWSGAMELIFT_DEV)

@ -8,10 +8,13 @@
#pragma once #pragma once
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/std/smart_ptr/shared_ptr.h> #include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/Matchmaking/MatchmakingNotifications.h> #include <AzFramework/Matchmaking/MatchmakingNotifications.h>
#include <Request/IAWSGameLiftRequests.h> #include <Request/AWSGameLiftRequestBus.h>
#include <Request/AWSGameLiftSessionRequestBus.h>
#include <Request/AWSGameLiftMatchmakingRequestBus.h>
namespace AWSGameLift namespace AWSGameLift
{ {
@ -23,22 +26,37 @@ namespace AWSGameLift
struct AWSGameLiftStartMatchmakingRequest; struct AWSGameLiftStartMatchmakingRequest;
struct AWSGameLiftStopMatchmakingRequest; struct AWSGameLiftStopMatchmakingRequest;
// MatchAcceptanceNotificationBus EBus handler for scripting // MatchmakingNotificationBus EBus handler for scripting
class AWSGameLiftMatchAcceptanceNotificationBusHandler class AWSGameLiftMatchmakingNotificationBusHandler
: public AzFramework::MatchAcceptanceNotificationBus::Handler : public AzFramework::MatchmakingNotificationBus::Handler
, public AZ::BehaviorEBusHandler , public AZ::BehaviorEBusHandler
{ {
public: public:
AZ_EBUS_BEHAVIOR_BINDER( AZ_EBUS_BEHAVIOR_BINDER(
AWSGameLiftMatchAcceptanceNotificationBusHandler, AWSGameLiftMatchmakingNotificationBusHandler,
"{CBE057D3-F5CE-46D3-B02D-8A6A1446B169}", "{CBE057D3-F5CE-46D3-B02D-8A6A1446B169}",
AZ::SystemAllocator, AZ::SystemAllocator,
OnMatchAcceptance); OnMatchAcceptance, OnMatchComplete, OnMatchError, OnMatchFailure);
void OnMatchAcceptance() override void OnMatchAcceptance() override
{ {
Call(FN_OnMatchAcceptance); Call(FN_OnMatchAcceptance);
} }
void OnMatchComplete() override
{
Call(FN_OnMatchComplete);
}
void OnMatchError() override
{
Call(FN_OnMatchError);
}
void OnMatchFailure() override
{
Call(FN_OnMatchFailure);
}
}; };
// MatchmakingAsyncRequestNotificationBus EBus handler for scripting // MatchmakingAsyncRequestNotificationBus EBus handler for scripting

@ -65,13 +65,6 @@ namespace AWSGameLift
->Event("CreatePlayerId", &AWSGameLiftRequestBus::Events::CreatePlayerId, ->Event("CreatePlayerId", &AWSGameLiftRequestBus::Events::CreatePlayerId,
{ { { "IncludeBrackets", "" }, { { { "IncludeBrackets", "" },
{ "IncludeDashes", "" } } }); { "IncludeDashes", "" } } });
behaviorContext->EBus<AWSGameLiftMatchmakingEventRequestBus>("AWSGameLiftMatchmakingEventRequestBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
->Event("StartPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StartPolling,
{ { { "TicketId", "" },
{ "PlayerId", "" } } })
->Event("StopPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StopPolling);
} }
} }
@ -128,7 +121,7 @@ namespace AWSGameLift
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context)) if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{ {
behaviorContext->EBus<AWSGameLiftMatchmakingAsyncRequestBus>("AWSGameLiftMatchmakingAsyncRequestBus") behaviorContext->EBus<AWSGameLiftMatchmakingAsyncRequestBus>("AWSGameLiftMatchmakingAsyncRequestBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking")
->Event("AcceptMatchAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::AcceptMatchAsync, ->Event("AcceptMatchAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::AcceptMatchAsync,
{ { { "AcceptMatchRequest", "" } } }) { { { "AcceptMatchRequest", "" } } })
->Event("StartMatchmakingAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::StartMatchmakingAsync, ->Event("StartMatchmakingAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::StartMatchmakingAsync,
@ -137,20 +130,28 @@ namespace AWSGameLift
{ { { "StopMatchmakingRequest", "" } } }); { { { "StopMatchmakingRequest", "" } } });
behaviorContext->EBus<AzFramework::MatchmakingAsyncRequestNotificationBus>("AWSGameLiftMatchmakingAsyncRequestNotificationBus") behaviorContext->EBus<AzFramework::MatchmakingAsyncRequestNotificationBus>("AWSGameLiftMatchmakingAsyncRequestNotificationBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking")
->Handler<AWSGameLiftMatchmakingAsyncRequestNotificationBusHandler>(); ->Handler<AWSGameLiftMatchmakingAsyncRequestNotificationBusHandler>();
behaviorContext->EBus<AWSGameLiftMatchmakingRequestBus>("AWSGameLiftMatchmakingRequestBus") behaviorContext->EBus<AWSGameLiftMatchmakingRequestBus>("AWSGameLiftMatchmakingRequestBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking")
->Event("AcceptMatch", &AWSGameLiftMatchmakingRequestBus::Events::AcceptMatch, { { { "AcceptMatchRequest", "" } } }) ->Event("AcceptMatch", &AWSGameLiftMatchmakingRequestBus::Events::AcceptMatch,
{ { { "AcceptMatchRequest", "" } } })
->Event("StartMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StartMatchmaking, ->Event("StartMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StartMatchmaking,
{ { { "StartMatchmakingRequest", "" } } }) { { { "StartMatchmakingRequest", "" } } })
->Event("StopMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StopMatchmaking, ->Event("StopMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StopMatchmaking,
{ { { "StopMatchmakingRequest", "" } } }); { { { "StopMatchmakingRequest", "" } } });
behaviorContext->EBus<AzFramework::MatchAcceptanceNotificationBus>("AWSGameLiftMatchAcceptanceNotificationBus") behaviorContext->EBus<AWSGameLiftMatchmakingEventRequestBus>("AWSGameLiftMatchmakingEventRequestBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking")
->Handler<AWSGameLiftMatchAcceptanceNotificationBusHandler>(); ->Event("StartPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StartPolling,
{ { { "TicketId", "" },
{ "PlayerId", "" } } })
->Event("StopPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StopPolling);
behaviorContext->EBus<AzFramework::MatchmakingNotificationBus>("AWSGameLiftMatchmakingNotificationBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking")
->Handler<AWSGameLiftMatchmakingNotificationBusHandler>();
} }
} }
@ -166,7 +167,7 @@ namespace AWSGameLift
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context)) if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{ {
behaviorContext->EBus<AWSGameLiftSessionAsyncRequestBus>("AWSGameLiftSessionAsyncRequestBus") behaviorContext->EBus<AWSGameLiftSessionAsyncRequestBus>("AWSGameLiftSessionAsyncRequestBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session")
->Event("CreateSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::CreateSessionAsync, ->Event("CreateSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::CreateSessionAsync,
{ { { "CreateSessionRequest", "" } } }) { { { "CreateSessionRequest", "" } } })
->Event("JoinSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::JoinSessionAsync, { { { "JoinSessionRequest", "" } } }) ->Event("JoinSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::JoinSessionAsync, { { { "JoinSessionRequest", "" } } })
@ -175,11 +176,11 @@ namespace AWSGameLift
->Event("LeaveSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::LeaveSessionAsync); ->Event("LeaveSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::LeaveSessionAsync);
behaviorContext->EBus<AzFramework::SessionAsyncRequestNotificationBus>("AWSGameLiftSessionAsyncRequestNotificationBus") behaviorContext->EBus<AzFramework::SessionAsyncRequestNotificationBus>("AWSGameLiftSessionAsyncRequestNotificationBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session")
->Handler<AWSGameLiftSessionAsyncRequestNotificationBusHandler>(); ->Handler<AWSGameLiftSessionAsyncRequestNotificationBusHandler>();
behaviorContext->EBus<AWSGameLiftSessionRequestBus>("AWSGameLiftSessionRequestBus") behaviorContext->EBus<AWSGameLiftSessionRequestBus>("AWSGameLiftSessionRequestBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session")
->Event("CreateSession", &AWSGameLiftSessionRequestBus::Events::CreateSession, { { { "CreateSessionRequest", "" } } }) ->Event("CreateSession", &AWSGameLiftSessionRequestBus::Events::CreateSession, { { { "CreateSessionRequest", "" } } })
->Event("JoinSession", &AWSGameLiftSessionRequestBus::Events::JoinSession, { { { "JoinSessionRequest", "" } } }) ->Event("JoinSession", &AWSGameLiftSessionRequestBus::Events::JoinSession, { { { "JoinSessionRequest", "" } } })
->Event("SearchSessions", &AWSGameLiftSessionRequestBus::Events::SearchSessions, { { { "SearchSessionsRequest", "" } } }) ->Event("SearchSessions", &AWSGameLiftSessionRequestBus::Events::SearchSessions, { { { "SearchSessionsRequest", "" } } })

@ -20,7 +20,7 @@ namespace Aws
namespace AWSGameLift namespace AWSGameLift
{ {
//! IAWSGameLiftRequests //! IAWSGameLiftInternalRequests
//! GameLift Gem internal interface which is used to fetch gem global GameLift client //! GameLift Gem internal interface which is used to fetch gem global GameLift client
class IAWSGameLiftInternalRequests class IAWSGameLiftInternalRequests
{ {

@ -82,27 +82,12 @@ protected:
m_gameliftClientMockPtr.reset(); m_gameliftClientMockPtr.reset();
} }
void WaitForProcessFinish(uint64_t expectedNum) void WaitForProcessFinish(AZStd::function<bool()> processFinishCondition)
{ {
int processingTime = 0; int processingTime = 0;
while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS) while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS)
{ {
if (::UnitTest::TestRunner::Instance().m_numAssertsFailed == expectedNum) if (processFinishCondition())
{
AZ_TEST_STOP_TRACE_SUPPRESSION(expectedNum);
return;
}
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(TEST_WAIT_BUFFER_TIME_MS));
processingTime += TEST_WAIT_BUFFER_TIME_MS;
}
}
void WaitForProcessFinish()
{
int processingTime = 0;
while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS)
{
if (m_gameliftClientTicketTracker->IsTrackerIdle())
{ {
return; return;
} }
@ -119,19 +104,27 @@ public:
TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithoutClientSetup_GetExpectedErrors) TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithoutClientSetup_GetExpectedErrors)
{ {
AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(nullptr); AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(nullptr);
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
AZ_TEST_START_TRACE_SUPPRESSION; AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(1); WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_MultipleCallsWithoutClientSetup_GetExpectedErrors) TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_MultipleCallsWithoutClientSetup_GetExpectedErrors)
{ {
AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(nullptr); AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(nullptr);
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
AZ_TEST_START_TRACE_SUPPRESSION; AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(1); WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
@ -144,9 +137,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButWithFailedOu
.Times(1) .Times(1)
.WillOnce(::testing::Return(outcome)); .WillOnce(::testing::Return(outcome));
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
AZ_TEST_START_TRACE_SUPPRESSION; AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(1); WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
@ -161,9 +157,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithMoreThanOne
.Times(1) .Times(1)
.WillOnce(::testing::Return(outcome)); .WillOnce(::testing::Return(outcome));
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
AZ_TEST_START_TRACE_SUPPRESSION; AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(1); WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
@ -189,13 +188,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithCompleteSta
.Times(1) .Times(1)
.WillOnce(::testing::Return(outcome)); .WillOnce(::testing::Return(outcome));
SessionHandlingClientRequestsMock handlerMock; SessionHandlingClientRequestsMock sessionHandlerMock;
EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_))
.Times(1) .Times(1)
.WillOnce(::testing::Return(true)); .WillOnce(::testing::Return(true));
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(); WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1);
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
@ -217,9 +218,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButNoPlayerSess
.Times(1) .Times(1)
.WillOnce(::testing::Return(outcome)); .WillOnce(::testing::Return(outcome));
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
AZ_TEST_START_TRACE_SUPPRESSION; AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(1); WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1);
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
@ -245,14 +249,17 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButFailedToJoin
.Times(1) .Times(1)
.WillOnce(::testing::Return(outcome)); .WillOnce(::testing::Return(outcome));
SessionHandlingClientRequestsMock handlerMock; SessionHandlingClientRequestsMock sessionHandlerMock;
EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_))
.Times(1) .Times(1)
.WillOnce(::testing::Return(false)); .WillOnce(::testing::Return(false));
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
AZ_TEST_START_TRACE_SUPPRESSION; AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(1); WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1);
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
@ -269,9 +276,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketTimeOu
.Times(1) .Times(1)
.WillOnce(::testing::Return(outcome)); .WillOnce(::testing::Return(outcome));
AZ_TEST_START_TRACE_SUPPRESSION; MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(1); WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1);
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
@ -288,9 +296,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketFailed
.Times(1) .Times(1)
.WillOnce(::testing::Return(outcome)); .WillOnce(::testing::Return(outcome));
AZ_TEST_START_TRACE_SUPPRESSION; MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(1); WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1);
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
@ -307,9 +316,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketCancel
.Times(1) .Times(1)
.WillOnce(::testing::Return(outcome)); .WillOnce(::testing::Return(outcome));
AZ_TEST_START_TRACE_SUPPRESSION; MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(1); WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1);
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
@ -342,13 +352,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallAndTicketComple
.WillOnce(::testing::Return(outcome1)) .WillOnce(::testing::Return(outcome1))
.WillOnce(::testing::Return(outcome2)); .WillOnce(::testing::Return(outcome2));
SessionHandlingClientRequestsMock handlerMock; SessionHandlingClientRequestsMock sessionHandlerMock;
EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_))
.Times(1) .Times(1)
.WillOnce(::testing::Return(true)); .WillOnce(::testing::Return(true));
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(); WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1);
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }
@ -365,7 +377,9 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_RequiresAcceptanceA
connectionInfo.SetIpAddress("DummyIpAddress"); connectionInfo.SetIpAddress("DummyIpAddress");
connectionInfo.SetPort(123); connectionInfo.SetPort(123);
connectionInfo.AddMatchedPlayerSessions( connectionInfo.AddMatchedPlayerSessions(
Aws::GameLift::Model::MatchedPlayerSession().WithPlayerId("player1").WithPlayerSessionId("playersession1")); Aws::GameLift::Model::MatchedPlayerSession()
.WithPlayerId("player1")
.WithPlayerSessionId("playersession1"));
Aws::GameLift::Model::MatchmakingTicket ticket2; Aws::GameLift::Model::MatchmakingTicket ticket2;
ticket2.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED); ticket2.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED);
@ -379,13 +393,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_RequiresAcceptanceA
.WillOnce(::testing::Return(outcome1)) .WillOnce(::testing::Return(outcome1))
.WillOnce(::testing::Return(outcome2)); .WillOnce(::testing::Return(outcome2));
MatchAcceptanceNotificationsHandlerMock handlerMock1; SessionHandlingClientRequestsMock sessionHandlerMock;
EXPECT_CALL(handlerMock1, OnMatchAcceptance()).Times(1); EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_))
.Times(1)
SessionHandlingClientRequestsMock handlerMock2; .WillOnce(::testing::Return(true));
EXPECT_CALL(handlerMock2, RequestPlayerJoinSession(::testing::_)).Times(1).WillOnce(::testing::Return(true));
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish(); WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchAcceptance == 1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1);
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
} }

@ -266,6 +266,8 @@ const char* const AWSGameLiftClientManagerTest::DummyPlayerId = "dummyPlayerId";
TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_GetFalseAsResult) TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_GetFalseAsResult)
{ {
AWSCoreRequestsHandlerMock coreHandlerMock;
EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr);
AZ_TEST_START_TRACE_SUPPRESSION; AZ_TEST_START_TRACE_SUPPRESSION;
auto result = m_gameliftClientManager->ConfigureGameLiftClient(""); auto result = m_gameliftClientManager->ConfigureGameLiftClient("");
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message
@ -274,6 +276,8 @@ TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_G
TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutCredential_GetFalseAsResult) TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutCredential_GetFalseAsResult)
{ {
AWSCoreRequestsHandlerMock coreHandlerMock;
EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr);
AWSResourceMappingRequestsHandlerMock handlerMock; AWSResourceMappingRequestsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, GetDefaultRegion()).Times(1).WillOnce(::testing::Return("us-west-2")); EXPECT_CALL(handlerMock, GetDefaultRegion()).Times(1).WillOnce(::testing::Return("us-west-2"));
AZ_TEST_START_TRACE_SUPPRESSION; AZ_TEST_START_TRACE_SUPPRESSION;
@ -284,6 +288,8 @@ TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutCredenti
TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithRegionAndCredential_GetTrueAsResult) TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithRegionAndCredential_GetTrueAsResult)
{ {
AWSCoreRequestsHandlerMock coreHandlerMock;
EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr);
AWSCredentialRequestsHandlerMock handlerMock; AWSCredentialRequestsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, GetCredentialsProvider()) EXPECT_CALL(handlerMock, GetCredentialsProvider())
.Times(1) .Times(1)

@ -9,9 +9,9 @@
#pragma once #pragma once
#include <AzCore/Interface/Interface.h> #include <AzCore/Interface/Interface.h>
#include <AzFramework/Matchmaking/MatchmakingNotifications.h>
#include <AzFramework/Session/ISessionRequests.h> #include <AzFramework/Session/ISessionRequests.h>
#include <AzFramework/Session/ISessionHandlingRequests.h> #include <AzFramework/Session/ISessionHandlingRequests.h>
#include <AzFramework/Matchmaking/IMatchmakingRequests.h>
#include <AzFramework/Matchmaking/MatchmakingNotifications.h> #include <AzFramework/Matchmaking/MatchmakingNotifications.h>
#include <AzTest/AzTest.h> #include <AzTest/AzTest.h>
@ -76,21 +76,44 @@ public:
MOCK_METHOD0(OnStopMatchmakingAsyncComplete, void()); MOCK_METHOD0(OnStopMatchmakingAsyncComplete, void());
}; };
class MatchAcceptanceNotificationsHandlerMock class MatchmakingNotificationsHandlerMock
: public AzFramework::MatchAcceptanceNotificationBus::Handler : public AzFramework::MatchmakingNotificationBus::Handler
{ {
public: public:
MatchAcceptanceNotificationsHandlerMock() MatchmakingNotificationsHandlerMock()
{
AzFramework::MatchmakingNotificationBus::Handler::BusConnect();
}
~MatchmakingNotificationsHandlerMock()
{
AzFramework::MatchmakingNotificationBus::Handler::BusDisconnect();
}
void OnMatchAcceptance() override
{
++m_numMatchAcceptance;
}
void OnMatchComplete() override
{
++m_numMatchComplete;
}
void OnMatchError() override
{ {
AzFramework::MatchAcceptanceNotificationBus::Handler::BusConnect(); ++m_numMatchError;
} }
~MatchAcceptanceNotificationsHandlerMock() void OnMatchFailure() override
{ {
AzFramework::MatchAcceptanceNotificationBus::Handler::BusDisconnect(); ++m_numMatchFailure;
} }
MOCK_METHOD0(OnMatchAcceptance, void()); int m_numMatchAcceptance = 0;
int m_numMatchComplete = 0;
int m_numMatchError = 0;
int m_numMatchFailure = 0;
}; };
class SessionAsyncRequestNotificationsHandlerMock class SessionAsyncRequestNotificationsHandlerMock

@ -17,7 +17,9 @@ set(FILES
Include/Request/AWSGameLiftSearchSessionsRequest.h Include/Request/AWSGameLiftSearchSessionsRequest.h
Include/Request/AWSGameLiftStartMatchmakingRequest.h Include/Request/AWSGameLiftStartMatchmakingRequest.h
Include/Request/AWSGameLiftStopMatchmakingRequest.h Include/Request/AWSGameLiftStopMatchmakingRequest.h
Include/Request/IAWSGameLiftRequests.h Include/Request/AWSGameLiftRequestBus.h
Include/Request/AWSGameLiftSessionRequestBus.h
Include/Request/AWSGameLiftMatchmakingRequestBus.h
Source/Activity/AWSGameLiftActivityUtils.cpp Source/Activity/AWSGameLiftActivityUtils.cpp
Source/Activity/AWSGameLiftActivityUtils.h Source/Activity/AWSGameLiftActivityUtils.h
Source/Activity/AWSGameLiftAcceptMatchActivity.cpp Source/Activity/AWSGameLiftAcceptMatchActivity.cpp

@ -5,7 +5,7 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT * SPDX-License-Identifier: Apache-2.0 OR MIT
* *
*/ */
#pragma once #pragma once
#include <AzCore/EBus/EBus.h> #include <AzCore/EBus/EBus.h>
@ -45,7 +45,7 @@ namespace AWSGameLift
virtual bool StopMatchBackfill(const AZStd::string& ticketId) = 0; virtual bool StopMatchBackfill(const AZStd::string& ticketId) = 0;
}; };
// IAWSGameLiftServerRequests EBus wrapper for scripting // IAWSGameLiftServerRequests EBus wrapper
class AWSGameLiftServerRequests class AWSGameLiftServerRequests
: public AZ::EBusTraits : public AZ::EBusTraits
{ {

@ -20,7 +20,7 @@
#include <AzFramework/Session/SessionConfig.h> #include <AzFramework/Session/SessionConfig.h>
#include <AWSGameLiftPlayer.h> #include <AWSGameLiftPlayer.h>
#include <Request/IAWSGameLiftServerRequests.h> #include <Request/AWSGameLiftServerRequestBus.h>
namespace AWSGameLift namespace AWSGameLift
{ {

@ -10,7 +10,7 @@ set(FILES
../AWSGameLiftCommon/Include/AWSGameLiftPlayer.h ../AWSGameLiftCommon/Include/AWSGameLiftPlayer.h
../AWSGameLiftCommon/Source/AWSGameLiftPlayer.cpp ../AWSGameLiftCommon/Source/AWSGameLiftPlayer.cpp
../AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h ../AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h
Include/Request/IAWSGameLiftServerRequests.h Include/Request/AWSGameLiftServerRequestBus.h
Source/AWSGameLiftServerManager.cpp Source/AWSGameLiftServerManager.cpp
Source/AWSGameLiftServerManager.h Source/AWSGameLiftServerManager.h
Source/AWSGameLiftServerSystemComponent.cpp Source/AWSGameLiftServerSystemComponent.cpp

@ -31,7 +31,7 @@ namespace AWSMetrics
static const unsigned int DesiredMaxWorkers = 2; static const unsigned int DesiredMaxWorkers = 2;
MetricsManager(); MetricsManager();
~MetricsManager(); virtual ~MetricsManager();
//! Initializing the metrics manager //! Initializing the metrics manager
//! @return Whether the operation is successful. //! @return Whether the operation is successful.
@ -93,6 +93,12 @@ namespace AWSMetrics
//! @return Total number of requests for sending metrics events. //! @return Total number of requests for sending metrics events.
int GetNumTotalRequests() const; int GetNumTotalRequests() const;
protected:
//! Send metrics to a local file.
//! @param metricsQueue metricsQueue Metrics queue that stores the metrics.
//! @return Outcome of the operation.
virtual AZ::Outcome<void, AZStd::string> SendMetricsToFile(AZStd::shared_ptr<MetricsQueue> metricsQueue);
private: private:
//! Job management //! Job management
void SetupJobContext(); void SetupJobContext();
@ -112,11 +118,6 @@ namespace AWSMetrics
//! @param metricsQueue Metrics events to send. //! @param metricsQueue Metrics events to send.
void SendMetricsToServiceApiAsync(const MetricsQueue& metricsQueue); void SendMetricsToServiceApiAsync(const MetricsQueue& metricsQueue);
//! Send metrics to a local file.
//! @param metricsQueue metricsQueue Metrics queue that stores the metrics.
//! @return Outcome of the operation.
AZ::Outcome<void, AZStd::string> SendMetricsToFile(AZStd::shared_ptr<MetricsQueue> metricsQueue);
//! Push metrics events to the front of the queue for retry. //! Push metrics events to the front of the queue for retry.
//! @param metricsEventsForRetry Metrics events for retry. //! @param metricsEventsForRetry Metrics events for retry.
void PushMetricsForRetry(MetricsQueue& metricsEventsForRetry); void PushMetricsForRetry(MetricsQueue& metricsEventsForRetry);

@ -139,11 +139,6 @@ namespace AWSMetrics
return true; return true;
} }
bool RemoveDirectory(const AZStd::string& directory)
{
return AZ::IO::SystemFile::DeleteDir(directory.c_str());
}
AZ::IO::FileIOBase* m_priorFileIO = nullptr; AZ::IO::FileIOBase* m_priorFileIO = nullptr;
AZ::IO::FileIOBase* m_localFileIO = nullptr; AZ::IO::FileIOBase* m_localFileIO = nullptr;

@ -75,6 +75,23 @@ namespace AZ
namespace AWSMetrics namespace AWSMetrics
{ {
class MetricsManagerMock
: public MetricsManager
{
private:
AZ::Outcome<void, AZStd::string> SendMetricsToFile(AZStd::shared_ptr<MetricsQueue> metricsQueue) override
{
if (AZ::IO::FileIOBase::GetInstance())
{
return AZ::Success();
}
else
{
return AZ::Failure(AZStd::string{ "Invalid File IO" });
}
}
};
class AWSMetricsNotificationBusMock class AWSMetricsNotificationBusMock
: protected AWSMetricsNotificationBus::Handler : protected AWSMetricsNotificationBus::Handler
{ {
@ -134,13 +151,11 @@ namespace AWSMetrics
AWSMetricsGemAllocatorFixture::SetUp(); AWSMetricsGemAllocatorFixture::SetUp();
AWSMetricsRequestBus::Handler::BusConnect(); AWSMetricsRequestBus::Handler::BusConnect();
m_metricsManager = AZStd::make_unique<MetricsManager>(); m_metricsManager = AZStd::make_unique<MetricsManagerMock>();
AZStd::string configFilePath = CreateClientConfigFile(true, (double) TestMetricsEventSizeInBytes / MbToBytes * 2, DefaultFlushPeriodInSeconds, 0); AZStd::string configFilePath = CreateClientConfigFile(true, (double) TestMetricsEventSizeInBytes / MbToBytes * 2, DefaultFlushPeriodInSeconds, 0);
m_settingsRegistry->MergeSettingsFile(configFilePath, AZ::SettingsRegistryInterface::Format::JsonMergePatch, {}); m_settingsRegistry->MergeSettingsFile(configFilePath, AZ::SettingsRegistryInterface::Format::JsonMergePatch, {});
m_metricsManager->Init(); m_metricsManager->Init();
RemoveFile(m_metricsManager->GetMetricsFilePath());
ReplaceLocalFileIOWithMockIO(); ReplaceLocalFileIOWithMockIO();
} }
@ -149,8 +164,6 @@ namespace AWSMetrics
RevertMockIOToLocalFileIO(); RevertMockIOToLocalFileIO();
RemoveFile(GetDefaultTestFilePath()); RemoveFile(GetDefaultTestFilePath());
RemoveFile(m_metricsManager->GetMetricsFilePath());
RemoveDirectory(m_metricsManager->GetMetricsFileDirectory());
m_metricsManager.reset(); m_metricsManager.reset();
@ -233,7 +246,7 @@ namespace AWSMetrics
} }
} }
AZStd::unique_ptr<MetricsManager> m_metricsManager; AZStd::unique_ptr<MetricsManagerMock> m_metricsManager;
AWSMetricsNotificationBusMock m_notifications; AWSMetricsNotificationBusMock m_notifications;
AZ::IO::FileIOBase* m_fileIOMock; AZ::IO::FileIOBase* m_fileIOMock;

@ -111,16 +111,18 @@ void DirectionalLightShadow::GetShadowCoords(
float3 worldPosition, float3 worldPosition,
out float3 shadowCoords[ViewSrg::MaxCascadeCount]) out float3 shadowCoords[ViewSrg::MaxCascadeCount])
{ {
const float4x4 depthBiasMatrices[ViewSrg::MaxCascadeCount] =
ViewSrg::m_directionalLightShadows[lightIndex].m_depthBiasMatrices;
const uint cascadeCount = ViewSrg::m_directionalLightShadows[lightIndex].m_cascadeCount; const uint cascadeCount = ViewSrg::m_directionalLightShadows[lightIndex].m_cascadeCount;
const float shadowBias = ViewSrg::m_directionalLightShadows[lightIndex].m_shadowBias;
const float4x4 lightViewToShadowmapMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_lightViewToShadowmapMatrices;
const float4x4 worldToLightViewMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_worldToLightViewMatrices;
for (uint index = 0; index < cascadeCount; ++index) for (uint index = 0; index < cascadeCount; ++index)
{ {
const float4x4 depthBiasMatrix = depthBiasMatrices[index]; float4 lightSpacePos = mul(worldToLightViewMatrices[index], float4(worldPosition, 1.));
const float4 shadowCoordHomogeneous = mul(depthBiasMatrix, lightSpacePos.z += shadowBias;
float4(worldPosition, 1.));
shadowCoords[index] = shadowCoordHomogeneous.xyz / shadowCoordHomogeneous.w; const float4 clipSpacePos = mul(lightViewToShadowmapMatrices[index], lightSpacePos);
shadowCoords[index] = clipSpacePos.xyz / clipSpacePos.w;
} }
} }

@ -102,18 +102,19 @@ partial ShaderResourceGroup ViewSrg
struct DirectionalLightShadow struct DirectionalLightShadow
{ {
float4x4 m_depthBiasMatrices[MaxCascadeCount];
float4x4 m_lightViewToShadowmapMatrices[MaxCascadeCount]; float4x4 m_lightViewToShadowmapMatrices[MaxCascadeCount];
float4x4 m_worldToLightViewMatrices[MaxCascadeCount]; float4x4 m_worldToLightViewMatrices[MaxCascadeCount];
float m_slopeBiasBase[MaxCascadeCount]; float m_slopeBiasBase[MaxCascadeCount];
float m_boundaryScale; float m_boundaryScale;
uint m_shadowmapSize; // width and height of shadowmap uint m_shadowmapSize; // width and height of shadowmap
uint m_cascadeCount; uint m_cascadeCount;
float m_shadowBias;
uint m_predictionSampleCount; uint m_predictionSampleCount;
uint m_filteringSampleCount; uint m_filteringSampleCount;
uint m_debugFlags; uint m_debugFlags;
uint m_shadowFilterMethod; uint m_shadowFilterMethod;
float m_far_minus_near; float m_far_minus_near;
float3 m_padding;
}; };
enum ShadowFilterMethod enum ShadowFilterMethod

@ -157,6 +157,9 @@ namespace AZ
//! Sets whether the directional shadowmap should use receiver plane bias. //! Sets whether the directional shadowmap should use receiver plane bias.
//! This attempts to reduce shadow acne when using large pcf filters. //! This attempts to reduce shadow acne when using large pcf filters.
virtual void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) = 0; virtual void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) = 0;
//! Reduces acne by applying a small amount of bias along shadow-space z.
virtual void SetShadowBias(LightHandle handle, float bias) = 0;
}; };
} // namespace Render } // namespace Render
} // namespace AZ } // namespace AZ

@ -589,6 +589,15 @@ namespace AZ
m_shadowProperties.GetData(handle.GetIndex()).m_isReceiverPlaneBiasEnabled = enable; m_shadowProperties.GetData(handle.GetIndex()).m_isReceiverPlaneBiasEnabled = enable;
} }
void DirectionalLightFeatureProcessor::SetShadowBias(LightHandle handle, float bias)
{
for (auto& it : m_shadowData)
{
it.second.GetData(handle.GetIndex()).m_shadowBias = bias;
}
m_shadowBufferNeedsUpdate = true;
}
void DirectionalLightFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) void DirectionalLightFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline)
{ {
PrepareForChangingRenderPipelineAndCameraView(); PrepareForChangingRenderPipelineAndCameraView();
@ -1522,10 +1531,6 @@ namespace AZ
for (uint16_t cascadeIndex = 0; cascadeIndex < GetCascadeCount(handle); ++cascadeIndex) for (uint16_t cascadeIndex = 0; cascadeIndex < GetCascadeCount(handle); ++cascadeIndex)
{ {
const Matrix4x4& worldToLightClipMatrix = property.m_segments.at(cameraView)[cascadeIndex].m_view->GetWorldToClipMatrix();
const Matrix4x4 depthBiasMatrix = Shadow::GetClipToShadowmapTextureMatrix() * worldToLightClipMatrix;
shadowData.m_depthBiasMatrices[cascadeIndex] = depthBiasMatrix;
const Matrix4x4& lightViewToLightClipMatrix = property.m_segments.at(cameraView)[cascadeIndex].m_view->GetViewToClipMatrix(); const Matrix4x4& lightViewToLightClipMatrix = property.m_segments.at(cameraView)[cascadeIndex].m_view->GetViewToClipMatrix();
const Matrix4x4 lightViewToShadowmapMatrix = Shadow::GetClipToShadowmapTextureMatrix() * lightViewToLightClipMatrix; const Matrix4x4 lightViewToShadowmapMatrix = Shadow::GetClipToShadowmapTextureMatrix() * lightViewToLightClipMatrix;
shadowData.m_lightViewToShadowmapMatrices[cascadeIndex] = lightViewToShadowmapMatrix; shadowData.m_lightViewToShadowmapMatrices[cascadeIndex] = lightViewToShadowmapMatrix;

@ -72,12 +72,6 @@ namespace AZ
// [GFX TODO][ATOM-15172] Look into compacting struct DirectionalLightShadowData // [GFX TODO][ATOM-15172] Look into compacting struct DirectionalLightShadowData
struct DirectionalLightShadowData struct DirectionalLightShadowData
{ {
AZStd::array<Matrix4x4, Shadow::MaxNumberOfCascades> m_depthBiasMatrices =
{ {
Matrix4x4::CreateIdentity(),
Matrix4x4::CreateIdentity(),
Matrix4x4::CreateIdentity(),
Matrix4x4::CreateIdentity() } };
AZStd::array<Matrix4x4, Shadow::MaxNumberOfCascades> m_lightViewToShadowmapMatrices = AZStd::array<Matrix4x4, Shadow::MaxNumberOfCascades> m_lightViewToShadowmapMatrices =
{ { { {
Matrix4x4::CreateIdentity(), Matrix4x4::CreateIdentity(),
@ -97,11 +91,14 @@ namespace AZ
float m_boundaryScale = 0.f; float m_boundaryScale = 0.f;
uint32_t m_shadowmapSize = 1; // width and height of shadowmap uint32_t m_shadowmapSize = 1; // width and height of shadowmap
uint32_t m_cascadeCount = 1; uint32_t m_cascadeCount = 1;
// Reduce acne by applying a small amount of bias to apply along shadow-space z.
float m_shadowBias = 0.0f;
uint32_t m_predictionSampleCount = 0; uint32_t m_predictionSampleCount = 0;
uint32_t m_filteringSampleCount = 0; uint32_t m_filteringSampleCount = 0;
uint32_t m_debugFlags = 0; uint32_t m_debugFlags = 0;
uint32_t m_shadowFilterMethod = 0; uint32_t m_shadowFilterMethod = 0;
float m_far_minus_near = 0; float m_far_minus_near = 0;
float m_padding[3];
}; };
class DirectionalLightFeatureProcessor final class DirectionalLightFeatureProcessor final
@ -218,6 +215,7 @@ namespace AZ
void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) override; void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) override;
void SetFilteringSampleCount(LightHandle handle, uint16_t count) override; void SetFilteringSampleCount(LightHandle handle, uint16_t count) override;
void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) override; void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) override;
void SetShadowBias(LightHandle handle, float bias) override;
const Data::Instance<RPI::Buffer> GetLightBuffer() const; const Data::Instance<RPI::Buffer> GetLightBuffer() const;
uint32_t GetLightCount() const; uint32_t GetLightCount() const;

@ -168,6 +168,14 @@ namespace AZ
//! Sets whether the directional shadowmap should use receiver plane bias. //! Sets whether the directional shadowmap should use receiver plane bias.
//! @param enable flag specifying whether to enable the receiver plane bias feature //! @param enable flag specifying whether to enable the receiver plane bias feature
virtual void SetShadowReceiverPlaneBiasEnabled(bool enable) = 0; virtual void SetShadowReceiverPlaneBiasEnabled(bool enable) = 0;
//! Shadow bias reduces acne by applying a small amount of offset along shadow-space z.
//! @return Returns the amount of bias to apply.
virtual float GetShadowBias() const = 0;
//! Shadow bias reduces acne by applying a small amount of offset along shadow-space z.
//! @param Sets the amount of bias to apply.
virtual void SetShadowBias(float bias) = 0;
}; };
using DirectionalLightRequestBus = EBus<DirectionalLightRequests>; using DirectionalLightRequestBus = EBus<DirectionalLightRequests>;

@ -109,6 +109,9 @@ namespace AZ
//! This uses partial derivatives to reduce shadow acne when using large pcf kernels. //! This uses partial derivatives to reduce shadow acne when using large pcf kernels.
bool m_receiverPlaneBiasEnabled = true; bool m_receiverPlaneBiasEnabled = true;
//! Reduces shadow acne by applying a small amount of offset along shadow-space z.
float m_shadowBias = 0.0f;
bool IsSplitManual() const; bool IsSplitManual() const;
bool IsSplitAutomatic() const; bool IsSplitAutomatic() const;
bool IsCascadeCorrectionDisabled() const; bool IsCascadeCorrectionDisabled() const;

@ -38,7 +38,8 @@ namespace AZ
->Field("IsDebugColoringEnabled", &DirectionalLightComponentConfig::m_isDebugColoringEnabled) ->Field("IsDebugColoringEnabled", &DirectionalLightComponentConfig::m_isDebugColoringEnabled)
->Field("ShadowFilterMethod", &DirectionalLightComponentConfig::m_shadowFilterMethod) ->Field("ShadowFilterMethod", &DirectionalLightComponentConfig::m_shadowFilterMethod)
->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount) ->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount)
->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled); ->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled)
->Field("Shadow Bias", &DirectionalLightComponentConfig::m_shadowBias);
} }
} }

@ -84,6 +84,8 @@ namespace AZ
->Event("SetFilteringSampleCount", &DirectionalLightRequestBus::Events::SetFilteringSampleCount) ->Event("SetFilteringSampleCount", &DirectionalLightRequestBus::Events::SetFilteringSampleCount)
->Event("GetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::GetShadowReceiverPlaneBiasEnabled) ->Event("GetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::GetShadowReceiverPlaneBiasEnabled)
->Event("SetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::SetShadowReceiverPlaneBiasEnabled) ->Event("SetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::SetShadowReceiverPlaneBiasEnabled)
->Event("GetShadowBias", &DirectionalLightRequestBus::Events::GetShadowBias)
->Event("SetShadowBias", &DirectionalLightRequestBus::Events::SetShadowBias)
->VirtualProperty("Color", "GetColor", "SetColor") ->VirtualProperty("Color", "GetColor", "SetColor")
->VirtualProperty("Intensity", "GetIntensity", "SetIntensity") ->VirtualProperty("Intensity", "GetIntensity", "SetIntensity")
->VirtualProperty("AngularDiameter", "GetAngularDiameter", "SetAngularDiameter") ->VirtualProperty("AngularDiameter", "GetAngularDiameter", "SetAngularDiameter")
@ -98,7 +100,8 @@ namespace AZ
->VirtualProperty("DebugColoringEnabled", "GetDebugColoringEnabled", "SetDebugColoringEnabled") ->VirtualProperty("DebugColoringEnabled", "GetDebugColoringEnabled", "SetDebugColoringEnabled")
->VirtualProperty("ShadowFilterMethod", "GetShadowFilterMethod", "SetShadowFilterMethod") ->VirtualProperty("ShadowFilterMethod", "GetShadowFilterMethod", "SetShadowFilterMethod")
->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount") ->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount")
->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled"); ->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled")
->VirtualProperty("ShadowBias", "GetShadowBias", "SetShadowBias");
; ;
} }
} }
@ -406,6 +409,20 @@ namespace AZ
return aznumeric_cast<uint32_t>(m_configuration.m_filteringSampleCount); return aznumeric_cast<uint32_t>(m_configuration.m_filteringSampleCount);
} }
void DirectionalLightComponentController::SetShadowBias(float bias)
{
m_configuration.m_shadowBias = bias;
if (m_featureProcessor)
{
m_featureProcessor->SetShadowBias(m_lightHandle, bias);
}
}
float DirectionalLightComponentController::GetShadowBias() const
{
return m_configuration.m_shadowBias;
}
void DirectionalLightComponentController::SetFilteringSampleCount(uint32_t count) void DirectionalLightComponentController::SetFilteringSampleCount(uint32_t count)
{ {
const uint16_t count16 = GetMin(Shadow::MaxPcfSamplingCount, aznumeric_cast<uint16_t>(count)); const uint16_t count16 = GetMin(Shadow::MaxPcfSamplingCount, aznumeric_cast<uint16_t>(count));
@ -499,6 +516,7 @@ namespace AZ
SetViewFrustumCorrectionEnabled(m_configuration.m_isCascadeCorrectionEnabled); SetViewFrustumCorrectionEnabled(m_configuration.m_isCascadeCorrectionEnabled);
SetDebugColoringEnabled(m_configuration.m_isDebugColoringEnabled); SetDebugColoringEnabled(m_configuration.m_isDebugColoringEnabled);
SetShadowFilterMethod(m_configuration.m_shadowFilterMethod); SetShadowFilterMethod(m_configuration.m_shadowFilterMethod);
SetShadowBias(m_configuration.m_shadowBias);
SetFilteringSampleCount(m_configuration.m_filteringSampleCount); SetFilteringSampleCount(m_configuration.m_filteringSampleCount);
SetShadowReceiverPlaneBiasEnabled(m_configuration.m_receiverPlaneBiasEnabled); SetShadowReceiverPlaneBiasEnabled(m_configuration.m_receiverPlaneBiasEnabled);

@ -80,6 +80,8 @@ namespace AZ
void SetFilteringSampleCount(uint32_t count) override; void SetFilteringSampleCount(uint32_t count) override;
bool GetShadowReceiverPlaneBiasEnabled() const override; bool GetShadowReceiverPlaneBiasEnabled() const override;
void SetShadowReceiverPlaneBiasEnabled(bool enable) override; void SetShadowReceiverPlaneBiasEnabled(bool enable) override;
float GetShadowBias() const override;
void SetShadowBias(float width) override;
private: private:
friend class EditorDirectionalLightComponent; friend class EditorDirectionalLightComponent;

@ -133,8 +133,8 @@ namespace AZ
->EnumAttribute(ShadowFilterMethod::Esm, "ESM") ->EnumAttribute(ShadowFilterMethod::Esm, "ESM")
->EnumAttribute(ShadowFilterMethod::EsmPcf, "ESM+PCF") ->EnumAttribute(ShadowFilterMethod::EsmPcf, "ESM+PCF")
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count", ->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count\n",
"This is used only when the pixel is predicted as on the boundary. " "This is used only when the pixel is predicted as on the boundary.\n"
"Specific to PCF and ESM+PCF.") "Specific to PCF and ESM+PCF.")
->Attribute(Edit::Attributes::Min, 4) ->Attribute(Edit::Attributes::Min, 4)
->Attribute(Edit::Attributes::Max, 64) ->Attribute(Edit::Attributes::Max, 64)
@ -142,10 +142,19 @@ namespace AZ
->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled) ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled)
->DataElement( ->DataElement(
Edit::UIHandlers::CheckBox, &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled, Edit::UIHandlers::CheckBox, &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled,
"Shadow Receiver Plane Bias Enable", "Shadow Receiver Plane Bias Enable\n",
"This reduces shadow acne when using large pcf kernels.") "This reduces shadow acne when using large pcf kernels.")
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled); ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled)
->DataElement(
Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_shadowBias,
"Shadow Bias\n",
"Reduces acne by applying a fixed bias along z in shadow-space.\n"
"If this is 0, no biasing is applied.")
->Attribute(Edit::Attributes::Min, 0.f)
->Attribute(Edit::Attributes::Max, 0.2)
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
;
} }
} }

@ -92,6 +92,44 @@ def process_add_o3de_repo(file_name: str or pathlib.Path,
return 0 return 0
def get_gem_json_paths_from_cached_repo(repo_uri: str) -> list:
url = f'{repo_uri}/repo.json'
repo_sha256 = hashlib.sha256(url.encode())
cache_folder = manifest.get_o3de_cache_folder()
cache_filename = cache_folder / str(repo_sha256.hexdigest() + '.json')
gem_list = []
file_name = pathlib.Path(cache_filename).resolve()
if not file_name.is_file():
logger.error(f'Could not find cached repo json file for {repo_uri}')
return gem_list
with file_name.open('r') as f:
try:
repo_data = json.load(f)
except json.JSONDecodeError as e:
logger.error(f'{file_name} failed to load: {str(e)}')
return gem_list
# Get list of gems, then add all json paths to the list if they exist in the cache
repo_gems = []
try:
repo_gems.append((repo_data['gems'], 'gem.json'))
except KeyError:
pass
for o3de_object_uris, manifest_json in repo_gems:
for o3de_object_uri in o3de_object_uris:
manifest_json_uri = f'{o3de_object_uri}/{manifest_json}'
manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode())
cache_gem_json_filepath = cache_folder / str(manifest_json_sha256.hexdigest() + '.json')
if cache_gem_json_filepath.is_file():
logger.warn(f'Could not find cached gem json file for {o3de_object_uri} in repo {repo_uri}')
gem_list.append(cache_gem_json_filepath)
return gem_list
def refresh_repos() -> int: def refresh_repos() -> int:
json_data = manifest.load_o3de_manifest() json_data = manifest.load_o3de_manifest()

Loading…
Cancel
Save