Re-merging latest dev after rebasing to fix DCO. Re-resolving conflicts

Signed-off-by: Gene Walters <genewalt@amazon.com>
monroegm-disable-blank-issue-2
Gene Walters 4 years ago
commit 6d673643be

@ -37,6 +37,10 @@ class TestAutomation(EditorTestSuite):
@pytest.mark.test_case_id("C32078115")
class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module
@pytest.mark.test_case_id("C32078122")
class AtomEditorComponents_GridAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_GridAdded as test_module
@pytest.mark.test_case_id("C32078117")
class AtomEditorComponents_LightAdded(EditorSharedTest):

@ -0,0 +1,158 @@
"""
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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
grid_entity_creation = (
"Grid Entity successfully created",
"Grid Entity failed to be created")
grid_component_added = (
"Entity has a Grid component",
"Entity failed to find Grid component")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_Grid_AddedToEntity():
"""
Summary:
Tests the Grid component can be added to an entity and has the expected functionality.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
Expected Behavior:
The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create a Grid entity with no components.
2) Add a Grid component to Grid entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Enter/Exit game mode.
6) Test IsHidden.
7) Test IsVisible.
8) Delete Grid entity.
9) UNDO deletion.
10) REDO deletion.
11) Look for errors.
:return: None
"""
import os
import azlmbr.legacy.general as general
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
from Atom.atom_utils.atom_constants import AtomComponentProperties
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Test steps begin.
# 1. Create a Grid entity with no components.
grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid())
Report.critical_result(Tests.grid_entity_creation, grid_entity.exists())
# 2. Add a Grid component to Grid entity.
grid_component = grid_entity.add_component(AtomComponentProperties.grid())
Report.critical_result(
Tests.grid_component_added,
grid_entity.has_component(AtomComponentProperties.grid()))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not grid_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, grid_entity.exists())
# 5. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
# 6. Test IsHidden.
grid_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, grid_entity.is_hidden() is True)
# 7. Test IsVisible.
grid_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, grid_entity.is_visible() is True)
# 8. Delete Grid entity.
grid_entity.delete()
Report.result(Tests.entity_deleted, not grid_entity.exists())
# 9. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, grid_entity.exists())
# 10. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not grid_entity.exists())
# 11. Look for errors or asserts.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_Grid_AddedToEntity)

@ -9,7 +9,7 @@
#import <AppKit/NSEvent.h>
#include "EditorDefs.h"
#include "QtEditorApplication.h"
#include "QtEditorApplication_mac.h"
// AzFramework
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>

@ -1788,6 +1788,11 @@ AZStd::string SandboxIntegrationManager::GetComponentEditorIcon(const AZ::Uuid&
return iconPath;
}
AZStd::string SandboxIntegrationManager::GetComponentTypeEditorIcon(const AZ::Uuid& componentType)
{
return GetComponentEditorIcon(componentType, nullptr);
}
AZStd::string SandboxIntegrationManager::GetComponentIconPath(const AZ::Uuid& componentType,
AZ::Crc32 componentIconAttrib, AZ::Component* component)
{

@ -233,6 +233,7 @@ private:
}
AZStd::string GetComponentEditorIcon(const AZ::Uuid& componentType, AZ::Component* component) override;
AZStd::string GetComponentTypeEditorIcon(const AZ::Uuid& componentType) override;
AZStd::string GetComponentIconPath(const AZ::Uuid& componentType, AZ::Crc32 componentIconAttrib, AZ::Component* component) override;
//////////////////////////////////////////////////////////////////////////

@ -139,7 +139,7 @@ ComponentDataModel::ComponentDataModel(QObject* parent)
if (element.m_elementId == AZ::Edit::ClassElements::EditorData)
{
AZStd::string iconPath;
EBUS_EVENT_RESULT(iconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, classData->m_typeId, nullptr);
AzToolsFramework::EditorRequestBus::BroadcastResult(iconPath, &AzToolsFramework::EditorRequests::GetComponentTypeEditorIcon, classData->m_typeId);
if (!iconPath.empty())
{
m_componentIcons[classData->m_typeId] = QIcon(iconPath.c_str());

@ -408,7 +408,7 @@ CTrackViewNodesCtrl::CTrackViewNodesCtrl(QWidget* hParentWnd, CTrackViewDialog*
serializeContext->EnumerateDerived<AZ::Component>([this](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid&) -> bool
{
AZStd::string iconPath;
EBUS_EVENT_RESULT(iconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, classData->m_typeId, nullptr);
AzToolsFramework::EditorRequestBus::BroadcastResult(iconPath, &AzToolsFramework::EditorRequests::GetComponentTypeEditorIcon, classData->m_typeId);
if (!iconPath.empty())
{
m_componentTypeToIconMap[classData->m_typeId] = QIcon(iconPath.c_str());

@ -258,10 +258,17 @@ namespace AzFramework
////////////////////////////////////////////////////////////////////////////////////////////////
void XcbNativeWindow::SetWindowTitle(const AZStd::string& title)
{
// Set the title of both the window and the task bar by using
// a buffer to hold the title twice, separated by a null-terminator
auto doubleTitleSize = (title.size() + 1) * 2;
AZStd::string doubleTitle(doubleTitleSize, '\0');
azstrncpy(doubleTitle.data(), doubleTitleSize, title.c_str(), title.size());
azstrncpy(&doubleTitle.data()[title.size() + 1], title.size(), title.c_str(), title.size());
xcb_void_cookie_t xcbCheckResult;
xcbCheckResult = xcb_change_property(
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, static_cast<uint32_t>(title.size()),
title.c_str());
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, static_cast<uint32_t>(doubleTitle.size()),
doubleTitle.c_str());
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set window title.");
}

@ -826,6 +826,10 @@ namespace AzToolsFramework
/// Path will be empty if component should have no icon.
virtual AZStd::string GetComponentEditorIcon(const AZ::Uuid& /*componentType*/, AZ::Component* /*component*/) { return AZStd::string(); }
//! Return path to icon for component type.
//! Path will be empty if component type should have no icon.
virtual AZStd::string GetComponentTypeEditorIcon(const AZ::Uuid& /*componentType*/) { return AZStd::string(); }
/**
* Return the icon image path based on the component type and where it is used.
* \param componentType component type

@ -43,6 +43,8 @@ namespace AzToolsFramework
};
//! Provides a bus to notify when the different editor modes are entered/exit.
//! @note The editor modes are not discrete states but rather each progression of mode retain the active the parent
//! mode that the new mode progressed from.
class ViewportEditorModeNotifications : public AZ::EBusTraits
{
public:

@ -9,9 +9,27 @@
#include <AzToolsFramework/Application/EditorEntityManager.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
namespace AzToolsFramework
{
static bool AreEntitiesValidForDuplication(const EntityIdList& entityIds)
{
for (AZ::EntityId entityId : entityIds)
{
if (GetEntityById(entityId) == nullptr)
{
AZ_Error(
"Entity", false,
"Entity with id '%llu' is not found. This can happen when you try to duplicate the entity before it is created. Please "
"ensure entities are created before trying to duplicate them.",
static_cast<AZ::u64>(entityId));
return false;
}
}
return true;
}
void EditorEntityManager::Start()
{
m_prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
@ -62,7 +80,11 @@ namespace AzToolsFramework
EntityIdList selectedEntities;
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
m_prefabPublicInterface->DuplicateEntitiesInInstance(selectedEntities);
if (AreEntitiesValidForDuplication(selectedEntities))
{
m_prefabPublicInterface->DuplicateEntitiesInInstance(selectedEntities);
}
}
void EditorEntityManager::DuplicateEntityById(AZ::EntityId entityId)
@ -72,7 +94,10 @@ namespace AzToolsFramework
void EditorEntityManager::DuplicateEntities(const EntityIdList& entities)
{
m_prefabPublicInterface->DuplicateEntitiesInInstance(entities);
if (AreEntitiesValidForDuplication(entities))
{
m_prefabPublicInterface->DuplicateEntitiesInInstance(entities);
}
}
}

@ -34,5 +34,4 @@ namespace AzToolsFramework
private:
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
};
}

@ -176,7 +176,7 @@ namespace AzToolsFramework
, public AZ::BehaviorEBusHandler
{
AZ_EBUS_BEHAVIOR_BINDER(ToolsApplicationNotificationBusHandler, "{7EB67956-FF86-461A-91E2-7B08279CFACF}", AZ::SystemAllocator,
EntityRegistered, EntityDeregistered);
EntityRegistered, EntityDeregistered, AfterEntitySelectionChanged);
void EntityRegistered(AZ::EntityId entityId) override
{
@ -187,6 +187,11 @@ namespace AzToolsFramework
{
Call(FN_EntityDeregistered, entityId);
}
void AfterEntitySelectionChanged(const EntityIdList& newlySelectedEntities, const EntityIdList& newlyDeselectedEntities) override
{
Call(FN_AfterEntitySelectionChanged, newlySelectedEntities, newlyDeselectedEntities);
}
};
struct ViewPaneCallbackBusHandler final
@ -410,6 +415,7 @@ namespace AzToolsFramework
->Handler<Internal::ToolsApplicationNotificationBusHandler>()
->Event("EntityRegistered", &ToolsApplicationEvents::EntityRegistered)
->Event("EntityDeregistered", &ToolsApplicationEvents::EntityDeregistered)
->Event("AfterEntitySelectionChanged", &ToolsApplicationEvents::AfterEntitySelectionChanged)
;
behaviorContext->Class<ViewPaneOptions>()
@ -428,6 +434,7 @@ namespace AzToolsFramework
->Attribute(AZ::Script::Attributes::Module, "editor")
->Event("RegisterCustomViewPane", &EditorRequests::RegisterCustomViewPane)
->Event("UnregisterViewPane", &EditorRequests::UnregisterViewPane)
->Event("GetComponentTypeEditorIcon", &EditorRequests::GetComponentTypeEditorIcon)
;
behaviorContext->EBus<EditorEventsBus>("EditorEventBus")

@ -1110,7 +1110,8 @@ namespace AzToolsFramework
// Select the duplicated entities/instances
auto selectionUndo = aznew SelectionCommand(duplicatedEntityAndInstanceIds, "Select Duplicated Entities/Instances");
selectionUndo->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
ToolsApplicationRequestBus::Broadcast(
&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
}
return AZ::Success(AZStd::move(duplicatedEntityAndInstanceIds));

@ -90,7 +90,7 @@ namespace AzToolsFramework
}
AZStd::string componentIconPath;
EBUS_EVENT_RESULT(componentIconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, componentClass->m_typeId, nullptr);
AzToolsFramework::EditorRequestBus::BroadcastResult(componentIconPath, &AzToolsFramework::EditorRequests::GetComponentTypeEditorIcon, componentClass->m_typeId);
componentIconTable[componentClass] = QString::fromUtf8(componentIconPath.c_str());
}

@ -72,7 +72,7 @@ namespace AzToolsFramework
void EntityOutlinerTreeView::leaveEvent([[maybe_unused]] QEvent* event)
{
m_mousePosition = QPoint();
m_mousePosition = QPoint(-1, -1);
m_currentHoveredIndex = QModelIndex();
update();
}
@ -200,7 +200,7 @@ namespace AzToolsFramework
const bool isEnabled = (this->model()->flags(index) & Qt::ItemIsEnabled);
const bool isSelected = selectionModel()->isSelected(index);
const bool isHovered = (index == indexAt(m_mousePosition)) && isEnabled;
const bool isHovered = (index == indexAt(m_mousePosition).siblingAtColumn(0)) && isEnabled;
// Paint the branch Selection/Hover Rect
PaintBranchSelectionHoverRect(painter, rect, isSelected, isHovered);

@ -153,6 +153,9 @@ namespace AzToolsFramework
{
initEntityOutlinerWidgetResources();
m_editorEntityUiInterface = AZ::Interface<AzToolsFramework::EditorEntityUiInterface>::Get();
AZ_Assert(m_editorEntityUiInterface != nullptr, "EntityOutlinerWidget requires a EditorEntityUiInterface instance on Initialize.");
m_gui = new Ui::EntityOutlinerWidgetUI();
m_gui->setupUi(this);
@ -282,12 +285,6 @@ namespace AzToolsFramework
m_listModel->Initialize();
m_editorEntityUiInterface = AZ::Interface<AzToolsFramework::EditorEntityUiInterface>::Get();
AZ_Assert(
m_editorEntityUiInterface != nullptr,
"EntityOutlinerWidget requires a EditorEntityUiInterface instance on Initialize.");
EditorPickModeNotificationBus::Handler::BusConnect(GetEntityContextId());
EntityHighlightMessages::Bus::Handler::BusConnect();
EntityOutlinerModelNotificationBus::Handler::BusConnect();

@ -583,7 +583,7 @@ namespace AzToolsFramework
}
AZStd::string iconPath;
EBUS_EVENT_RESULT(iconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, componentType, const_cast<AZ::Component*>(&componentInstance));
AzToolsFramework::EditorRequestBus::BroadcastResult(iconPath, &AzToolsFramework::EditorRequests::GetComponentEditorIcon, componentType, const_cast<AZ::Component*>(&componentInstance));
GetHeader()->SetIcon(QIcon(iconPath.c_str()));
bool isExpanded = true;

@ -8,6 +8,7 @@
#include <AzTest/AzTest.h>
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/ViewportSelection/EditorPickEntitySelection.h>
@ -187,10 +188,13 @@ namespace UnitTest
ASSERT_NE(m_viewportEditorModeTracker, nullptr);
m_viewportEditorModes = m_viewportEditorModeTracker->GetViewportEditorModes({AzToolsFramework::GetEntityContextId()});
ASSERT_NE(m_viewportEditorModes, nullptr);
m_focusModeInterface = AZ::Interface<AzToolsFramework::FocusModeInterface>::Get();
ASSERT_NE(m_focusModeInterface, nullptr);
}
ViewportEditorModeTrackerInterface* m_viewportEditorModeTracker = nullptr;
const ViewportEditorModesInterface* m_viewportEditorModes = nullptr;
AzToolsFramework::FocusModeInterface* m_focusModeInterface = nullptr;
};
TEST_F(ViewportEditorModesTestsFixture, NumberOfEditorModesIsEqualTo4)
@ -522,32 +526,48 @@ namespace UnitTest
}
TEST_F(
ViewportEditorModeTrackerIntegrationTestFixture, EnteringComponentModeAfterInitialStateHasViewportEditorModesDefaultAndComponentModeActive)
ViewportEditorModeTrackerIntegrationTestFixture,
EnteringComponentModeAfterInitialStateHasViewportEditorModesDefaultAndComponentModeActive)
{
// When component mode is entered
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode,
AZStd::vector<AzToolsFramework::ComponentModeFramework::EntityAndComponentModeBuilders>{});
bool inComponentMode = false;
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::BroadcastResult(
inComponentMode, &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::InComponentMode);
// Expect to be in component mode
EXPECT_TRUE(inComponentMode);
EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode());
// Expect the default and component viewport editor modes to be active
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default));
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component));
// Do not expect the pick and focus viewport editor modes to be active
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick));
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus));
}
TEST_F(
ViewportEditorModeTrackerIntegrationTestFixture,
EnteringEditorPickEntitySelectionAfterInitialStateHasOnlyViewportEditorModePickModeActive)
ExitingComponentModeAfterEnteringFrominitialStateHasViewportEditorModesDefaultActive)
{
// When component mode is entered and exited
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode,
AZStd::vector<AzToolsFramework::ComponentModeFramework::EntityAndComponentModeBuilders>{});
EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode());
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::EndComponentMode);
// Expect to not be in component mode
EXPECT_FALSE(AzToolsFramework::ComponentModeFramework::InComponentMode());
// Expect only the default viewport editor mode to be active
ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Default);
}
TEST_F(
ViewportEditorModeTrackerIntegrationTestFixture,
EnteringEditorPickEntitySelectionAfterInitialStateHasOnlyViewportEditorModePickActive)
{
// When entering pick mode
using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
@ -563,6 +583,96 @@ namespace UnitTest
ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Pick);
}
// FocusMode integration tests will follow (LYN-6995)
TEST_F(
ViewportEditorModeTrackerIntegrationTestFixture,
EnteringEditorDefaultEntitySelectionFromEditorPickEntitySelectionHasOnlyViewportEditorModeDefaultActive)
{
// When pick mode is entered and exited
using AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus;
EditorInteractionSystemViewportSelectionRequestBus::Event(
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
{
return AZStd::make_unique<AzToolsFramework::EditorPickEntitySelection>(entityDataCache, viewportEditorModeTracker);
});
EditorInteractionSystemViewportSelectionRequestBus::Event(
AzToolsFramework::GetEntityContextId(), &EditorInteractionSystemViewportSelectionRequestBus::Events::SetHandler,
[](const AzToolsFramework::EditorVisibleEntityDataCache* entityDataCache,
[[maybe_unused]] AzToolsFramework::ViewportEditorModeTrackerInterface* viewportEditorModeTracker)
{
return AZStd::make_unique<AzToolsFramework::EditorDefaultSelection>(entityDataCache, viewportEditorModeTracker);
});
// Expect only the default viewport editor mode to be active
ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Default);
}
TEST_F(ViewportEditorModeTrackerIntegrationTestFixture, EnteringFocusModeAfterInitialStateHasViewportEditorModeDefaultAndPickActive)
{
// When entering focus mode
m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 });
// Expect the default and focus viewport editor modes to be active
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default));
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus));
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick));
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component));
}
TEST_F(
ViewportEditorModeTrackerIntegrationTestFixture,
ExitingFocusModeAfterEnteringFromInitialStateHasOnlyViewportEditorModeDefaultActive)
{
// When entering and leaving focus mode
m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 });
m_focusModeInterface->SetFocusRoot(AZ::EntityId());
// Expect only the default mode to be active
ExpectOnlyModeActive(*m_viewportEditorModes, ViewportEditorMode::Default);
}
TEST_F(ViewportEditorModeTrackerIntegrationTestFixture, EnteringComponentModeFromFocusModeStateHasViewportEditorModeDefaultAndFocusAndComponentActive)
{
// When entering component mode from focus mode
m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 });
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode,
AZStd::vector<AzToolsFramework::ComponentModeFramework::EntityAndComponentModeBuilders>{});
// Expect to be in component mode
EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode());
// Expect the default, focus and component viewport editor modes to be active
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default));
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus));
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component));
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick));
}
TEST_F(
ViewportEditorModeTrackerIntegrationTestFixture,
ExitingComponentModeAfterEnteringFromFocusModeHasViewportEditorModeDefaultAndFocusActive)
{
// When entering and leaving component mode from focus mode
m_focusModeInterface->SetFocusRoot(AZ::EntityId{ 1 });
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::BeginComponentMode,
AZStd::vector<AzToolsFramework::ComponentModeFramework::EntityAndComponentModeBuilders>{});
EXPECT_TRUE(AzToolsFramework::ComponentModeFramework::InComponentMode());
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::EndComponentMode);
// Expect to not be in component mode
EXPECT_FALSE(AzToolsFramework::ComponentModeFramework::InComponentMode());
// Expect the default and focus viewport editor modes to be active
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Default));
EXPECT_TRUE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Focus));
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Component));
EXPECT_FALSE(m_viewportEditorModes->IsModeActive(ViewportEditorMode::Pick));
}
} // namespace UnitTest

@ -691,6 +691,12 @@ QProgressBar::chunk {
#gemRepoAddDialogInstructionTitleLabel {
font-size:14px;
font-weight:bold;
}
#gemRepoAddDialogWarningLabel {
font-size:12px;
font-style:italic;
}
#addGemRepoDialog #formFrame {

@ -167,6 +167,10 @@ namespace O3DE::ProjectManager
{
notification += " " + tr("and") + " ";
}
if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded)
{
m_downloadController->AddGemDownload(GemModel::GetName(modelIndex));
}
}
if (numChangedDependencies == 1 )
@ -223,6 +227,20 @@ namespace O3DE::ProjectManager
m_gemModel->AddGem(gemInfo);
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos();
if (allRepoGemInfosResult.IsSuccess())
{
const QVector<GemInfo> allRepoGemInfos = allRepoGemInfosResult.GetValue();
for (const GemInfo& gemInfo : allRepoGemInfos)
{
m_gemModel->AddGem(gemInfo);
}
}
else
{
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems from repos.<br><br>Error:<br>%1").arg(allRepoGemInfosResult.GetError().c_str()));
}
m_gemModel->UpdateGemDependencies();
m_notificationsEnabled = false;
@ -249,14 +267,14 @@ namespace O3DE::ProjectManager
}
else
{
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.<br><br>Error:<br>%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
}
m_notificationsEnabled = true;
}
else
{
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str()));
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.<br><br>Error:<br>%2").arg(projectPath, allGemInfosResult.GetError().c_str()));
}
}

@ -225,21 +225,22 @@ namespace O3DE::ProjectManager
QVector<QString> elementNames;
QVector<int> elementCounts;
const int totalGems = m_gemModel->rowCount();
const int selectedGemTotal = m_gemModel->TotalAddedGems();
const int selectedGemTotal = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true).size();
const int unselectedGemTotal = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true).size();
const int enabledGemTotal = m_gemModel->TotalAddedGems(/*includeDependencies=*/true);
elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Unselected));
elementCounts.push_back(totalGems - selectedGemTotal);
elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Selected));
elementCounts.push_back(selectedGemTotal);
elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Inactive));
elementCounts.push_back(totalGems - enabledGemTotal);
elementNames.push_back(GemSortFilterProxyModel::GetGemSelectedString(GemSortFilterProxyModel::GemSelected::Unselected));
elementCounts.push_back(unselectedGemTotal);
elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Active));
elementCounts.push_back(enabledGemTotal);
elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Inactive));
elementCounts.push_back(totalGems - enabledGemTotal);
bool wasCollapsed = false;
if (m_statusFilter)
{
@ -262,43 +263,50 @@ namespace O3DE::ProjectManager
const QList<QAbstractButton*> buttons = m_statusFilter->GetButtonGroup()->buttons();
QAbstractButton* unselectedButton = buttons[0];
QAbstractButton* selectedButton = buttons[1];
unselectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Unselected);
selectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Selected);
QAbstractButton* selectedButton = buttons[0];
QAbstractButton* unselectedButton = buttons[1];
selectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Selected);
unselectedButton->setChecked(m_filterProxyModel->GetGemSelected() == GemSortFilterProxyModel::GemSelected::Unselected);
auto updateGemSelection = [=]([[maybe_unused]] bool checked)
{
if (unselectedButton->isChecked() && !selectedButton->isChecked())
if (!unselectedButton->isChecked() && selectedButton->isChecked())
{
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Unselected);
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Selected);
}
else if (!unselectedButton->isChecked() && selectedButton->isChecked())
else if (unselectedButton->isChecked() && !selectedButton->isChecked())
{
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Selected);
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Unselected);
}
else
{
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::NoFilter);
if (unselectedButton->isChecked() && selectedButton->isChecked())
{
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::Both);
}
else
{
m_filterProxyModel->SetGemSelected(GemSortFilterProxyModel::GemSelected::NoFilter);
}
}
};
connect(unselectedButton, &QAbstractButton::toggled, this, updateGemSelection);
connect(selectedButton, &QAbstractButton::toggled, this, updateGemSelection);
QAbstractButton* inactiveButton = buttons[2];
QAbstractButton* activeButton = buttons[3];
inactiveButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Inactive);
activeButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Active);
QAbstractButton* activeButton = buttons[2];
QAbstractButton* inactiveButton = buttons[3];
activeButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Active);
inactiveButton->setChecked(m_filterProxyModel->GetGemActive() == GemSortFilterProxyModel::GemActive::Inactive);
auto updateGemActive = [=]([[maybe_unused]] bool checked)
{
if (inactiveButton->isChecked() && !activeButton->isChecked())
if (!inactiveButton->isChecked() && activeButton->isChecked())
{
m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Inactive);
m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Active);
}
else if (!inactiveButton->isChecked() && activeButton->isChecked())
else if (inactiveButton->isChecked() && !activeButton->isChecked())
{
m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Active);
m_filterProxyModel->SetGemActive(GemSortFilterProxyModel::GemActive::Inactive);
}
else
{

@ -50,11 +50,26 @@ namespace O3DE::ProjectManager
}
}
// Gem selected
if (m_gemSelectedFilter != GemSelected::NoFilter)
// Gem selected
if (m_gemSelectedFilter == GemSelected::Selected)
{
const GemSelected sourceGemStatus = static_cast<GemSelected>(GemModel::IsAdded(sourceIndex));
if (m_gemSelectedFilter != sourceGemStatus)
if (!GemModel::NeedsToBeAdded(sourceIndex, true))
{
return false;
}
}
// Gem unselected
else if (m_gemSelectedFilter == GemSelected::Unselected)
{
if (!GemModel::NeedsToBeRemoved(sourceIndex, true))
{
return false;
}
}
// Gem selected or unselected
else if (m_gemSelectedFilter == GemSelected::Both)
{
if (!GemModel::NeedsToBeAdded(sourceIndex, true) && !GemModel::NeedsToBeRemoved(sourceIndex, true))
{
return false;
}

@ -29,7 +29,8 @@ namespace O3DE::ProjectManager
{
NoFilter = -1,
Unselected,
Selected
Selected,
Both
};
enum class GemActive
{

@ -27,6 +27,7 @@ namespace O3DE::ProjectManager
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setContentsMargins(30, 30, 25, 10);
vLayout->setSpacing(0);
vLayout->setAlignment(Qt::AlignTop);
setLayout(vLayout);
QLabel* instructionTitleLabel = new QLabel(tr("Enter a valid path to add a new user repository"));
@ -41,9 +42,18 @@ namespace O3DE::ProjectManager
vLayout->addWidget(instructionContextLabel);
m_repoPath = new FormFolderBrowseEditWidget(tr("Repository Path"), "", this);
m_repoPath->setFixedWidth(600);
m_repoPath->setFixedSize(QSize(600, 100));
vLayout->addWidget(m_repoPath);
vLayout->addSpacing(10);
QLabel* warningLabel = new QLabel(tr("Online repositories may contain files that could potentially harm your computer,"
" please ensure you understand the risks before downloading Gems from third-party sources."));
warningLabel->setObjectName("gemRepoAddDialogWarningLabel");
warningLabel->setWordWrap(true);
warningLabel->setAlignment(Qt::AlignLeft);
vLayout->addWidget(warningLabel);
vLayout->addSpacing(40);
QDialogButtonBox* dialogButtons = new QDialogButtonBox();

@ -108,7 +108,7 @@ namespace O3DE::ProjectManager
// Draw refresh button
painter->drawPixmap(
repoUpdatedDateRect.left() + repoUpdatedDateRect.width() + s_refreshIconSpacing,
repoUpdatedDateRect.left() + s_updatedMaxWidth + s_refreshIconSpacing,
contentRect.center().y() - s_refreshIconSize / 3, // Dividing size by 3 centers much better
m_refreshIcon);
@ -150,6 +150,11 @@ namespace O3DE::ProjectManager
emit RemoveRepo(modelIndex);
return true;
}
else if (keyEvent->key() == Qt::Key_R || keyEvent->key() == Qt::Key_F5)
{
emit RefreshRepo(modelIndex);
return true;
}
}
if (event->type() == QEvent::MouseButtonPress)
@ -160,6 +165,7 @@ namespace O3DE::ProjectManager
CalcRects(option, fullRect, itemRect, contentRect);
const QRect buttonRect = CalcButtonRect(contentRect);
const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect);
const QRect refreshButtonRect = CalcRefreshButtonRect(contentRect, buttonRect);
if (buttonRect.contains(mouseEvent->pos()))
{
@ -172,6 +178,11 @@ namespace O3DE::ProjectManager
emit RemoveRepo(modelIndex);
return true;
}
else if (refreshButtonRect.contains(mouseEvent->pos()))
{
emit RefreshRepo(modelIndex);
return true;
}
}
return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
@ -231,6 +242,13 @@ namespace O3DE::ProjectManager
return QRect(topLeft, QSize(s_iconSize, s_iconSize));
}
QRect GemRepoItemDelegate::CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) const
{
const int topLeftX = buttonRect.left() + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 2 + s_refreshIconSpacing;
const QPoint topLeft = QPoint(topLeftX, contentRect.center().y() - s_refreshIconSize / 3);
return QRect(topLeft, QSize(s_refreshIconSize, s_refreshIconSize));
}
void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const
{
painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon);

@ -68,12 +68,14 @@ namespace O3DE::ProjectManager
signals:
void RemoveRepo(const QModelIndex& modelIndex);
void RefreshRepo(const QModelIndex& modelIndex);
protected:
void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
QRect CalcButtonRect(const QRect& contentRect) const;
QRect CalcDeleteButtonRect(const QRect& contentRect) const;
QRect CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) const;
void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
void DrawEditButtons(QPainter* painter, const QRect& contentRect) const;

@ -24,6 +24,7 @@ namespace O3DE::ProjectManager
GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, this);
connect(itemDelegate, &GemRepoItemDelegate::RemoveRepo, this, &GemRepoListView::RemoveRepo);
connect(itemDelegate, &GemRepoItemDelegate::RefreshRepo, this, &GemRepoListView::RefreshRepo);
setItemDelegate(itemDelegate);
}
} // namespace O3DE::ProjectManager

@ -28,5 +28,6 @@ namespace O3DE::ProjectManager
signals:
void RemoveRepo(const QModelIndex& modelIndex);
void RefreshRepo(const QModelIndex& modelIndex);
};
} // namespace O3DE::ProjectManager

@ -126,6 +126,36 @@ namespace O3DE::ProjectManager
}
}
void GemRepoScreen::HandleRefreshAllButton()
{
bool refreshResult = PythonBindingsInterface::Get()->RefreshAllGemRepos();
Reinit();
if (!refreshResult)
{
QMessageBox::critical(
this, tr("Operation failed"), QString("Some repos failed to refresh."));
}
}
void GemRepoScreen::HandleRefreshRepoButton(const QModelIndex& modelIndex)
{
const QString repoUri = m_gemRepoModel->GetRepoUri(modelIndex);
AZ::Outcome<void, AZStd::string> refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(repoUri);
if (refreshResult.IsSuccess())
{
Reinit();
}
else
{
QMessageBox::critical(
this, tr("Operation failed"),
QString("Failed to refresh gem repo %1<br>Error:<br>%2")
.arg(m_gemRepoModel->GetName(modelIndex), refreshResult.GetError().c_str()));
}
}
void GemRepoScreen::FillModel()
{
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> allGemRepoInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoInfos();
@ -237,6 +267,8 @@ namespace O3DE::ProjectManager
m_AllUpdateButton->setObjectName("gemRepoHeaderRefreshButton");
topMiddleHLayout->addWidget(m_AllUpdateButton);
connect(m_AllUpdateButton, &QPushButton::clicked, this, &GemRepoScreen::HandleRefreshAllButton);
topMiddleHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
QPushButton* addRepoButton = new QPushButton(tr("Add Repository"), this);
@ -280,6 +312,7 @@ namespace O3DE::ProjectManager
middleVLayout->addWidget(m_gemRepoListView);
connect(m_gemRepoListView, &GemRepoListView::RemoveRepo, this, &GemRepoScreen::HandleRemoveRepoButton);
connect(m_gemRepoListView, &GemRepoListView::RefreshRepo, this, &GemRepoScreen::HandleRefreshRepoButton);
hLayout->addLayout(middleVLayout);

@ -40,6 +40,8 @@ namespace O3DE::ProjectManager
public slots:
void HandleAddRepoButton();
void HandleRemoveRepoButton(const QModelIndex& modelIndex);
void HandleRefreshAllButton();
void HandleRefreshRepoButton(const QModelIndex& modelIndex);
private:
void FillModel();

@ -303,6 +303,7 @@ namespace O3DE::ProjectManager
m_disableGemProject = pybind11::module::import("o3de.disable_gem");
m_editProjectProperties = pybind11::module::import("o3de.project_properties");
m_download = pybind11::module::import("o3de.download");
m_repo = pybind11::module::import("o3de.repo");
m_pathlib = pybind11::module::import("pathlib");
// make sure the engine is registered
@ -982,6 +983,46 @@ namespace O3DE::ProjectManager
}
}
AZ::Outcome<void, AZStd::string> PythonBindings::RefreshGemRepo(const QString& repoUri)
{
bool refreshResult = false;
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
[&]
{
auto pyUri = QString_To_Py_String(repoUri);
auto pythonRefreshResult = m_repo.attr("refresh_repo")(pyUri);
// Returns an exit code so boolify it then invert result
refreshResult = !pythonRefreshResult.cast<bool>();
});
if (!result.IsSuccess())
{
return result;
}
else if (!refreshResult)
{
return AZ::Failure<AZStd::string>("Failed to refresh repo.");
}
return AZ::Success();
}
bool PythonBindings::RefreshAllGemRepos()
{
bool refreshResult = false;
bool result = ExecuteWithLock(
[&]
{
auto pythonRefreshResult = m_repo.attr("refresh_repos")();
// Returns an exit code so boolify it then invert result
refreshResult = !pythonRefreshResult.cast<bool>();
});
return result && refreshResult;
}
bool PythonBindings::AddGemRepo(const QString& repoUri)
{
bool registrationResult = false;
@ -1160,4 +1201,31 @@ namespace O3DE::ProjectManager
{
m_requestCancelDownload = true;
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos()
{
QVector<GemInfo> gemInfos;
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
[&]
{
auto gemPaths = m_repo.attr("get_gem_json_paths_from_all_cached_repos")();
if (pybind11::isinstance<pybind11::set>(gemPaths))
{
for (auto path : gemPaths)
{
GemInfo gemInfo = GemInfoFromPath(path, pybind11::none());
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded;
gemInfos.push_back(gemInfo);
}
}
});
if (!result.IsSuccess())
{
return AZ::Failure(result.GetError());
}
return AZ::Success(AZStd::move(gemInfos));
}
}

@ -59,11 +59,14 @@ namespace O3DE::ProjectManager
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) override;
// Gem Repos
AZ::Outcome<void, AZStd::string> RefreshGemRepo(const QString& repoUri) override;
bool RefreshAllGemRepos() override;
bool AddGemRepo(const QString& repoUri) override;
bool RemoveGemRepo(const QString& repoUri) override;
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) override;
void CancelDownload() override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() override;
private:
AZ_DISABLE_COPY_MOVE(PythonBindings);
@ -91,6 +94,7 @@ namespace O3DE::ProjectManager
pybind11::handle m_disableGemProject;
pybind11::handle m_editProjectProperties;
pybind11::handle m_download;
pybind11::handle m_repo;
pybind11::handle m_pathlib;
bool m_requestCancelDownload = false;

@ -176,6 +176,19 @@ namespace O3DE::ProjectManager
// Gem Repos
/**
* Refresh gem repo in the current engine.
* @param repoUri the absolute filesystem path or url to the gem repo.
* @return An outcome with the success flag as well as an error message in case of a failure.
*/
virtual AZ::Outcome<void, AZStd::string> RefreshGemRepo(const QString& repoUri) = 0;
/**
* Refresh all gem repos in the current engine.
* @return true on success, false on failure.
*/
virtual bool RefreshAllGemRepos() = 0;
/**
* Registers this gem repo with the current engine.
* @param repoUri the absolute filesystem path or url to the gem repo.
@ -208,6 +221,12 @@ namespace O3DE::ProjectManager
* Cancels the current download.
*/
virtual void CancelDownload() = 0;
/**
* Gathers all gem infos for all gems registered from repos.
* @return A list of gem infos.
*/
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() = 0;
};
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;

@ -133,6 +133,11 @@ namespace O3DE::ProjectManager
return true;
}
else
{
// If we are already on this screen still notify we are on this screen to refresh it
newScreen->NotifyCurrentScreen();
}
}
return false;

@ -108,7 +108,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithoutClientSe
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; });
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
@ -122,7 +122,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_MultipleCallsWithou
AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; });
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
@ -140,7 +140,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButWithFailedOu
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; });
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
@ -160,7 +160,7 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithMoreThanOne
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
WaitForProcessFinish([&](){ return matchmakingHandlerMock.m_numMatchError == 1; });
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());

@ -23,6 +23,7 @@
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <CommonFiles/Preprocessor.h>
#include <CommonFiles/GlobalBuildOptions.h>
@ -91,19 +92,31 @@ namespace AZ
m_shaderAssetBuilder.BusConnect(shaderAssetBuilderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderAssetBuilderDescriptor);
// Register Shader Variant Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderVariantAssetBuilderDescriptor;
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work.
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
shaderVariantAssetBuilderDescriptor.m_processJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::ProcessJob, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
// If, either the SettingsRegistry doesn't exist, or the property @EnableShaderVariantAssetBuilderRegistryKey is not found,
// the default is to enable the ShaderVariantAssetBuilder.
m_enableShaderVariantAssetBuilder = true;
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
settingsRegistry->Get(m_enableShaderVariantAssetBuilder, EnableShaderVariantAssetBuilderRegistryKey);
}
m_shaderVariantAssetBuilder.BusConnect(shaderVariantAssetBuilderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderVariantAssetBuilderDescriptor);
if (m_enableShaderVariantAssetBuilder)
{
// Register Shader Variant Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderVariantAssetBuilderDescriptor;
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work.
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
shaderVariantAssetBuilderDescriptor.m_processJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::ProcessJob, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
m_shaderVariantAssetBuilder.BusConnect(shaderVariantAssetBuilderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderVariantAssetBuilderDescriptor);
}
// Register Precompiled Shader Builder
AssetBuilderSDK::AssetBuilderDesc precompiledShaderBuilderDescriptor;
@ -121,7 +134,10 @@ namespace AZ
void AzslShaderBuilderSystemComponent::Deactivate()
{
m_shaderAssetBuilder.BusDisconnect();
m_shaderVariantAssetBuilder.BusDisconnect();
if (m_enableShaderVariantAssetBuilder)
{
m_shaderVariantAssetBuilder.BusDisconnect();
}
m_precompiledShaderBuilder.BusDisconnect();
RHI::ShaderPlatformInterfaceRegisterBus::Handler::BusDisconnect();

@ -61,7 +61,17 @@ namespace AZ
private:
ShaderAssetBuilder m_shaderAssetBuilder;
// The ShaderVariantAssetBuilder can be disabled with this registry key.
// By default it is enabled. A user might want to disable it when doing look development
// work with shaders or doing lots of iterative changes to shaders. In these cases
// GPU performance doesn't matter at all so it is important to not waste time
// building ShaderVariantAssets (Other than the Root ShaderVariantAsset, of course.).
static constexpr char EnableShaderVariantAssetBuilderRegistryKey[] = "/O3DE/Atom/Shaders/BuildVariants";
bool m_enableShaderVariantAssetBuilder = true;
ShaderVariantAssetBuilder m_shaderVariantAssetBuilder;
PrecompiledShaderBuilder m_precompiledShaderBuilder;
/// Contains the ShaderPlatformInterface for all registered RHIs

@ -477,8 +477,8 @@ namespace AZ
preprocessorOptions.m_predefinedMacros.end(), macroDefinitionsToAdd.begin(), macroDefinitionsToAdd.end());
// Run the preprocessor.
PreprocessorData output;
PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true);
RHI::ReportErrorMessages(ShaderAssetBuilderName, output.diagnostics);
const bool preprocessorSuccess = PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true);
RHI::ReportMessages(ShaderAssetBuilderName, output.diagnostics, !preprocessorSuccess);
// Dump the preprocessed string as a flat AZSL file with extension .azslin, which will be given to AZSLc to generate the HLSL file.
AZStd::string superVariantAzslinStemName = shaderFileName;
if (!supervariantInfo.m_name.IsEmpty())

@ -0,0 +1,10 @@
{
"O3DE": {
"Atom": {
"Shaders": {
"BuildVariants": true
}
}
}
}
}

@ -14,6 +14,7 @@
#include <AzCore/NativeUI/NativeUIRequests.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Components/TransformComponent.h>
@ -115,7 +116,9 @@ namespace AZ
{
// GFX TODO - investigate window creation being part of the GameApplication.
m_nativeWindow = AZStd::make_unique<AzFramework::NativeWindow>("O3DELauncher", AzFramework::WindowGeometry(0, 0, 1920, 1080));
auto projectTitle = AZ::Utils::GetProjectName();
m_nativeWindow = AZStd::make_unique<AzFramework::NativeWindow>(projectTitle.c_str(), AzFramework::WindowGeometry(0, 0, 1920, 1080));
AZ_Assert(m_nativeWindow, "Failed to create the game window\n");
m_nativeWindow->Activate();

@ -1,6 +1,14 @@
{
"description": "Material Type with properties used to define Enhanced PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model, with advanced features like subsurface scattering, transmission, and anisotropy.",
"version": 3,
"version": 4,
"versionUpdates": [
{
"toVersion": 4,
"actions": [
{"op": "rename", "from": "opacity.doubleSided", "to": "general.doubleSided"}
]
}
],
"propertyLayout": {
"groups": [
{
@ -92,6 +100,12 @@
],
"properties": {
"general": [
{
"name": "doubleSided",
"displayName": "Double-sided",
"description": "Whether to render back-faces or just front-faces.",
"type": "Bool"
},
{
"name": "applySpecularAA",
"displayName": "Apply Specular AA",
@ -709,12 +723,6 @@
"name": "m_opacityFactor"
}
},
{
"name": "doubleSided",
"displayName": "Double-sided",
"description": "Whether to render back-faces or just front-faces.",
"type": "Bool"
},
{
"name": "alphaAffectsSpecular",
"displayName": "Alpha affects specular",

@ -1,6 +1,14 @@
{
"description": "Material Type with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.",
"version": 3,
"version": 4,
"versionUpdates": [
{
"toVersion": 4,
"actions": [
{"op": "rename", "from": "opacity.doubleSided", "to": "general.doubleSided"}
]
}
],
"propertyLayout": {
"groups": [
{
@ -72,6 +80,12 @@
],
"properties": {
"general": [
{
"name": "doubleSided",
"displayName": "Double-sided",
"description": "Whether to render back-faces or just front-faces.",
"type": "Bool"
},
{
"name": "applySpecularAA",
"displayName": "Apply Specular AA",
@ -650,12 +664,6 @@
"name": "m_opacityFactor"
}
},
{
"name": "doubleSided",
"displayName": "Double-sided",
"description": "Whether to render back-faces or just front-faces.",
"type": "Bool"
},
{
"name": "alphaAffectsSpecular",
"displayName": "Alpha affects specular",

@ -10,14 +10,14 @@
----------------------------------------------------------------------------------------------------
function GetMaterialPropertyDependencies()
return {"opacity.doubleSided"}
return {"general.doubleSided"}
end
ForwardPassIndex = 0
ForwardPassEdsIndex = 1
function Process(context)
local doubleSided = context:GetMaterialPropertyValue_bool("opacity.doubleSided")
local doubleSided = context:GetMaterialPropertyValue_bool("general.doubleSided")
local lastShader = context:GetShaderCount() - 1;
if(doubleSided) then

@ -81,7 +81,6 @@ function ProcessEditor(context)
context:SetMaterialPropertyVisibility("opacity.textureMap", mainVisibility)
context:SetMaterialPropertyVisibility("opacity.textureMapUv", mainVisibility)
context:SetMaterialPropertyVisibility("opacity.factor", mainVisibility)
context:SetMaterialPropertyVisibility("opacity.doubleSided", mainVisibility)
if(opacityMode == OpacityMode_Blended or opacityMode == OpacityMode_TintedTransparent) then
context:SetMaterialPropertyVisibility("opacity.alphaAffectsSpecular", MaterialPropertyVisibility_Enabled)

@ -23,8 +23,8 @@
"DrawList" : "forward",
"CompilerHints" : {
"DxcDisableOptimizations" : false,
"DxcGenerateDebugInfo" : false
"DisableOptimizations" : false,
"GenerateDebugInfo" : false
},
"ProgramSettings":

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:57d6744696768f9fb8a5fe5fee9aa36fee1eb87a9dbc1e60d4a35ed3c39d68e6
size 810620

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:513f47f6fea5105f603170a8881b7e3b1cd2c4258636d64a6399c725032b500d
size 38689

@ -5,12 +5,3 @@
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
if(LY_MONOLITHIC_GAME) # Do not use OpenImageIO in monolithic game
return()
endif()
set(LY_BUILD_DEPENDENCIES
PRIVATE
3rdParty::ilmbase
)

@ -57,12 +57,19 @@ namespace AZ
AZStd::string m_azslcAdditionalFreeArguments;
// note: if you add new sort of arguments here, don't forget to update HasDifferentAzslcArguments()
//! DXC
bool m_dxcDisableWarnings = false;
bool m_dxcWarningAsError = false;
bool m_dxcDisableOptimizations = false;
bool m_dxcGenerateDebugInfo = false;
uint8_t m_dxcOptimizationLevel = LevelUnset;
//! Remark: To the user, the following parameters are exposed without the
//! "Dxc" prefix because these are common options for the "main" compiler
//! for the given RHI. At the moment the only "main" compiler is Dxc, but in
//! the future AZSLc may transpile from AZSL to some other proprietary language
//! and in that case the "main" compiler won't be DXC
bool m_disableWarnings = false;
bool m_warningAsError = false;
bool m_disableOptimizations = false;
bool m_generateDebugInfo = false;
uint8_t m_optimizationLevel = LevelUnset;
//! "DxcAdditionalFreeArguments" keeps the "Dxc" prefix because these arguments
//! are specific to DXC, and it will be relevant only if DXC is the "main" compiler
//! for a given RHI, otherwise this parameter won't matter.
AZStd::string m_dxcAdditionalFreeArguments;
//! both

@ -83,11 +83,12 @@ namespace AZ
const AZStd::string& shaderSourcePathForDebug,
const char* toolNameForLog);
//! Reports error messages to AZ_Error and/or AZ_Warning, given a text blob that potentially contains many lines of errors and warnings.
//! @param window Debug window name used for AZ Trace functions
//! @param errorMessages String that may contain many lines of errors and warnings
//! @param return true if Errors were detected and reported (Warnings don't count)
bool ReportErrorMessages(AZStd::string_view window, AZStd::string_view errorMessages);
//! Reports messages with AZ_Error or AZ_Warning (See @reportAsErrors).
//! @param window Debug window name used for AZ Trace functions.
//! @param errorMessages Message string.
//! @param reportAsErrors If true, messages are traced with AZ_Error, otherwise AZ_Warning is used.
//! @returns true If the input text blob contains at least one line with the "error" string.
bool ReportMessages(AZStd::string_view window, AZStd::string_view errorMessages, bool reportAsErrors);
//! Converts from a RHI::ShaderHardwareStage to an RHI::ShaderStage
ShaderStage ToRHIShaderStage(ShaderHardwareStage stageType);

@ -33,17 +33,17 @@ namespace AZ
RegisterEnumerators<MatrixOrder>(serializeContext);
serializeContext->Class<ShaderCompilerArguments>()
->Version(2)
->Version(3)
->Field("AzslcWarningLevel", &ShaderCompilerArguments::m_azslcWarningLevel)
->Field("AzslcWarningAsError", &ShaderCompilerArguments::m_azslcWarningAsError)
->Field("AzslcAdditionalFreeArguments", &ShaderCompilerArguments::m_azslcAdditionalFreeArguments)
->Field("DxcDisableWarnings", &ShaderCompilerArguments::m_dxcDisableWarnings)
->Field("DxcWarningAsError", &ShaderCompilerArguments::m_dxcWarningAsError)
->Field("DxcDisableOptimizations", &ShaderCompilerArguments::m_dxcDisableOptimizations)
->Field("DxcGenerateDebugInfo", &ShaderCompilerArguments::m_dxcGenerateDebugInfo)
->Field("DxcOptimizationLevel", &ShaderCompilerArguments::m_dxcOptimizationLevel)
->Field("DxcAdditionalFreeArguments", &ShaderCompilerArguments::m_dxcAdditionalFreeArguments)
->Field("DisableWarnings", &ShaderCompilerArguments::m_disableWarnings)
->Field("WarningAsError", &ShaderCompilerArguments::m_warningAsError)
->Field("DisableOptimizations", &ShaderCompilerArguments::m_disableOptimizations)
->Field("GenerateDebugInfo", &ShaderCompilerArguments::m_generateDebugInfo)
->Field("OptimizationLevel", &ShaderCompilerArguments::m_optimizationLevel)
->Field("DefaultMatrixOrder", &ShaderCompilerArguments::m_defaultMatrixOrder)
->Field("DxcAdditionalFreeArguments", &ShaderCompilerArguments::m_dxcAdditionalFreeArguments)
;
}
}
@ -62,13 +62,13 @@ namespace AZ
}
m_azslcWarningAsError = m_azslcWarningAsError || right.m_azslcWarningAsError;
m_azslcAdditionalFreeArguments = CommandLineArgumentUtils::MergeCommandLineArguments(m_azslcAdditionalFreeArguments, right.m_azslcAdditionalFreeArguments);
m_dxcDisableWarnings = m_dxcDisableWarnings || right.m_dxcDisableWarnings;
m_dxcWarningAsError = m_dxcWarningAsError || right.m_dxcWarningAsError;
m_dxcDisableOptimizations = m_dxcDisableOptimizations || right.m_dxcDisableOptimizations;
m_dxcGenerateDebugInfo = m_dxcGenerateDebugInfo || right.m_dxcGenerateDebugInfo;
if (right.m_dxcOptimizationLevel != LevelUnset)
m_disableWarnings = m_disableWarnings || right.m_disableWarnings;
m_warningAsError = m_warningAsError || right.m_warningAsError;
m_disableOptimizations = m_disableOptimizations || right.m_disableOptimizations;
m_generateDebugInfo = m_generateDebugInfo || right.m_generateDebugInfo;
if (right.m_optimizationLevel != LevelUnset)
{
m_dxcOptimizationLevel = right.m_dxcOptimizationLevel;
m_optimizationLevel = right.m_optimizationLevel;
}
m_dxcAdditionalFreeArguments = CommandLineArgumentUtils::MergeCommandLineArguments(m_dxcAdditionalFreeArguments, right.m_dxcAdditionalFreeArguments);
if (right.m_defaultMatrixOrder != MatrixOrder::Default)
@ -131,21 +131,21 @@ namespace AZ
AZStd::string ShaderCompilerArguments::MakeAdditionalDxcCommandLineString() const
{
AZStd::string arguments;
if (m_dxcDisableWarnings)
if (m_disableWarnings)
{
arguments += " -no-warnings";
}
else if (m_dxcWarningAsError)
else if (m_warningAsError)
{
arguments += " -WX";
}
if (m_dxcDisableOptimizations)
if (m_disableOptimizations)
{
arguments += " -Od";
}
else if (m_dxcOptimizationLevel <= 3)
else if (m_optimizationLevel <= 3)
{
arguments = " -O" + AZStd::to_string(m_dxcOptimizationLevel);
arguments = " -O" + AZStd::to_string(m_optimizationLevel);
}
if (m_defaultMatrixOrder == MatrixOrder::Column)
{

@ -330,7 +330,7 @@ namespace AZ
// Pump one last time to make sure the streams have been flushed
pumpOuputStreams();
const bool reportedErrors = ReportErrorMessages(toolNameForLog, errorMessages);
const bool reportedErrors = ReportMessages(toolNameForLog, errorMessages, exitCode != 0);
if (timedOut)
{
@ -367,32 +367,20 @@ namespace AZ
return true;
}
bool ReportErrorMessages([[maybe_unused]] AZStd::string_view window, AZStd::string_view errorMessages)
bool ReportMessages([[maybe_unused]] AZStd::string_view window, AZStd::string_view errorMessages, bool reportAsErrors)
{
// There are more efficient ways to do this, but this approach is simple and gets us moving for now.
AZStd::vector<AZStd::string> lines;
AzFramework::StringFunc::Tokenize(errorMessages.data(), lines, "\n\r");
bool foundErrors = false;
for (auto& line : lines)
if (reportAsErrors)
{
if (AZStd::string::npos != AzFramework::StringFunc::Find(line, "error"))
{
AZ_Error(window.data(), false, "%s", line.data());
foundErrors = true;
}
else if (AZStd::string::npos != AzFramework::StringFunc::Find(line, "warning"))
{
AZ_Warning(window.data(), false, "%s", line.data());
}
else
{
AZ_TracePrintf(window.data(), "%s", line.data());
}
AZ_Error(window.data(), false, "%.*s", aznumeric_cast<int>(errorMessages.size()), errorMessages.data());
}
return foundErrors;
else
{
// Using AZ_Warning instead of AZ_TracePrintf because this function is commonly
// used to report messages from stderr when executing applications. Applications
// when ran successfully, only output to stderr for errors or warnings.
AZ_Warning(window.data(), false, "%.*s", aznumeric_cast<int>(errorMessages.size()), errorMessages.data());
}
return AZStd::string::npos != AzFramework::StringFunc::Find(errorMessages, "error");
}
ShaderStage ToRHIShaderStage(ShaderHardwareStage stageType)

@ -114,7 +114,7 @@ namespace AZ
}
}
if (shaderCompilerArguments.m_dxcDisableOptimizations)
if (shaderCompilerArguments.m_disableOptimizations)
{
// When optimizations are disabled (-Od), all resources declared in the source file are available to all stages
// (when enabled only the resources which are referenced in a stage are bound to the stage)
@ -195,7 +195,7 @@ namespace AZ
bool ShaderPlatformInterface::BuildHasDebugInfo(const RHI::ShaderCompilerArguments& shaderCompilerArguments) const
{
return shaderCompilerArguments.m_dxcGenerateDebugInfo;
return shaderCompilerArguments.m_generateDebugInfo;
}
const char* ShaderPlatformInterface::GetAzslHeader(const AssetBuilderSDK::PlatformInfo& platform) const

@ -167,7 +167,7 @@ namespace AZ
bool ShaderPlatformInterface::BuildHasDebugInfo(const RHI::ShaderCompilerArguments& shaderCompilerArguments) const
{
return shaderCompilerArguments.m_dxcGenerateDebugInfo;
return shaderCompilerArguments.m_generateDebugInfo;
}
const char* ShaderPlatformInterface::GetAzslHeader(const AssetBuilderSDK::PlatformInfo& platform) const

@ -109,7 +109,7 @@ namespace AZ
bool ShaderPlatformInterface::BuildHasDebugInfo(const RHI::ShaderCompilerArguments& shaderCompilerArguments) const
{
return shaderCompilerArguments.m_dxcGenerateDebugInfo;
return shaderCompilerArguments.m_generateDebugInfo;
}
const char* ShaderPlatformInterface::GetAzslHeader(const AssetBuilderSDK::PlatformInfo& platform) const

@ -729,8 +729,7 @@ namespace AZ
void CommandList::SetStencilRef(uint8_t stencilRef)
{
vkCmdSetStencilReference(m_nativeCommandBuffer, VK_STENCIL_FACE_FRONT_BIT, static_cast<uint32_t>(stencilRef));
vkCmdSetStencilReference(m_nativeCommandBuffer, VK_STENCIL_FACE_BACK_BIT, static_cast<uint32_t>(stencilRef));
vkCmdSetStencilReference(m_nativeCommandBuffer, VK_STENCIL_FACE_FRONT_AND_BACK, aznumeric_cast<uint32_t>(stencilRef));
}
void CommandList::BindPipeline(const PipelineState* pipelineState)

@ -93,31 +93,28 @@ namespace AZ
// Note that the only kind of property update currently supported is rename...
PropertyGroupMap newPropertyGroups;
for (auto& groupPair : m_properties)
{
PropertyMap& propertyMap = groupPair.second;
PropertyMap newPropertyMap;
for (auto& propertyPair : propertyMap)
{
MaterialPropertyId propertyId{groupPair.first, propertyPair.first};
if (materialTypeSourceData.ApplyPropertyRenames(propertyId, m_materialTypeVersion))
{
newPropertyMap[propertyId.GetPropertyName().GetStringView()] = propertyPair.second;
changesWereApplied = true;
}
else
{
newPropertyMap[propertyPair.first] = propertyPair.second;
}
newPropertyGroups[propertyId.GetGroupName().GetStringView()][propertyId.GetPropertyName().GetStringView()] = propertyPair.second;
}
propertyMap = newPropertyMap;
}
if (changesWereApplied)
{
m_properties = AZStd::move(newPropertyGroups);
AZ_Warning("MaterialSourceData", false,
"This material is based on version '%u' of '%s', but the material type is now at version '%u'. "
"Automatic updates are available. Consider updating the .material source file.",

@ -164,16 +164,14 @@ namespace AZ
const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName, uint32_t materialTypeVersion) const
{
auto groupIter = m_propertyLayout.m_properties.find(groupName);
if (groupIter == m_propertyLayout.m_properties.end())
if (groupIter != m_propertyLayout.m_properties.end())
{
return nullptr;
}
for (const PropertyDefinition& property : groupIter->second)
{
if (property.m_name == propertyName)
for (const PropertyDefinition& property : groupIter->second)
{
return &property;
if (property.m_name == propertyName)
{
return &property;
}
}
}
@ -185,16 +183,14 @@ namespace AZ
// Do the search again with the new names
groupIter = m_propertyLayout.m_properties.find(propertyId.GetGroupName().GetStringView());
if (groupIter == m_propertyLayout.m_properties.end())
if (groupIter != m_propertyLayout.m_properties.end())
{
return nullptr;
}
for (const PropertyDefinition& property : groupIter->second)
{
if (property.m_name == propertyId.GetPropertyName().GetStringView())
for (const PropertyDefinition& property : groupIter->second)
{
return &property;
if (property.m_name == propertyId.GetPropertyName().GetStringView())
{
return &property;
}
}
}

@ -264,7 +264,6 @@ namespace AZ
{
// When rebuilding shaders we may be in a state where the ShaderAsset and root ShaderVariantAsset have been rebuilt and reloaded, but some (or all)
// shader variants haven't been built yet. Since we want to use the latest version of the shader code, ignore the old variants and fall back to the newer root variant instead.
AZ_Warning("ShaderAsset", false, "ShaderAsset and ShaderVariantAsset are out of sync; defaulting to root shader variant. (This is common while reloading shaders).");
return GetRootVariant(supervariantIndex);
}
}

@ -105,6 +105,13 @@ namespace UnitTest
{"op": "rename", "from": "general.testColorNameB", "to": "general.testColorNameC"}
]
},
{
"toVersion": 6,
"actions": [
{"op": "rename", "from": "oldGroup.MyFloat", "to": "general.MyFloat"},
{"op": "rename", "from": "oldGroup.MyIntOldName", "to": "general.MyInt"}
]
},
{
"toVersion": 10,
"actions": [
@ -751,6 +758,72 @@ namespace UnitTest
material.ApplyVersionUpdates();
}
TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionUpdate_MovePropertiesToAnotherGroup)
{
const AZStd::string inputJson = R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"materialTypeVersion": 3,
"properties": {
"oldGroup": {
"MyFloat": 1.2,
"MyIntOldName": 5
}
}
}
)";
MaterialSourceData material;
JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson);
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
// Initially, the loaded material data will match the .material file exactly. This gives us the accurate representation of
// what's actually saved on disk.
EXPECT_NE(material.m_properties["oldGroup"].find("MyFloat"), material.m_properties["oldGroup"].end());
EXPECT_NE(material.m_properties["oldGroup"].find("MyIntOldName"), material.m_properties["oldGroup"].end());
EXPECT_EQ(material.m_properties["general"].find("MyFloat"), material.m_properties["general"].end());
EXPECT_EQ(material.m_properties["general"].find("MyInt"), material.m_properties["general"].end());
float myFloat = material.m_properties["oldGroup"]["MyFloat"].m_value.GetValue<float>();
EXPECT_EQ(myFloat, 1.2f);
int32_t myInt = material.m_properties["oldGroup"]["MyIntOldName"].m_value.GetValue<int32_t>();
EXPECT_EQ(myInt, 5);
EXPECT_EQ(3, material.m_materialTypeVersion);
// Then we force the material data to update to the latest material type version specification
ErrorMessageFinder warningFinder; // Note this finds errors and warnings, and we're looking for a warning.
warningFinder.AddExpectedErrorMessage("Automatic updates are available. Consider updating the .material source file");
warningFinder.AddExpectedErrorMessage("This material is based on version '3'");
warningFinder.AddExpectedErrorMessage("material type is now at version '10'");
material.ApplyVersionUpdates();
warningFinder.CheckExpectedErrorsFound();
// Now the material data should match the latest material type.
// Look for the property under the latest name in the material type, not the name used in the .material file.
EXPECT_EQ(material.m_properties["oldGroup"].find("MyFloat"), material.m_properties["oldGroup"].end());
EXPECT_EQ(material.m_properties["oldGroup"].find("MyIntOldName"), material.m_properties["oldGroup"].end());
EXPECT_NE(material.m_properties["general"].find("MyFloat"), material.m_properties["general"].end());
EXPECT_NE(material.m_properties["general"].find("MyInt"), material.m_properties["general"].end());
myFloat = material.m_properties["general"]["MyFloat"].m_value.GetValue<float>();
EXPECT_EQ(myFloat, 1.2f);
myInt = material.m_properties["general"]["MyInt"].m_value.GetValue<int32_t>();
EXPECT_EQ(myInt, 5);
EXPECT_EQ(10, material.m_materialTypeVersion);
// Calling ApplyVersionUpdates() again should not report the warning again, since the material has already been updated.
warningFinder.Reset();
material.ApplyVersionUpdates();
}
TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionPartialUpdate)
{
// This case is similar to Load_MaterialTypeVersionUpdate but we start at a later

@ -1346,7 +1346,8 @@ namespace UnitTest
{
"toVersion": 7,
"actions": [
{ "op": "rename", "from": "general.bazA", "to": "otherGroup.bazB" }
{ "op": "rename", "from": "general.bazA", "to": "otherGroup.bazB" },
{ "op": "rename", "from": "onlyOneProperty.bopA", "to": "otherGroup.bopB" } // This tests a group 'onlyOneProperty' that no longer exists in the material type
]
}
],
@ -1370,6 +1371,10 @@ namespace UnitTest
{
"name": "bazB",
"type": "Float"
},
{
"name": "bopB",
"type": "Float"
}
]
}
@ -1386,13 +1391,16 @@ namespace UnitTest
const MaterialTypeSourceData::PropertyDefinition* foo = materialType.FindProperty("general", "fooC");
const MaterialTypeSourceData::PropertyDefinition* bar = materialType.FindProperty("general", "barC");
const MaterialTypeSourceData::PropertyDefinition* baz = materialType.FindProperty("otherGroup", "bazB");
const MaterialTypeSourceData::PropertyDefinition* bop = materialType.FindProperty("otherGroup", "bopB");
EXPECT_TRUE(foo);
EXPECT_TRUE(bar);
EXPECT_TRUE(baz);
EXPECT_TRUE(bop);
EXPECT_EQ(foo->m_name, "fooC");
EXPECT_EQ(bar->m_name, "barC");
EXPECT_EQ(baz->m_name, "bazB");
EXPECT_EQ(bop->m_name, "bopB");
// Now try doing the property lookup using old versions of the name and make sure the same property can be found
@ -1401,12 +1409,15 @@ namespace UnitTest
EXPECT_EQ(bar, materialType.FindProperty("general", "barA"));
EXPECT_EQ(bar, materialType.FindProperty("general", "barB"));
EXPECT_EQ(baz, materialType.FindProperty("general", "bazA"));
EXPECT_EQ(bop, materialType.FindProperty("onlyOneProperty", "bopA"));
EXPECT_EQ(nullptr, materialType.FindProperty("general", "fooX"));
EXPECT_EQ(nullptr, materialType.FindProperty("general", "barX"));
EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazX"));
EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazB"));
EXPECT_EQ(nullptr, materialType.FindProperty("otherGroup", "bazA"));
EXPECT_EQ(nullptr, materialType.FindProperty("onlyOneProperty", "bopB"));
EXPECT_EQ(nullptr, materialType.FindProperty("otherGroup", "bopA"));
}
TEST_F(MaterialTypeSourceDataTests, FindPropertyUsingOldName_Error_UnsupportedVersionUpdate)

@ -0,0 +1,14 @@
{
"description": "",
"materialType": "Materials/Types/StandardPBR.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"baseColor": {
"textureMap": "Textures/Default/checker_uv_basecolor.png"
},
"general": {
"doubleSided": true
}
}
}

@ -24,7 +24,7 @@
},
"CompilerHints" : {
"DxcDisableOptimizations" : false
"DisableOptimizations" : false
},
"ProgramSettings":

@ -24,7 +24,7 @@
},
"CompilerHints" : {
"DxcDisableOptimizations" : false
"DisableOptimizations" : false
},
"ProgramSettings":

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b2ecc32cd3052f3cb5836c8be7bf5cba54d98f46e6a0eeac95aaef00a123411a
size 27340

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:93a7e033d9fb0fcac221647322bde03716643d789390f79078c4fcc37ecfd005
size 68327
oid sha256:513f47f6fea5105f603170a8881b7e3b1cd2c4258636d64a6399c725032b500d
size 38689

@ -86,6 +86,8 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS)
FILES_CMAKE
atomlyintegration_commonfeatures_editor_files.cmake
${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
PLATFORM_INCLUDE_FILES
${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
INCLUDE_DIRECTORIES
PRIVATE
.

@ -0,0 +1,7 @@
#
# 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
#
#

@ -0,0 +1,8 @@
#
# 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
#
#

@ -0,0 +1,7 @@
#
# 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
#
#

@ -0,0 +1,7 @@
#
# 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
#
#

@ -0,0 +1,13 @@
#
# 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
#
#
if(NOT LY_MONOLITHIC_GAME) # Do not use OpenImageIO in monolithic game
set(LY_RUNTIME_DEPENDENCIES
3rdParty::OpenImageIO
)
endif()

@ -0,0 +1,7 @@
#
# 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
#
#

@ -739,14 +739,32 @@ void ViewportInteraction::MouseWheelEvent(QWheelEvent* ev)
bool ViewportInteraction::KeyPressEvent(QKeyEvent* ev)
{
if (ev->key() == Qt::Key_Space)
switch (ev->key())
{
case Qt::Key_Space:
if (!ev->isAutoRepeat())
{
ActivateSpaceBar();
}
return true;
case Qt::Key_Up:
Nudge(ViewportInteraction::NudgeDirection::Up,
(ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow);
return true;
case Qt::Key_Down:
Nudge(ViewportInteraction::NudgeDirection::Down,
(ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow);
return true;
case Qt::Key_Left:
Nudge(ViewportInteraction::NudgeDirection::Left,
(ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow);
return true;
case Qt::Key_Right:
Nudge(ViewportInteraction::NudgeDirection::Right,
(ev->modifiers() & Qt::ShiftModifier) ? ViewportInteraction::NudgeSpeed::Fast : ViewportInteraction::NudgeSpeed::Slow);
return true;
default:
break;
}
return false;

@ -220,6 +220,7 @@ ViewportWidget::ViewportWidget(EditorWindow* parent)
InitUiRenderer();
SetupShortcuts();
installEventFilter(m_editorWindow);
// Setup a timer for the maximum refresh rate we want.
// Refresh is actually triggered by interaction events and by the IdleUpdate. This avoids the UI
@ -258,6 +259,8 @@ ViewportWidget::~ViewportWidget()
LyShinePassDataRequestBus::Handler::BusDisconnect();
AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect();
removeEventFilter(m_editorWindow);
m_uiRenderer.reset();
// Notify LyShine that this is no longer a valid UiRenderer.
@ -688,9 +691,9 @@ void ViewportWidget::wheelEvent(QWheelEvent* ev)
Refresh();
}
bool ViewportWidget::event(QEvent* ev)
bool ViewportWidget::eventFilter([[maybe_unused]] QObject* watched, QEvent* event)
{
if (ev->type() == QEvent::ShortcutOverride)
if (event->type() == QEvent::ShortcutOverride)
{
// When a shortcut is matched, Qt's event processing sends out a shortcut override event
// to allow other systems to override it. If it's not overridden, then the key events
@ -698,40 +701,48 @@ bool ViewportWidget::event(QEvent* ev)
// handler. In our case this causes a problem in preview mode for the Key_Delete event.
// So, if we are preview mode avoid treating Key_Delete as a shortcut.
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
int key = keyEvent->key();
// Override the space bar shortcut so that the key gets handled by the viewport's KeyPress/KeyRelease
// events when the viewport has the focus. The space bar is set up as a shortcut in order to give the
// viewport the focus and activate the space bar when another widget has the focus. Once the shortcut
// is pressed and focus is given to the viewport, the viewport takes over handling the space bar via
// the KeyPress/KeyRelease events
if (key == Qt::Key_Space)
// the KeyPress/KeyRelease events.
// Also ignore nudge shortcuts in edit/preview mode so that the KeyPressEvent will be sent.
switch (key)
{
case Qt::Key_Space:
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_Left:
case Qt::Key_Right:
{
ev->accept();
event->accept();
return true;
}
default:
{
break;
}
}
UiEditorMode editorMode = m_editorWindow->GetEditorMode();
if (editorMode == UiEditorMode::Preview)
{
switch (key)
{
case Qt::Key_Delete:
// Ignore nudge shortcuts in preview mode so that the KeyPressEvent will be sent
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_Left:
case Qt::Key_Right:
if (key == Qt::Key_Delete)
{
ev->accept();
event->accept();
return true;
}
break;
};
}
}
return false;
}
bool ViewportWidget::event(QEvent* ev)
{
bool result = RenderViewportWidget::event(ev);
return result;
}
@ -742,8 +753,7 @@ void ViewportWidget::keyPressEvent(QKeyEvent* event)
if (editorMode == UiEditorMode::Edit)
{
// in Edit mode just send input to ViewportInteraction
bool handled = m_viewportInteraction->KeyPressEvent(event);
if (!handled)
if (!m_viewportInteraction->KeyPressEvent(event))
{
RenderViewportWidget::keyPressEvent(event);
}
@ -1246,115 +1256,6 @@ void ViewportWidget::SetupShortcuts()
{
// Actions with shortcuts are created instead of direct shortcuts because the shortcut dispatcher only looks for matching actions
// Create nudge shortcuts that are active across the entire UI Editor window. Any widgets (such as the spin box widget) that
// handle the same keys and want the shortcut to be ignored need to handle that with a shortcut override event.
// In preview mode, the nudge shortcuts are ignored via the shortcut override event. KeyPressEvents are sent instead,
// and passed along to the canvas
// Nudge up
{
QAction* action = new QAction("Up", this);
action->setShortcut(QKeySequence(Qt::Key_Up));
QObject::connect(action,
&QAction::triggered,
[this]()
{
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Up, ViewportInteraction::NudgeSpeed::Slow);
});
addAction(action);
}
// Nudge up fast
{
QAction* action = new QAction("Up Fast", this);
action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Up));
QObject::connect(action,
&QAction::triggered,
[this]()
{
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Up, ViewportInteraction::NudgeSpeed::Fast);
});
addAction(action);
}
// Nudge down
{
QAction* action = new QAction("Down", this);
action->setShortcut(QKeySequence(Qt::Key_Down));
QObject::connect(action,
&QAction::triggered,
[this]()
{
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Down, ViewportInteraction::NudgeSpeed::Slow);
});
addAction(action);
}
// Nudge down fast
{
QAction* action = new QAction("Down Fast", this);
action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Down));
QObject::connect(action,
&QAction::triggered,
[this]()
{
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Down, ViewportInteraction::NudgeSpeed::Fast);
});
addAction(action);
}
// Nudge left
{
QAction* action = new QAction("Left", this);
action->setShortcut(QKeySequence(Qt::Key_Left));
QObject::connect(action,
&QAction::triggered,
[this]()
{
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Left, ViewportInteraction::NudgeSpeed::Slow);
});
addAction(action);
}
// Nudge left fast
{
QAction* action = new QAction("Left Fast", this);
action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Left));
QObject::connect(action,
&QAction::triggered,
[this]()
{
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Left, ViewportInteraction::NudgeSpeed::Fast);
});
addAction(action);
}
// Nudge right
{
QAction* action = new QAction("Right", this);
action->setShortcut(QKeySequence(Qt::Key_Right));
QObject::connect(action,
&QAction::triggered,
[this]()
{
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Right, ViewportInteraction::NudgeSpeed::Slow);
});
addAction(action);
}
// Nudge right fast
{
QAction* action = new QAction("Right Fast", this);
action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Right));
QObject::connect(action,
&QAction::triggered,
[this]()
{
m_viewportInteraction->Nudge(ViewportInteraction::NudgeDirection::Right, ViewportInteraction::NudgeSpeed::Fast);
});
addAction(action);
}
// Give the viewport focus and activate the space bar
{
QAction* action = new QAction("Viewport Focus", this);

@ -122,12 +122,15 @@ protected:
void wheelEvent(QWheelEvent* ev) override;
//! Prevents shortcuts from interfering with preview mode.
bool eventFilter(QObject* watched, QEvent* event) override;
//! Handle events from Qt.
bool event(QEvent* ev) override;
//! Key press event from Qt
//! Key press event from Qt.
void keyPressEvent(QKeyEvent* event) override;
//! Key release event from Qt
//! Key release event from Qt.
void keyReleaseEvent(QKeyEvent* event) override;
void focusOutEvent(QFocusEvent* ev) override;

@ -17,8 +17,11 @@ namespace Multiplayer
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual void SendEditorServerInitPacket(AzNetworking::IConnection* connection) = 0;
//! Sends a packet that initializes a local server launched from the editor.
//! The editor will package the data required for loading the current editor level on the editor-server; data includes entities and asset data.
//! @param connection The connection to the editor-server
virtual void SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) = 0;
};
using MultiplayerEditorServerRequestBus = AZ::EBus<MultiplayerEditorServerRequests>;
} // namespace Multiplayer

@ -5,14 +5,14 @@
<Include File="Multiplayer/MultiplayerTypes.h" />
<Include File="Multiplayer/NetworkTime/INetworkTime.h" />
<Packet Name="EditorServerReadyForInit" Desc="A packet the local server will send on startup once it's ready for the EditorServerInit packet."/>
<Packet Name="EditorServerReadyForLevelData" Desc="A packet the editor-server will send on startup once it's ready to receive all the current level data from the Editor."/>
<Packet Name="EditorServerInit" Desc="A packet that initializes a local server launched from the editor">
<Packet Name="EditorServerLevelData" Desc="A packet that initializes the editor-server with level data from the editor. The packet includes data required for loading the current level on the server (entities and asset data).">
<Member Type="bool" Name="lastUpdate" Init="false"/>
<!--16379 is 16384 (max TCP packet size) - 1 byte (bool lastUpdate) - 4 bytes (serialization overhead for ByteBuffer) -->
<Member Type="AzNetworking::ByteBuffer&lt;16379&gt;" Name="assetData"/>
</Packet>
<Packet Name="EditorServerReady" Desc="A response packet the local server should send when ready for traffic"/>
<Packet Name="EditorServerReady" Desc="A response packet the editor-server should send after getting the editor's level data when it's ready to begin the actual game-mode network simulation."/>
</PacketGroup>

@ -37,10 +37,11 @@ namespace Multiplayer
{
uint16_t editorsv_port = DefaultServerEditorPort;
const auto console = AZ::Interface<AZ::IConsole>::Get();
AZ_Warning(
"MultiplayerEditorConnection", console->GetCvarValue("editorsv_port", editorsv_port) == AZ::GetValueResult::Success,
"MultiplayerEditorConnection failed! Could not find the editorsv_port cvar; we may not be able to connect to the editor's port!")
if (console->GetCvarValue("editorsv_port", editorsv_port) != AZ::GetValueResult::Success)
{
AZ_Assert( false,
"MultiplayerEditorConnection failed! Could not find the editorsv_port cvar; we may not be able to connect to the editor's port! Please update this code to use a valid cvar!")
}
AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.")
@ -54,7 +55,7 @@ namespace Multiplayer
}
else
{
m_networkEditorInterface->SendReliablePacket(editorServerToEditorConnectionId, MultiplayerEditorPackets::EditorServerReadyForInit());
m_networkEditorInterface->SendReliablePacket(editorServerToEditorConnectionId, MultiplayerEditorPackets::EditorServerReadyForLevelData());
}
}
}
@ -63,7 +64,7 @@ namespace Multiplayer
(
[[maybe_unused]] AzNetworking::IConnection* connection,
[[maybe_unused]] const IPacketHeader& packetHeader,
[[maybe_unused]] MultiplayerEditorPackets::EditorServerInit& packet
[[maybe_unused]] MultiplayerEditorPackets::EditorServerLevelData& packet
)
{
// Editor Server Init is intended for non-release targets
@ -90,7 +91,7 @@ namespace Multiplayer
AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream<AZ::Data::AssetData>(m_byteStream, nullptr);
if (!assetDatum)
{
AZLOG_ERROR("EditorServerInit packet contains no asset data. Asset: %s", assetHint.c_str());
AZLOG_ERROR("EditorServerLevelData packet contains no asset data. Asset: %s", assetHint.c_str())
return false;
}
assetSize = m_byteStream.GetCurPos() - assetSize;
@ -127,8 +128,12 @@ namespace Multiplayer
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpNetworkInterfaceName));
uint16_t sv_port = DefaultServerPort;
AZ_Warning("MultiplayerEditorConnection", console->GetCvarValue("sv_port", sv_port) == AZ::GetValueResult::Success,
"MultiplayerEditorConnection::HandleRequest for EditorServerInit failed! Could not find the sv_port cvar; we won't be able to listen on the correct port for incoming network messages!")
if (console->GetCvarValue("sv_port", sv_port) != AZ::GetValueResult::Success)
{
AZ_Assert(false,
"MultiplayerEditorConnection::HandleRequest for EditorServerLevelData failed! Could not find the sv_port cvar; we won't be able to listen on the correct port for incoming network messages! Please update this code to use a valid cvar!")
}
networkInterface->Listen(sv_port);
AZLOG_INFO("Editor Server completed asset receive, responding to Editor...");
@ -141,9 +146,9 @@ namespace Multiplayer
bool MultiplayerEditorConnection::HandleRequest(
[[maybe_unused]] AzNetworking::IConnection* connection,
[[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader,
[[maybe_unused]] MultiplayerEditorPackets::EditorServerReadyForInit& packet)
[[maybe_unused]] MultiplayerEditorPackets::EditorServerReadyForLevelData& packet)
{
MultiplayerEditorServerRequestBus::Broadcast(&MultiplayerEditorServerRequestBus::Events::SendEditorServerInitPacket, connection);
MultiplayerEditorServerRequestBus::Broadcast(&MultiplayerEditorServerRequestBus::Events::SendEditorServerLevelDataPacket, connection);
return true;
}
@ -160,15 +165,19 @@ namespace Multiplayer
AZ::CVarFixedString editorsv_serveraddr = AZ::CVarFixedString(LocalHost);
uint16_t sv_port = DefaultServerEditorPort;
AZ_Warning(
"MultiplayerEditorConnection", console->GetCvarValue("sv_port", sv_port) == AZ::GetValueResult::Success,
"MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the sv_port cvar; we may not be able to "
"connect to the correct port for incoming network messages!")
if (console->GetCvarValue("sv_port", sv_port) != AZ::GetValueResult::Success)
{
AZ_Assert(false,
"MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the sv_port cvar; we may not be able to "
"connect to the correct port for incoming network messages! Please update this code to use a valid cvar!")
}
AZ_Warning(
"MultiplayerEditorConnection", console->GetCvarValue("editorsv_serveraddr", editorsv_serveraddr) == AZ::GetValueResult::Success,
"MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the editorsv_serveraddr cvar; we may not be able to "
"connect to the correct port for incoming network messages!")
if (console->GetCvarValue("editorsv_serveraddr", editorsv_serveraddr) != AZ::GetValueResult::Success)
{
AZ_Assert(false,
"MultiplayerEditorConnection::HandleRequest for EditorServerReady failed! Could not find the editorsv_serveraddr cvar; we may not be able to "
"connect to the correct port for incoming network messages! Please update this code to use a valid cvar!")
}
// Connect the Editor to the editor server for Multiplayer simulation
AZ::Interface<IMultiplayer>::Get()->Connect(editorsv_serveraddr.c_str(), sv_port);
@ -195,26 +204,5 @@ namespace Multiplayer
{
return MultiplayerEditorPackets::DispatchPacket(connection, packetHeader, serializer, *this);
}
void MultiplayerEditorConnection::OnPacketLost([[maybe_unused]] IConnection* connection, [[maybe_unused]] PacketId packetId)
{
;
}
void MultiplayerEditorConnection::OnDisconnect([[maybe_unused]] AzNetworking::IConnection* connection, [[maybe_unused]] DisconnectReason reason, [[maybe_unused]] TerminationEndpoint endpoint)
{
const auto console = AZ::Interface<AZ::IConsole>::Get();
bool editorsv_launch = false;
AZ_Warning(
"MultiplayerEditorConnection", console->GetCvarValue("editorsv_launch", editorsv_launch) == AZ::GetValueResult::Success,
"MultiplayerEditorConnection::OnDisconnect failed! Could not find the editorsv_launch cvar.")
if (editorsv_isDedicated && editorsv_launch && m_networkEditorInterface->GetConnectionSet().GetConnectionCount() == 1)
{
if (m_networkEditorInterface->GetPort() != 0)
{
m_networkEditorInterface->StopListening();
}
}
}
}

@ -10,8 +10,6 @@
#include <Source/AutoGen/MultiplayerEditor.AutoPacketDispatcher.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/IO/ByteContainerStream.h>
@ -33,8 +31,8 @@ namespace Multiplayer
MultiplayerEditorConnection();
~MultiplayerEditorConnection() = default;
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReadyForInit& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReadyForLevelData& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerLevelData& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet);
//! IConnectionListener interface
@ -42,8 +40,8 @@ namespace Multiplayer
AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
void OnConnect(AzNetworking::IConnection* connection) override;
AzNetworking::PacketDispatchResult OnPacketReceived(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
void OnPacketLost(AzNetworking::IConnection* connection, AzNetworking::PacketId packetId) override;
void OnDisconnect(AzNetworking::IConnection* connection, AzNetworking::DisconnectReason reason, AzNetworking::TerminationEndpoint endpoint) override;
void OnPacketLost([[maybe_unused]]AzNetworking::IConnection* connection, [[maybe_unused]]AzNetworking::PacketId packetId) override {}
void OnDisconnect([[maybe_unused]]AzNetworking::IConnection* connection, [[maybe_unused]]AzNetworking::DisconnectReason reason, [[maybe_unused]]AzNetworking::TerminationEndpoint endpoint) override {}
//! @}
private:

@ -273,7 +273,7 @@ namespace Multiplayer
}
// Begin listening for MPEditor packets before we launch the editor-server.
// The editor-server will send us (the editor) an "EditorServerReadyForInit" packet to let us know it's ready to receive data.
// The editor-server will send us (the editor) an "EditorServerReadyForLevelData" packet to let us know it's ready to receive data.
INetworkInterface* editorNetworkInterface =
AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.");
@ -285,7 +285,7 @@ namespace Multiplayer
else
{
// Editorsv_launch=false, so we're expecting an editor-server already exists.
// Connect to the editor-server and then send the EditorServerInit packet.
// Connect to the editor-server and then send the EditorServerLevelData packet.
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.")
@ -295,14 +295,14 @@ namespace Multiplayer
{
AZ_Warning(
"MultiplayerEditor", false,
"Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i)."
"Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i). "
"Either set editorsv_launch=true so the editor launches an editor-server for you, or launch your own editor-server by hand before entering game-mode. Remember editor-servers must use editorsv_isDedicated=true.",
remoteAddress.c_str(),
static_cast < uint16_t>(editorsv_port))
return;
}
SendEditorServerInitPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId));
SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId));
}
}
}
@ -319,7 +319,7 @@ namespace Multiplayer
AZ::Interface<IMultiplayer>::Get()->SendReadyForEntityUpdates(true);
}
void MultiplayerEditorSystemComponent::SendEditorServerInitPacket(AzNetworking::IConnection* connection)
void MultiplayerEditorSystemComponent::SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection)
{
const auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
@ -349,13 +349,13 @@ namespace Multiplayer
// Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets
AZ::Interface<INetworkSpawnableLibrary>::Get()->BuildSpawnablesList();
// Read the buffer into EditorServerInit packets until we've flushed the whole thing
// Read the buffer into EditorServerLevelData packets until we've flushed the whole thing
byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
while (byteStream.GetCurPos() < byteStream.GetLength())
{
MultiplayerEditorPackets::EditorServerInit editorServerInitPacket;
auto& outBuffer = editorServerInitPacket.ModifyAssetData();
MultiplayerEditorPackets::EditorServerLevelData editorServerLevelDataPacket;
auto& outBuffer = editorServerLevelDataPacket.ModifyAssetData();
// Size the packet's buffer appropriately
size_t readSize = outBuffer.GetCapacity();
@ -371,10 +371,10 @@ namespace Multiplayer
// If we've run out of buffer, mark that we're done
if (byteStream.GetCurPos() == byteStream.GetLength())
{
editorServerInitPacket.SetLastUpdate(true);
editorServerLevelDataPacket.SetLastUpdate(true);
}
connection->SendReliablePacket(editorServerInitPacket);
connection->SendReliablePacket(editorServerLevelDataPacket);
}
}

@ -98,7 +98,7 @@ namespace Multiplayer
//! MultiplayerEditorServerRequestBus::Handler
//! @{
void SendEditorServerInitPacket(AzNetworking::IConnection* connection) override;
void SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) override;
//! @}
IEditor* m_editor = nullptr;

@ -348,19 +348,7 @@ namespace PhysX
LmbrCentral::BoxShapeComponentRequestsBus::EventResult(boxDimensions, GetEntityId(),
&LmbrCentral::BoxShapeComponentRequests::GetBoxDimensions);
if (m_shapeType != ShapeType::Box)
{
m_shapeConfigs.clear();
m_shapeConfigs.emplace_back(AZStd::make_shared<Physics::BoxShapeConfiguration>(boxDimensions));
m_shapeType = ShapeType::Box;
}
else
{
Physics::BoxShapeConfiguration& configuration =
static_cast<Physics::BoxShapeConfiguration&>(*m_shapeConfigs.back());
configuration = Physics::BoxShapeConfiguration(boxDimensions);
}
SetShapeConfig(ShapeType::Box, Physics::BoxShapeConfiguration(boxDimensions));
m_shapeConfigs.back()->m_scale = scale;
m_geometryCache.m_boxDimensions = scale * boxDimensions;
@ -374,19 +362,7 @@ namespace PhysX
const Physics::CapsuleShapeConfiguration& capsuleShapeConfig =
Utils::ConvertFromLmbrCentralCapsuleConfig(lmbrCentralCapsuleShapeConfig);
if (m_shapeType != ShapeType::Capsule)
{
m_shapeConfigs.clear();
m_shapeConfigs.emplace_back(AZStd::make_shared<Physics::CapsuleShapeConfiguration>(capsuleShapeConfig));
m_shapeType = ShapeType::Capsule;
}
else
{
Physics::CapsuleShapeConfiguration& configuration =
static_cast<Physics::CapsuleShapeConfiguration&>(*m_shapeConfigs.back());
configuration = capsuleShapeConfig;
}
SetShapeConfig(ShapeType::Capsule, capsuleShapeConfig);
m_shapeConfigs.back()->m_scale = scale;
const float scalarScale = scale.GetMaxElement();
@ -400,19 +376,7 @@ namespace PhysX
LmbrCentral::SphereShapeComponentRequestsBus::EventResult(radius, GetEntityId(),
&LmbrCentral::SphereShapeComponentRequests::GetRadius);
if (m_shapeType != ShapeType::Sphere)
{
m_shapeConfigs.clear();
m_shapeConfigs.emplace_back(AZStd::make_shared<Physics::SphereShapeConfiguration>(radius));
m_shapeType = ShapeType::Sphere;
}
else
{
Physics::SphereShapeConfiguration& configuration =
static_cast<Physics::SphereShapeConfiguration&>(*m_shapeConfigs.back());
configuration = Physics::SphereShapeConfiguration(radius);
}
SetShapeConfig(ShapeType::Sphere, Physics::SphereShapeConfiguration(radius));
m_shapeConfigs.back()->m_scale = scale;
m_geometryCache.m_radius = scale.GetMaxElement() * radius;
@ -455,19 +419,7 @@ namespace PhysX
if (shapeConfig.has_value())
{
if (m_shapeType != ShapeType::Cylinder)
{
m_shapeConfigs.clear();
m_shapeConfigs.push_back(AZStd::make_shared<Physics::CookedMeshShapeConfiguration>(shapeConfig.value()));
m_shapeType = ShapeType::Cylinder;
}
else
{
Physics::CookedMeshShapeConfiguration& configuration =
static_cast<Physics::CookedMeshShapeConfiguration&>(*m_shapeConfigs.back());
configuration = Physics::CookedMeshShapeConfiguration(shapeConfig.value());
}
SetShapeConfig(ShapeType::Cylinder, shapeConfig.value());
CreateStaticEditorCollider();
}

@ -8,6 +8,7 @@
#pragma once
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/NonUniformScaleBus.h>
#include <AzFramework/Physics/Shape.h>
@ -90,12 +91,15 @@ namespace PhysX
void UpdateBoxConfig(const AZ::Vector3& scale);
void UpdateCapsuleConfig(const AZ::Vector3& scale);
void UpdateSphereConfig(const AZ::Vector3& scale);
void UpdateCylinderConfig(const AZ::Vector3& scale);
void UpdatePolygonPrismDecomposition();
void UpdatePolygonPrismDecomposition(const AZ::PolygonPrismPtr polygonPrismPtr);
void RefreshUiProperties();
// Helper function to set a specific shape configuration
template<typename ConfigType>
void SetShapeConfig(ShapeType shapeType, const ConfigType& shapeConfig);
void UpdateCylinderConfig(const AZ::Vector3& scale);
void RefreshUiProperties();
AZ::u32 OnSubdivisionCountChange();
AZ::Crc32 SubdivisionCountVisibility();
@ -154,4 +158,28 @@ namespace PhysX
AZ::NonUniformScaleChangedEvent::Handler m_nonUniformScaleChangedHandler; //!< Responds to changes in non-uniform scale.
AZ::Vector3 m_currentNonUniformScale = AZ::Vector3::CreateOne(); //!< Caches the current non-uniform scale.
};
template<typename ConfigType>
void EditorShapeColliderComponent::SetShapeConfig(ShapeType shapeType, const ConfigType& shapeConfig)
{
if (m_shapeType != shapeType)
{
m_shapeConfigs.clear();
m_shapeType = shapeType;
}
if (m_shapeConfigs.empty())
{
m_shapeConfigs.emplace_back(AZStd::make_shared<ConfigType>(shapeConfig));
}
else
{
AZ_Assert(m_shapeConfigs.back()->GetShapeType() == shapeConfig.GetShapeType(),
"Expected Physics shape configuration with shape type %d but found one with shape type %d.",
static_cast<int>(shapeConfig.GetShapeType()), static_cast<int>(m_shapeConfigs.back()->GetShapeType()));
ConfigType& configuration =
static_cast<ConfigType&>(*m_shapeConfigs.back());
configuration = shapeConfig;
}
}
} // namespace PhysX

@ -102,7 +102,7 @@ namespace PhysX
const float scaleFactor = (maxHeightBounds <= minHeightBounds) ? 1.0f : AZStd::numeric_limits<int16_t>::max() / halfBounds;
const float heightScale{ 1.0f / scaleFactor };
const uint8_t physxMaximumMaterialIndex = 0x7f;
[[maybe_unused]] const uint8_t physxMaximumMaterialIndex = 0x7f;
// Delete the cached heightfield object if it is there, and create a new one and save in the shape configuration
heightfieldConfig.SetCachedNativeHeightfield(nullptr);

@ -320,7 +320,7 @@ namespace PhysXEditorTests
TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNullHeight_HandledGracefully)
{
ValidateInvalidEditorShapeColliderComponentParams(0.f, 1.f);
ValidateInvalidEditorShapeColliderComponentParams(1.f, 0.f);
}
TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderWithNullRadiusAndNullHeight_HandledGracefully)
@ -338,6 +338,44 @@ namespace PhysXEditorTests
ValidateInvalidEditorShapeColliderComponentParams(0.f, -1.f);
}
TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithCylinderSwitchingFromNullHeightToValidHeight_HandledGracefully)
{
// create an editor entity with a shape collider component and a cylinder shape component
EntityPtr editorEntity = CreateInactiveEditorEntity("ShapeColliderComponentEditorEntity");
editorEntity->CreateComponent<PhysX::EditorShapeColliderComponent>();
editorEntity->CreateComponent(LmbrCentral::EditorCylinderShapeComponentTypeId);
editorEntity->Activate();
const float validRadius = 1.0f;
const float nullHeight = 0.0f;
const float validHeight = 1.0f;
LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(),
&LmbrCentral::CylinderShapeComponentRequests::SetRadius, validRadius);
{
UnitTest::ErrorHandler dimensionWarningHandler("Negative or zero cylinder dimensions are invalid");
UnitTest::ErrorHandler colliderWarningHandler("No Collider or Shape information found when creating Rigid body");
LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(),
&LmbrCentral::CylinderShapeComponentRequests::SetHeight, nullHeight);
EXPECT_EQ(dimensionWarningHandler.GetExpectedWarningCount(), 1);
EXPECT_EQ(colliderWarningHandler.GetExpectedWarningCount(), 1);
}
{
UnitTest::ErrorHandler dimensionWarningHandler("Negative or zero cylinder dimensions are invalid");
UnitTest::ErrorHandler colliderWarningHandler("No Collider or Shape information found when creating Rigid body");
LmbrCentral::CylinderShapeComponentRequestsBus::Event(editorEntity->GetId(),
&LmbrCentral::CylinderShapeComponentRequests::SetHeight, validHeight);
EXPECT_EQ(dimensionWarningHandler.GetExpectedWarningCount(), 0);
EXPECT_EQ(colliderWarningHandler.GetExpectedWarningCount(), 0);
}
}
TEST_F(PhysXEditorFixture, EditorShapeColliderComponent_ShapeColliderWithBoxAndRigidBody_CorrectRuntimeComponents)
{
// create an editor entity with a shape collider component and a box shape component

@ -46,7 +46,7 @@ namespace Terrain
WorldSizeCount,
};
WorldSize m_worldSize;
WorldSize m_worldSize = WorldSize::_1024Meters;
};

@ -33,7 +33,7 @@ ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-linux
ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-linux TARGETS googletest PACKAGE_HASH 7b7ad330f369450c316a4c4592d17fbb4c14c731c95bd8f37757203e8c2bbc1b)
ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-linux TARGETS GoogleBenchmark PACKAGE_HASH 4038878f337fc7e0274f0230f71851b385b2e0327c495fc3dd3d1c18a807928d)
ly_associate_package(PACKAGE_NAME unwind-1.2.1-linux TARGETS unwind PACKAGE_HASH 3453265fb056e25432f611a61546a25f60388e315515ad39007b5925dd054a77)
ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-linux TARGETS Qt PACKAGE_HASH 76b395897b941a173002845c7219a5f8a799e44b269ffefe8091acc048130f28)
ly_associate_package(PACKAGE_NAME qt-5.15.2-rev6-linux TARGETS Qt PACKAGE_HASH a37bd9989f1e8fe57d94b98cbf9bd5c3caaea740e2f314e5162fa77300551531)
ly_associate_package(PACKAGE_NAME libpng-1.6.37-rev1-linux TARGETS libpng PACKAGE_HASH 896451999f1de76375599aec4b34ae0573d8d34620d9ab29cc30b8739c265ba6)
ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-linux TARGETS libsamplerate PACKAGE_HASH 41643c31bc6b7d037f895f89d8d8d6369e906b92eff42b0fe05ee6a100f06261)
ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-linux TARGETS OpenSSL PACKAGE_HASH b779426d1e9c5ddf71160d5ae2e639c3b956e0fb5e9fcaf9ce97c4526024e3bc)

@ -502,7 +502,10 @@ def get_gem_json_data(gem_name: str = None, gem_path: str or pathlib.Path = None
if gem_name and not gem_path:
gem_path = get_registered(gem_name=gem_name, project_path=project_path)
return get_json_data('gem', gem_path, validation.valid_o3de_gem_json)
if pathlib.Path(gem_path).is_file():
return get_json_data_file(gem_path, 'gem', validation.valid_o3de_gem_json)
else:
return get_json_data('gem', gem_path, validation.valid_o3de_gem_json)
def get_template_json_data(template_name: str = None, template_path: str or pathlib.Path = None,

@ -13,7 +13,7 @@ import shutil
import urllib.parse
import urllib.request
import hashlib
from datetime import datetime
from o3de import manifest, utils, validation
logger = logging.getLogger()
@ -27,6 +27,7 @@ def process_add_o3de_repo(file_name: str or pathlib.Path,
return 1
cache_folder = manifest.get_o3de_cache_folder()
repo_data = {}
with file_name.open('r') as f:
try:
repo_data = json.load(f)
@ -34,83 +35,94 @@ def process_add_o3de_repo(file_name: str or pathlib.Path,
logger.error(f'{file_name} failed to load: {str(e)}')
return 1
# A repo may not contain all types of object.
manifest_download_list = []
try:
manifest_download_list.append((repo_data['engines'], 'engine.json'))
except KeyError:
pass
try:
manifest_download_list.append((repo_data['projects'], 'project.json'))
except KeyError:
pass
try:
manifest_download_list.append((repo_data['gems'], 'gem.json'))
except KeyError:
pass
try:
manifest_download_list.append((repo_data['templates'], 'template.json'))
except KeyError:
pass
with file_name.open('w') as f:
try:
manifest_download_list.append((repo_data['restricted'], 'restricted.json'))
except KeyError:
pass
time_now = datetime.now()
# Convert to lower case because AM/PM is capitalized
time_str = time_now.strftime('%d/%m/%Y %I:%M%p').lower()
repo_data.update({'last_updated': time_str})
f.write(json.dumps(repo_data, indent=4) + '\n')
except Exception as e:
logger.error(f'{file_name} failed to save: {str(e)}')
return 1
# A repo may not contain all types of object.
manifest_download_list = []
try:
manifest_download_list.append((repo_data['engines'], 'engine.json'))
except KeyError:
pass
try:
manifest_download_list.append((repo_data['projects'], 'project.json'))
except KeyError:
pass
try:
manifest_download_list.append((repo_data['gems'], 'gem.json'))
except KeyError:
pass
try:
manifest_download_list.append((repo_data['templates'], 'template.json'))
except KeyError:
pass
try:
manifest_download_list.append((repo_data['restricted'], 'restricted.json'))
except KeyError:
pass
for o3de_object_uris, manifest_json in manifest_download_list:
for o3de_object_uris, manifest_json in manifest_download_list:
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_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json')
if not cache_file.is_file():
parsed_uri = urllib.parse.urlparse(manifest_json_uri)
download_file_result = utils.download_file(parsed_uri, cache_file)
if download_file_result != 0:
return download_file_result
# Having a repo is also optional
repo_list = []
try:
repo_list.append(repo_data['repos'])
except KeyError:
pass
for repo in repo_list:
if repo not in repo_set:
repo_set.add(repo)
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())
parsed_uri = urllib.parse.urlparse(f'{repo}/repo.json')
manifest_json_sha256 = hashlib.sha256(parsed_uri.geturl().encode())
cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json')
if not cache_file.is_file():
parsed_uri = urllib.parse.urlparse(manifest_json_uri)
download_file_result = utils.download_file(parsed_uri, cache_file)
if download_file_result != 0:
return download_file_result
# Having a repo is also optional
repo_list = []
try:
repo_list.append(repo_data['repos'])
except KeyError:
pass
if cache_file.is_file():
cache_file.unlink()
download_file_result = utils.download_file(parsed_uri, cache_file)
if download_file_result != 0:
return download_file_result
for repo in repo_list:
if repo not in repo_set:
repo_set.add(repo)
for o3de_object_uri in o3de_object_uris:
parsed_uri = urllib.parse.urlparse(f'{repo}/repo.json')
manifest_json_sha256 = hashlib.sha256(parsed_uri.geturl().encode())
cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json')
if cache_file.is_file():
cache_file.unlink()
download_file_result = utils.download_file(parsed_uri, cache_file)
if download_file_result != 0:
return download_file_result
return process_add_o3de_repo(parsed_uri.geturl(), repo_set)
return process_add_o3de_repo(parsed_uri.geturl(), repo_set)
return 0
def get_gem_json_paths_from_cached_repo(repo_uri: str) -> list:
def get_gem_json_paths_from_cached_repo(repo_uri: str) -> set:
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 = []
gem_set = set()
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
return gem_set
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
return gem_set
# Get list of gems, then add all json paths to the list if they exist in the cache
repo_gems = []
@ -125,10 +137,44 @@ def get_gem_json_paths_from_cached_repo(repo_uri: str) -> list:
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)
gem_set.add(cache_gem_json_filepath)
else:
logger.warn(f'Could not find cached gem json file {cache_gem_json_filepath} for {o3de_object_uri} in repo {repo_uri}')
return gem_set
def get_gem_json_paths_from_all_cached_repos() -> set:
json_data = manifest.load_o3de_manifest()
gem_set = set()
for repo_uri in json_data['repos']:
gem_set.update(get_gem_json_paths_from_cached_repo(repo_uri))
return gem_set
def refresh_repo(repo_uri: str,
cache_folder: str = None,
repo_set: set = None) -> int:
if not cache_folder:
cache_folder = manifest.get_o3de_cache_folder()
if not repo_set:
repo_set = set()
parsed_uri = urllib.parse.urlparse(f'{repo_uri}/repo.json')
repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode())
cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json')
download_file_result = utils.download_file(parsed_uri, cache_file)
if download_file_result != 0:
return download_file_result
if not validation.valid_o3de_repo_json(cache_file):
logger.error(f'Repo json {repo_uri} is not valid.')
cache_file.unlink()
return 1
return gem_list
return process_add_o3de_repo(cache_file, repo_set)
def refresh_repos() -> int:
json_data = manifest.load_o3de_manifest()
@ -147,22 +193,9 @@ def refresh_repos() -> int:
if repo_uri not in repo_set:
repo_set.add(repo_uri)
parsed_uri = urllib.parse.urlparse(f'{repo_uri}/repo.json')
repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode())
cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json')
if not cache_file.is_file():
download_file_result = utils.download_file(parsed_uri, cache_file)
if download_file_result != 0:
return download_file_result
if not validation.valid_o3de_repo_json(cache_file):
logger.error(f'Repo json {repo_uri} is not valid.')
cache_file.unlink()
return 1
last_failure = process_add_o3de_repo(cache_file, repo_set)
if last_failure:
result = last_failure
last_failure = refresh_repo(repo_uri, cache_folder, repo_set)
if last_failure:
result = last_failure
return result

Loading…
Cancel
Save