diff --git a/.clang-format b/.clang-format index 2a9205219a..565f28130e 100644 --- a/.clang-format +++ b/.clang-format @@ -7,28 +7,33 @@ AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: None AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: true BreakBeforeBraces: Custom BraceWrapping: AfterClass: true + AfterControlStatement: true AfterEnum: true AfterFunction: true AfterNamespace: true + BeforeLambdaBody: true AfterStruct: true - SplitEmptyFunction: true - AfterControlStatement: true BeforeElse: true + SplitEmptyFunction: true BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma ColumnLimit: 140 ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 -Cpp11BracedListStyle: true +Cpp11BracedListStyle: false FixNamespaceComments: true IncludeBlocks: Preserve +IndentCaseBlocks: true IndentCaseLabels: false IndentPPDirectives: None IndentWidth: 4 @@ -38,27 +43,17 @@ NamespaceIndentation: All PenaltyReturnTypeOnItsOwnLine: 1000 PointerAlignment: Left SortIncludes: true +SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false +Standard: c++17 UseTab: Never - -# Not available in clang-format version 6.0.0 -# BasedOnStyle: Microsoft -# Standard: c++17 -# AllowAllArgumentsOnNextLine: true -# AllowShortLambdasOnASingleLine: None -# BreakInheritanceList: BeforeComma -# SpaceAfterLogicalNot: false -# SpaceBeforeCpp11BracedList: false -# SpaceBeforeCtorInitializerColon: true -# SpaceBeforeInheritanceColon: true -# SpaceBeforeRangeBasedForLoopColon: true - -# Not available in clang-format version 10.0.0 -# BeforeLambdaBody: true (BraceWrapping) -# IndentCaseBlocks: true diff --git a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake index 15715f2136..33c2bf8d5f 100644 --- a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake +++ b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake @@ -43,7 +43,6 @@ set(GEM_DEPENDENCIES Gem::GradientSignal Gem::Vegetation Gem::Atom_AtomBridge - Gem::AtomFont Gem::NvCloth Gem::Blast Gem::AWSCore diff --git a/AutomatedTesting/Gem/Code/tool_dependencies.cmake b/AutomatedTesting/Gem/Code/tool_dependencies.cmake index 1c0db5753b..c8eccab947 100644 --- a/AutomatedTesting/Gem/Code/tool_dependencies.cmake +++ b/AutomatedTesting/Gem/Code/tool_dependencies.cmake @@ -55,7 +55,6 @@ set(GEM_DEPENDENCIES Gem::Atom_RHI.Private Gem::Atom_Feature_Common.Editor Gem::Atom_AtomBridge.Editor - Gem::AtomFont Gem::NvCloth.Editor Gem::Blast.Editor Gem::AWSCore.Editor diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp index 555eedf034..a72fe4e013 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp @@ -133,7 +133,15 @@ namespace AZ if (!id.m_guid.IsNull()) { *instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), instance->GetAutoLoadBehavior()); - + if (!instance->GetId().IsValid()) + { + // If the asset failed to be created, FindOrCreateAsset returns an asset instance with a null + // id. To preserve the asset id in the source json, reset the asset to an empty one, but with + // the right id. + const auto loadBehavior = instance->GetAutoLoadBehavior(); + *instance = Asset(id, instance->GetType()); + instance->SetAutoLoadBehavior(loadBehavior); + } result.Combine(context.Report(result, "Successfully created Asset with id.")); } diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp index 2421c75be3..dbd8df4df1 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp @@ -880,6 +880,13 @@ namespace AZ const Specializations& specializations, const rapidjson::Pointer& historyPointer, AZStd::string_view folderPath) { using namespace rapidjson; + + if (&lhs == &rhs) + { + // Early return to avoid setting the collisionFound reference to true + // std::sort is allowed to pass in the same memory address for the left and right elements + return false; + } AZ_Assert(!lhs.m_tags.empty(), "Comparing a settings file without at least a name tag."); AZ_Assert(!rhs.m_tags.empty(), "Comparing a settings file without at least a name tag."); diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.cpp index 01bff3ccbb..4aea643c6c 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.cpp @@ -30,6 +30,16 @@ namespace AzPhysics classElement.AddElementWithData(context, "name", name); return true; } + + bool SimulatedBodyVersionConverter([[maybe_unused]] AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) + { + if (classElement.GetVersion() <= 1) + { + classElement.RemoveElementByName(AZ_CRC_CE("scale")); + } + + return true; + } } AZ_CLASS_ALLOCATOR_IMPL(SimulatedBodyConfiguration, AZ::SystemAllocator, 0); @@ -40,11 +50,10 @@ namespace AzPhysics { serializeContext->ClassDeprecate("WorldBodyConfiguration", "{6EEB377C-DC60-4E10-AF12-9626C0763B2D}", &Internal::DeprecateWorldBodyConfiguration); serializeContext->Class() - ->Version(1) + ->Version(2, &Internal::SimulatedBodyVersionConverter) ->Field("name", &SimulatedBodyConfiguration::m_debugName) ->Field("position", &SimulatedBodyConfiguration::m_position) ->Field("orientation", &SimulatedBodyConfiguration::m_orientation) - ->Field("scale", &SimulatedBodyConfiguration::m_scale) ->Field("entityId", &SimulatedBodyConfiguration::m_entityId) ->Field("startSimulationEnabled", &SimulatedBodyConfiguration::m_startSimulationEnabled) ; diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h index 6862bfccb8..5ac920ab9d 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h @@ -38,7 +38,6 @@ namespace AzPhysics // Basic initial settings. AZ::Vector3 m_position = AZ::Vector3::CreateZero(); AZ::Quaternion m_orientation = AZ::Quaternion::CreateIdentity(); - AZ::Vector3 m_scale = AZ::Vector3::CreateOne(); bool m_startSimulationEnabled = true; // Entity/object association. diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index bd826544a1..e4833ccb3c 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -18,7 +18,6 @@ #include #include #include -#include namespace AzFramework { @@ -160,24 +159,27 @@ namespace AzFramework bool CameraSystem::HandleEvents(const InputEvent& event) { - if (const auto& cursor = AZStd::get_if(&event)) + if (const auto& horizonalMotion = AZStd::get_if(&event)) { - m_cursorState.SetCurrentPosition(cursor->m_position); + m_motionDelta.m_x = horizonalMotion->m_delta; + } + else if (const auto& verticalMotion = AZStd::get_if(&event)) + { + m_motionDelta.m_y = verticalMotion->m_delta; } else if (const auto& scroll = AZStd::get_if(&event)) { m_scrollDelta = scroll->m_delta; } - return m_cameras.HandleEvents(event, m_cursorState.CursorDelta(), m_scrollDelta); + return m_cameras.HandleEvents(event, m_motionDelta, m_scrollDelta); } Camera CameraSystem::StepCamera(const Camera& targetCamera, const float deltaTime) { - const auto nextCamera = m_cameras.StepCamera(targetCamera, m_cursorState.CursorDelta(), m_scrollDelta, deltaTime); - - m_cursorState.Update(); + const auto nextCamera = m_cameras.StepCamera(targetCamera, m_motionDelta, m_scrollDelta, deltaTime); + m_motionDelta = ScreenVector{0, 0}; m_scrollDelta = 0.0f; return nextCamera; @@ -720,7 +722,7 @@ namespace AzFramework return camera; } - InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize) + InputEvent BuildInputEvent(const InputChannel& inputChannel) { const auto& inputChannelId = inputChannel.GetInputChannelId(); const auto& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId(); @@ -730,13 +732,13 @@ namespace AzFramework return button == inputChannelId; }); - if (inputChannelId == InputDeviceMouse::Movement::X || inputChannelId == InputDeviceMouse::Movement::Y) + if (inputChannelId == InputDeviceMouse::Movement::X) { - const auto* position = inputChannel.GetCustomData(); - AZ_Assert(position, "Expected PositionData2D but found nullptr"); - - return CursorEvent{ScreenPoint( - position->m_normalizedPosition.GetX() * windowSize.m_width, position->m_normalizedPosition.GetY() * windowSize.m_height)}; + return HorizontalMotionEvent{(int)inputChannel.GetValue()}; + } + else if (inputChannelId == InputDeviceMouse::Movement::Y) + { + return VerticalMotionEvent{(int)inputChannel.GetValue()}; } else if (inputChannelId == InputDeviceMouse::Movement::Z) { diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index 582fb5a6de..ec70fc00de 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -18,7 +18,6 @@ #include #include #include -#include #include #include @@ -72,11 +71,16 @@ namespace AzFramework void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform); - struct CursorEvent + //! Generic motion type + template + struct MotionEvent { - ScreenPoint m_position; + int m_delta; }; + using HorizontalMotionEvent = MotionEvent; + using VerticalMotionEvent = MotionEvent; + struct ScrollEvent { float m_delta; @@ -88,7 +92,7 @@ namespace AzFramework InputChannel::State m_state; //!< Channel state. (e.g. Begin/update/end event). }; - using InputEvent = AZStd::variant; + using InputEvent = AZStd::variant; class CameraInput { @@ -194,6 +198,7 @@ namespace AzFramework m_activeCameraInputs.begin(), m_activeCameraInputs.end(), [](const auto& cameraInput) { return cameraInput->Exclusive(); }); } + //! Responsible for updating a series of cameras given various inputs. class CameraSystem { public: @@ -203,8 +208,8 @@ namespace AzFramework Cameras m_cameras; private: - CursorState m_cursorState; - float m_scrollDelta = 0.0f; + ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional. + float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional. }; class RotateCameraInput : public CameraInput @@ -419,8 +424,6 @@ namespace AzFramework return true; } - struct WindowSize; - //! Map from a generic InputChannel event to a camera specific InputEvent. - InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize); + InputEvent BuildInputEvent(const InputChannel& inputChannel); } // namespace AzFramework diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworking.h b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworking.h index 72ffce4202..fb6d217b80 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworking.h +++ b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworking.h @@ -19,6 +19,8 @@ namespace AzNetworking { + using NetworkInterfaces = AZStd::unordered_map>; + //! @class INetworking //! @brief The interface for creating and working with network interfaces. class INetworking @@ -60,5 +62,25 @@ namespace AzNetworking //! @param name The name of the Compressor factory to unregister, must match result of factory->GetFactoryName() //! @return Whether the factory was found and unregistered virtual bool UnregisterCompressorFactory(AZ::Name name) = 0; + + //! Returns the raw network interfaces owned by the networking instance. + //! @return the raw network interfaces owned by the networking instance + virtual const NetworkInterfaces& GetNetworkInterfaces() const = 0; + + //! Returns the number of sockets monitored by our TcpListenThread. + //! @return the number of sockets monitored by our TcpListenThread + virtual uint32_t GetTcpListenThreadSocketCount() const = 0; + + //! Returns the total time spent updating our TcpListenThread. + //! @return the total time spent updating our TcpListenThread + virtual AZ::TimeMs GetTcpListenThreadUpdateTime() const = 0; + + //! Returns the number of sockets monitored by our UdpReaderThread. + //! @return the number of sockets monitored by our UdpReaderThread + virtual uint32_t GetUdpReaderThreadSocketCount() const = 0; + + //! Returns the total time spent updating our UdpReaderThread. + //! @return the total time spent updating our UdpReaderThread + virtual AZ::TimeMs GetUdpReaderThreadUpdateTime() const = 0; }; } diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.cpp b/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.cpp index c275ad9057..1a1476dcc8 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.cpp @@ -149,12 +149,37 @@ namespace AzNetworking return m_compressorFactories.erase(name) > 0; } + const NetworkInterfaces& NetworkingSystemComponent::GetNetworkInterfaces() const + { + return m_networkInterfaces; + } + + uint32_t NetworkingSystemComponent::GetTcpListenThreadSocketCount() const + { + return m_listenThread->GetSocketCount(); + } + + AZ::TimeMs NetworkingSystemComponent::GetTcpListenThreadUpdateTime() const + { + return m_listenThread->GetUpdateTimeMs(); + } + + uint32_t NetworkingSystemComponent::GetUdpReaderThreadSocketCount() const + { + return m_readerThread->GetSocketCount(); + } + + AZ::TimeMs NetworkingSystemComponent::GetUdpReaderThreadUpdateTime() const + { + return m_readerThread->GetUpdateTimeMs(); + } + void NetworkingSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { - AZLOG_INFO("Total sockets monitored by TcpListenThread: %u", m_listenThread->GetSocketCount()); - AZLOG_INFO("Total time spent updating TcpListenThread: %lld", aznumeric_cast(m_listenThread->GetUpdateTimeMs())); - AZLOG_INFO("Total sockets monitored by UdpReaderThread: %u", m_readerThread->GetSocketCount()); - AZLOG_INFO("Total time spent updating UdpReaderThread: %lld", aznumeric_cast(m_readerThread->GetUpdateTimeMs())); + AZLOG_INFO("Total sockets monitored by TcpListenThread: %u", GetTcpListenThreadSocketCount()); + AZLOG_INFO("Total time spent updating TcpListenThread: %lld", aznumeric_cast(GetTcpListenThreadUpdateTime())); + AZLOG_INFO("Total sockets monitored by UdpReaderThread: %u", GetUdpReaderThreadSocketCount()); + AZLOG_INFO("Total time spent updating UdpReaderThread: %lld", aznumeric_cast(GetUdpReaderThreadUpdateTime())); for (auto& networkInterface : m_networkInterfaces) { diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.h b/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.h index b0b4d83d54..2fdc773fb0 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.h +++ b/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.h @@ -63,6 +63,11 @@ namespace AzNetworking void RegisterCompressorFactory(ICompressorFactory* factory) override; AZStd::unique_ptr CreateCompressor(AZ::Name name) override; bool UnregisterCompressorFactory(AZ::Name name) override; + const NetworkInterfaces& GetNetworkInterfaces() const override; + uint32_t GetTcpListenThreadSocketCount() const override; + AZ::TimeMs GetTcpListenThreadUpdateTime() const override; + uint32_t GetUdpReaderThreadSocketCount() const override; + AZ::TimeMs GetUdpReaderThreadUpdateTime() const override; //! @} //! Console commands. @@ -74,7 +79,6 @@ namespace AzNetworking AZ_CONSOLEFUNC(NetworkingSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for all instantiated network interfaces"); - using NetworkInterfaces = AZStd::unordered_map>; NetworkInterfaces m_networkInterfaces; AZStd::unique_ptr m_listenThread; AZStd::unique_ptr m_readerThread; diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocketManager_Select.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocketManager_Select.cpp index 4070f74d67..e8b2527638 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocketManager_Select.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocketManager_Select.cpp @@ -46,6 +46,12 @@ namespace AzNetworking void TcpSocketManager::ProcessEvents(AZ::TimeMs maxBlockMs, const SocketEventCallback& readCallback, const SocketEventCallback& writeCallback) { + if(static_cast(m_maxFd) <= 0 && m_socketFds.empty()) + { + // There are no available sockets to process + return; + } + m_readerFdSet = m_sourceFdSet; m_writerFdSet = m_sourceFdSet; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index bc7afbf085..3edc190fb7 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -93,28 +93,12 @@ namespace AzToolsFramework EditorContextMenuBus::Handler::BusConnect(); PrefabInstanceContainerNotificationBus::Handler::BusConnect(); AZ::Interface::Register(this); - - bool prefabWipFeaturesEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - - if (prefabWipFeaturesEnabled) - { - AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension); - } + AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension); } PrefabIntegrationManager::~PrefabIntegrationManager() { - bool prefabWipFeaturesEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - - if (prefabWipFeaturesEnabled) - { - AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect(); - } - + AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect(); AZ::Interface::Unregister(this); PrefabInstanceContainerNotificationBus::Handler::BusDisconnect(); EditorContextMenuBus::Handler::BusDisconnect(); @@ -137,66 +121,63 @@ namespace AzToolsFramework void PrefabIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu) const { - bool prefabWipFeaturesEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - AzToolsFramework::EntityIdList selectedEntities; AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult( selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities); - if (prefabWipFeaturesEnabled) + bool prefabWipFeaturesEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult( + prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); + + // Create Prefab { - // Create Prefab + if (!selectedEntities.empty()) { - if (!selectedEntities.empty()) + // Hide if the only selected entity is the Level Container + if (selectedEntities.size() > 1 || !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntities[0])) { - // Hide if the only selected entity is the Level Container - if (selectedEntities.size() > 1 || !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntities[0])) - { - bool layerInSelection = false; + bool layerInSelection = false; - for (AZ::EntityId entityId : selectedEntities) + for (AZ::EntityId entityId : selectedEntities) + { + if (!layerInSelection) { - if (!layerInSelection) - { - AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult( - layerInSelection, entityId, - &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer); + AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult( + layerInSelection, entityId, + &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer); - if (layerInSelection) - { - break; - } + if (layerInSelection) + { + break; } } + } - // Layers can't be in prefabs. - if (!layerInSelection) - { - QAction* createAction = menu->addAction(QObject::tr("Create Prefab...")); - createAction->setToolTip(QObject::tr("Creates a prefab out of the currently selected entities.")); + // Layers can't be in prefabs. + if (!layerInSelection) + { + QAction* createAction = menu->addAction(QObject::tr("Create Prefab...")); + createAction->setToolTip(QObject::tr("Creates a prefab out of the currently selected entities.")); - QObject::connect(createAction, &QAction::triggered, createAction, [this, selectedEntities] { - ContextMenu_CreatePrefab(selectedEntities); - }); - } + QObject::connect(createAction, &QAction::triggered, createAction, [this, selectedEntities] { + ContextMenu_CreatePrefab(selectedEntities); + }); } } } + } - // Instantiate Prefab - { - QAction* instantiateAction = menu->addAction(QObject::tr("Instantiate Prefab...")); - instantiateAction->setToolTip(QObject::tr("Instantiates a prefab file in the scene.")); - - QObject::connect( - instantiateAction, &QAction::triggered, instantiateAction, [this] { ContextMenu_InstantiatePrefab(); }); - } + // Instantiate Prefab + { + QAction* instantiateAction = menu->addAction(QObject::tr("Instantiate Prefab...")); + instantiateAction->setToolTip(QObject::tr("Instantiates a prefab file in the scene.")); - menu->addSeparator(); + QObject::connect( + instantiateAction, &QAction::triggered, instantiateAction, [this] { ContextMenu_InstantiatePrefab(); }); } + menu->addSeparator(); + bool itemWasShown = false; // Edit/Save Prefab diff --git a/Code/Framework/Tests/CameraInputTests.cpp b/Code/Framework/Tests/CameraInputTests.cpp index 6fe9837c22..3fc826975e 100644 --- a/Code/Framework/Tests/CameraInputTests.cpp +++ b/Code/Framework/Tests/CameraInputTests.cpp @@ -15,7 +15,6 @@ #include #include #include -#include namespace UnitTest { @@ -68,23 +67,21 @@ namespace UnitTest TEST_F(CameraInputFixture, BeginEndOrbitCameraConsumesCorrectEvents) { - // set initial mouse position - const bool consumed1 = HandleEventAndUpdate(AzFramework::CursorEvent{AzFramework::ScreenPoint(5, 5)}); // begin orbit camera - const bool consumed2 = HandleEventAndUpdate( + const bool consumed1 = HandleEventAndUpdate( AzFramework::DiscreteInputEvent{AzFramework::InputDeviceKeyboard::Key::ModifierAltL, AzFramework::InputChannel::State::Began}); // begin listening for orbit rotate (click detector) - event is not consumed - const bool consumed3 = HandleEventAndUpdate( + const bool consumed2 = HandleEventAndUpdate( AzFramework::DiscreteInputEvent{AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began}); // begin orbit rotate (mouse has moved sufficient distance to initiate) - const bool consumed4 = HandleEventAndUpdate(AzFramework::CursorEvent{AzFramework::ScreenPoint(10, 10)}); + const bool consumed3 = HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{5}); // end orbit (mouse up) - event is not consumed - const bool consumed5 = HandleEventAndUpdate( + const bool consumed4 = HandleEventAndUpdate( AzFramework::DiscreteInputEvent{AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended}); - const auto allConsumed = AZStd::vector{consumed1, consumed2, consumed3, consumed4, consumed5}; + const auto allConsumed = AZStd::vector{consumed1, consumed2, consumed3, consumed4}; using ::testing::ElementsAre; - EXPECT_THAT(allConsumed, ElementsAre(false, true, false, true, false)); + EXPECT_THAT(allConsumed, ElementsAre(true, false, true, false)); } } // namespace UnitTest diff --git a/Code/Sandbox/Editor/ModernViewportCameraController.cpp b/Code/Sandbox/Editor/ModernViewportCameraController.cpp index 83ab2ef0b5..0779542878 100644 --- a/Code/Sandbox/Editor/ModernViewportCameraController.cpp +++ b/Code/Sandbox/Editor/ModernViewportCameraController.cpp @@ -109,13 +109,9 @@ namespace SandboxEditor bool ModernViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event) { - AzFramework::WindowSize windowSize; - AzFramework::WindowRequestBus::EventResult( - windowSize, event.m_windowHandle, &AzFramework::WindowRequestBus::Events::GetClientAreaSize); - if (ShouldHandle(event.m_priority, m_cameraSystem.m_cameras.Exclusive())) { - return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel, windowSize)); + return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel)); } return false; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 2c2c143845..e4642c95e0 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -508,6 +508,42 @@ namespace O3DE::ProjectManager } } + bool PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath) + { + bool result = ExecuteWithLock([&] { + pybind11::str pyGemPath = gemPath.toStdString(); + pybind11::str pyProjectPath = projectPath.toStdString(); + + m_registration.attr("add_gem_to_project")( + pybind11::none(), // gem_name + pyGemPath, + pybind11::none(), // gem_target + pybind11::none(), // project_name + pyProjectPath + ); + }); + + return result; + } + + bool PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath) + { + bool result = ExecuteWithLock([&] { + pybind11::str pyGemPath = gemPath.toStdString(); + pybind11::str pyProjectPath = projectPath.toStdString(); + + m_registration.attr("remove_gem_to_project")( + pybind11::none(), // gem_name + pyGemPath, + pybind11::none(), // gem_target + pybind11::none(), // project_name + pyProjectPath + ); + }); + + return result; + } + bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo) { return false; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index ffabf99b49..892e13a65b 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -47,6 +47,8 @@ namespace O3DE::ProjectManager AZ::Outcome GetProject(const QString& path) override; AZ::Outcome> GetProjects() override; bool UpdateProject(const ProjectInfo& projectInfo) override; + bool AddGemToProject(const QString& gemPath, const QString& projectPath) override; + bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override; // ProjectTemplate AZ::Outcome> GetProjectTemplates() override; diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 2377da1461..b5c8f1a76a 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -96,6 +96,22 @@ namespace O3DE::ProjectManager */ virtual bool UpdateProject(const ProjectInfo& projectInfo) = 0; + /** + * Add a gem to a project + * @param gemPath the absolute path to the gem + * @param projectPath the absolute path to the project + * @return true on success, false on failure + */ + virtual bool AddGemToProject(const QString& gemPath, const QString& projectPath) = 0; + + /** + * Remove gem to a project + * @param gemPath the absolute path to the gem + * @param projectPath the absolute path to the project + * @return true on success, false on failure + */ + virtual bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0; + // Project Templates diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp index 2ce9bc14f4..0b366d96ea 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp @@ -75,13 +75,13 @@ namespace AZ const bool allMeshesHaveTangentsAndBitangents = AZStd::all_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents); if (!allMeshesHaveTangentsAndBitangents) { - const char* mixedBitangentsError = - "Node with name %s has meshes with and without bitangents. " - "Placeholder incorrect bitangents will be generated to allow the data to process, " - "but the source art needs to be fixed to correct this. Either apply bitangents to all meshes on this node, " - "or remove all bitangents from all meshes on this node."; AZ_Error( - Utilities::ErrorWindow, false, mixedBitangentsError, currentNode->mName.C_Str()); + Utilities::ErrorWindow, false, + "Node with name %s has meshes with and without bitangents. " + "Placeholder incorrect bitangents will be generated to allow the data to process, " + "but the source art needs to be fixed to correct this. Either apply bitangents to all meshes on this node, " + "or remove all bitangents from all meshes on this node.", + currentNode->mName.C_Str()); } const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene); diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp index 47b7e410b4..b61baa9ff6 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp @@ -77,13 +77,13 @@ namespace AZ const bool allMeshesHaveTangentsAndBitangents = AZStd::all_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents); if (!allMeshesHaveTangentsAndBitangents) { - const char* mixedTangentsError = - "Node with name %s has meshes with and without tangents. " - "Placeholder incorrect tangents will be generated to allow the data to process, " - "but the source art needs to be fixed to correct this. Either apply tangents to all meshes on this node, " - "or remove all tangents from all meshes on this node."; AZ_Error( - Utilities::ErrorWindow, false, mixedTangentsError, currentNode->mName.C_Str()); + Utilities::ErrorWindow, false, + "Node with name %s has meshes with and without tangents. " + "Placeholder incorrect tangents will be generated to allow the data to process, " + "but the source art needs to be fixed to correct this. Either apply tangents to all meshes on this node, " + "or remove all tangents from all meshes on this node.", + currentNode->mName.C_Str()); } const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene); diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp index e37a4f4285..8c1e0e7caa 100644 --- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp +++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp @@ -89,13 +89,13 @@ namespace AZ for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex) { - int meshesWithIndex = meshesPerTextureCoordinateIndex[texCoordIndex]; AZ_Error( Utilities::ErrorWindow, - meshesWithIndex == 0 || meshesWithIndex == currentNode->mNumMeshes, + meshesPerTextureCoordinateIndex[texCoordIndex] == 0 || + meshesPerTextureCoordinateIndex[texCoordIndex] == currentNode->mNumMeshes, "Texture coordinate index %d for node %s is not on all meshes on this node. " - "Placeholder arbitrary texture values will be generated to allow the data to process, but the source art " - "needs to be fixed to correct this. All meshes on this node should have the same number of texture coordinate channels.", + "Placeholder arbitrary texture values will be generated to allow the data to process, but the source art " + "needs to be fixed to correct this. All meshes on this node should have the same number of texture coordinate channels.", texCoordIndex, currentNode->mName.C_Str()); } diff --git a/Gems/AWSClientAuth/Code/Source/AWSClientAuthModule.cpp b/Gems/AWSClientAuth/Code/Source/AWSClientAuthModule.cpp index 9f027613b6..ccf71cb89e 100644 --- a/Gems/AWSClientAuth/Code/Source/AWSClientAuthModule.cpp +++ b/Gems/AWSClientAuth/Code/Source/AWSClientAuthModule.cpp @@ -39,4 +39,4 @@ namespace AWSClientAuth // DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM // The first parameter should be GemName_GemIdLower // The second should be the fully qualified name of the class above -AZ_DECLARE_MODULE_CLASS(AWSClientAuth_c74f2756f5874c0d8d29646dfc9cb0ad, AWSClientAuth::AWSClientAuthModule) +AZ_DECLARE_MODULE_CLASS(Gem_AWSClientAuth, AWSClientAuth::AWSClientAuthModule) diff --git a/Gems/AWSMetrics/Code/Source/AWSMetricsModule.cpp b/Gems/AWSMetrics/Code/Source/AWSMetricsModule.cpp index 4967202483..f180e3e248 100644 --- a/Gems/AWSMetrics/Code/Source/AWSMetricsModule.cpp +++ b/Gems/AWSMetrics/Code/Source/AWSMetricsModule.cpp @@ -32,4 +32,4 @@ namespace AWSMetrics // DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM // The first parameter should be GemName_GemIdLower // The second should be the fully qualified name of the class above -AZ_DECLARE_MODULE_CLASS(AWSMetrics_cc6fc7a18fc047039a369a26100fcbbe, AWSMetrics::AWSMetricsModule) +AZ_DECLARE_MODULE_CLASS(Gem_AWSMetrics, AWSMetrics::AWSMetricsModule) diff --git a/Gems/Atom/Asset/Shader/Code/CMakeLists.txt b/Gems/Atom/Asset/Shader/Code/CMakeLists.txt index 7d82ca5869..a06aa79d24 100644 --- a/Gems/Atom/Asset/Shader/Code/CMakeLists.txt +++ b/Gems/Atom/Asset/Shader/Code/CMakeLists.txt @@ -98,7 +98,6 @@ ly_add_target( Gem::Atom_RPI.Edit RUNTIME_DEPENDENCIES 3rdParty::DirectXShaderCompilerDxc - 3rdParty::DirectXShaderCompilerDxcAz 3rdParty::SPIRVCross 3rdParty::azslc ) diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp index fb4d370621..bc2e810f84 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp @@ -118,7 +118,7 @@ namespace AZ // Register Shader Asset Builder AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor; shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder"; - shaderAssetBuilderDescriptor.m_version = 99; // ATOM-15276 + shaderAssetBuilderDescriptor.m_version = 100; // ATOM-14298 // .shader file changes trigger rebuilds shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderAssetBuilderDescriptor.m_busId = azrtti_typeid(); @@ -133,7 +133,7 @@ namespace AZ 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 = 20; // ATOM-15276 + shaderVariantAssetBuilderDescriptor.m_version = 21; // ATOM-14298 shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid(); shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader index 7964e3c84a..a0e9708468 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.shader @@ -31,8 +31,7 @@ }, "CompilerHints" : { - "DisableOptimizations" : true, - "DxcGenerateDebugInfo" : true + "DisableOptimizations" : false }, "ProgramSettings": diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl index a5d761d815..c96828e23f 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseComposite_nomsaa.azsl @@ -91,7 +91,7 @@ float3 SampleProbeIrradiance(uint2 probeIrradianceCoords, float depth, float3 no { for (int x = -extent; x <= extent; ++x) { - float3 downsampledNormal = PassSrg::m_downsampledNormal.Load(int3(probeIrradianceCoords, 0), int2(x, y)).rgb; + float3 downsampledNormal = PassSrg::m_downsampledNormal.Load(int3(probeIrradianceCoords + int2(x, y), 0)).rgb; downsampledNormal = downsampledNormal * 2.0f - 1.0f; float normalDot = dot(downsampledNormal, normal); @@ -100,10 +100,10 @@ float3 SampleProbeIrradiance(uint2 probeIrradianceCoords, float depth, float3 no if (normalDot > NormalMatchTolerance) { // the normals are almost identical, if the depth is within the tolerance we can optimize by just taking this sample - float downsampledDepth = PassSrg::m_downsampledDepth.Load(int3(probeIrradianceCoords, 0), int2(x, y)).r; + float downsampledDepth = PassSrg::m_downsampledDepth.Load(int3(probeIrradianceCoords + int2(x, y), 0)).r; if (abs(depth - downsampledDepth) <= DepthTolerance) { - float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords,0), int2(x, y)).rgb; + float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords + int2(x, y),0)).rgb; probeIrradiance = saturate(probeIrradiance); return probeIrradiance; } @@ -115,7 +115,7 @@ float3 SampleProbeIrradiance(uint2 probeIrradianceCoords, float depth, float3 no } } - float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords, 0), closestOffset).rgb; + float3 probeIrradiance = PassSrg::m_downsampledProbeIrradiance.Load(int3(probeIrradianceCoords + closestOffset, 0)).rgb; probeIrradiance = saturate(probeIrradiance); return probeIrradiance; } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl index f10fc67da5..d2e73927e0 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/DiffuseProbeGridDownsample_nomsaa.azsl @@ -70,8 +70,8 @@ PSOutput MainPS(VSOutput IN) { for (uint x = 0; x < ImageScale; ++x) { - float depth = PassSrg::m_depth.Load(int3(screenCoords, 0), int2(x, y)).r; - float4 encodedNormal = PassSrg::m_normal.Load(int3(screenCoords, 0), int2(x, y)); + float depth = PassSrg::m_depth.Load(int3(screenCoords + int2(x, y), 0)).r; + float4 encodedNormal = PassSrg::m_normal.Load(int3(screenCoords + int2(x, y), 0)); // take the closest depth sample to ensure we're getting the normal closest to the viewer // (larger depth value due to reverse depth) diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h b/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h index c7c9902115..c1314aaf66 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h +++ b/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Windows/Vulkan_Traits_Windows.h @@ -11,7 +11,7 @@ */ #pragma once -#define AZ_TRAIT_ATOM_SHADERBUILDER_DXC "Builders/DirectXShaderCompilerAz/dxc.exe" +#define AZ_TRAIT_ATOM_SHADERBUILDER_DXC "Builders/DirectXShaderCompiler/dxc.exe" #define AZ_TRAIT_ATOM_VULKAN_DISABLE_DUAL_SOURCE_BLENDING 0 #define AZ_TRAIT_ATOM_VULKAN_DLL "vulkan.dll" #define AZ_TRAIT_ATOM_VULKAN_DLL_1 "vulkan-1.dll" diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp index a41ab9eea6..2979819aa6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialFunctor.cpp @@ -334,6 +334,7 @@ namespace AZ if (it == m_propertyMetadata.end()) { AZ_Error("MaterialFunctor", false, "Couldn't find metadata for material property: %s.", propertyName.GetCStr()); + return nullptr; } return &it->second; @@ -345,6 +346,7 @@ namespace AZ if (it == m_propertyGroupMetadata.end()) { AZ_Error("MaterialFunctor", false, "Couldn't find metadata for material property group: %s.", propertyGroupName.GetCStr()); + return nullptr; } return &it->second; diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl index e7e743adda..23eb79650a 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl @@ -269,13 +269,13 @@ namespace AZ { ImGui::BeginChild(heapMemoryId.c_str()); ImGui::SetScrollY(scrollingY); - ImGui::End(); + ImGui::EndChild(); } { ImGui::BeginChild(scopesId.c_str()); ImGui::SetScrollX(scrollingX); - ImGui::End(); + ImGui::EndChild(); } ImGui::PopStyleVar(3); diff --git a/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt b/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt index 1066cc33e8..d3b8c8cde7 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt @@ -12,7 +12,7 @@ ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) ly_add_target( - NAME AtomFont ${PAL_TRAIT_MONOLITHIC_DRIVEN_LIBRARY_TYPE} + NAME AtomFont ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} NAMESPACE Gem FILES_CMAKE atomfont_files.cmake diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index 31d69569db..3192900ca4 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -304,6 +304,11 @@ namespace AZ for (auto& groupPair : m_groups) { AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{groupPair.first}]; + + for (auto& property : groupPair.second.m_properties) + { + AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig()); + } // It's significant that we check IsGroupHidden rather than IsGroupVisisble, because it follows the same rules as QWidget::isHidden(). // We don't care whether the widget and all its parents are visible, we only care about whether the group was hidden within the context diff --git a/Gems/Blast/Code/Source/Actor/BlastActorDesc.h b/Gems/Blast/Code/Source/Actor/BlastActorDesc.h index a58fbd04df..67a3c8d338 100644 --- a/Gems/Blast/Code/Source/Actor/BlastActorDesc.h +++ b/Gems/Blast/Code/Source/Actor/BlastActorDesc.h @@ -32,10 +32,11 @@ namespace Blast Physics::MaterialId m_physicsMaterialId; AZ::Vector3 m_parentLinearVelocity = AZ::Vector3::CreateZero(); AZ::Vector3 m_parentCenterOfMass = AZ::Vector3::CreateZero(); - AzPhysics::RigidBodyConfiguration m_bodyConfiguration; //! Either rigid dynamic or rigid static - AZStd::vector m_chunkIndices; //! Chunks that are going to simulate this actor. - AZStd::shared_ptr m_entity; //! Entity that the actor should use to simulate rigid body - bool m_isStatic = false; //! Denotes whether actor should be simulated by a static or dynamic rigid body. - bool m_isLeafChunk = false; //! Denotes whether this actor represented by a single leaf chunk. + AzPhysics::RigidBodyConfiguration m_bodyConfiguration; //!< Either rigid dynamic or rigid static + AZStd::vector m_chunkIndices; //!< Chunks that are going to simulate this actor. + AZStd::shared_ptr m_entity; //!< Entity that the actor should use to simulate rigid body + bool m_isStatic = false; //!< Denotes whether actor should be simulated by a static or dynamic rigid body. + bool m_isLeafChunk = false; //!< Denotes whether this actor represented by a single leaf chunk. + float m_scale = 1.0f; //!< Uniform scale applied to the actor. }; } // namespace Blast diff --git a/Gems/Blast/Code/Source/Actor/BlastActorImpl.cpp b/Gems/Blast/Code/Source/Actor/BlastActorImpl.cpp index 1f05793b4b..0336ae8c09 100644 --- a/Gems/Blast/Code/Source/Actor/BlastActorImpl.cpp +++ b/Gems/Blast/Code/Source/Actor/BlastActorImpl.cpp @@ -45,6 +45,7 @@ namespace Blast , m_parentLinearVelocity(desc.m_parentLinearVelocity) , m_parentCenterOfMass(desc.m_parentCenterOfMass) , m_bodyConfiguration(desc.m_bodyConfiguration) + , m_scale(desc.m_scale) { // Store pointer to ourselves in the blast toolkit actor's userData m_tkActor.userData = this; @@ -67,7 +68,7 @@ namespace Blast auto transform = AZ::Transform::CreateFromQuaternionAndTranslation( m_bodyConfiguration.m_orientation, m_bodyConfiguration.m_position); - transform.MultiplyByScale(m_bodyConfiguration.m_scale); + transform.MultiplyByScale(AZ::Vector3(m_scale)); AZ::TransformBus::Event(m_entity->GetId(), &AZ::TransformInterface::SetWorldTM, transform); @@ -130,7 +131,7 @@ namespace Blast Physics::NativeShapeConfiguration shapeConfiguration; shapeConfiguration.m_nativeShapePtr = reinterpret_cast(const_cast(&subchunk.geometry)->convexMesh); - shapeConfiguration.m_nativeShapeScale = m_bodyConfiguration.m_scale; + shapeConfiguration.m_nativeShapeScale = AZ::Vector3(m_scale); AZStd::shared_ptr shape = AZ::Interface::Get()->CreateShape( colliderConfiguration, shapeConfiguration); diff --git a/Gems/Blast/Code/Source/Actor/BlastActorImpl.h b/Gems/Blast/Code/Source/Actor/BlastActorImpl.h index 3b686b3641..e3ba8880be 100644 --- a/Gems/Blast/Code/Source/Actor/BlastActorImpl.h +++ b/Gems/Blast/Code/Source/Actor/BlastActorImpl.h @@ -77,5 +77,6 @@ namespace Blast AZ::Vector3 m_parentLinearVelocity = AZ::Vector3::CreateZero(); AZ::Vector3 m_parentCenterOfMass = AZ::Vector3::CreateZero(); AzPhysics::RigidBodyConfiguration m_bodyConfiguration; + float m_scale = 1.0f; }; } // namespace Blast diff --git a/Gems/Blast/Code/Source/Components/BlastFamilyComponent.cpp b/Gems/Blast/Code/Source/Components/BlastFamilyComponent.cpp index 841163d2ab..0f2668442c 100644 --- a/Gems/Blast/Code/Source/Components/BlastFamilyComponent.cpp +++ b/Gems/Blast/Code/Source/Components/BlastFamilyComponent.cpp @@ -147,6 +147,7 @@ namespace Blast void BlastFamilyComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC("BlastFamilyService")); + incompatible.push_back(AZ_CRC_CE("NonUniformScaleService")); } void BlastFamilyComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) diff --git a/Gems/Blast/Code/Source/Editor/EditorBlastFamilyComponent.cpp b/Gems/Blast/Code/Source/Editor/EditorBlastFamilyComponent.cpp index fc6967b96e..9241449483 100644 --- a/Gems/Blast/Code/Source/Editor/EditorBlastFamilyComponent.cpp +++ b/Gems/Blast/Code/Source/Editor/EditorBlastFamilyComponent.cpp @@ -85,6 +85,7 @@ namespace Blast void EditorBlastFamilyComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC_CE("BlastFamilyService")); + incompatible.push_back(AZ_CRC_CE("NonUniformScaleService")); } void EditorBlastFamilyComponent::OnAssetReady(AZ::Data::Asset asset) diff --git a/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.cpp b/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.cpp index f8fd97dc52..77788b6aee 100644 --- a/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.cpp +++ b/Gems/Blast/Code/Source/Editor/EditorBlastMeshDataComponent.cpp @@ -43,6 +43,7 @@ namespace Blast AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC("BlastMeshDataService")); + incompatible.push_back(AZ_CRC_CE("NonUniformScaleService")); } void EditorBlastMeshDataComponent::Reflect(AZ::ReflectContext* context) diff --git a/Gems/Blast/Code/Source/Family/BlastFamilyImpl.cpp b/Gems/Blast/Code/Source/Family/BlastFamilyImpl.cpp index 87865d0e08..5fe1e0ab30 100644 --- a/Gems/Blast/Code/Source/Family/BlastFamilyImpl.cpp +++ b/Gems/Blast/Code/Source/Family/BlastFamilyImpl.cpp @@ -202,7 +202,7 @@ namespace Blast if (parentBody) { parentTransform = parentBody->GetTransform(); - parentTransform.MultiplyByScale(m_initialTransform.GetScale()); + parentTransform.MultiplyByScale(AZ::Vector3(m_initialTransform.GetScale().GetMaxElement())); } else { @@ -239,7 +239,6 @@ namespace Blast AzPhysics::RigidBodyConfiguration configuration; configuration.m_position = transform.GetTranslation(); configuration.m_orientation = transform.GetRotation(); - configuration.m_scale = transform.GetScale(); configuration.m_ccdEnabled = m_actorConfiguration.m_isCcdEnabled; configuration.m_startSimulationEnabled = m_actorConfiguration.m_isSimulated; configuration.m_initialAngularVelocity = AZ::Vector3::CreateZero(); @@ -255,6 +254,7 @@ namespace Blast actorDesc.m_parentCenterOfMass = transform.GetTranslation(); actorDesc.m_parentLinearVelocity = AZ::Vector3::CreateZero(); actorDesc.m_bodyConfiguration = configuration; + actorDesc.m_scale = transform.GetScale().GetMaxElement(); return actorDesc; } diff --git a/Gems/Blast/Code/Tests/BlastFamilyTest.cpp b/Gems/Blast/Code/Tests/BlastFamilyTest.cpp index 2e6fd7f2bb..06af890a44 100644 --- a/Gems/Blast/Code/Tests/BlastFamilyTest.cpp +++ b/Gems/Blast/Code/Tests/BlastFamilyTest.cpp @@ -137,7 +137,7 @@ namespace Blast .Times(1) .WillOnce(Return(false)); - AZ::Transform transform = AZ::Transform::CreateScale(AZ::Vector3::CreateOne()); + AZ::Transform transform = AZ::Transform::CreateIdentity(); blastFamily->Spawn(transform); } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 41503396fb..4fe60f14a3 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -35,6 +35,7 @@ namespace Multiplayer using EntityStopEvent = AZ::Event; using EntityDirtiedEvent = AZ::Event<>; + using EntitySyncRewindEvent = AZ::Event<>; using EntityMigrationStartEvent = AZ::Event; using EntityMigrationEndEvent = AZ::Event<>; using EntityServerMigrationEvent = AZ::Event; @@ -73,6 +74,7 @@ namespace Multiplayer NetworkEntityHandle GetEntityHandle(); void SetOwningConnectionId(AzNetworking::ConnectionId connectionId); + AzNetworking::ConnectionId GetOwningConnectionId() const; void SetAllowAutonomy(bool value); MultiplayerComponentInputVector AllocateComponentInputs(); bool IsProcessingInput() const; @@ -91,12 +93,14 @@ namespace Multiplayer void MarkDirty(); void NotifyLocalChanges(); + void NotifySyncRewindState(); void NotifyMigrationStart(ClientInputId migratedInputId); void NotifyMigrationEnd(); void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId); void AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler); void AddEntityDirtiedEventHandler(EntityDirtiedEvent::Handler& eventHandler); + void AddEntitySyncRewindEventHandler(EntitySyncRewindEvent::Handler& eventHandler); void AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler); void AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler); void AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler); @@ -144,6 +148,7 @@ namespace Multiplayer EntityStopEvent m_entityStopEvent; EntityDirtiedEvent m_dirtiedEvent; + EntitySyncRewindEvent m_syncRewindEvent; EntityMigrationStartEvent m_entityMigrationStartEvent; EntityMigrationEndEvent m_entityMigrationEndEvent; EntityServerMigrationEvent m_entityServerMigrationEvent; @@ -157,6 +162,8 @@ namespace Multiplayer NetEntityRole m_netEntityRole = NetEntityRole::InvalidRole; NetEntityId m_netEntityId = InvalidNetEntityId; + AzNetworking::ConnectionId m_owningConnectionId = AzNetworking::InvalidConnectionId; + bool m_isProcessingInput = false; bool m_isMigrationDataValid = false; bool m_needsToBeStopped = false; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 6a615465c2..579ca195e5 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -48,7 +48,6 @@ namespace Multiplayer using ConnectionAcquiredEvent = AZ::Event; using SessionInitEvent = AZ::Event; using SessionShutdownEvent = AZ::Event; - using OnConnectFunctor = AZStd::function; //! IMultiplayer provides insight into the Multiplayer session and its Agents class IMultiplayer @@ -78,10 +77,6 @@ namespace Multiplayer //! @param handler The SessionShutdownEvent handler to add virtual void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) = 0; - //! Overrides the default connect behaviour with the provided functor. - //! @param functor the function to invoke during a new connection event - virtual void SetOnConnectFunctor(const OnConnectFunctor& functor) = 0; - //! Sends a packet telling if entity update messages can be sent //! @param readyForEntityUpdates Ready for entity updates or not virtual void SendReadyForEntityUpdates(bool readyForEntityUpdates) = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h index e9f3865563..16cc4146dd 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace Multiplayer { @@ -85,8 +86,7 @@ namespace Multiplayer Activate }; - // This is just a placeholder - // The level/prefab cooking will devise the actual solution for identifying a dynamically spawnable entity within a prefab + // Structure for identifying a specific entity within a spawnable struct PrefabEntityId { AZ_TYPE_INFO(PrefabEntityId, "{EFD37465-CCAC-4E87-A825-41B4010A2C75}"); @@ -121,6 +121,13 @@ namespace Multiplayer return serializer.IsValid(); } }; + + struct EntityMigrationMessage + { + NetEntityId m_entityId; + PrefabEntityId m_prefabEntityId; + AzNetworking::PacketEncodingBuffer m_propertyUpdateData; + }; } AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::HostId); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h index f6eb93c4ba..3dfc4b8016 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h @@ -24,14 +24,12 @@ namespace Multiplayer ReplicationRecordStats() = default; ReplicationRecordStats ( - uint32_t authorityToAuthorityCount, uint32_t authorityToClientCount, uint32_t authorityToServerCount, uint32_t authorityToAutonomousCount, uint32_t autonomousToAuthorityCount ); - uint32_t m_authorityToAuthorityCount = 0; uint32_t m_authorityToClientCount = 0; uint32_t m_authorityToServerCount = 0; uint32_t m_authorityToAutonomousCount = 0; @@ -63,19 +61,16 @@ namespace Multiplayer bool Serialize(AzNetworking::ISerializer& serializer); - void ConsumeAuthorityToAuthorityBits(uint32_t consumedBits); void ConsumeAuthorityToClientBits(uint32_t consumedBits); void ConsumeAuthorityToServerBits(uint32_t consumedBits); void ConsumeAuthorityToAutonomousBits(uint32_t consumedBits); void ConsumeAutonomousToAuthorityBits(uint32_t consumedBits); - bool ContainsAuthorityToAuthorityBits() const; bool ContainsAuthorityToClientBits() const; bool ContainsAuthorityToServerBits() const; bool ContainsAuthorityToAutonomousBits() const; bool ContainsAutonomousToAuthorityBits() const; - uint32_t GetRemainingAuthorityToAuthorityBits() const; uint32_t GetRemainingAuthorityToClientBits() const; uint32_t GetRemainingAuthorityToServerBits() const; uint32_t GetRemainingAuthorityToAutonomousBits() const; @@ -84,13 +79,11 @@ namespace Multiplayer ReplicationRecordStats GetStats() const; using RecordBitset = AzNetworking::FixedSizeVectorBitset; - RecordBitset m_authorityToAuthority; RecordBitset m_authorityToClient; RecordBitset m_authorityToServer; RecordBitset m_authorityToAutonomous; RecordBitset m_autonomousToAuthority; - uint32_t m_authorityToAuthorityConsumedBits = 0; uint32_t m_authorityToClientConsumedBits = 0; uint32_t m_authorityToServerConsumedBits = 0; uint32_t m_authorityToAutonomousConsumedBits = 0; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja index 4279c26cf2..61dcacaa94 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja @@ -188,6 +188,69 @@ virtual void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnect {% endmacro %} {# +#} +{% macro DeclareRpcEventGetter(Property, HandleOn) %} +{% set paramNames = [] %} +{% set paramTypes = [] %} +{% set paramDefines = [] %} +{% set PropertyName = UpperFirst(Property.attrib['Name']) %} +{{ ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} +AZ::Event<{{ ', '.join(paramTypes) }}>& Get{{ PropertyName }}Event() { return m_{{ PropertyName }}Event; } +{% endmacro %} +{# + +#} +{% macro DeclareRpcEventGetters(Component, InvokeFrom, HandleOn) %} +{% call(Property) ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %} +{% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} +{{- DeclareRpcEventGetter(Property, HandleOn) -}} +{% endif %} +{% endcall %} +{% endmacro %} +{# + +#} +{% macro DeclareRpcEvent(Property, HandleOn) %} +{% set paramNames = [] %} +{% set paramTypes = [] %} +{% set paramDefines = [] %} +{% set PropertyName = UpperFirst(Property.attrib['Name']) %} +{{ ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} +AZ::Event<{{ ', '.join(paramTypes) }}> m_{{ PropertyName }}Event; +{% endmacro %} +{# + +#} +{% macro DeclareRpcEvents(Component, InvokeFrom, HandleOn) %} +{% call(Property) ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %} +{% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} +{{- DeclareRpcEvent(Property, HandleOn) -}} +{% endif %} +{% endcall %} +{% endmacro %} +{# + +#} +{% macro DeclareRpcSignal(Property, HandleOn) %} +{% set paramNames = [] %} +{% set paramTypes = [] %} +{% set paramDefines = [] %} +{% set PropertyName = UpperFirst(Property.attrib['Name']) %} +{{ ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} +void Signal{{ PropertyName }}({{ ', '.join(paramDefines) }}); +{% endmacro %} +{# + +#} +{% macro DeclareRpcSignals(Component, InvokeFrom, HandleOn) %} +{% call(Property) ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %} +{% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} +{{- DeclareRpcSignal(Property, HandleOn) -}} +{% endif %} +{% endcall %} +{% endmacro %} +{# + #} {%- macro EmitDerivedClassesComment(dataFileNames, Component, ComponentName, ComponentNameBase, ComponentDerived, ControllerName, ControllerNameBase, ControllerDerived, NetworkInputCount) -%} {% if ComponentDerived or ControllerDerived %} @@ -214,6 +277,10 @@ namespace {{ Component.attrib['Namespace'] }} void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; {{ DeclareRpcHandlers(Component, 'Authority', 'Client', true)|indent(8) }} + {{ DeclareRpcSignals(Component, 'Authority', 'Client')|indent(8) }} + {{ DeclareRpcEventGetters(Component, 'Authority', 'Client')|indent(8) }} + protected: + {{ DeclareRpcEvents(Component, 'Authority', 'Client')|indent(8) }} }; {% endif %} @@ -236,6 +303,20 @@ namespace {{ Component.attrib['Namespace'] }} {{ DeclareRpcHandlers(Component, 'Client', 'Authority', true)|indent(8) }} {{ DeclareRpcHandlers(Component, 'Autonomous', 'Authority', true)|indent(8) }} {{ DeclareRpcHandlers(Component, 'Authority', 'Autonomous', true)|indent(8) }} + {{ DeclareRpcSignals(Component, 'Server', 'Authority')|indent(8) }} + {{ DeclareRpcSignals(Component, 'Client', 'Authority')|indent(8) }} + {{ DeclareRpcSignals(Component, 'Autonomous', 'Authority')|indent(8) }} + {{ DeclareRpcSignals(Component, 'Authority', 'Autonomous')|indent(8) }} + {{ DeclareRpcEventGetters(Component, 'Server', 'Authority')|indent(8) }} + {{ DeclareRpcEventGetters(Component, 'Client', 'Authority')|indent(8) }} + {{ DeclareRpcEventGetters(Component, 'Autonomous', 'Authority')|indent(8) }} + {{ DeclareRpcEventGetters(Component, 'Authority', 'Autonomous')|indent(8) }} + + protected: + {{ DeclareRpcEvents(Component, 'Server', 'Authority')|indent(8) }} + {{ DeclareRpcEvents(Component, 'Client', 'Authority')|indent(8) }} + {{ DeclareRpcEvents(Component, 'Autonomous', 'Authority')|indent(8) }} + {{ DeclareRpcEvents(Component, 'Authority', 'Autonomous')|indent(8) }} }; {% endif %} } diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index 8ae8fee618..71e81b6bfb 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -125,7 +125,7 @@ void {{ PropertyName }}({{ ', '.join(paramDefines) }}); {% macro DeclareRpcInvocations(Component, Section, HandleOn, ProctectedSection) %} {% call(Property) AutoComponentMacros.ParseRemoteProcedures(Component, Section, HandleOn) %} {% if Property.attrib['IsPublic']|booleanTrue == ProctectedSection %} -{{- DeclareRpcInvocation(Property, HandleOn) -}} +{{ DeclareRpcInvocation(Property, HandleOn) -}} {% endif %} {% endcall %} {% endmacro %} @@ -274,13 +274,6 @@ namespace {{ Component.attrib['Namespace'] }} //! Sets the bits in the attached record that correspond to predictable network properties. void SetPredictableBits(); -{% set networkPropertyCount = {'value' : 0} %} -{% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, 'Authority', 'Authority') %} -{%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%} -{% endcall %} -{% if networkPropertyCount.value > 0 %} - AzNetworking::FixedSizeBitsetView m_authorityToAuthority; -{% endif %} {% set networkPropertyCount = {'value' : 0} %} {% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, 'Authority', 'Client') %} {%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%} @@ -293,7 +286,7 @@ namespace {{ Component.attrib['Namespace'] }} {%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%} {% endcall %} {% if networkPropertyCount.value > 0 %} - AzNetworking::FixedSizeBitsetView m_authorityToServer}; + AzNetworking::FixedSizeBitsetView m_authorityToServer; {% endif %} {% set networkPropertyCount = {'value' : 0} %} {% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, 'Authority', 'Autonomous') %} @@ -314,7 +307,6 @@ namespace {{ Component.attrib['Namespace'] }} {{ RecordName }} ( Multiplayer::ReplicationRecord& replicationRecord, - uint32_t authorityToAuthoritySimluationStartOffset, uint32_t authorityToClientSimluationStartOffset, uint32_t authorityToServerSimluationStartOffset, uint32_t authorityToAutonomousStartOffset, @@ -367,14 +359,12 @@ namespace {{ Component.attrib['Namespace'] }} void ProcessInput([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime) override {} //! @} - {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Authority', false)|indent(8) -}} - {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Authority', true)|indent(8) -}} {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Server', false)|indent(8) -}} {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Server', true)|indent(8) -}} {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Client', false)|indent(8) -}} {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Client', true)|indent(8) -}} - {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', false)|indent(8) }} - {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', true)|indent(8) }} + {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', false)|indent(8) -}} + {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', true)|indent(8) -}} {{ DeclareArchetypePropertyGetters(Component)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', true)|indent(8) -}} @@ -384,17 +374,31 @@ namespace {{ Component.attrib['Namespace'] }} {{ DeclareRpcInvocations(Component, 'Autonomous', 'Authority', true)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Authority', 'Autonomous', false)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Authority', 'Autonomous', true)|indent(8) -}} - {{ DeclareRpcInvocations(Component, 'Authority', 'Client', false)|indent(8) }} - {{ DeclareRpcInvocations(Component, 'Authority', 'Client', true)|indent(8) }} - {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Server', 'Authority', false)|indent(8) }} - {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Client', 'Authority', false)|indent(8) }} - {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Autonomous', 'Authority', false)|indent(8) }} - {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Authority', 'Autonomous', false)|indent(8) }} + {{ DeclareRpcInvocations(Component, 'Authority', 'Client', false)|indent(8) -}} + {{ DeclareRpcInvocations(Component, 'Authority', 'Client', true)|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Server', 'Authority', false)|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Client', 'Authority', false)|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Autonomous', 'Authority', false)|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Authority', 'Autonomous', false)|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcSignals(Component, 'Server', 'Authority')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcSignals(Component, 'Client', 'Authority')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcSignals(Component, 'Autonomous', 'Authority')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcSignals(Component, 'Authority', 'Autonomous')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcEventGetters(Component, 'Server', 'Authority')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcEventGetters(Component, 'Client', 'Authority')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcEventGetters(Component, 'Autonomous', 'Authority')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcEventGetters(Component, 'Authority', 'Autonomous')|indent(8) }} {% for Service in Component.iter('ComponentRelation') %} {% if (Service.attrib['HasController']|booleanTrue) and (Service.attrib['Constraint'] != 'Incompatible') %} {{ Service.attrib['Namespace'] }}::{{ Service.attrib['Name'] }}Controller* Get{{ Service.attrib['Name'] }}Controller(); {% endif %} {% endfor %} + + protected: + {{ AutoComponentMacros.DeclareRpcEvents(Component, 'Server', 'Authority')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcEvents(Component, 'Client', 'Authority')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcEvents(Component, 'Autonomous', 'Authority')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcEvents(Component, 'Authority', 'Autonomous')|indent(8) }} }; static const AZ::Uuid s_{{ LowerFirst(ComponentName) }}ConcreteUuid = "{{ (ComponentName) | createHashGuid }}"; @@ -435,9 +439,10 @@ namespace {{ Component.attrib['Namespace'] }} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', false)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Autonomous', false)|indent(8) -}} - {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', false)|indent(8) }} + {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', false)|indent(8) -}} {{ DeclareArchetypePropertyGetters(Component)|indent(8) -}} - {{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) }} + {{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcEventGetters(Component, 'Authority', 'Client')|indent(8) -}} //! MultiplayerComponent interface //! @{ @@ -457,13 +462,14 @@ namespace {{ Component.attrib['Namespace'] }} void NetworkAttach(Multiplayer::NetBindComponent* netBindComponent, Multiplayer::ReplicationRecord& currentEntityRecord, Multiplayer::ReplicationRecord& predictableEntityRecord) override; //! @} - {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Authority', true)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', true)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Autonomous', true)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Autonomous', 'Authority', true)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', true)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', true)|indent(8) -}} - {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Authority', 'Client', false)|indent(8) }} + {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Authority', 'Client', false)|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcSignals(Component, 'Authority', 'Client')|indent(8) -}} + {{ AutoComponentMacros.DeclareRpcEvents(Component, 'Authority', 'Client')|indent(8) }} {% for Service in Component.iter('ComponentRelation') %} {% if Service.attrib['Constraint'] != 'Incompatible' %} const {{ Service.attrib['Namespace'] }}::{{ Service.attrib['Name'] }}* Get{{ Service.attrib['Name'] }}() const; @@ -471,10 +477,6 @@ namespace {{ Component.attrib['Namespace'] }} {% endif %} {% endfor %} private: - //! Authority To Authority serializers (hot backup in case of server failure) - bool SerializeAuthorityToAuthorityProperties({{ RecordName }}& replicationRecord, AzNetworking::ISerializer& serializer); - void NotifyChangesAuthorityToAuthorityProperties(const {{ RecordName }}& replicationRecord) const; - //! Authority to Client serializers bool SerializeAuthorityToClientProperties({{ RecordName }}& replicationRecord, AzNetworking::ISerializer& serializer); void NotifyChangesAuthorityToClientProperties(const {{ RecordName }}& replicationRecord) const; @@ -499,21 +501,18 @@ namespace {{ Component.attrib['Namespace'] }} AZStd::unique_ptr<{{ ControllerName }}> m_controller; //! Network Properties - {{ DeclareNetworkPropertyVars(Component, 'Authority', 'Authority')|indent(8) -}} {{ DeclareNetworkPropertyVars(Component, 'Authority', 'Server')|indent(8) -}} {{ DeclareNetworkPropertyVars(Component, 'Authority', 'Client')|indent(8) -}} {{ DeclareNetworkPropertyVars(Component, 'Authority', 'Autonomous')|indent(8) -}} {{ DeclareNetworkPropertyVars(Component, 'Autonomous', 'Authority')|indent(8) }} //! Network Properties for reflection and editor support - {{ DeclareNetworkPropertyReflectVars(Component, 'Authority', 'Authority')|indent(8) -}} {{ DeclareNetworkPropertyReflectVars(Component, 'Authority', 'Server')|indent(8) -}} {{ DeclareNetworkPropertyReflectVars(Component, 'Authority', 'Client')|indent(8) -}} {{ DeclareNetworkPropertyReflectVars(Component, 'Authority', 'Autonomous')|indent(8) -}} {{ DeclareNetworkPropertyReflectVars(Component, 'Autonomous', 'Authority')|indent(8) }} //! NetworkProperty Events - {{ DeclareNetworkPropertyEvents(Component, 'Authority', 'Authority')|indent(8) -}} {{ DeclareNetworkPropertyEvents(Component, 'Authority', 'Server')|indent(8) -}} {{ DeclareNetworkPropertyEvents(Component, 'Authority', 'Client')|indent(8) -}} {{ DeclareNetworkPropertyEvents(Component, 'Authority', 'Autonomous')|indent(8) -}} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index ffb09cf8f5..7d5295aabb 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -315,16 +315,86 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(par {% endmacro %} {# +#} +{% macro DefineRpcSignal(Component, ClassName, Property, InvokeFrom) %} +{% set paramNames = [] %} +{% set paramTypes = [] %} +{% set paramDefines = [] %} +{{ AutoComponentMacros.ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} +void {{ ClassName }}::Signal{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramDefines) }}) +{ + m_{{ UpperFirst(Property.attrib['Name']) }}Event.Signal({{ ', '.join(paramNames) }}); +} +{% endmacro %} +{# + #} {% macro DefineRpcInvocations(Component, ClassName, InvokeFrom, HandleOn, ProctectedSection) %} {% call(Property) AutoComponentMacros.ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %} {% if Property.attrib['IsPublic']|booleanTrue == ProctectedSection %} -{{ DefineRpcInvocation(Component, ClassName, Property, InvokeFrom, HandleOn) }} +{{ DefineRpcInvocation(Component, ClassName, Property, InvokeFrom, HandleOn) -}} +{% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} +{{ DefineRpcSignal(Component, ClassName, Property, InvokeFrom) -}} +{% endif %} +{% endif %} +{% endcall %} +{% endmacro %} +{# + +#} +{% macro ReflectRpcInvocations(Component, ClassName, InvokeFrom, HandleOn) %} +{% call(Property) AutoComponentMacros.ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %} +{% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} +{% set paramNames = [] %} +{% set paramTypes = [] %} +{% set paramDefines = [] %} +{{ AutoComponentMacros.ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} + ->Method("{{ UpperFirst(Property.attrib['Name']) }}", [](const {{ ClassName }}* self, {{ ', '.join(paramDefines) }}) { + self->m_controller->{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramNames) }}); + }) {% endif %} {% endcall %} {% endmacro %} {# +#} +{% macro ReflectRpcEventDescs(Component, ClassName, InvokeFrom, HandleOn) %} +{% call(Property) AutoComponentMacros.ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %} +{% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} +{% set paramNames = [] %} +{% set paramTypes = [] %} +{% set paramDefines = [] %} +{{ AutoComponentMacros.ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} + // Create the BehaviorAZEventDescription needed to reflect the + // Get{{ UpperFirst(Property.attrib['Name']) }}Event method to the BehaviorContext without errors + AZ::BehaviorAzEventDescription {{ LowerFirst(Property.attrib['Name']) }}EventDesc; + {{ LowerFirst(Property.attrib['Name']) }}EventDesc.m_eventName = "{{ UpperFirst(Property.attrib['Name']) }} Notify Event"; +{% for Param in Property.iter('Param') %} + {{ LowerFirst(Property.attrib['Name']) }}EventDesc.m_parameterNames.push_back("{{ LowerFirst(Param.attrib['Name']) }}"); +{% endfor %} +{% endif %} +{% endcall %} +{% endmacro %} +{# + +#} +{% macro ReflectRpcEvents(Component, ClassName, InvokeFrom, HandleOn) %} +{% call(Property) AutoComponentMacros.ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %} +{% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} +{% set paramNames = [] %} +{% set paramTypes = [] %} +{% set paramDefines = [] %} +{{ AutoComponentMacros.ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} + ->Method("Get{{ UpperFirst(Property.attrib['Name']) }}Event", [](const {{ ClassName }}* self) -> AZ::Event<{{ ', '.join(paramTypes) }}>& + { + return self->m_controller->Get{{ UpperFirst(Property.attrib['Name']) }}Event(); + }) + ->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move({{ LowerFirst(Property.attrib['Name']) }}EventDesc)) +{% endif %} +{% endcall %} +{% endmacro %} +{# + #} {% macro DeclareRpcHandleCases(Component, ComponentDerived, InvokeFrom, HandleOn, ValidationFunction) %} {% call(Property) AutoComponentMacros.ParseRemoteProcedures(Component, InvokeFrom, HandleOn) %} @@ -347,6 +417,9 @@ case {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ Upp { AZ_Assert(GetNetBindComponent()->GetNetEntityRole() == Multiplayer::NetEntityRole::Authority, "Entity proxy does not have authority"); m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }}); +{% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} + m_controller->Signal{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }}); +{% endif %} } {% if Property.attrib['IsReliable']|booleanTrue %} {# if the rpc is not reliable we can simply drop it, also note message reliability type is default reliable in EntityRpcMessage #} @@ -361,6 +434,9 @@ case {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ Upp { AZ_Assert(GetNetBindComponent()->GetNetEntityRole() == Multiplayer::NetEntityRole::Autonomous, "Entity proxy does not have autonomy"); m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }}); +{% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} + m_controller->Signal{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }}); +{% endif %} } {% else %} Handle{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }}); @@ -1017,7 +1093,6 @@ namespace {{ Component.attrib['Namespace'] }} { {{ DeclareRemoteProcedureEnumerations(Component)|indent(8) }} {{ DeclareNetworkPropertyEnumerations(Component)|indent(8) }} - {{ DefineNetworkPropertyDirtyEnumeration(Component, ClassType, 'Authority', 'Authority')|indent(8) }} {{ DefineNetworkPropertyDirtyEnumeration(Component, ClassType, 'Authority', 'Client')|indent(8) }} {{ DefineNetworkPropertyDirtyEnumeration(Component, ClassType, 'Authority', 'Server')|indent(8) }} {{ DefineNetworkPropertyDirtyEnumeration(Component, ClassType, 'Authority', 'Autonomous')|indent(8) }} @@ -1032,7 +1107,6 @@ namespace {{ Component.attrib['Namespace'] }} {{ RecordName }}::{{ RecordName }} ( [[maybe_unused]] Multiplayer::ReplicationRecord& replicationRecord, - [[maybe_unused]] uint32_t authorityToAuthorityStartOffset, [[maybe_unused]] uint32_t authorityToClientStartOffset, [[maybe_unused]] uint32_t authorityToServerStartOffset, [[maybe_unused]] uint32_t authorityToAutonomousStartOffset, @@ -1040,13 +1114,6 @@ namespace {{ Component.attrib['Namespace'] }} ) {% set comma = joiner(" ,") %} {% set networkPropertyCount = {'value' : 0} %} -{% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, 'Authority', 'Authority') %} -{%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%} -{% endcall %} -{% if networkPropertyCount.value > 0 %} -{{ comma()|default(" :", true) }} m_authorityToAuthority(replicationRecord.m_authorityToAuthority, authorityToAuthorityStartOffset, replicationRecord.ContainsAuthorityToAuthorityBits() ? static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Authority') }}::Count) : 0) -{% endif %} -{% set networkPropertyCount = {'value' : 0} %} {% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, 'Authority', 'Client') %} {%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%} {% endcall %} @@ -1058,7 +1125,7 @@ namespace {{ Component.attrib['Namespace'] }} {%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%} {% endcall %} {% if networkPropertyCount.value > 0 %} - {{ comma()|default(" :", true) }} authorityToServer }}(replicationRecord.m_authorityToServer, authorityToServerStartOffset, replicationRecord.ContainsAuthorityToServerBits() ? static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Server') }}::Count) : 0) + {{ comma()|default(" :", true) }} m_authorityToServer(replicationRecord.m_authorityToServer, authorityToServerStartOffset, replicationRecord.ContainsAuthorityToServerBits() ? static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Server') }}::Count) : 0) {% endif %} {% set networkPropertyCount = {'value' : 0} %} {% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, 'Authority', 'Autonomous') %} @@ -1080,9 +1147,6 @@ namespace {{ Component.attrib['Namespace'] }} AZStd::unique_ptr<{{ RecordName }}> {{ RecordName }}::AllocateRecord(Multiplayer::ReplicationRecord& replicationRecord) { - uint32_t authorityToAuthorityStart = replicationRecord.m_authorityToAuthority.GetSize(); - replicationRecord.m_authorityToAuthority.Resize(authorityToAuthorityStart + static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Authority') }}::Count)); - uint32_t authorityToClientStart = replicationRecord.m_authorityToClient.GetSize(); replicationRecord.m_authorityToClient.Resize(authorityToClientStart + static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Client') }}::Count)); @@ -1096,7 +1160,6 @@ namespace {{ Component.attrib['Namespace'] }} replicationRecord.m_autonomousToAuthority.Resize(autonomousToAuthorityStart + static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Autonomous', 'Authority') }}::Count)); return AZStd::unique_ptr<{{ RecordName }}>(new {{ RecordName }}(replicationRecord, - authorityToAuthorityStart, authorityToClientStart, authorityToServerStart, authorityToAutonomousStart, @@ -1106,7 +1169,6 @@ namespace {{ Component.attrib['Namespace'] }} bool {{ RecordName }}::CanAttachRecord(Multiplayer::ReplicationRecord& replicationRecord) { bool canAttach{ true }; - canAttach &= replicationRecord.ContainsAuthorityToAuthorityBits() ? (replicationRecord.GetRemainingAuthorityToAuthorityBits() >= static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Authority') }}::Count)) : true; canAttach &= replicationRecord.ContainsAuthorityToClientBits() ? (replicationRecord.GetRemainingAuthorityToClientBits() >= static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Client') }}::Count)) : true; canAttach &= replicationRecord.ContainsAuthorityToServerBits() ? (replicationRecord.GetRemainingAuthorityToServerBits() >= static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Server') }}::Count)) : true; canAttach &= replicationRecord.ContainsAuthorityToAutonomousBits() ? (replicationRecord.GetRemainingAuthorityToAutonomousBits() >= static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Autonomous') }}::Count)) : true; @@ -1116,9 +1178,6 @@ namespace {{ Component.attrib['Namespace'] }} {{ RecordName }} {{ RecordName }}::AttachRecord(Multiplayer::ReplicationRecord& replicationRecord) { - uint32_t authorityToAuthorityStart = replicationRecord.m_authorityToAuthorityConsumedBits; - replicationRecord.ConsumeAuthorityToAuthorityBits(static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Authority') }}::Count)); - uint32_t authorityToClientStart = replicationRecord.m_authorityToClientConsumedBits; replicationRecord.ConsumeAuthorityToClientBits(static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Authority', 'Client') }}::Count)); @@ -1132,7 +1191,6 @@ namespace {{ Component.attrib['Namespace'] }} replicationRecord.ConsumeAutonomousToAuthorityBits(static_cast({{ AutoComponentMacros.GetNetPropertiesDirtyEnumName(ComponentName, 'Autonomous', 'Authority') }}::Count)); return {{ RecordName }}(replicationRecord, - authorityToAuthorityStart, authorityToClientStart, authorityToServerStart, authorityToAutonomousStart, @@ -1142,9 +1200,9 @@ namespace {{ Component.attrib['Namespace'] }} void {{ RecordName }}::SetPredictableBits() { {{ GenerateModelReplicationRecordPredictableBits(Component, ClassType, 'Authority', 'Client')|indent(8) -}} -{{ GenerateModelReplicationRecordPredictableBits(Component, ClassType, 'Authority', 'Server')|indent(8) -}} -{{ GenerateModelReplicationRecordPredictableBits(Component, ClassType, 'Authority', 'Autonomous')|indent(8) -}} -{{ GenerateModelReplicationRecordPredictableBits(Component, ClassType, 'Autonomous', 'Authority')|indent(8) }} + {{ GenerateModelReplicationRecordPredictableBits(Component, ClassType, 'Authority', 'Server')|indent(8) -}} + {{ GenerateModelReplicationRecordPredictableBits(Component, ClassType, 'Authority', 'Autonomous')|indent(8) -}} + {{ GenerateModelReplicationRecordPredictableBits(Component, ClassType, 'Autonomous', 'Authority')|indent(8) }} } {% if NetworkInputCount > 0 %} @@ -1206,23 +1264,22 @@ namespace {{ Component.attrib['Namespace'] }} return static_cast<{{ ComponentName }}&>(GetOwner()); } - {{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Authority', false, ControllerBaseName)|indent(4) -}} -{{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Authority', true, ControllerBaseName)|indent(4) -}} -{{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Server', false, ControllerBaseName)|indent(4) -}} -{{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Server', true, ControllerBaseName)|indent(4) -}} -{{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Client', false, ControllerBaseName)|indent(4) -}} -{{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Client', true, ControllerBaseName)|indent(4) -}} -{{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', false, ControllerBaseName)|indent(4) -}} -{{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', true, ControllerBaseName)|indent(4) -}} -{{ DefineNetworkPropertyAccessors(Component, 'Autonomous', 'Authority', false, ControllerBaseName)|indent(4) -}} -{{ DefineNetworkPropertyAccessors(Component, 'Autonomous', 'Authority', true, ControllerBaseName)|indent(4) }} + {{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Server', false, ControllerBaseName)|indent(4) -}} + {{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Server', true, ControllerBaseName)|indent(4) -}} + {{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Client', false, ControllerBaseName)|indent(4) -}} + {{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Client', true, ControllerBaseName)|indent(4) -}} + {{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', false, ControllerBaseName)|indent(4) -}} + {{ DefineNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', true, ControllerBaseName)|indent(4) -}} + {{ DefineNetworkPropertyAccessors(Component, 'Autonomous', 'Authority', false, ControllerBaseName)|indent(4) -}} + {{ DefineNetworkPropertyAccessors(Component, 'Autonomous', 'Authority', true, ControllerBaseName)|indent(4) }} {{ DefineArchetypePropertyGets(Component, ClassType, ControllerBaseName, "GetParent().")|indent(4) -}} -{{ DefineRpcInvocations(Component, ControllerBaseName, 'Autonomous', 'Authority', false)|indent(4) -}} -{{ DefineRpcInvocations(Component, ControllerBaseName, 'Autonomous', 'Authority', true)|indent(4) -}} -{{ DefineRpcInvocations(Component, ControllerBaseName, 'Authority', 'Autonomous', false)|indent(4) -}} -{{ DefineRpcInvocations(Component, ControllerBaseName, 'Authority', 'Autonomous', true)|indent(4) -}} -{{ DefineRpcInvocations(Component, ControllerBaseName, 'Authority', 'Client', false)|indent(4) -}} -{{ DefineRpcInvocations(Component, ControllerBaseName, 'Authority', 'Client', true)|indent(4) }} + {{ DefineRpcInvocations(Component, ControllerBaseName, 'Autonomous', 'Authority', false)|indent(4) -}} + {{ DefineRpcInvocations(Component, ControllerBaseName, 'Autonomous', 'Authority', true)|indent(4) -}} + {{ DefineRpcInvocations(Component, ControllerBaseName, 'Authority', 'Autonomous', false)|indent(4) -}} + {{ DefineRpcInvocations(Component, ControllerBaseName, 'Authority', 'Autonomous', true)|indent(4) -}} + {{ DefineRpcInvocations(Component, ControllerBaseName, 'Authority', 'Client', false)|indent(4) -}} + {{ DefineRpcInvocations(Component, ControllerBaseName, 'Authority', 'Client', true)|indent(4) }} + {% for Service in Component.iter('ComponentRelation') %} {% if (Service.attrib['HasController']|booleanTrue) and (Service.attrib['Constraint'] != 'Incompatible') %} {{ Service.attrib['Namespace'] }}::{{ Service.attrib['Name'] }}Controller* {{ ControllerBaseName }}::Get{{ Service.attrib['Name'] }}Controller() @@ -1241,11 +1298,10 @@ namespace {{ Component.attrib['Namespace'] }} { serializeContext->Class<{{ ComponentBaseName }}, Multiplayer::MultiplayerComponent>() ->Version(1) - {{ DefineNetworkPropertyReflection(Component, 'Authority', 'Authority', ComponentBaseName)|indent(16) -}} -{{ DefineNetworkPropertyReflection(Component, 'Authority', 'Server', ComponentBaseName)|indent(16) -}} -{{ DefineNetworkPropertyReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(16) -}} -{{ DefineNetworkPropertyReflection(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(16) -}} -{{ DefineNetworkPropertyReflection(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(16) }} + {{ DefineNetworkPropertyReflection(Component, 'Authority', 'Server', ComponentBaseName)|indent(16) -}} + {{ DefineNetworkPropertyReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(16) -}} + {{ DefineNetworkPropertyReflection(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(16) -}} + {{ DefineNetworkPropertyReflection(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(16) }} {{ DefineArchetypePropertyReflection(Component, ComponentBaseName)|indent(16) }}; } ReflectToEditContext(context); @@ -1264,11 +1320,10 @@ namespace {{ Component.attrib['Namespace'] }} ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "{{ Component.attrib['Namespace'] }}") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) - {{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Authority', ComponentBaseName)|indent(20) -}} -{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Server', ComponentBaseName)|indent(20) -}} -{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(20) -}} -{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(20) -}} -{{ DefineNetworkPropertyEditReflection(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(20) }} + {{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Server', ComponentBaseName)|indent(20) -}} + {{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(20) -}} + {{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(20) -}} + {{ DefineNetworkPropertyEditReflection(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(20) }} {{ DefineArchetypePropertyEditReflection(Component, ComponentBaseName)|indent(20) }}; {% if ComponentDerived %} @@ -1286,18 +1341,34 @@ namespace {{ Component.attrib['Namespace'] }} AZ::BehaviorContext* behaviorContext = azrtti_cast(context); if (behaviorContext) { + {{ ReflectRpcEventDescs(Component, ComponentName, 'Server', 'Authority')|indent(4) -}} + {{ ReflectRpcEventDescs(Component, ComponentName, 'Autonomous', 'Authority')|indent(4) -}} + {{ ReflectRpcEventDescs(Component, ComponentName, 'Authority', 'Autonomous')|indent(4) -}} + {{ ReflectRpcEventDescs(Component, ComponentName, 'Authority', 'Client')|indent(4) }} + behaviorContext->Class<{{ ComponentName }}>("{{ ComponentName }}") ->Attribute(AZ::Script::Attributes::Module, "{{ LowerFirst(Component.attrib['Namespace']) }}") ->Attribute(AZ::Script::Attributes::Category, "{{ UpperFirst(Component.attrib['Namespace']) }}") - // Reflect Network Properties Get, Set, and OnChanged methods - {{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Authority', ComponentName) | indent(16) -}} - {{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Server', ComponentName) | indent(16) -}} - {{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Client', ComponentName) | indent(16) -}} - {{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Autonomous', ComponentName) | indent(16) -}} - {{ DefineNetworkPropertyBehaviorReflection(Component, 'Autonomous', 'Authority', ComponentName) | indent(16) -}} - {{- DefineArchetypePropertyBehaviorReflection(Component, ComponentName) | indent(16) }} - ; + // Reflect Network Properties Get, Set, and OnChanged methods + {{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Authority', ComponentName) | indent(16) -}} + {{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Server', ComponentName) | indent(16) -}} + {{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Client', ComponentName) | indent(16) -}} + {{ DefineNetworkPropertyBehaviorReflection(Component, 'Authority', 'Autonomous', ComponentName) | indent(16) -}} + {{ DefineNetworkPropertyBehaviorReflection(Component, 'Autonomous', 'Authority', ComponentName) | indent(16) -}} + + // Reflect RPCs + {{ ReflectRpcInvocations(Component, ComponentName, 'Server', 'Authority')|indent(4) -}} + {{ ReflectRpcInvocations(Component, ComponentName, 'Autonomous', 'Authority')|indent(4) -}} + {{ ReflectRpcInvocations(Component, ComponentName, 'Authority', 'Autonomous')|indent(4) -}} + {{ ReflectRpcInvocations(Component, ComponentName, 'Authority', 'Client')|indent(4) -}} + {{ ReflectRpcEvents(Component, ComponentName, 'Server', 'Authority')|indent(4) -}} + {{ ReflectRpcEvents(Component, ComponentName, 'Autonomous', 'Authority')|indent(4) -}} + {{ ReflectRpcEvents(Component, ComponentName, 'Authority', 'Autonomous')|indent(4) -}} + {{ ReflectRpcEvents(Component, ComponentName, 'Authority', 'Client')|indent(4) -}} + + {{- DefineArchetypePropertyBehaviorReflection(Component, ComponentName) | indent(16) }} + ; } } @@ -1385,17 +1456,15 @@ namespace {{ Component.attrib['Namespace'] }} {% endcall %} } - {{ DefineNetworkPropertyGets(Component, 'Authority', 'Authority', false, ComponentBaseName)|indent(4) -}} -{{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', false, ComponentBaseName)|indent(4) -}} + {{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', false, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Autonomous', false, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Autonomous', 'Authority', false, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Client', false, ComponentBaseName)|indent(4) -}} -{{ DefineNetworkPropertyGets(Component, 'Authority', 'Authority', true, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', true, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Autonomous', true, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Autonomous', 'Authority', true, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Client', true, ComponentBaseName)|indent(4) }} - {{ DefineArchetypePropertyGets(Component, ClassType, ComponentBaseName)|indent(4) -}} +{{ DefineArchetypePropertyGets(Component, ClassType, ComponentBaseName)|indent(4) -}} {{ DefineRpcInvocations(Component, ComponentBaseName, 'Server', 'Authority', false)|indent(4) -}} {{ DefineRpcInvocations(Component, ComponentBaseName, 'Server', 'Authority', true)|indent(4) }} @@ -1448,10 +1517,6 @@ namespace {{ Component.attrib['Namespace'] }} {{ RecordName }} record = {{ RecordName }}::AttachRecord(replicationRecord); - if (replicationRecord.ContainsAuthorityToAuthorityBits()) - { - SerializeAuthorityToAuthorityProperties(record, serializer); - } if (replicationRecord.ContainsAuthorityToClientBits()) { SerializeAuthorityToClientProperties(record, serializer); @@ -1527,8 +1592,7 @@ namespace {{ Component.attrib['Namespace'] }} void {{ ComponentBaseName }}::NetworkAttach(Multiplayer::NetBindComponent* netBindComponent, Multiplayer::ReplicationRecord& currentEntityRecord, Multiplayer::ReplicationRecord& predictableEntityRecord) { m_netBindComponent = netBindComponent; - {{ DefineNetworkPropertyEditConstruction(Component, 'Authority', 'Authority', ComponentBaseName)|indent(8) -}} -{{ DefineNetworkPropertyEditConstruction(Component, 'Authority', 'Server', ComponentBaseName)|indent(8) -}} + {{ DefineNetworkPropertyEditConstruction(Component, 'Authority', 'Server', ComponentBaseName)|indent(8) -}} {{ DefineNetworkPropertyEditConstruction(Component, 'Authority', 'Client', ComponentBaseName)|indent(8) -}} {{ DefineNetworkPropertyEditConstruction(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(8) -}} {{ DefineNetworkPropertyEditConstruction(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(8) }} @@ -1541,8 +1605,6 @@ namespace {{ Component.attrib['Namespace'] }} m_controller.get()->NetworkAttach(netBindComponent, predictableEntityRecord); } - {{ DeclareNetworkPropertySetSerializer(Component, 'Authority', 'Authority', ComponentBaseName, RecordName)|indent(4) }} - {{ DeclareNetworkPropertySetNotifyChanges(Component, 'Authority', 'Authority', ComponentBaseName, RecordName)|indent(4) }} {{ DeclareNetworkPropertySetSerializer(Component, 'Authority', 'Server', ComponentBaseName, RecordName)|indent(4) }} {{ DeclareNetworkPropertySetNotifyChanges(Component, 'Authority', 'Server', ComponentBaseName, RecordName)|indent(4) }} {{ DeclareNetworkPropertySetSerializer(Component, 'Authority', 'Client', ComponentBaseName, RecordName)|indent(4) }} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml index 94bdac2b5d..b6edd0e3be 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml @@ -17,20 +17,20 @@ - + - + - + - + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index a1e68aa708..642832805d 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -28,12 +28,6 @@ - - - - - - @@ -49,15 +43,4 @@ - - - - - - - - - - - diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp index 8542288b23..21ae9d9f01 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp @@ -24,6 +24,80 @@ namespace Multiplayer serializeContext->Class() ->Version(1); } + + AZ::BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class("MultiplayerComponent") + ->Attribute(AZ::Script::Attributes::Module, "multiplayer") + ->Attribute(AZ::Script::Attributes::Category, "Multiplayer") + + ->Method("IsAuthority", [](AZ::EntityId id) -> bool { + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(id); + if (!entity) + { + AZ_Warning( "MultiplayerComponent", false, "MultiplayerComponent IsAuthority failed. The entity with id %s doesn't exist, please provide a valid entity id.", id.ToString().c_str()) + return false; + } + + MultiplayerComponent* multiplayerComponent = entity->FindComponent(); + if (!multiplayerComponent) + { + AZ_Warning( "MultiplayerComponent", false, "MultiplayerComponent IsAuthority failed. Entity '%s' (id: %s) is missing a MultiplayerComponent, make sure this entity contains a component which derives from MultiplayerComponent.", entity->GetName().c_str(), id.ToString().c_str()) + return false; + } + return multiplayerComponent->IsAuthority(); + }) + ->Method("IsAutonomous", [](AZ::EntityId id) -> bool { + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(id); + if (!entity) + { + AZ_Warning( "MultiplayerComponent", false, "MultiplayerComponent IsAutonomous failed. The entity with id %s doesn't exist, please provide a valid entity id.", id.ToString().c_str()) + return false; + } + + MultiplayerComponent* multiplayerComponent = entity->FindComponent(); + if (!multiplayerComponent) + { + AZ_Warning("MultiplayerComponent", false, "MultiplayerComponent IsAutonomous failed. Entity '%s' (id: %s) is missing a MultiplayerComponent, make sure this entity contains a component which derives from MultiplayerComponent.", entity->GetName().c_str(), id.ToString().c_str()) + return false; + } + return multiplayerComponent->IsAutonomous(); + }) + ->Method("IsClient", [](AZ::EntityId id) -> bool { + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(id); + if (!entity) + { + AZ_Warning( "MultiplayerComponent", false, "MultiplayerComponent IsClient failed. The entity with id %s doesn't exist, please provide a valid entity id.", id.ToString().c_str()) + return false; + } + + MultiplayerComponent* multiplayerComponent = entity->FindComponent(); + if (!multiplayerComponent) + { + AZ_Warning("MultiplayerComponent", false, "MultiplayerComponent IsClient failed. Entity '%s' (id: %s) is missing a MultiplayerComponent, make sure this entity contains a component which derives from MultiplayerComponent.", entity->GetName().c_str(), id.ToString().c_str()) + return false; + } + return multiplayerComponent->IsClient(); + }) + ->Method("IsServer", [](AZ::EntityId id) -> bool { + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(id); + if (!entity) + { + AZ_Warning( "MultiplayerComponent", false, "MultiplayerComponent IsServer failed. The entity with id %s doesn't exist, please provide a valid entity id.", id.ToString().c_str()) + return false; + } + + MultiplayerComponent* multiplayerComponent = entity->FindComponent(); + if (!multiplayerComponent) + { + AZ_Warning("MultiplayerComponent", false, "MultiplayerComponent IsServer failed. Entity '%s' (id: %s) is missing a MultiplayerComponent, make sure this entity contains a component which derives from MultiplayerComponent.", entity->GetName().c_str(), id.ToString().c_str()) + return false; + } + return multiplayerComponent->IsServer(); + }) + ; + } } void MultiplayerComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index e48d0b0d09..adc369e9ed 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -154,12 +154,18 @@ namespace Multiplayer void NetBindComponent::SetOwningConnectionId(AzNetworking::ConnectionId connectionId) { + m_owningConnectionId = connectionId; for (MultiplayerComponent* multiplayerComponent : m_multiplayerInputComponentVector) { multiplayerComponent->SetOwningConnectionId(connectionId); } } + AzNetworking::ConnectionId NetBindComponent::GetOwningConnectionId() const + { + return m_owningConnectionId; + } + void NetBindComponent::SetAllowAutonomy(bool value) { // This flag allows a player host to autonomously control their player entity, even though the entity is in an authority role @@ -290,6 +296,11 @@ namespace Multiplayer m_localNotificationRecord.Clear(); } + void NetBindComponent::NotifySyncRewindState() + { + m_syncRewindEvent.Signal(); + } + void NetBindComponent::NotifyMigrationStart(ClientInputId migratedInputId) { m_entityMigrationStartEvent.Signal(migratedInputId); @@ -315,6 +326,11 @@ namespace Multiplayer eventHandler.Connect(m_dirtiedEvent); } + void NetBindComponent::AddEntitySyncRewindEventHandler(EntitySyncRewindEvent::Handler& eventHandler) + { + eventHandler.Connect(m_syncRewindEvent); + } + void NetBindComponent::AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler) { eventHandler.Connect(m_entityMigrationStartEvent); diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index 4ae7c3fdfe..88029430c3 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include namespace Multiplayer @@ -233,6 +235,43 @@ namespace Multiplayer const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + if (m_displayNetworkingStats) + { + if (ImGui::Begin("Networking Stats", &m_displayNetworkingStats, ImGuiWindowFlags_None)) + { + AzNetworking::INetworking* networking = AZ::Interface::Get(); + + ImGui::Text("Total sockets monitored by TcpListenThread: %u", networking->GetTcpListenThreadSocketCount()); + ImGui::Text("Total time spent updating TcpListenThread: %lld", aznumeric_cast(networking->GetTcpListenThreadUpdateTime())); + ImGui::Text("Total sockets monitored by UdpReaderThread: %u", networking->GetUdpReaderThreadSocketCount()); + ImGui::Text("Total time spent updating UdpReaderThread: %lld", aznumeric_cast(networking->GetUdpReaderThreadUpdateTime())); + + for (auto& networkInterface : networking->GetNetworkInterfaces()) + { + const char* protocol = networkInterface.second->GetType() == AzNetworking::ProtocolType::Tcp ? "Tcp" : "Udp"; + const char* trustZone = networkInterface.second->GetTrustZone() == AzNetworking::TrustZone::ExternalClientToServer ? "ExternalClientToServer" : "InternalServerToServer"; + const uint32_t port = aznumeric_cast(networkInterface.second->GetPort()); + ImGui::Text("%sNetworkInterface: %s - open to %s on port %u", protocol, networkInterface.second->GetName().GetCStr(), trustZone, port); + + const AzNetworking::NetworkInterfaceMetrics& metrics = networkInterface.second->GetMetrics(); + ImGui::Text(" - Total time spent updating in milliseconds: %lld", aznumeric_cast(metrics.m_updateTimeMs)); + ImGui::Text(" - Total number of connections: %llu", aznumeric_cast(metrics.m_connectionCount)); + ImGui::Text(" - Total send time in milliseconds: %lld", aznumeric_cast(metrics.m_sendTimeMs)); + ImGui::Text(" - Total sent packets: %llu", aznumeric_cast(metrics.m_sendPackets)); + ImGui::Text(" - Total sent bytes after compression: %llu", aznumeric_cast(metrics.m_sendBytes)); + ImGui::Text(" - Total sent bytes before compression: %llu", aznumeric_cast(metrics.m_sendBytesUncompressed)); + ImGui::Text(" - Total sent compressed packets without benefit: %llu", aznumeric_cast(metrics.m_sendCompressedPacketsNoGain)); + ImGui::Text(" - Total gain from packet compression: %lld", aznumeric_cast(metrics.m_sendBytesCompressedDelta)); + ImGui::Text(" - Total packets resent: %llu", aznumeric_cast(metrics.m_resentPackets)); + ImGui::Text(" - Total receive time in milliseconds: %lld", aznumeric_cast(metrics.m_recvTimeMs)); + ImGui::Text(" - Total received packets: %llu", aznumeric_cast(metrics.m_recvPackets)); + ImGui::Text(" - Total received bytes after compression: %llu", aznumeric_cast(metrics.m_recvBytes)); + ImGui::Text(" - Total received bytes before compression: %llu", aznumeric_cast(metrics.m_recvBytesUncompressed)); + ImGui::Text(" - Total packets discarded due to load: %llu", aznumeric_cast(metrics.m_discardedPackets)); + } + } + } + if (m_displayMultiplayerStats) { if (ImGui::Begin("Multiplayer Stats", &m_displayMultiplayerStats, ImGuiWindowFlags_None)) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 829fe7e495..523ffd90de 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -113,6 +113,10 @@ namespace Multiplayer { editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient); } + if (auto console = AZ::Interface::Get(); console) + { + console->PerformCommand("disconnect"); + } break; } } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index aa6fe6e72c..ef8627fe54 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -163,9 +163,13 @@ namespace Multiplayer // Handle deferred local rpc messages that were generated during the updates m_networkEntityManager.DispatchLocalDeferredRpcMessages(); - m_networkEntityManager.NotifyEntitiesChanged(); + + // INetworking ticks immediately before IMultiplayer, so all our pending RPC's and network property updates have now been processed + // Restore any entities that were rewound during input processing so that normal gameplay updates have the correct state + Multiplayer::GetNetworkTime()->ClearRewoundEntities(); // Let the network system know the frame is done and we can collect dirty bits + m_networkEntityManager.NotifyEntitiesChanged(); m_networkEntityManager.NotifyEntitiesDirtied(); MultiplayerStats& stats = GetStats(); @@ -304,25 +308,32 @@ namespace Multiplayer bool MultiplayerSystemComponent::HandleRequest ( - [[maybe_unused]] AzNetworking::IConnection* connection, - [[maybe_unused]] const IPacketHeader& packetHeader, - [[maybe_unused]] MultiplayerPackets::SyncConsole& packet + AzNetworking::IConnection* connection, + [[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader, + MultiplayerPackets::ReadyForEntityUpdates& packet ) { - ExecuteConsoleCommandList(connection, packet.GetCommandSet()); - return true; + IConnectionData* connectionData = reinterpret_cast(connection->GetUserData()); + if (connectionData) + { + connectionData->SetCanSendUpdates(packet.GetReadyForEntityUpdates()); + return true; + } + return false; } bool MultiplayerSystemComponent::HandleRequest ( [[maybe_unused]] AzNetworking::IConnection* connection, [[maybe_unused]] const IPacketHeader& packetHeader, - [[maybe_unused]] MultiplayerPackets::ConsoleCommand& packet + [[maybe_unused]] MultiplayerPackets::SyncConsole& packet ) { - const bool isAcceptor = (connection->GetConnectionRole() == ConnectionRole::Acceptor); // We're hosting if we accepted the connection - const AZ::ConsoleFunctorFlags requiredSet = isAcceptor ? AZ::ConsoleFunctorFlags::AllowClientSet : AZ::ConsoleFunctorFlags::Null; - AZ::Interface::Get()->PerformCommand(packet.GetCommand().c_str(), AZ::ConsoleSilentMode::NotSilent, AZ::ConsoleInvokedFrom::AzNetworking, requiredSet); + if (GetAgentType() != MultiplayerAgentType::Client) + { + return false; + } + ExecuteConsoleCommandList(connection, packet.GetCommandSet()); return true; } @@ -330,10 +341,12 @@ namespace Multiplayer ( [[maybe_unused]] AzNetworking::IConnection* connection, [[maybe_unused]] const IPacketHeader& packetHeader, - [[maybe_unused]] MultiplayerPackets::SyncConnectionCvars& packet + [[maybe_unused]] MultiplayerPackets::ConsoleCommand& packet ) { - connection->SetConnectionQuality(ConnectionQuality(packet.GetLossPercent(), packet.GetLatencyMs(), packet.GetVarianceMs())); + const bool isClient = (GetAgentType() == MultiplayerAgentType::Client); + const AZ::ConsoleFunctorFlags requiredSet = isClient ? AZ::ConsoleFunctorFlags::Null : AZ::ConsoleFunctorFlags::AllowClientSet; + AZ::Interface::Get()->PerformCommand(packet.GetCommand().c_str(), AZ::ConsoleSilentMode::NotSilent, AZ::ConsoleInvokedFrom::AzNetworking, requiredSet); return true; } @@ -397,39 +410,6 @@ namespace Multiplayer return false; } - bool MultiplayerSystemComponent::HandleRequest - ( - [[maybe_unused]] AzNetworking::IConnection* connection, - [[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader, - [[maybe_unused]] MultiplayerPackets::NotifyClientMigration& packet - ) - { - return false; - } - - bool MultiplayerSystemComponent::HandleRequest - ( - [[maybe_unused]] AzNetworking::IConnection* connection, - [[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader, - [[maybe_unused]] MultiplayerPackets::EntityMigration& packet - ) - { - return false; - } - - bool MultiplayerSystemComponent::HandleRequest( AzNetworking::IConnection* connection, - [[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet) - { - IConnectionData* connectionData = reinterpret_cast(connection->GetUserData()); - if (connectionData) - { - connectionData->SetCanSendUpdates(packet.GetReadyForEntityUpdates()); - return true; - } - - return false; - } - ConnectResult MultiplayerSystemComponent::ValidateConnect ( [[maybe_unused]] const IpAddress& remoteAddress, @@ -458,44 +438,36 @@ namespace Multiplayer m_connAcquiredEvent.Signal(datum); } - if (m_onConnectFunctor) + if (GetAgentType() == MultiplayerAgentType::ClientServer + || GetAgentType() == MultiplayerAgentType::DedicatedServer) { - // Default OnConnect behaviour has been overridden - m_onConnectFunctor(connection, datum); + PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast(sv_defaultPlayerSpawnAsset).c_str()), 1); + INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity()); + + NetworkEntityHandle controlledEntity; + if (entityList.size() > 0) + { + controlledEntity = entityList[0]; + controlledEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId()); + } + + if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so + { + connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); + } + + AZStd::unique_ptr window = AZStd::make_unique(controlledEntity, connection); + reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); } else { - if (GetAgentType() == MultiplayerAgentType::ClientServer - || GetAgentType() == MultiplayerAgentType::DedicatedServer) + if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so { - PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast(sv_defaultPlayerSpawnAsset).c_str()), 1); - INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity()); - - NetworkEntityHandle controlledEntity; - if (entityList.size() > 0) - { - controlledEntity = entityList[0]; - controlledEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId()); - } - - if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so - { - connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); - } - - AZStd::unique_ptr window = AZStd::make_unique(controlledEntity, connection); - reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); + connection->SetUserData(new ClientToServerConnectionData(connection, *this)); } - else - { - if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so - { - connection->SetUserData(new ClientToServerConnectionData(connection, *this)); - } - AZStd::unique_ptr window = AZStd::make_unique(); - reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetEntityActivationTimeSliceMs(cl_defaultNetworkEntityActivationTimeSliceMs); - } + AZStd::unique_ptr window = AZStd::make_unique(); + reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetEntityActivationTimeSliceMs(cl_defaultNetworkEntityActivationTimeSliceMs); } } @@ -568,11 +540,6 @@ namespace Multiplayer handler.Connect(m_shutdownEvent); } - void MultiplayerSystemComponent::SetOnConnectFunctor(const OnConnectFunctor& functor) - { - m_onConnectFunctor = functor; - } - void MultiplayerSystemComponent::SendReadyForEntityUpdates(bool readyForEntityUpdates) { IConnectionSet& connectionSet = m_networkInterface->GetConnectionSet(); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index 1410a82a3c..db83c50fb5 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -66,15 +66,12 @@ namespace Multiplayer bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::Connect& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::Accept& packet); + bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::SyncConsole& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ConsoleCommand& packet); - bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::SyncConnectionCvars& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityUpdates& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityRpcs& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ClientMigration& packet); - bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::NotifyClientMigration& packet); - bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityMigration& packet); - bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet); //! IConnectionListener interface //! @{ @@ -92,7 +89,6 @@ namespace Multiplayer void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override; void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; - void SetOnConnectFunctor(const OnConnectFunctor& functor) override; void SendReadyForEntityUpdates(bool readyForEntityUpdates) override; AZ::TimeMs GetCurrentHostTimeMs() const override; INetworkTime* GetNetworkTime() override; @@ -124,8 +120,6 @@ namespace Multiplayer SessionShutdownEvent m_shutdownEvent; ConnectionAcquiredEvent m_connAcquiredEvent; - OnConnectFunctor m_onConnectFunctor = nullptr; - AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 }; HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index ba8158ca18..bd30c5e37f 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -656,7 +656,7 @@ namespace Multiplayer { case Mode::LocalServerToRemoteClient: { - // don't trust the client by default + // Don't trust the client by default result = UpdateValidationResult::DropMessageAndDisconnect; // Clients sending data must have a replicator and be sending in the correct mode, further, they must have a replicator and can never delete a replicator if (updateMessage.GetNetworkRole() == NetEntityRole::Authority && entityReplicator && !updateMessage.GetIsDelete()) @@ -671,7 +671,7 @@ namespace Multiplayer } else { - // we can process this + // We can process this result = UpdateValidationResult::HandleMessage; } } // If we've migrated the entity away from the server, but we get this late, just drop it @@ -699,7 +699,7 @@ namespace Multiplayer case Mode::LocalServerToRemoteServer: { AZ_Assert(updateMessage.GetNetworkRole() == NetEntityRole::Server || updateMessage.GetIsDelete(), "Unexpected update type coming from peer server"); - // trust messages from a peer server by default + // Trust messages from a peer server by default result = UpdateValidationResult::HandleMessage; // If we have a replicator, make sure we're in the correct state if (entityReplicator) @@ -782,7 +782,7 @@ namespace Multiplayer PrefabEntityId prefabEntityId; if (updateMessage.GetHasValidPrefabId()) { - // If the update packet contained a sliceEntryId, use that directly + // If the update packet contained a PrefabEntityId, use that directly prefabEntityId = updateMessage.GetPrefabEntityId(); } else @@ -940,7 +940,7 @@ namespace Multiplayer { const ReplicationSet& newWindow = m_replicationWindow->GetReplicationSet(); - // walk both for adds and removals + // Walk both for adds and removals auto newWindowIter = newWindow.begin(); auto currWindowIter = m_entityReplicatorMap.begin(); while (newWindowIter != newWindow.end() && currWindowIter != m_entityReplicatorMap.end()) @@ -959,9 +959,9 @@ namespace Multiplayer } ++currWindowIter; } - else // same entity + else // Same entity { - // check if we changed modes + // Check if we changed modes EntityReplicator* currReplicator = currWindowIter->second.get(); if (currReplicator->GetRemoteNetworkRole() != newWindowIter->second.m_netEntityRole) { @@ -973,14 +973,14 @@ namespace Multiplayer } } - // do remaining adds + // Do remaining adds while (newWindowIter != newWindow.end()) { AddEntityReplicator(newWindowIter->first, newWindowIter->second.m_netEntityRole); ++newWindowIter; } - // do remaining removes + // Do remaining removes while (currWindowIter != m_entityReplicatorMap.end()) { EntityReplicator* currReplicator = currWindowIter->second.get(); @@ -1028,13 +1028,13 @@ namespace Multiplayer const EntityReplicator* entityReplicator = GetEntityReplicator(entityHandle.GetNetEntityId()); hasAuthority = (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority); // Make sure someone hasn't migrated this already - isInDomain = (m_remoteEntityDomain && m_remoteEntityDomain->IsInDomain(entityHandle)); // Make sure the remote side would want it + isInDomain = (m_remoteEntityDomain && m_remoteEntityDomain->IsInDomain(entityHandle)); // Make sure the remote side would want it if (entityReplicator && entityReplicator->GetBoundLocalNetworkRole() == NetEntityRole::Authority) { - isMarkedForRemoval = entityReplicator->IsMarkedForRemoval(); // Make sure we aren't telling the other side to remove the replicator + isMarkedForRemoval = entityReplicator->IsMarkedForRemoval(); // Make sure we aren't telling the other side to remove the replicator const PropertyPublisher* propertyPublisher = entityReplicator->GetPropertyPublisher(); AZ_Assert(propertyPublisher, "Expected to have a property publisher"); - isRemoteReplicatorEstablished = propertyPublisher->IsRemoteReplicatorEstablished(); // Make sure they are setup to receive the replicator + isRemoteReplicatorEstablished = propertyPublisher->IsRemoteReplicatorEstablished(); // Make sure they are setup to receive the replicator } return hasAuthority && isInDomain && !isMarkedForRemoval && isRemoteReplicatorEstablished; @@ -1094,9 +1094,9 @@ namespace Multiplayer } bool didSucceed = true; - MultiplayerPackets::EntityMigration message; - message.SetEntityId(replicator->GetEntityHandle().GetNetEntityId()); - message.SetPrefabEntityId(netBindComponent->GetPrefabEntityId()); + EntityMigrationMessage message; + message.m_entityId = replicator->GetEntityHandle().GetNetEntityId(); + message.m_prefabEntityId = netBindComponent->GetPrefabEntityId(); if (localEnt->GetState() == AZ::Entity::State::Active) { @@ -1110,17 +1110,18 @@ namespace Multiplayer // Send an update packet if it needs one propPublisher->GenerateRecord(); bool needsNetworkPropertyUpdate = propPublisher->PrepareSerialization(); - AzNetworking::NetworkInputSerializer inputSerializer(message.ModifyPropertyUpdateData().GetBuffer(), message.ModifyPropertyUpdateData().GetCapacity()); + AzNetworking::NetworkInputSerializer inputSerializer(message.m_propertyUpdateData.GetBuffer(), message.m_propertyUpdateData.GetCapacity()); if (needsNetworkPropertyUpdate) { - // write out entity state into the buffer + // Write out entity state into the buffer propPublisher->UpdateSerialization(inputSerializer); } didSucceed &= inputSerializer.IsValid(); - message.ModifyPropertyUpdateData().Resize(inputSerializer.GetSize()); + message.m_propertyUpdateData.Resize(inputSerializer.GetSize()); } AZ_Assert(didSucceed, "Failed to migrate entity from server"); - m_connection.SendReliablePacket(message); + // TODO: Move this to an event + //m_connection.SendReliablePacket(message); AZLOG(NET_RepDeletes, "Migration packet sent %u to remote manager id %d", netEntityId, aznumeric_cast(GetRemoteHostId())); // Immediately add a new replicator so that we catch RPC invocations, the remote side will make us a new one, and then remove us if needs be @@ -1128,21 +1129,21 @@ namespace Multiplayer } } - bool EntityReplicationManager::HandleMessage([[maybe_unused]] AzNetworking::IConnection* invokingConnection, MultiplayerPackets::EntityMigration& message) + bool EntityReplicationManager::HandleEntityMigration([[maybe_unused]] AzNetworking::IConnection* invokingConnection, EntityMigrationMessage& message) { - EntityReplicator* replicator = GetEntityReplicator(message.GetEntityId()); + EntityReplicator* replicator = GetEntityReplicator(message.m_entityId); { - if (message.GetPropertyUpdateData().GetSize() > 0) + if (message.m_propertyUpdateData.GetSize() > 0) { - AzNetworking::TrackChangedSerializer outputSerializer(message.ModifyPropertyUpdateData().GetBuffer(), message.ModifyPropertyUpdateData().GetSize()); + AzNetworking::TrackChangedSerializer outputSerializer(message.m_propertyUpdateData.GetBuffer(), message.m_propertyUpdateData.GetSize()); if (!HandlePropertyChangeMessage ( replicator, AzNetworking::InvalidPacketId, - message.GetEntityId(), + message.m_entityId, NetEntityRole::Server, outputSerializer, - message.GetPrefabEntityId() + message.m_prefabEntityId )) { AZ_Assert(false, "Unable to process network properties during server entity migration"); @@ -1150,10 +1151,10 @@ namespace Multiplayer } } } - // the HandlePropertyChangeMessage will have made a replicator if we didn't have one already + // The HandlePropertyChangeMessage will have made a replicator if we didn't have one already if (!replicator) { - replicator = GetEntityReplicator(message.GetEntityId()); + replicator = GetEntityReplicator(message.m_entityId); } AZ_Assert(replicator, "Do not have replicator after handling migration message"); @@ -1170,7 +1171,7 @@ namespace Multiplayer netBindComponent->ActivateControllers(EntityIsMigrating::True); } - // change the role on the replicator + // Change the role on the replicator AddEntityReplicator(entityHandle, NetEntityRole::Server); AZLOG(NET_RepDeletes, "Handle Migration %u new authority from remote manager id %d", entityHandle.GetNetEntityId(), aznumeric_cast(GetRemoteHostId())); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h index 083413e19e..6172f30e8a 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -26,7 +28,6 @@ #include #include #include -#include namespace AzNetworking { @@ -82,7 +83,7 @@ namespace Multiplayer void AddAutonomousEntityReplicatorCreatedHandle(AZ::Event::Handler& handler); - bool HandleMessage(AzNetworking::IConnection* invokingConnection, MultiplayerPackets::EntityMigration& message); + bool HandleEntityMigration(AzNetworking::IConnection* invokingConnection, EntityMigrationMessage& message); bool HandleEntityDeleteMessage(EntityReplicator* entityReplicator, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage); bool HandleEntityUpdateMessage(AzNetworking::IConnection* invokingConnection, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage); bool HandleEntityRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& message); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp index 41cc86aaee..7fe0efd323 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp @@ -16,14 +16,12 @@ namespace Multiplayer { ReplicationRecordStats::ReplicationRecordStats ( - uint32_t authorityToAuthorityCount, uint32_t authorityToClientCount, uint32_t authorityToServerCount, uint32_t authorityToAutonomousCount, uint32_t autonomousToAuthorityCount ) - : m_authorityToAuthorityCount(authorityToAuthorityCount) - , m_authorityToClientCount(authorityToClientCount) + : m_authorityToClientCount(authorityToClientCount) , m_authorityToServerCount(authorityToServerCount) , m_authorityToAutonomousCount(authorityToAutonomousCount) , m_autonomousToAuthorityCount(autonomousToAuthorityCount) @@ -33,8 +31,7 @@ namespace Multiplayer bool ReplicationRecordStats::operator ==(const ReplicationRecordStats& rhs) const { - return (m_authorityToAuthorityCount == rhs.m_authorityToAuthorityCount) - && (m_authorityToClientCount == rhs.m_authorityToClientCount) + return (m_authorityToClientCount == rhs.m_authorityToClientCount) && (m_authorityToServerCount == rhs.m_authorityToServerCount) && (m_authorityToAutonomousCount == rhs.m_authorityToAutonomousCount) && (m_autonomousToAuthorityCount == rhs.m_autonomousToAuthorityCount); @@ -44,7 +41,6 @@ namespace Multiplayer { return ReplicationRecordStats { - (m_authorityToAuthorityCount - rhs.m_authorityToAuthorityCount), (m_authorityToClientCount - rhs.m_authorityToClientCount), (m_authorityToServerCount - rhs.m_authorityToServerCount), (m_authorityToAutonomousCount - rhs.m_authorityToAutonomousCount), @@ -71,7 +67,6 @@ namespace Multiplayer bool ReplicationRecord::AreAllBitsConsumed() const { bool ret = true; - ret &= m_authorityToAuthorityConsumedBits == m_authorityToAuthority.GetSize(); ret &= m_authorityToClientConsumedBits == m_authorityToClient.GetSize(); ret &= m_authorityToServerConsumedBits == m_authorityToServer.GetSize(); ret &= m_authorityToAutonomousConsumedBits == m_authorityToAutonomous.GetSize(); @@ -81,7 +76,6 @@ namespace Multiplayer void ReplicationRecord::ResetConsumedBits() { - m_authorityToAuthorityConsumedBits = 0; m_authorityToClientConsumedBits = 0; m_authorityToServerConsumedBits = 0; m_authorityToAutonomousConsumedBits = 0; @@ -92,11 +86,7 @@ namespace Multiplayer { ResetConsumedBits(); - uint32_t recordSize = m_authorityToAuthority.GetSize(); - m_authorityToAuthority.Clear(); - m_authorityToAuthority.Resize(recordSize); - - recordSize = m_authorityToClient.GetSize(); + uint32_t recordSize = m_authorityToClient.GetSize(); m_authorityToClient.Clear(); m_authorityToClient.Resize(recordSize); @@ -115,7 +105,6 @@ namespace Multiplayer void ReplicationRecord::Append(const ReplicationRecord &rhs) { - m_authorityToAuthority |= rhs.m_authorityToAuthority; m_authorityToClient |= rhs.m_authorityToClient; m_authorityToServer |= rhs.m_authorityToServer; m_authorityToAutonomous |= rhs.m_authorityToAutonomous; @@ -124,7 +113,6 @@ namespace Multiplayer void ReplicationRecord::Subtract(const ReplicationRecord &rhs) { - m_authorityToAuthority.Subtract(rhs.m_authorityToAuthority); m_authorityToClient.Subtract(rhs.m_authorityToClient); m_authorityToServer.Subtract(rhs.m_authorityToServer); m_authorityToAutonomous.Subtract(rhs.m_authorityToAutonomous); @@ -134,10 +122,6 @@ namespace Multiplayer bool ReplicationRecord::HasChanges() const { bool hasChanges(false); - if (ContainsAuthorityToAuthorityBits()) - { - hasChanges = hasChanges ? hasChanges : m_authorityToAuthority.AnySet(); - } if (ContainsAuthorityToClientBits()) { hasChanges = hasChanges ? hasChanges : m_authorityToClient.AnySet(); @@ -159,10 +143,6 @@ namespace Multiplayer bool ReplicationRecord::Serialize(AzNetworking::ISerializer& serializer) { - if (ContainsAuthorityToAuthorityBits()) - { - serializer.Serialize(m_authorityToAuthority, "AuthorityToAuthorityRecord"); - } if (ContainsAuthorityToClientBits()) { serializer.Serialize(m_authorityToClient, "AuthorityToClientRecord"); @@ -182,14 +162,6 @@ namespace Multiplayer return serializer.IsValid(); } - void ReplicationRecord::ConsumeAuthorityToAuthorityBits(uint32_t consumedBits) - { - if (ContainsAuthorityToAuthorityBits()) - { - m_authorityToAuthorityConsumedBits += consumedBits; - } - } - void ReplicationRecord::ConsumeAuthorityToClientBits(uint32_t consumedBits) { if (ContainsAuthorityToClientBits()) @@ -222,12 +194,6 @@ namespace Multiplayer } } - bool ReplicationRecord::ContainsAuthorityToAuthorityBits() const - { - return (m_netEntityRole == NetEntityRole::Authority) - || (m_netEntityRole == NetEntityRole::InvalidRole); - } - bool ReplicationRecord::ContainsAuthorityToClientBits() const { return (m_netEntityRole != NetEntityRole::Authority) @@ -252,11 +218,6 @@ namespace Multiplayer || (m_netEntityRole == NetEntityRole::InvalidRole); } - uint32_t ReplicationRecord::GetRemainingAuthorityToAuthorityBits() const - { - return m_authorityToAuthorityConsumedBits < m_authorityToAuthority.GetValidBitCount() ? m_authorityToAuthority.GetValidBitCount() - m_authorityToAuthorityConsumedBits : 0; - } - uint32_t ReplicationRecord::GetRemainingAuthorityToClientBits() const { return m_authorityToClientConsumedBits < m_authorityToClient.GetValidBitCount() ? m_authorityToClient.GetValidBitCount() - m_authorityToClientConsumedBits : 0; @@ -281,7 +242,6 @@ namespace Multiplayer { return ReplicationRecordStats { - m_authorityToAuthorityConsumedBits, m_authorityToClientConsumedBits, m_authorityToServerConsumedBits, m_authorityToAutonomousConsumedBits, diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index 98ece0a8cc..f94a8c59d0 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -13,10 +13,15 @@ #include #include #include +#include +#include #include +#include namespace Multiplayer { + AZ_CVAR(float, sv_RewindVolumeExtrudeDistance, 50.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The amount to increase rewind volume checks to account for fast moving entities"); + NetworkTime::NetworkTime() { AZ::Interface::Register(this); @@ -73,35 +78,59 @@ namespace Multiplayer void NetworkTime::SyncEntitiesToRewindState(const AZ::Aabb& rewindVolume) { - // TODO: extrude rewind volume for initial gather - AZStd::vector gatheredEntries; - AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(rewindVolume, [&gatheredEntries](const AzFramework::IVisibilityScene::NodeData& nodeData) + // Since the vis system doesn't support rewound queries, first query with an expanded volume to catch any fast moving entities + const AZ::Aabb expandedVolume = rewindVolume.GetExpanded(AZ::Vector3(sv_RewindVolumeExtrudeDistance)); + + AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface::Get(); + AZStd::vector gatheredEntities; + AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(expandedVolume, + [entityBoundsUnion, rewindVolume, &gatheredEntities](const AzFramework::IVisibilityScene::NodeData& nodeData) { - gatheredEntries.reserve(gatheredEntries.size() + nodeData.m_entries.size()); + gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size()); for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries) { if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) { - // TODO: offset aabb for exact rewound position and check against the non-extruded rewind volume - gatheredEntries.push_back(visEntry); + AZ::Entity* entity = static_cast(visEntry->m_userData); + const AZ::Aabb currentBounds = entityBoundsUnion->GetEntityLocalBoundsUnion(entity->GetId()); + const AZ::Vector3 currentCenter = currentBounds.GetCenter(); + + NetworkTransformComponent* networkTransform = entity->template FindComponent(); + + if (networkTransform != nullptr) + { + const AZ::Vector3 rewindCenter = networkTransform->GetTranslation(); // Get the rewound position + const AZ::Vector3 rewindOffset = rewindCenter - currentCenter; // Compute offset between rewound and current positions + const AZ::Aabb rewoundAabb = currentBounds.GetTranslated(rewindOffset); // Apply offset to the entity aabb + + if (AZ::ShapeIntersection::Overlaps(rewoundAabb, rewindVolume)) // Validate the rewound aabb intersects our rewind volume + { + // Due to component constraints, netBindComponent must exist if networkTransform exists + NetBindComponent* netBindComponent = entity->template FindComponent(); + gatheredEntities.push_back(netBindComponent); + } + } } } }); - for (AzFramework::VisibilityEntry* visEntry : gatheredEntries) + NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker(); + for (NetBindComponent* netBindComponent : gatheredEntities) { - AZ::Entity* entity = static_cast(visEntry->m_userData); - [[maybe_unused]] NetBindComponent* entryNetBindComponent = entity->template FindComponent(); - if (entryNetBindComponent != nullptr) - { - // TODO: invoke the sync to rewind event on the netBindComponent and add the entity to the rewound entity set - } + netBindComponent->NotifySyncRewindState(); + m_rewoundEntities.push_back(NetworkEntityHandle(netBindComponent, networkEntityTracker)); } } void NetworkTime::ClearRewoundEntities() { AZ_Assert(!IsTimeRewound(), "Cannot clear rewound entity state while still within scoped rewind"); - // TODO: iterate all rewound entities, signal them to sync rewind state, and clear the rewound entity set + + for (NetworkEntityHandle entityHandle : m_rewoundEntities) + { + NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent(); + netBindComponent->NotifySyncRewindState(); + } + m_rewoundEntities.clear(); } } diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h index f714e046b3..c36b04be27 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h @@ -13,6 +13,7 @@ #pragma once #include +#include #include #include @@ -42,6 +43,8 @@ namespace Multiplayer private: + AZStd::vector m_rewoundEntities; + HostFrameId m_hostFrameId = HostFrameId{ 0 }; HostFrameId m_unalteredFrameId = HostFrameId{ 0 }; AZ::TimeMs m_hostTimeMs = AZ::TimeMs{ 0 }; diff --git a/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.cpp b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.cpp new file mode 100644 index 0000000000..e8d4948cc9 --- /dev/null +++ b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.cpp @@ -0,0 +1,99 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include "ImageProcessing_precompiled.h" +#include "AtlasBuilderComponent.h" + +#include + +namespace TextureAtlasBuilder +{ + // AZ Components should only initialize their members to null and empty in constructor + // Allocation of data should occur in Init(), once we can guarantee reflection and registration of types + AtlasBuilderComponent::AtlasBuilderComponent() + { + } + + // Handle deallocation of your memory allocated in Init() + AtlasBuilderComponent::~AtlasBuilderComponent() + { + } + + // Init is where you'll actually allocate memory or create objects + // This ensures that any dependency components will have been been created and serialized + void AtlasBuilderComponent::Init() + { + } + + // Activate is where you'd perform registration with other objects and systems. + // All builder classes owned by this component should be registered here + // Any EBuses for the builder classes should also be connected at this point + void AtlasBuilderComponent::Activate() + { + AssetBuilderSDK::AssetBuilderDesc builderDescriptor; + builderDescriptor.m_name = "Atlas Worker Builder"; + builderDescriptor.m_version = 1; + builderDescriptor.m_patterns.emplace_back(AssetBuilderSDK::AssetBuilderPattern("*.texatlas", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); + builderDescriptor.m_busId = azrtti_typeid(); + builderDescriptor.m_createJobFunction = AZStd::bind(&AtlasBuilderWorker::CreateJobs, &m_atlasBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); + builderDescriptor.m_processJobFunction = AZStd::bind(&AtlasBuilderWorker::ProcessJob, &m_atlasBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); + + m_atlasBuilder.BusConnect(builderDescriptor.m_busId); + + AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDescriptor); + } + + // Disconnects from any EBuses we connected to in Activate() + // Unregisters from objects and systems we register with in Activate() + void AtlasBuilderComponent::Deactivate() + { + m_atlasBuilder.BusDisconnect(); + + // We don't need to unregister the builder - the AP will handle this for us, because it is managing the lifecycle of this component + } + + // Reflect the input and output formats for the serializer + void AtlasBuilderComponent::Reflect(AZ::ReflectContext* context) + { + // components also get Reflect called automatically + // this is your opportunity to perform static reflection or type registration of any types you want the serializer to know about + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class() + ->Version(0) + ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector({ AssetBuilderSDK::ComponentTags::AssetBuilder })) + ; + } + + AtlasBuilderInput::Reflect(context); + } + + void AtlasBuilderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC("Atlas Builder Plugin Service", 0x35974d0d)); + } + + void AtlasBuilderComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC("Atlas Builder Plugin Service", 0x35974d0d)); + } + + void AtlasBuilderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + AZ_UNUSED(required); + } + + void AtlasBuilderComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + AZ_UNUSED(dependent); + } +} diff --git a/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.h b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.h new file mode 100644 index 0000000000..eb8b85dfcf --- /dev/null +++ b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderComponent.h @@ -0,0 +1,44 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include "AtlasBuilderWorker.h" + +namespace TextureAtlasBuilder +{ + class AtlasBuilderComponent : public AZ::Component + { + public: + AZ_COMPONENT(AtlasBuilderComponent, "{F49987FB-3375-4417-AB83-97B44C78B335}"); + + AtlasBuilderComponent(); + ~AtlasBuilderComponent() override; + + void Init() override; + void Activate() override; + void Deactivate() override; + + //! Reflect formats for input and output + static void Reflect(AZ::ReflectContext* context); + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + private: + AtlasBuilderWorker m_atlasBuilder; + }; +} // namespace TextureAtlasBuilder diff --git a/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.cpp b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.cpp new file mode 100644 index 0000000000..81e98251cc --- /dev/null +++ b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.cpp @@ -0,0 +1,1607 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include "ImageProcessing_precompiled.h" +#include "AtlasBuilderWorker.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace TextureAtlasBuilder +{ + //! Counts leading zeros + uint32 CountLeadingZeros32(uint32 x) + { + return x == 0 ? 32 : az_clz_u32(x); + } + + //! Integer log2 + uint32 IntegerLog2(uint32 x) + { + return 31 - CountLeadingZeros32(x); + } + + bool IsFolderPath(const AZStd::string& path) + { + bool hasExtension = AzFramework::StringFunc::Path::HasExtension(path.c_str()); + return !hasExtension; + } + + bool HasTrailingSlash(const AZStd::string& path) + { + size_t pathLength = path.size(); + return (pathLength > 0 && (path.at(pathLength - 1) == '/' || path.at(pathLength - 1) == '\\')); + } + + bool GetCanonicalPathFromFullPath(const AZStd::string& fullPath, AZStd::string& canonicalPathOut) + { + AZStd::string curPath = fullPath; + + // We avoid using LocalFileIO::ConvertToAbsolutePath for this because it does not behave consistently across platforms. + // On non-Windows platforms, LocalFileIO::ConvertToAbsolutePath requires that the path exist, otherwise the path + // remains unchanged. This won't work for paths that include wildcards. + // Also, on non-Windows platforms, if the path is already a full path, it will remain unchanged even if it contains + // "./" or "../" somewhere other than the beginning of the path + + // Normalize path + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, curPath); + + const AZStd::string slash("/"); + + // Replace "/./" occurrances with "/" + const AZStd::string slashDotSlash("/./"); + bool replaced = false; + do + { + // Replace first occurrance + replaced = AzFramework::StringFunc::Replace(curPath, slashDotSlash.c_str(), slash.c_str(), false, true, false); + } while (replaced); + + // Replace "/xxx/../" with "/" + const AZStd::regex slashDotDotSlash("\\/[^/.]*\\/\\.\\.\\/"); + AZStd::string prevPath; + while (prevPath != curPath) + { + prevPath = curPath; + curPath = AZStd::regex_replace(prevPath, slashDotDotSlash, slash, AZStd::regex_constants::match_flag_type::format_first_only); + } + + if ((curPath.find("..") != AZStd::string::npos) || (curPath.find("./") != AZStd::string::npos) || (curPath.find("/.") != AZStd::string::npos)) + { + return false; + } + + canonicalPathOut = curPath; + return true; + } + + bool ResolveRelativePath(const AZStd::string& relativePath, const AZStd::string& watchDirectory, AZStd::string& resolvedFullPathOut) + { + bool resolved = false; + + // Get full path by appending the relative path to the watch directory + AZStd::string fullPath = watchDirectory; + fullPath.append("/"); + fullPath.append(relativePath); + + // Resolve to canonical path (remove "./" and "../") + resolved = GetCanonicalPathFromFullPath(fullPath, resolvedFullPathOut); + + return resolved; + } + + bool GetAbsoluteSourcePathFromRelativePath(const AZStd::string& relativeSourcePath, AZStd::string& absoluteSourcePathOut) + { + bool result = false; + AZ::Data::AssetInfo info; + AZStd::string watchFolder; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, relativeSourcePath.c_str(), info, watchFolder); + if (result) + { + absoluteSourcePathOut = AZStd::string::format("%s/%s", watchFolder.c_str(), info.m_relativePath.c_str()); + + // Normalize path + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, absoluteSourcePathOut); + } + return result; + } + + const ImageProcessing::PresetSettings* GetImageProcessPresetSettings(const AZStd::string& presetName, const AZStd::string& platformIdentifier) + { + // Get the specified presetId + AZ::Uuid presetId = ImageProcessing::BuilderSettingManager::Instance()->GetPresetIdFromName(presetName); + if (presetId.IsNull()) + { + AZ_Error("Texture Editor", false, "Texture Preset %s has no associated UUID.", presetName.c_str()); + return nullptr; + } + + // Get the preset settings for the platform this job is building for + const ImageProcessing::PresetSettings* presetSettings = ImageProcessing::BuilderSettingManager::Instance()->GetPreset( + presetId, platformIdentifier); + + return presetSettings; + } + + // Reflect the input parameters + void AtlasBuilderInput::Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class() + ->Version(1) + ->Field("Force Square", &AtlasBuilderInput::m_forceSquare) + ->Field("Force Power of Two", &AtlasBuilderInput::m_forcePowerOf2) + ->Field("Include White Texture", &AtlasBuilderInput::m_includeWhiteTexture) + ->Field("Maximum Dimension", &AtlasBuilderInput::m_maxDimension) + ->Field("Padding", &AtlasBuilderInput::m_padding) + ->Field("UnusedColor", &AtlasBuilderInput::m_unusedColor) + ->Field("PresetName", &AtlasBuilderInput::m_presetName) + ->Field("Textures to Add", &AtlasBuilderInput::m_filePaths); + } + } + + // Supports a custom parser format + AtlasBuilderInput AtlasBuilderInput::ReadFromFile(const AZStd::string& path, const AZStd::string& directory, bool& valid) + { + // Open the file + AZ::IO::FileIOBase* input = AZ::IO::FileIOBase::GetInstance(); + AZ::IO::HandleType handle; + input->Open(path.c_str(), AZ::IO::OpenMode::ModeRead, handle); + + // Read the file + AZ::u64 size; + input->Size(handle, size); + char* buffer = new char[size + 1]; + input->Read(handle, buffer, size); + buffer[size] = 0; + + // Close the file + input->Close(handle); + + // Prepare the output + AtlasBuilderInput data; + + // Parse the input into lines + AZStd::vector lines; + AzFramework::StringFunc::Tokenize(buffer, lines, "\n\t"); + delete[] buffer; + + // Parse the individual lines + for (auto line : lines) + { + line = AzFramework::StringFunc::TrimWhiteSpace(line, true, true); + // Check for comments and empty lines + if ((line.length() >= 2 && line[0] == '/' && line[1] == '/') || line.length() < 1) + { + continue; + } + else if (line.find('=') != -1) + { + AZStd::vector args; + AzFramework::StringFunc::Tokenize(line.c_str(), args, '=', true, true); + + if (args.size() > 2) + { + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to parse line: Excessive '=' symbols were found: \"%s\"", line.c_str()).c_str()); + valid = false; + } + + // Trim whitespace + args[0] = AzFramework::StringFunc::TrimWhiteSpace(args[0], true, true); + args[1] = AzFramework::StringFunc::TrimWhiteSpace(args[1], true, true); + + // No case sensitivity for property names + AZStd::to_lower(args[0].begin(), args[0].end()); + + // Keep track of if the value is rejected + bool accepted = false; + + if (args[0] == "square") + { + accepted = AzFramework::StringFunc::LooksLikeBool(args[1].c_str()); + if (accepted) + { + data.m_forceSquare = AzFramework::StringFunc::ToBool(args[1].c_str()); + } + } + else if (args[0] == "poweroftwo") + { + accepted = AzFramework::StringFunc::LooksLikeBool(args[1].c_str()); + if (accepted) + { + data.m_forcePowerOf2 = AzFramework::StringFunc::ToBool(args[1].c_str()); + } + } + else if (args[0] == "whitetexture") + { + accepted = AzFramework::StringFunc::LooksLikeBool(args[1].c_str()); + if (accepted) + { + data.m_includeWhiteTexture = AzFramework::StringFunc::ToBool(args[1].c_str()); + } + } + else if (args[0] == "maxdimension") + { + accepted = AzFramework::StringFunc::LooksLikeInt(args[1].c_str()); + if (accepted) + { + data.m_maxDimension = AzFramework::StringFunc::ToInt(args[1].c_str()); + } + } + else if (args[0] == "padding") + { + accepted = AzFramework::StringFunc::LooksLikeInt(args[1].c_str()); + if (accepted) + { + data.m_padding = AzFramework::StringFunc::ToInt(args[1].c_str()); + } + } + else if (args[0] == "unusedcolor") + { + accepted = args[1].at(0) == '#' && args[1].length() == 9; + if (accepted) + { + AZStd::string color = AZStd::string::format("%s%s%s%s", args[1].substr(7).c_str(), args[1].substr(5, 2).c_str(), + args[1].substr(3, 2).c_str(), args[1].substr(1, 2).c_str()); + data.m_unusedColor.FromU32(AZStd::stoul(color, nullptr, 16)); + } + } + else if (args[0] == "presetname") + { + accepted = true; + data.m_presetName = args[1]; + } + else + { + // Supress accepted error because this error superceeds it + accepted = true; + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to parse line: Unrecognized property: \"%s\"", args[0].c_str()).c_str()); + } + + // If the property is recognized but the value is rejected, fail the job + if (!accepted) + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to parse line: Invalid value assigned to property: Property: \"%s\" Value: \"%s\"", args[0].c_str(), args[1].c_str()).c_str()); + } + } + else if ((line[0] == '-')) + { + // Remove image files + AZStd::string remove = line.substr(1); + remove = AzFramework::StringFunc::TrimWhiteSpace(remove, true, true); + if (remove.find('*') != -1) + { + AZStd::string resolvedAbsolutePath; + bool resolved = ResolveRelativePath(remove, directory, resolvedAbsolutePath); + if (resolved) + { + RemoveFilesUsingWildCard(data.m_filePaths, resolvedAbsolutePath); + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to resolve relative path: %s", remove.c_str()).c_str()); + } + } + else if (IsFolderPath(remove)) + { + AZStd::string resolvedAbsolutePath; + bool resolved = ResolveRelativePath(remove, directory, resolvedAbsolutePath); + if (resolved) + { + RemoveFolderContents(data.m_filePaths, resolvedAbsolutePath); + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to resolve relative path: %s", remove.c_str()).c_str()); + } + } + else + { + // Get the full path to the source image from the relative source path + AZStd::string fullSourceAssetPathName; + bool fullPathFound = GetAbsoluteSourcePathFromRelativePath(remove, fullSourceAssetPathName); + + if (!fullPathFound) + { + // Try to resolve relative path as it might be using "./" or "../" + fullPathFound = ResolveRelativePath(remove, directory, fullSourceAssetPathName); + } + + if (fullPathFound) + { + for (size_t i = 0; i < data.m_filePaths.size(); ++i) + { + if (data.m_filePaths[i] == fullSourceAssetPathName) + { + data.m_filePaths.erase(data.m_filePaths.begin() + i); + } + } + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to get source asset path for image: %s", remove.c_str()).c_str()); + } + } + } + else + { + // Add image files + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, line); + bool duplicate = false; + if (line.find('*') != -1) + { + AZStd::string resolvedAbsolutePath; + bool resolved = ResolveRelativePath(line, directory, resolvedAbsolutePath); + if (resolved) + { + AddFilesUsingWildCard(data.m_filePaths, resolvedAbsolutePath); + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to resolve relative path: %s", line.c_str()).c_str()); + } + } + else if (IsFolderPath(line)) + { + AZStd::string resolvedAbsolutePath; + bool resolved = ResolveRelativePath(line, directory, resolvedAbsolutePath); + if (resolved) + { + AddFolderContents(data.m_filePaths, resolvedAbsolutePath, valid); + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to resolve relative path: %s", line.c_str()).c_str()); + } + } + else + { + // Get the full path to the source image from the relative source path + AZStd::string fullSourceAssetPathName; + bool fullPathFound = GetAbsoluteSourcePathFromRelativePath(line, fullSourceAssetPathName); + + if (!fullPathFound) + { + // Try to resolve relative path as it might be using "./" or "../" + fullPathFound = ResolveRelativePath(line, directory, fullSourceAssetPathName); + } + + if (fullPathFound) + { + // Prevent duplicates + for (size_t i = 0; i < data.m_filePaths.size() && !duplicate; ++i) + { + duplicate = data.m_filePaths[i] == fullSourceAssetPathName; + } + if (!duplicate) + { + data.m_filePaths.push_back(fullSourceAssetPathName); + } + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to get source asset path for image: %s", line.c_str()).c_str()); + } + } + } + } + + return data; + } + + void AtlasBuilderInput::AddFilesUsingWildCard(AZStd::vector& paths, const AZStd::string& insert) + { + const AZStd::string& fullPath = insert; + + AZStd::vector candidates; + AZStd::string fixedPath = fullPath.substr(0, fullPath.find('*')); + fixedPath = fixedPath.substr(0, fixedPath.find_last_of('/')); + candidates.push_back(fixedPath); + + AZStd::vector wildPath; + AzFramework::StringFunc::Tokenize(fullPath.substr(fixedPath.length()).c_str(), wildPath, "/"); + + for (size_t i = 0; i < wildPath.size() && candidates.size() > 0; ++i) + { + AZStd::vector nextCandidates; + for (size_t j = 0; j < candidates.size(); ++j) + { + AZStd::string compare = AZStd::string::format("%s/%s", candidates[j].c_str(), wildPath[i].c_str()); + QDir inputFolder(candidates[j].c_str()); + if (inputFolder.exists()) + { + QFileInfoList entries = inputFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files); + for (const QFileInfo& entry : entries) + { + AZStd::string child = (entry.filePath().toStdString()).c_str(); + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, child); + if (DoesPathnameMatchWildCard(compare, child)) + { + nextCandidates.push_back(child); + } + } + } + } + candidates = nextCandidates; + } + + for (size_t i = 0; i < candidates.size(); ++i) + { + if (!IsFolderPath(candidates[i]) && !HasTrailingSlash(fullPath)) + { + AZStd::string ext; + AzFramework::StringFunc::Path::GetExtension(candidates[i].c_str(), ext, false); + if (ImageProcessing::IsExtensionSupported(ext.c_str()) && ext != "dds") + { + bool duplicate = false; + for (size_t j = 0; j < paths.size() && !duplicate; ++j) + { + duplicate = paths[j] == candidates[i]; + } + if (!duplicate) + { + paths.push_back(candidates[i]); + } + } + } + else if (IsFolderPath(candidates[i]) && HasTrailingSlash(fullPath)) + { + bool waste = true; + AddFolderContents(paths, candidates[i], waste); + } + } + } + + void AtlasBuilderInput::RemoveFilesUsingWildCard(AZStd::vector& paths, const AZStd::string& remove) + { + bool isDir = (remove.at(remove.length() - 1) == '/'); + for (size_t i = 0; i < paths.size(); ++i) + { + if (isDir ? DoesWildCardDirectoryIncludePathname(remove, paths[i]) : DoesPathnameMatchWildCard(remove, paths[i])) + { + paths.erase(paths.begin() + i); + --i; + } + } + } + + // Tells us if the child follows the rule + bool AtlasBuilderInput::DoesPathnameMatchWildCard(const AZStd::string& rule, const AZStd::string& child) + { + AZStd::vector rulePathTokens; + AzFramework::StringFunc::Tokenize(rule.c_str(), rulePathTokens, "/"); + AZStd::vector pathTokens; + AzFramework::StringFunc::Tokenize(child.c_str(), pathTokens, "/"); + if (rulePathTokens.size() != pathTokens.size()) + { + return false; + } + for (size_t i = 0; i < rulePathTokens.size(); ++i) + { + if (!TokenMatchesWildcard(rulePathTokens[i], pathTokens[i])) + { + return false; + } + } + return true; + } + + bool AtlasBuilderInput::DoesWildCardDirectoryIncludePathname(const AZStd::string& rule, const AZStd::string& child) + { + AZStd::vector rulePathTokens; + AzFramework::StringFunc::Tokenize(rule.c_str(), rulePathTokens, "/"); + AZStd::vector pathTokens; + AzFramework::StringFunc::Tokenize(child.c_str(), pathTokens, "/"); + if (rulePathTokens.size() >= pathTokens.size()) + { + return false; + } + for (size_t i = 0; i < rulePathTokens.size(); ++i) + { + if (!TokenMatchesWildcard(rulePathTokens[i], pathTokens[i])) + { + return false; + } + } + return true; + } + + bool AtlasBuilderInput::TokenMatchesWildcard(const AZStd::string& rule, const AZStd::string& child) + { + AZStd::vector ruleTokens; + AzFramework::StringFunc::Tokenize(rule.c_str(), ruleTokens, "*"); + size_t pos = 0; + int token = 0; + if (rule.at(0) != '*' && child.find(ruleTokens[0]) != 0) + { + return false; + } + + while (pos != AZStd::string::npos && token < ruleTokens.size()) + { + pos = child.find(ruleTokens[token], pos); + if (pos != AZStd::string::npos) + { + pos += ruleTokens[token].size(); + } + ++token; + } + return pos == child.size() || (pos != AZStd::string::npos && rule.at(rule.length() - 1) == '*'); + } + + // Replaces all folder paths with the files they contain + void AtlasBuilderInput::AddFolderContents(AZStd::vector& paths, const AZStd::string& insert, bool& valid) + { + QDir inputFolder(insert.c_str()); + + if (inputFolder.exists()) + { + QFileInfoList entries = inputFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files); + for (const QFileInfo& entry : entries) + { + AZStd::string child = (entry.filePath().toStdString()).c_str(); + AZStd::string ext; + bool isDir = !AzFramework::StringFunc::Path::GetExtension(child.c_str(), ext, false); + if (isDir) + { + AddFolderContents(paths, child, valid); + } + else if (ImageProcessing::IsExtensionSupported(ext.c_str()) && ext != "dds") + { + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, child); + bool duplicate = false; + for (size_t i = 0; i < paths.size() && !duplicate; ++i) + { + duplicate = paths[i] == child; + } + if (!duplicate) + { + paths.push_back(child); + } + } + } + } + else + { + valid = false; + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to find requested directory: %s", insert.c_str()).c_str()); + } + } + + // Removes all of the contents of a folder + void AtlasBuilderInput::RemoveFolderContents(AZStd::vector& paths, const AZStd::string& remove) + { + AZStd::string folder = remove; + AzFramework::StringFunc::Strip(folder, "/", false, false, true); + folder.append("/"); + for (size_t i = 0; i < paths.size(); ++i) + { + if (paths[i].find(folder) == 0) + { + paths.erase(paths.begin() + i); + --i; + } + } + } + + // Note - Shutdown will be called on a different thread than your process job thread + void AtlasBuilderWorker::ShutDown() { m_isShuttingDown = true; } + + void AtlasBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, + AssetBuilderSDK::CreateJobsResponse& response) + { + // Read in settings/filepaths to set dependencies + AZStd::string fullPath; + AzFramework::StringFunc::Path::Join( + request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullPath, true, true); + // Check if input is valid + bool valid = true; + AtlasBuilderInput input = AtlasBuilderInput::ReadFromFile(fullPath, request.m_watchFolder, valid); + + // Set dependencies + for (int i = 0; i < input.m_filePaths.size(); ++i) + { + AssetBuilderSDK::SourceFileDependency dependency; + dependency.m_sourceFileDependencyPath = input.m_filePaths[i].c_str(); + response.m_sourceFileDependencyList.push_back(dependency); + } + + // We process the same file for all platforms + for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms) + { + if (ImageProcessing::BuilderSettingManager::Instance()->DoesSupportPlatform(info.m_identifier)) + { + AssetBuilderSDK::JobDescriptor descriptor = GetJobDescriptor(request.m_sourceFile, input); + descriptor.SetPlatformIdentifier(info.m_identifier.c_str()); + response.m_createJobOutputs.push_back(descriptor); + } + } + + if (valid) + { + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; + } + + return; + } + + AssetBuilderSDK::JobDescriptor AtlasBuilderWorker::GetJobDescriptor(const AZStd::string& sourceFile, const AtlasBuilderInput& input) + { + // Get the extension of the file + AZStd::string ext; + AzFramework::StringFunc::Path::GetExtension(sourceFile.c_str(), ext, false); + AZStd::to_upper(ext.begin(), ext.end()); + + AssetBuilderSDK::JobDescriptor descriptor; + descriptor.m_jobKey = ext + " Atlas"; + descriptor.m_critical = false; + descriptor.m_jobParameters[AZ_CRC("forceSquare")] = input.m_forceSquare ? "true" : "false"; + descriptor.m_jobParameters[AZ_CRC("forcePowerOf2")] = input.m_forcePowerOf2 ? "true" : "false"; + descriptor.m_jobParameters[AZ_CRC("includeWhiteTexture")] = input.m_includeWhiteTexture ? "true" : "false"; + descriptor.m_jobParameters[AZ_CRC("padding")] = AZStd::to_string(input.m_padding); + descriptor.m_jobParameters[AZ_CRC("maxDimension")] = AZStd::to_string(input.m_maxDimension); + descriptor.m_jobParameters[AZ_CRC("filePaths")] = AZStd::to_string(input.m_filePaths.size()); + + AZ::u32 col = input.m_unusedColor.ToU32(); + descriptor.m_jobParameters[AZ_CRC("unusedColor")] = AZStd::to_string(*reinterpret_cast(&col)); + descriptor.m_jobParameters[AZ_CRC("presetName")] = input.m_presetName; + + // The starting point for the list + const int start = static_cast(descriptor.m_jobParameters.size()) + 1; + descriptor.m_jobParameters[AZ_CRC("startPoint")] = AZStd::to_string(start); + + for (int i = 0; i < input.m_filePaths.size(); ++i) + { + descriptor.m_jobParameters[start + i] = input.m_filePaths[i]; + } + + return descriptor; + } + + void AtlasBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, + AssetBuilderSDK::ProcessJobResponse& response) + { + // Before we begin, let's make sure we are not meant to abort. + AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId); + + AZStd::vector productFilepaths; + + const AZStd::string path = request.m_fullPath; + + bool imageProcessingSuccessful = false; + + // read in settings/filepaths + AtlasBuilderInput input; + input.m_forceSquare = AzFramework::StringFunc::ToBool(request.m_jobDescription.m_jobParameters.find(AZ_CRC("forceSquare"))->second.c_str()); + input.m_forcePowerOf2 = AzFramework::StringFunc::ToBool(request.m_jobDescription.m_jobParameters.find(AZ_CRC("forcePowerOf2"))->second.c_str()); + input.m_includeWhiteTexture = AzFramework::StringFunc::ToBool(request.m_jobDescription.m_jobParameters.find(AZ_CRC("includeWhiteTexture"))->second.c_str()); + input.m_padding = AzFramework::StringFunc::ToInt(request.m_jobDescription.m_jobParameters.find(AZ_CRC("padding"))->second.c_str()); + input.m_maxDimension = AzFramework::StringFunc::ToInt(request.m_jobDescription.m_jobParameters.find(AZ_CRC("maxDimension"))->second.c_str()); + int startAsInt = AzFramework::StringFunc::ToInt(request.m_jobDescription.m_jobParameters.find(AZ_CRC("startPoint"))->second.c_str()); + int sizeAsInt = AzFramework::StringFunc::ToInt(request.m_jobDescription.m_jobParameters.find(AZ_CRC("filePaths"))->second.c_str()); + AZ::u32 start = static_cast(AZStd::max(0, startAsInt)); + AZ::u32 size = static_cast(AZStd::max(0, sizeAsInt)); + + int col = AzFramework::StringFunc::ToInt(request.m_jobDescription.m_jobParameters.find(AZ_CRC("unusedColor"))->second.c_str()); + input.m_unusedColor.FromU32(*reinterpret_cast(&col)); + + input.m_presetName = request.m_jobDescription.m_jobParameters.find(AZ_CRC("presetName"))->second; + + for (AZ::u32 i = 0; i < size; ++i) + { + input.m_filePaths.push_back(request.m_jobDescription.m_jobParameters.find(start + i)->second); + } + + if (input.m_filePaths.empty()) + { + AZ_Error("AtlasBuilder", false, "No image files specified. Cannot create an empty atlas."); + return; + } + + // Don't allow padding to be less than zero + if (input.m_padding < 0) + { + input.m_padding = 0; + } + + if (input.m_presetName.empty()) + { + // Default to the TextureAtlas preset which is currently set to use compression for all platforms except for iOS. + // Currently the only fully supported compression for iOS is PVRTC which requires the texture to be square and a power of 2. + // Due to this limitation, we default to using no compression for iOS until ASTC is fully supported + const AZStd::string defaultPresetName = "TextureAtlas"; + input.m_presetName = defaultPresetName; + } + + // Get a preset to use for the output image + const ImageProcessing::PresetSettings* preset = GetImageProcessPresetSettings(input.m_presetName, request.m_platformInfo.m_identifier); + if (preset) + { + // Check the preset's pixel format requirements + const ImageProcessing::PixelFormatInfo* pixelFormatInfo = ImageProcessing::CPixelFormats::GetInstance().GetPixelFormatInfo(preset->m_pixelFormat); + if (pixelFormatInfo && pixelFormatInfo->bSquarePow2) + { + // Override the user config settings to force square and power of 2. + // Otherwise the image conversion process will stretch the image to satisfy these requirements + input.m_forceSquare = true; + input.m_forcePowerOf2 = true; + } + } + else + { + AZ_Error("AtlasBuilder", false, "Could not find a preset setting for the output image."); + return; + } + + // Read in images + AZStd::vector images; + AZ::u64 totalArea = 0; + int maxArea = input.m_maxDimension * input.m_maxDimension; + bool sizeFailure = false; + for (int i = 0; i < input.m_filePaths.size() && !jobCancelListener.IsCancelled(); ++i) + { + ImageProcessing::IImageObject* inputImage = ImageProcessing::LoadImageFromFile(input.m_filePaths[i]); + // Check if we were able to load the image + if (inputImage) + { + ImageProcessing::IImageObjectPtr image = ImageProcessing::IImageObjectPtr(inputImage); + images.push_back(image); + totalArea += inputImage->GetWidth(0) * inputImage->GetHeight(0); + } + else + { + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to load file: %s", input.m_filePaths[i].c_str()).c_str()); + return; + } + if (maxArea < totalArea) + { + sizeFailure = true; + } + } + // If we get cancelled, return + if (jobCancelListener.IsCancelled()) + { + return; + } + + if (sizeFailure) + { + AZ_Error("AtlasBuilder", false, AZStd::string::format("Total image area exceeds maximum alotted area. %llu > %d", totalArea, maxArea).c_str()); + return; + } + + // Convert all image paths to their output format referenced at runtime + for (auto& filePath : input.m_filePaths) + { + // Get path relative to the watch folder + bool result = false; + AZ::Data::AssetInfo info; + AZStd::string watchFolder; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, filePath.c_str(), info, watchFolder); + if (!result) + { + AZ_Error("AtlasBuilder", false, AZStd::string::format("Atlas Builder unable to get relative source path for image: %s", filePath.c_str()).c_str()); + return; + } + + // Remove extension + filePath = info.m_relativePath.substr(0, info.m_relativePath.find_last_of('.')); + + // Normalize path + AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, filePath); + } + + // Add white texture if we need to + if (input.m_includeWhiteTexture) + { + ImageProcessing::IImageObjectPtr texture(ImageProcessing::IImageObject::CreateImage( + cellSize, cellSize, 1, ImageProcessing::EPixelFormat::ePixelFormat_R8G8B8A8)); + + // Make the texture white + texture->ClearColor(1, 1, 1, 1); + images.push_back(texture); + input.m_filePaths.push_back("WhiteTexture"); + } + + // Generate algorithm inputs + ImageDimensionData data; + for (int i = 0; i < images.size(); ++i) + { + data.push_back(IndexImageDimension(i, + ImageDimension(images[i]->GetWidth(0), + images[i]->GetHeight(0)))); + } + AZStd::sort(data.begin(), data.end()); + + // Run algorithm + + // Variables that keep track of the optimal solution + int resultWidth = -1; + int resultHeight = -1; + + // Check that the max dimension is not large enough for the area to loop past the maximum integer + // This is important because we do not want the area to be calculated negative + if (input.m_maxDimension > 65535) + { + input.m_maxDimension = 65535; + } + + // Get the optimal mappings based on the input settings + AZStd::vector paddedMap; + size_t amountFit = 0; + if (!TryTightening( + input, data, GetWidest(data), GetTallest(data), aznumeric_cast(totalArea), input.m_padding, resultWidth, resultHeight, amountFit, paddedMap)) + { + AZ_Error("AtlasBuilder", false, AZStd::string::format("Cannot fit images into given maximum atlas size (%dx%d). Only %zu out of %zu images fit.", input.m_maxDimension, input.m_maxDimension, amountFit, input.m_filePaths.size()).c_str()); + // For some reason, failing the assert isn't enough to stop the Asset builder. It will still fail further + // down when it tries to assemble the atlas, but returning here is cleaner. + return; + } + + // Move coordinates from algorithm space to padded result space + TextureAtlasNamespace::AtlasCoordinateSets output; + resultWidth = 0; + resultHeight = 0; + AZStd::vector map; + for (int i = 0; i < paddedMap.size(); ++i) + { + map.push_back(AtlasCoordinates(paddedMap[i].GetLeft(), paddedMap[i].GetLeft() + images[data[i].first]->GetWidth(0), paddedMap[i].GetTop(), paddedMap[i].GetTop() + images[data[i].first]->GetHeight(0))); + resultHeight = resultHeight > map[i].GetBottom() ? resultHeight : map[i].GetBottom(); + resultWidth = resultWidth > map[i].GetRight() ? resultWidth : map[i].GetRight(); + + const AZStd::string& outputFilePath = input.m_filePaths[data[i].first]; + output.push_back(AZStd::pair(outputFilePath, map[i])); + } + if (input.m_forcePowerOf2) + { + resultWidth = aznumeric_cast(pow(2, 1 + IntegerLog2(static_cast(resultWidth - 1)))); + resultHeight = aznumeric_cast(pow(2, 1 + IntegerLog2(static_cast(resultHeight - 1)))); + } + else + { + resultWidth = (resultWidth + (cellSize - 1)) / cellSize * cellSize; + resultHeight = (resultHeight + (cellSize - 1)) / cellSize * cellSize; + } + if (input.m_forceSquare) + { + if (resultWidth > resultHeight) + { + resultHeight = resultWidth; + } + else + { + resultWidth = resultHeight; + } + } + + // Process texture sheet + ImageProcessing::IImageObjectPtr outImage(ImageProcessing::IImageObject::CreateImage( + resultWidth, resultHeight, 1, ImageProcessing::EPixelFormat::ePixelFormat_R8G8B8A8)); + + // Clear the sheet + outImage->ClearColor(input.m_unusedColor.GetR(), input.m_unusedColor.GetG(), input.m_unusedColor.GetB(), input.m_unusedColor.GetA()); + + AZ::u8* outBuffer = nullptr; + AZ::u32 outPitch; + outImage->GetImagePointer(0, outBuffer, outPitch); + + // Copy images over + for (int i = 0; i < map.size() && !jobCancelListener.IsCancelled(); ++i) + { + AZ::u8* inBuffer = nullptr; + AZ::u32 inPitch; + images[data[i].first]->GetImagePointer(0, inBuffer, inPitch); + int j = 0; + + // The padding calculated here is the amount of excess horizontal space measured in bytes that are in each + // row of the destination space AFTER the placement of the source row. + int rightPadding = (paddedMap[i].GetRight() - map[i].GetRight() - input.m_padding); + if (map[i].GetRight() + rightPadding > resultWidth) + { + rightPadding = resultWidth - map[i].GetRight(); + } + rightPadding *= bytesPerPixel; + int bottomPadding = (paddedMap[i].GetBottom() - map[i].GetBottom() - input.m_padding); + if (map[i].GetBottom() + bottomPadding > resultHeight) + { + bottomPadding = resultHeight - map[i].GetBottom(); + } + + int leftPadding = 0; + if (map[i].GetLeft() - input.m_padding >= 0) + { + leftPadding = input.m_padding * bytesPerPixel; + } + + int topPadding = 0; + if (map[i].GetTop() - input.m_padding >= 0) + { + topPadding = input.m_padding; + } + + for (j = 0; j < map[i].GetHeight(); ++j) + { + // When we multiply `map[i].GetLeft()` by 4, we are changing the measure from atlas space, to byte array + // space. The number is 4 because in this format, each pixel is 4 bytes long. + memcpy(outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel), + inBuffer + inPitch * j, + inPitch); + // Fill in the last bit of the row in the destination space with the same colors + SetPixels(outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel) + inPitch, + outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel) + inPitch - bytesPerPixel, + rightPadding); + // Fill in the first bit of the row in the destination space with the same colors + SetPixels(outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel) - leftPadding, + outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel), + leftPadding); + } + // Fill in the last few rows of the buffer with the same colors + for (; j < map[i].GetHeight() + bottomPadding; ++j) + { + memcpy(outBuffer + (map[i].GetTop() + j) * outPitch + (map[i].GetLeft() * bytesPerPixel) - leftPadding, + outBuffer + (map[i].GetBottom() - 1) * outPitch + (map[i].GetLeft() * bytesPerPixel) - leftPadding, + inPitch + leftPadding + rightPadding); + } + for (j = 1; j <= topPadding; ++j) + { + memcpy(outBuffer + (map[i].GetTop() - j) * outPitch + (map[i].GetLeft() * bytesPerPixel) - leftPadding, + outBuffer + map[i].GetTop() * outPitch + (map[i].GetLeft() * bytesPerPixel) - leftPadding, + inPitch + rightPadding + leftPadding); + } + } + + // If we get cancelled, return + if (jobCancelListener.IsCancelled()) + { + return; + } + + // Output Atlas Coordinates + AZStd::string fileName; + AZStd::string outputPath; + AzFramework::StringFunc::Path::GetFullFileName(request.m_sourceFile.c_str(), fileName); + fileName = fileName.append("idx"); + AzFramework::StringFunc::Path::Join( + request.m_tempDirPath.c_str(), fileName.c_str(), outputPath, true, true); + + // Output texture sheet + AZStd::string imageFileName, imageOutputPath; + AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), imageFileName); + imageFileName += ".dds"; + AzFramework::StringFunc::Path::Join( + request.m_tempDirPath.c_str(), imageFileName.c_str(), imageOutputPath, true, true); + + // Let the ImageProcessor do the rest of the work. + ImageProcessing::TextureSettings textureSettings; + textureSettings.m_preset = preset->m_uuid; + + // Mipmaps for the texture atlas would require more work than the Image Processor does. This is because if we + // let the Image Processor make mipmaps, it might bleed the textures in the atlas together. + textureSettings.m_enableMipmap = false; + + // Check if the ImageBuilder wants to enable streaming + bool isStreaming = ImageProcessing::BuilderSettingManager::Instance() + ->GetBuilderSetting(request.m_platformInfo.m_identifier) + ->m_enableStreaming; + + bool canOverridePreset = false; + ImageProcessing::ImageConvertProcess* process = + new ImageProcessing::ImageConvertProcess(outImage, + textureSettings, + *preset, + false, + isStreaming, + canOverridePreset, + imageOutputPath, + request.m_platformInfo.m_identifier); + + if (process != nullptr) + { + // the process can be stopped if the job is cancelled or the worker is shutting down + while (!process->IsFinished() && !m_isShuttingDown && !jobCancelListener.IsCancelled()) + { + process->UpdateProcess(); + } + + // get process result + imageProcessingSuccessful = process->IsSucceed(); + process->GetAppendOutputFilePaths(productFilepaths); + + delete process; + } + else + { + imageProcessingSuccessful = false; + } + + if (imageProcessingSuccessful) + { + TextureAtlasNamespace::TextureAtlasRequestBus::Broadcast( + &TextureAtlasNamespace::TextureAtlasRequests::SaveAtlasToFile, outputPath, output, resultWidth, resultHeight); + response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(outputPath)); + response.m_outputProducts[static_cast(Product::TexatlasidxProduct)].m_productAssetType = azrtti_typeid(); + response.m_outputProducts[static_cast(Product::TexatlasidxProduct)].m_productSubID = 0; + + // The Image Processing Gem can produce multiple output files under certain + // circumstances, but the texture atlas is not expected to produce such output + if (productFilepaths.size() > 1) + { + AZ_Error("AtlasBuilder", false, "Image processing resulted in multiple output files. Texture atlas is expected to produce one output."); + response.m_outputProducts.clear(); + return; + } + + if (productFilepaths.size() > 0) + { + response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(productFilepaths[0])); + response.m_outputProducts.back().m_productAssetType = azrtti_typeid(); + response.m_outputProducts.back().m_productSubID = 1; + + // The texatlasidx file is a data file that indicates where the original parts are inside the atlas, + // and this would usually imply that it refers to its dds file in some way or needs it to function. + // The texatlasidx file should be the one that depends on the DDS because its possible to use the DDS + // without the texatlasid, but not the other way around + AZ::Data::AssetId productAssetId(request.m_sourceFileUUID, response.m_outputProducts.back().m_productSubID); + response.m_outputProducts[static_cast(Product::TexatlasidxProduct)].m_dependencies.push_back(AssetBuilderSDK::ProductDependency(productAssetId, 0)); + response.m_outputProducts[static_cast(Product::TexatlasidxProduct)].m_dependenciesHandled = true; // We've populated the dependencies immediately above so it's OK to tell the AP we've handled dependencies + } + response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; + } + } + + bool AtlasBuilderWorker::TryPack(const ImageDimensionData& images, + int targetWidth, + int targetHeight, + int padding, + size_t& amountFit, + AZStd::vector& out) + { + // Start with one open slot and initialize a vector to store the closed products + AZStd::vector open; + AZStd::vector closed; + open.push_back(AtlasCoordinates(0, targetWidth, 0, targetHeight)); + bool slotNotFound = false; + for (size_t i = 0; i < images.size() && !slotNotFound; ++i) + { + slotNotFound = true; + // Try to place the image in every open slot + for (size_t j = 0; j < open.size(); ++j) + { + if (CanInsert(open[j], images[i].second, padding, targetWidth, targetHeight)) + { + // if it fits, subdivide the excess space in the slot, add it back to the open list and place the + // filled space into the closed vector + slotNotFound = false; + AtlasCoordinates spent(open[j].GetLeft(), + open[j].GetLeft() + images[i].second.m_width, + open[j].GetTop(), + open[j].GetTop() + images[i].second.m_height); + + // We are going to try pushing the object up / left to try to avoid creating tight open spaces. + bool needTrim = false; + AtlasCoordinates coords = spent; + // Modifying left will preserve width + coords.SetLeft(coords.GetLeft() - 1); + AddPadding(coords, padding, targetWidth, targetHeight); + while (spent.GetLeft() > 0 && !Collides(coords, closed)) + { + spent.SetLeft(coords.GetLeft()); + coords = spent; + coords.SetLeft(coords.GetLeft() - 1); + AddPadding(coords, padding, targetWidth, targetHeight); + needTrim = true; + } + // Refocus the search to see if we can push up + coords = spent; + coords.SetTop(coords.GetTop() - 1); + AddPadding(coords, padding, targetWidth, targetHeight); + while (spent.GetTop() > 0 && !Collides(coords, closed)) + { + spent.SetTop(coords.GetTop()); + coords = spent; + coords.SetTop(coords.GetTop() - 1); + AddPadding(coords, padding, targetWidth, targetHeight); + needTrim = true; + } + AddPadding(spent, padding, targetWidth, targetHeight); + if (needTrim) + { + TrimOverlap(open, spent); + closed.push_back(spent); + break; + } + AtlasCoordinates bigCoords; + AtlasCoordinates smallCoords; + + // Create the largest possible subdivision and another subdivision that uses the left over space + if (open[j].GetBottom() - spent.GetBottom() < open[j].GetRight() - spent.GetRight()) + { + smallCoords = AtlasCoordinates( + open[j].GetLeft(), spent.GetRight(), spent.GetBottom(), open[j].GetBottom()); + bigCoords = AtlasCoordinates(spent.GetRight(), open[j].GetRight(), open[j].GetTop(), smallCoords.GetBottom()); + } + else + { + bigCoords = AtlasCoordinates( + open[j].GetLeft(), open[j].GetRight(), spent.GetBottom(), open[j].GetBottom()); + smallCoords = AtlasCoordinates(spent.GetRight(), open[j].GetRight(), open[j].GetTop(), bigCoords.GetTop()); + } + + open.erase(open.begin() + j, open.begin() + j + 1); + if (bigCoords.GetHeight() > 0 && bigCoords.GetHeight() > 0) + { + InsertInOrder(open, bigCoords); + } + if (smallCoords.GetHeight() > 0 && smallCoords.GetHeight() > 0) + { + InsertInOrder(open, smallCoords); + } + + closed.push_back(spent); + break; + } + } + if (slotNotFound) + { + // If no single open slot can fit the object, do one last check to see if we can fit it in at any open + // corner. The reason we perform this check is in case the object can be fit across multiple different + // open spaces. If there is a space that an object can be fit in, it will probably involve the top left + // corner of that object in the top left corner of an open slot. This may miss some odd fits, but due to + // the nature of the packing algorithm, such solutions are highly unlikely to exist. If we wanted to + // expand the algorithm, we could theoretically base it on edges instead of corners to find all results, + // but it would not be time efficient. + for (size_t j = 0; j < open.size(); ++j) + { + AtlasCoordinates insert = AtlasCoordinates(open[j].GetLeft(), + open[j].GetLeft() + images[i].second.m_width, + open[j].GetTop(), + open[j].GetTop() + images[i].second.m_height); + AddPadding(insert, padding, targetWidth, targetHeight); + if (insert.GetRight() <= targetWidth && insert.GetBottom() <= targetHeight) + { + bool collision = Collides(insert, closed); + if (!collision) + { + closed.push_back(insert); + // Trim overlapping open slots + TrimOverlap(open, insert); + slotNotFound = false; + break; + } + } + } + } + } + // If we succeeded, update the output + if (!slotNotFound) + { + out = closed; + } + amountFit = amountFit > closed.size() ? amountFit : closed.size(); + return !slotNotFound; + } + + // Modifies slotList so that no items in slotList overlap with item + void AtlasBuilderWorker::TrimOverlap(AZStd::vector& slotList, AtlasCoordinates item) + { + for (size_t i = 0; i < slotList.size(); ++i) + { + if (Collides(slotList[i], item)) + { + // Subdivide the overlapping slot to seperate overlapping and non overlapping portions + AtlasCoordinates overlap = GetOverlap(item, slotList[i]); + AZStd::vector excess; + excess.push_back(AtlasCoordinates( + slotList[i].GetLeft(), overlap.GetRight(), slotList[i].GetTop(), overlap.GetTop())); + excess.push_back(AtlasCoordinates( + slotList[i].GetLeft(), overlap.GetLeft(), overlap.GetTop(), slotList[i].GetBottom())); + excess.push_back(AtlasCoordinates( + overlap.GetRight(), slotList[i].GetRight(), slotList[i].GetTop(), overlap.GetBottom())); + excess.push_back(AtlasCoordinates( + overlap.GetLeft(), slotList[i].GetRight(), overlap.GetBottom(), slotList[i].GetBottom())); + slotList.erase(slotList.begin() + i); + for (size_t j = 0; j < excess.size(); ++j) + { + if (excess[j].GetWidth() > 0 && excess[j].GetHeight() > 0) + { + InsertInOrder(slotList, excess[j]); + } + } + --i; + } + } + } + + // This function interprets input and performs the proper tightening option + bool AtlasBuilderWorker::TryTightening(AtlasBuilderInput input, + const ImageDimensionData& images, + int smallestWidth, + int smallestHeight, + int targetArea, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out) + { + if (input.m_forceSquare) + { + return TryTighteningSquare(images, + smallestWidth > smallestHeight ? smallestWidth : smallestHeight, + input.m_maxDimension, + targetArea, + input.m_forcePowerOf2, + padding, + resultWidth, + resultHeight, + amountFit, + out); + } + else + { + return TryTighteningOptimal(images, + smallestWidth, + smallestHeight, + input.m_maxDimension, + targetArea, + input.m_forcePowerOf2, + padding, + resultWidth, + resultHeight, + amountFit, + out); + } + } + + // Finds the optimal square solution by starting with the ideal solution and expanding the size of the space until everything fits + bool AtlasBuilderWorker::TryTighteningSquare(const ImageDimensionData& images, + int lowerBound, + int maxDimension, + int targetArea, + bool powerOfTwo, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out) + { + // Square solution cannot be smaller than the target area + int dimension = aznumeric_cast(sqrt(static_cast(targetArea))); + // Solution cannot be smaller than the smallest side + dimension = dimension > lowerBound ? dimension : lowerBound; + if (powerOfTwo) + { + // Starting dimension needs to be rounded up to the nearest power of two + dimension = aznumeric_cast(pow(2, 1 + IntegerLog2(static_cast(dimension - 1)))); + } + + AZStd::vector track; + // Expand the square until the contents fit + while (!TryPack(images, dimension, dimension, padding, amountFit, track) && dimension <= maxDimension) + { + // Step to the next valid value + dimension = powerOfTwo ? dimension * 2 : dimension + cellSize; + } + // Make sure we found a solution + if (dimension > maxDimension) + { + return false; + } + + resultHeight = dimension; + resultWidth = dimension; + out = track; + return true; + } + + // Finds the optimal solution by starting with a somewhat optimal solution and searching for better solutions + bool AtlasBuilderWorker::TryTighteningOptimal(const ImageDimensionData& images, + int smallestWidth, + int smallestHeight, + int maxDimension, + int targetArea, + bool powerOfTwo, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out) + { + AZStd::vector track; + + // round max dimension down to a multiple of cellSize + AZ::u32 maxDimensionRounded = maxDimension - (maxDimension % cellSize); + + // The starting width is the larger of the widest individual texture and the width required + // to fit the total texture area given the max dimension + AZ::u32 smallestWidthDueToArea = targetArea / maxDimensionRounded; + AZ::u32 minWidth = AZStd::max(static_cast(smallestWidth), smallestWidthDueToArea); + + if (powerOfTwo) + { + // Starting dimension needs to be rounded up to the nearest power of two + minWidth = aznumeric_cast(pow(2, 1 + IntegerLog2(static_cast(minWidth - 1)))); + } + + // Round min width up to the nearest compression unit + minWidth = (minWidth + (cellSize - 1)) / cellSize * cellSize; + + AZ::u32 height = 0; + // Finds the optimal thin solution + // This uses a standard binary search to find the smallest width that can pack everything + AZ::u32 lower = minWidth; + AZ::u32 upper = maxDimensionRounded; + AZ::u32 width = 0; + while (lower <= upper) + { + AZ::u32 testWidth = (lower + upper) / 2; // must be divisible by cellSize because lower and upper are + bool canPack = TryPack(images, testWidth, maxDimension, padding, amountFit, track); + if (canPack) + { + // it packed, continue looking for smaller widths that pack + width = testWidth; // best fit so far + upper = testWidth - cellSize; + } + else + { + // it failed to pack, don't try any widths smaller than this + lower = testWidth + cellSize; + } + } + // Make sure we found a solution + if (width == 0) + { + return false; + } + + // Find the height of the solution + for (int i = 0; i < track.size(); ++i) + { + uint32 bottom = static_cast(AZStd::max(0, track[i].GetBottom())); + if (height < bottom) + { + height = bottom; + } + } + + // Fix height for power of two when applicable + if (powerOfTwo) + { + // Starting dimensions need to be rounded up to the nearest power of two + height = aznumeric_cast(pow(2, 1 + IntegerLog2(static_cast(height - 1)))); + } + + AZ::u32 resultArea = height * width; + // This for loop starts with the optimal thin width and makes it wider at each step. For each width, it + // calculates what height would be neccesary to have a more optimal solution than the stored solution. If the + // more optimal solution is valid, it tries shrinking the height until the solution fails. The loop ends when it + // is determined that a valid solution cannot exist at further steps + for (AZ::u32 testWidth = width; testWidth <= maxDimensionRounded && resultArea / testWidth >= static_cast(smallestHeight); + testWidth = powerOfTwo ? testWidth * 2 : testWidth + cellSize) + { + // The area of test height and width should be equal or less than resultArea + // Note: We don't need to force powers of two here because the Area and the width are already powers of two + int testHeight = resultArea / testWidth * cellSize / cellSize; + // Try the tighter pack + while (TryPack(images, static_cast(testWidth), testHeight, padding, amountFit, track)) + { + // Loop and continue to shrink the height until you cannot do so any further + width = testWidth; + height = testHeight; + resultArea = height * width; + // Try to step down a level + testHeight = powerOfTwo ? testHeight / 2 : testHeight - cellSize; + } + } + // Output the results of the function + out = track; + resultHeight = height; + resultWidth = width; + return true; + } + + // Allows us to keep the list of open spaces in order from lowest to highest area + void AtlasBuilderWorker::InsertInOrder(AZStd::vector& slotList, AtlasCoordinates item) + { + int area = item.GetWidth() * item.GetHeight(); + for (size_t i = 0; i < slotList.size(); ++i) + { + if (area < slotList[i].GetWidth() * slotList[i].GetHeight()) + { + slotList.insert(slotList.begin() + i, item); + return; + } + } + slotList.push_back(item); + } + + // Defines priority so that sorting can be meaningful. It may seem odd that larger items are "less than" smaller + // ones, but as this is a deduction of priority, not value, it is correct. + static bool operator<(ImageDimension a, ImageDimension b) + { + // Prioritize first by longest size + if ((a.m_width > a.m_height ? a.m_width : a.m_height) != (b.m_width > b.m_height ? b.m_width : b.m_height)) + { + return (a.m_width > a.m_height ? a.m_width : a.m_height) > (b.m_width > b.m_height ? b.m_width : b.m_height); + } + // Prioritize second by the length of the smaller side + if (a.m_width * a.m_height != b.m_width * b.m_height) + { + return a.m_width * a.m_height > b.m_width * b.m_height; + } + // Prioritize wider objects over taller objects for objects of the same size + else + { + return a.m_width > b.m_width; + } + } + + // Exposes priority logic to the sorting algorithm + static bool operator<(IndexImageDimension a, IndexImageDimension b) { return a.second < b.second; } + + // Tests if two coordinate sets intersect + bool Collides(AtlasCoordinates a, AtlasCoordinates b) + { + return !((a.GetRight() <= b.GetLeft()) || (a.GetBottom() <= b.GetTop()) || (b.GetRight() <= a.GetLeft()) + || (b.GetBottom() <= a.GetTop())); + } + + // Tests if an item collides with any items in a list + bool Collides(AtlasCoordinates item, AZStd::vector list) + { + for (size_t i = 0; i < list.size(); ++i) + { + if (Collides(list[i], item)) + { + return true; + } + } + return false; + } + + // Returns the overlap of two intersecting coordinate sets + AtlasCoordinates GetOverlap(AtlasCoordinates a, AtlasCoordinates b) + { + return AtlasCoordinates(b.GetLeft() > a.GetLeft() ? b.GetLeft() : a.GetLeft(), + b.GetRight() < a.GetRight() ? b.GetRight() : a.GetRight(), + b.GetTop() > a.GetTop() ? b.GetTop() : a.GetTop(), + b.GetBottom() < a.GetBottom() ? b.GetBottom() : a.GetBottom()); + } + + // Returns the width of the widest element in imageList + int AtlasBuilderWorker::GetWidest(const ImageDimensionData& imageList) + { + int max = 0; + for (size_t i = 0; i < imageList.size(); ++i) + { + if (max < imageList[i].second.m_width) + { + max = imageList[i].second.m_width; + } + } + return max; + } + + // Returns the height of the tallest element in imageList + int AtlasBuilderWorker::GetTallest(const ImageDimensionData& imageList) + { + int max = 0; + for (size_t i = 0; i < imageList.size(); ++i) + { + if (max < imageList[i].second.m_height) + { + max = imageList[i].second.m_height; + } + } + return max; + } + + // Performs an operation that copies a pixel to the output + void SetPixels(AZ::u8* dest, const AZ::u8* source, int destBytes) + { + if (destBytes >= bytesPerPixel) + { + memcpy(dest, source, bytesPerPixel); + int bytesCopied = bytesPerPixel; + while (bytesCopied * 2 < destBytes) + { + memcpy(dest + bytesCopied, dest, bytesCopied); + bytesCopied *= 2; + } + memcpy(dest + bytesCopied, dest, destBytes - bytesCopied); + } + } + + // Checks if we can insert an image into a slot + bool CanInsert(AtlasCoordinates slot, ImageDimension image, int padding, int farRight, int farBot) + { + int right = slot.GetLeft() + image.m_width; + if (slot.GetRight() < farRight) + { + // Add padding for my right border + right += padding; + // Round up to the nearest compression unit + right = (right + (cellSize - 1)) / cellSize * cellSize; + // Add padding for an adjacent unit's left border + right += padding; + } + + int bot = slot.GetTop() + image.m_height; + if (slot.GetBottom() < farBot) + { + // Add padding for my right border + bot += padding; + // Round up to the nearest compression unit + bot = (bot + (cellSize - 1)) / cellSize * cellSize; + // Add padding for an adjacent unit's left border + bot += padding; + } + + return slot.GetRight() >= right && slot.GetBottom() >= bot; + } + + // Adds the necessary padding to an Atlas Coordinate + void AddPadding(AtlasCoordinates& slot, int padding, [[maybe_unused]] int farRight, [[maybe_unused]] int farBot) + { + // Add padding for my right border + int right = slot.GetRight() + padding; + // Round up to the nearest compression unit + right = (right + (cellSize - 1)) / cellSize * cellSize; + // Add padding for an adjacent unit's left border + right += padding; + + // Add padding for my right border + int bot = slot.GetBottom() + padding; + // Round up to the nearest compression unit + bot = (bot + (cellSize - 1)) / cellSize * cellSize; + // Add padding for an adjacent unit's left border + bot += padding; + + slot.SetRight(right); + slot.SetBottom(bot); + } + +} diff --git a/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.h b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.h new file mode 100644 index 0000000000..94e2b5b226 --- /dev/null +++ b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.h @@ -0,0 +1,230 @@ +/* +* All or portions of this file Copyright(c) Amazon.com, Inc.or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +*or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace TextureAtlasBuilder +{ + //! Struct that is used to communicate input commands + struct AtlasBuilderInput + { + AZ_CLASS_ALLOCATOR(AtlasBuilderInput, AZ::SystemAllocator, 0); + AZ_TYPE_INFO(AtlasBuilderInput, "{F54477F9-1BDE-4274-8CC0-8320A3EF4A42}"); + + bool m_forceSquare; + bool m_forcePowerOf2; + // Includes a white default texture for the UI to use under certain circumstances + bool m_includeWhiteTexture; + int m_maxDimension; + // At least this much padding will surround each texture except on the edges of the atlas + int m_padding; + // Color used in wasted space + AZ::Color m_unusedColor; + // A preset to use for the texture atlas image processing + AZStd::string m_presetName; + + AZStd::vector m_filePaths; + AtlasBuilderInput(): + m_forceSquare(false), + m_forcePowerOf2(false), + m_includeWhiteTexture(true), + m_maxDimension(4096), + m_padding(1), + // Default color should be a non-transparent color that isn't used often in uis + m_unusedColor(.235f, .702f, .443f, 1) + { + } + + static void Reflect(AZ::ReflectContext* context); + + //! Attempts to read the input from a .texatlas file. "valid" is for reporting exceptions and telling the asset + //! proccesor to fail the job. Supports parsing through a human readable custom parser. + static AtlasBuilderInput ReadFromFile(const AZStd::string& path, const AZStd::string& directory, bool& valid); + + //! Resolves any wild cards in paths + static void AddFilesUsingWildCard(AZStd::vector& paths, const AZStd::string& insert); + + //! Removes anything that matches the wildcard + static void RemoveFilesUsingWildCard(AZStd::vector& paths, const AZStd::string& remove); + + //! Compare considering wildcards + static bool DoesPathnameMatchWildCard(const AZStd::string& rule, const AZStd::string& path); + + //! As FollowsRule but allows extra items after the last '/' + static bool DoesWildCardDirectoryIncludePathname(const AZStd::string& rule, const AZStd::string& path); + + //! Helper function for DoesPathnameMatchWildCard + static bool TokenMatchesWildcard(const AZStd::string& rule, const AZStd::string& token); + + //! Resolves any folder paths into image file paths + static void AddFolderContents(AZStd::vector& paths, const AZStd::string& insert, bool& valid); + + //! Resolves remove commands for folders + static void RemoveFolderContents(AZStd::vector& paths, const AZStd::string& remove); + }; + + //! Struct that is used to represent an object with a width and height in pixels + struct ImageDimension + { + int m_width; + int m_height; + + ImageDimension(int width, int height) + { + m_width = width; + m_height = height; + } + }; + + //! Typedef for an ImageDimension paired with an integer + using IndexImageDimension = AZStd::pair; + + //! Typedef for a list of ImageDimensions paired with integers + using ImageDimensionData = AZStd::vector; + + //! Typedef to simplify references to TextureAtlas::AtlasCoordinates + using AtlasCoordinates = TextureAtlasNamespace::AtlasCoordinates; + + //! Number of bytes in a pixel + const int bytesPerPixel = 4; + + //! The size of the padded sorting units (important for compression) + const int cellSize = 4; + + //! Indexes of the products + enum class Product + { + TexatlasidxProduct = 0, + DdsProduct = 1 + }; + + //! An asset builder for texture atlases + class AtlasBuilderWorker : public AssetBuilderSDK::AssetBuilderCommandBus::Handler + { + public: + AZ_RTTI(AtlasBuilderWorker, "{79036188-E017-4575-9EC0-8D39CB560EA6}"); + + AtlasBuilderWorker() = default; + ~AtlasBuilderWorker() = default; + + //! Asset Builder Callback Functions + + //! Called by asset processor to gather information on a job for a ".texatlas" file + void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, + AssetBuilderSDK::CreateJobsResponse& response); + //! Called by asset proccessor when it wants us to execute a job + void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, + AssetBuilderSDK::ProcessJobResponse& response); + + //! Returns the job related information used by the builder + static AssetBuilderSDK::JobDescriptor GetJobDescriptor(const AZStd::string& sourceFile, const AtlasBuilderInput& input); + + ////////////////////////////////////////////////////////////////////////// + //! AssetBuilderSDK::AssetBuilderCommandBus interface + void ShutDown() override; // if you get this you must fail all existing jobs and return. + ////////////////////////////////////////////////////////////////////////// + + private: + bool m_isShuttingDown = false; + + //! This is the main function that takes a set of inputs and attempts to pack them into an atlas of a given + //! size. Returns true if succesful, does not update out on failure. + static bool TryPack(const ImageDimensionData& images, + int targetWidth, + int targetHeight, + int padding, + size_t& amountFit, + AZStd::vector& out); + + //! Removes any overlap between slotList and the given item + static void TrimOverlap(AZStd::vector& slotList, AtlasCoordinates item); + + //! Uses the proper tightening method based on the input and returns the maximum number of items that were able to be fit + bool TryTightening(AtlasBuilderInput input, + const ImageDimensionData& images, + int smallestWidth, + int smallestHeight, + int targetArea, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out); + + //! Finds the tightest square fit achievable by expanding a square area until a valid fit is found + bool TryTighteningSquare(const ImageDimensionData& images, + int lowerBound, + int maxDimension, + int targetArea, + bool powerOfTwo, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out); + + //! Finds the tightest fit achievable by starting with the optimal thin solution and attempting to resize to be + //! a better shape + bool TryTighteningOptimal(const ImageDimensionData& images, + int smallestWidth, + int smallestHeight, + int maxDimension, + int targetArea, + bool powerOfTwo, + int padding, + int& resultWidth, + int& resultHeight, + size_t& amountFit, + AZStd::vector& out); + + //! Sorting logic for adding a slot to a sorted list in order to maintain increasing order + static void InsertInOrder(AZStd::vector& slotList, AtlasCoordinates item); + + //! Misc Logic For Estimating Target Shape + + //! Returns the width of the widest element + static int GetWidest(const ImageDimensionData& imageList); + + //! Returns the height of the tallest area + static int GetTallest(const ImageDimensionData& imageList); + }; + + //! Used for sorting ImageDimensions + static bool operator<(ImageDimension a, ImageDimension b); + + //! Used to expose the ImageDimension in a pair to AZStd::Sort + static bool operator<(IndexImageDimension a, IndexImageDimension b); + + //! Returns true if two coordinate sets overlap + static bool Collides(AtlasCoordinates a, AtlasCoordinates b); + + //! Returns true if item collides with any object in list + static bool Collides(AtlasCoordinates item, AZStd::vector list); + + //! Returns the portion of the second item that overlaps with the first + static AtlasCoordinates GetOverlap(AtlasCoordinates a, AtlasCoordinates b); + + //! Performs an operation that copies a pixel to the output + static void SetPixels(AZ::u8* dest, const AZ::u8* source, int destBytes); + + //! Checks if we can insert an image into a slot + static bool CanInsert(AtlasCoordinates slot, ImageDimension image, int padding, int farRight, int farBot); + + //! Adds the necessary padding to an Atlas Coordinate + static void AddPadding(AtlasCoordinates& slot, int padding, int farRight, int farBot); +} diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index 447083dca4..8df46e2b1a 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -27,8 +27,7 @@ ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform ly_associate_package(PACKAGE_NAME zstd-1.35-multiplatform TARGETS zstd PACKAGE_HASH 45d466c435f1095898578eedde85acf1fd27190e7ea99aeaa9acfd2f09e12665) ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform TARGETS SQLite PACKAGE_HASH dd4d3de6cbb4ce3d15fc504ba0ae0587e515dc89a25228037035fc0aef4831f4) ly_associate_package(PACKAGE_NAME SPIRVCross-2020.04.20-rev1-multiplatform TARGETS SPIRVCross PACKAGE_HASH 7c8c0eaa0166c26745c62d2238525af7e27ac058a5db3defdbaec1878e8798dd) -ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-2020.08.07-rev1-multiplatform TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 04a6850ce03d4c16e19ed206f7093d885276dfb74047e6aa99f0a834c8b7cc73) -ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxcAz-5.0.0_az-rev1-multiplatform TARGETS DirectXShaderCompilerDxcAz PACKAGE_HASH 94f24989a7a371d840b513aa5ffaff02747b3d19b119bc1f899427e29978f753) +ly_associate_package(PACKAGE_NAME DirectXShaderCompiler-1.6.2104-o3de-rev1-mac TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 4e97484f8fcf73fc39f22fc85ae86933a8f2e3ba0748fcec128bce05795035a6) ly_associate_package(PACKAGE_NAME azslc-1.7.21-rev1-multiplatform TARGETS azslc PACKAGE_HASH 772b7a2d9cc68aa1da4f0ee7db57ee1b4e7a8f20b81961fc5849af779582f4df) ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index d6f9270c68..4f3b91c633 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -27,8 +27,7 @@ ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform ly_associate_package(PACKAGE_NAME zstd-1.35-multiplatform TARGETS zstd PACKAGE_HASH 45d466c435f1095898578eedde85acf1fd27190e7ea99aeaa9acfd2f09e12665) ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform TARGETS SQLite PACKAGE_HASH dd4d3de6cbb4ce3d15fc504ba0ae0587e515dc89a25228037035fc0aef4831f4) ly_associate_package(PACKAGE_NAME SPIRVCross-2020.04.20-rev1-multiplatform TARGETS SPIRVCross PACKAGE_HASH 7c8c0eaa0166c26745c62d2238525af7e27ac058a5db3defdbaec1878e8798dd) -ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-2020.08.07-rev1-multiplatform TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 04a6850ce03d4c16e19ed206f7093d885276dfb74047e6aa99f0a834c8b7cc73) -ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxcAz-5.0.0_az-rev1-multiplatform TARGETS DirectXShaderCompilerDxcAz PACKAGE_HASH 94f24989a7a371d840b513aa5ffaff02747b3d19b119bc1f899427e29978f753) +ly_associate_package(PACKAGE_NAME DirectXShaderCompiler-1.6.2104-o3de-rev1-windows TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 2c60297758d73f7833911e5ae3006fe0b10ced6e0b1b54764b33ae2b86e0d41d) ly_associate_package(PACKAGE_NAME azslc-1.7.21-rev1-multiplatform TARGETS azslc PACKAGE_HASH 772b7a2d9cc68aa1da4f0ee7db57ee1b4e7a8f20b81961fc5849af779582f4df) ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) diff --git a/cmake/Platform/Windows/PackagingBootstrapper.wxs b/cmake/Platform/Windows/Packaging/Bootstrapper.wxs similarity index 90% rename from cmake/Platform/Windows/PackagingBootstrapper.wxs rename to cmake/Platform/Windows/Packaging/Bootstrapper.wxs index c3d1dd7a7b..55e8a8cd95 100644 --- a/cmake/Platform/Windows/PackagingBootstrapper.wxs +++ b/cmake/Platform/Windows/Packaging/Bootstrapper.wxs @@ -9,6 +9,7 @@ Version="$(var.CPACK_PACKAGE_VERSION)" Manufacturer="$(var.CPACK_PACKAGE_VENDOR)" UpgradeCode="$(var.CPACK_BOOTSTRAP_UPGRADE_GUID)" + IconSourceFile="$(var.CPACK_WIX_PRODUCT_ICON)" DisableModify="yes"> diff --git a/cmake/Platform/Windows/Packaging/Shortcuts.wxs b/cmake/Platform/Windows/Packaging/Shortcuts.wxs new file mode 100644 index 0000000000..fb9d359b5a --- /dev/null +++ b/cmake/Platform/Windows/Packaging/Shortcuts.wxs @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cmake/Platform/Windows/PackagingTemplate.wxs.in b/cmake/Platform/Windows/Packaging/Template.wxs.in similarity index 91% rename from cmake/Platform/Windows/PackagingTemplate.wxs.in rename to cmake/Platform/Windows/Packaging/Template.wxs.in index 0b3c597ab6..2900b96f41 100644 --- a/cmake/Platform/Windows/PackagingTemplate.wxs.in +++ b/cmake/Platform/Windows/Packaging/Template.wxs.in @@ -38,7 +38,10 @@ - + + + + diff --git a/cmake/Platform/Windows/Packaging/product_icon.ico b/cmake/Platform/Windows/Packaging/product_icon.ico new file mode 100644 index 0000000000..0680ceea19 --- /dev/null +++ b/cmake/Platform/Windows/Packaging/product_icon.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c042fce57915fc749abc7b37de765fd697c3c4d7de045a3d44805aa0ce29901a +size 107016 diff --git a/cmake/Platform/Windows/Packaging/product_logo.png b/cmake/Platform/Windows/Packaging/product_logo.png new file mode 100644 index 0000000000..d5fd60ffb8 --- /dev/null +++ b/cmake/Platform/Windows/Packaging/product_logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac0348c906c91de864cba91c0231b4794d8a00fafa630d13f2232351b90aa59b +size 11074 diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake index 3ba6ef2096..d379358bf4 100644 --- a/cmake/Platform/Windows/PackagingPostBuild.cmake +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -30,6 +30,7 @@ set(_addtional_defines -dCPACK_LOCAL_INSTALLER_DIR=${_cpack_wix_out_dir} -dCPACK_PACKAGE_FILE_NAME=${CPACK_PACKAGE_FILE_NAME} -dCPACK_PACKAGE_INSTALL_DIRECTORY=${_fixed_package_install_dir} + -dCPACK_WIX_PRODUCT_LOGO=${CPACK_WIX_PRODUCT_LOGO} ) if(CPACK_LICENSE_URL) @@ -43,7 +44,7 @@ set(_candle_command "-I${_cpack_wix_out_dir}" # to include cpack_variables.wxi ${_addtional_defines} ${_ext_flags} - "${CPACK_SOURCE_DIR}/Platform/Windows/PackagingBootstrapper.wxs" + "${CPACK_SOURCE_DIR}/Platform/Windows/Packaging/Bootstrapper.wxs" -o "${_bootstrap_out_dir}/" ) diff --git a/cmake/Platform/Windows/Packaging_windows.cmake b/cmake/Platform/Windows/Packaging_windows.cmake index 2ea35c1d9b..ce73e9a07b 100644 --- a/cmake/Platform/Windows/Packaging_windows.cmake +++ b/cmake/Platform/Windows/Packaging_windows.cmake @@ -48,29 +48,28 @@ set(_guid_seed_base "${PROJECT_NAME}_${LY_VERSION_STRING}") generate_wix_guid(_wix_default_product_guid "${_guid_seed_base}_ProductID" ) generate_wix_guid(_wix_default_upgrade_guid "${_guid_seed_base}_UpgradeCode") -set(LY_WIX_PRODUCT_GUID "${_wix_default_product_guid}" CACHE STRING "GUID for the Product ID field. Format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") -set(LY_WIX_UPGRADE_GUID "${_wix_default_upgrade_guid}" CACHE STRING "GUID for the Upgrade Code field. Format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") +set(LY_WIX_PRODUCT_GUID "" CACHE STRING "GUID for the Product ID field. Format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") +set(LY_WIX_UPGRADE_GUID "" CACHE STRING "GUID for the Upgrade Code field. Format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") -set(_uses_default_product_guid FALSE) -if(NOT LY_WIX_PRODUCT_GUID OR LY_WIX_PRODUCT_GUID STREQUAL ${_wix_default_product_guid}) - set(_uses_default_product_guid TRUE) - set(LY_WIX_PRODUCT_GUID ${_wix_default_product_guid}) +# clear previously cached default values to correct future runs. this will +# unfortunately only work if the seed properties still haven't changed +if(LY_WIX_PRODUCT_GUID STREQUAL ${_wix_default_product_guid}) + unset(LY_WIX_PRODUCT_GUID CACHE) endif() - -set(_uses_default_upgrade_guid FALSE) -if(NOT LY_WIX_UPGRADE_GUID OR LY_WIX_UPGRADE_GUID STREQUAL ${_wix_default_upgrade_guid}) - set(_uses_default_upgrade_guid TRUE) - set(LY_WIX_UPGRADE_GUID ${_wix_default_upgrade_guid}) +if(LY_WIX_UPGRADE_GUID STREQUAL ${_wix_default_upgrade_guid}) + unset(LY_WIX_UPGRADE_GUID CACHE) endif() -if(_uses_default_product_guid OR _uses_default_upgrade_guid) +if(NOT (LY_WIX_PRODUCT_GUID AND LY_WIX_UPGRADE_GUID)) message(STATUS "One or both WiX GUIDs were auto generated. It is recommended you supply your own GUIDs through LY_WIX_PRODUCT_GUID and LY_WIX_UPGRADE_GUID.") - if(_uses_default_product_guid) + if(NOT LY_WIX_PRODUCT_GUID) + set(LY_WIX_PRODUCT_GUID ${_wix_default_product_guid}) message(STATUS "-> Default LY_WIX_PRODUCT_GUID = ${LY_WIX_PRODUCT_GUID}") endif() - if(_uses_default_upgrade_guid) + if(NOT LY_WIX_UPGRADE_GUID) + set(LY_WIX_UPGRADE_GUID ${_wix_default_upgrade_guid}) message(STATUS "-> Default LY_WIX_UPGRADE_GUID = ${LY_WIX_UPGRADE_GUID}") endif() endif() @@ -78,7 +77,14 @@ endif() set(CPACK_WIX_PRODUCT_GUID ${LY_WIX_PRODUCT_GUID}) set(CPACK_WIX_UPGRADE_GUID ${LY_WIX_UPGRADE_GUID}) -set(CPACK_WIX_TEMPLATE "${CPACK_SOURCE_DIR}/Platform/Windows/PackagingTemplate.wxs.in") +set(CPACK_WIX_PRODUCT_LOGO ${CPACK_SOURCE_DIR}/Platform/Windows/Packaging/product_logo.png) +set(CPACK_WIX_PRODUCT_ICON ${CPACK_SOURCE_DIR}/Platform/Windows/Packaging/product_icon.ico) + +set(CPACK_WIX_TEMPLATE "${CPACK_SOURCE_DIR}/Platform/Windows/Packaging/Template.wxs.in") + +set(CPACK_WIX_EXTRA_SOURCES + "${CPACK_SOURCE_DIR}/Platform/Windows/Packaging/Shortcuts.wxs" +) set(_embed_artifacts "yes") diff --git a/cmake/Platform/Windows/platform_windows_files.cmake b/cmake/Platform/Windows/platform_windows_files.cmake index 84d1a3098c..3ce53fbcea 100644 --- a/cmake/Platform/Windows/platform_windows_files.cmake +++ b/cmake/Platform/Windows/platform_windows_files.cmake @@ -24,7 +24,8 @@ set(FILES PALDetection_windows.cmake Install_windows.cmake Packaging_windows.cmake - PackagingBootstrapper.wxs PackagingPostBuild.cmake - PackagingTemplate.wxs.in + Packaging/Bootstrapper.wxs + Packaging/Shortcuts.wxs + Packaging/Template.wxs.in ) diff --git a/scripts/build/build_node/Platform/Linux/install-ubuntu-python3.sh b/scripts/build/build_node/Platform/Linux/install-ubuntu-python3.sh new file mode 100755 index 0000000000..d0855aff43 --- /dev/null +++ b/scripts/build/build_node/Platform/Linux/install-ubuntu-python3.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +# This script must be run as root +if [[ $EUID -ne 0 ]] +then + echo "This script must be run as root (sudo)" + exit 1 +fi + +# Install python3 if necessary +python3 --version >/dev/null 2>&1 +if [ $? -ne 0 ] +then + echo Installing Python3 + apt-get install python3 + + if [ $? -ne 0 ] + then + echo Error installing python3 + exit 1 + fi + +else + PYTHON_VERSION=$(python3 --version) + echo Python3 already installed \($PYTHON_VERSION\) +fi + +# Install python3 pip if necessary +pip3 --version >/dev/null 2>&1 +if [ $? -ne 0 ] +then + echo Installing Python3 PIP + apt-get install -y python3-pip + + if [ $? -ne 0 ] + then + echo Error installing python3 + exit 1 + fi + +else + PYTHON_VERSION=$(pip3 --version | awk '{print $2}') + echo Python3 Pip already installed \($PYTHON_VERSION\) +fi + + +# Read from the package list and process each package +PIP_REQUIREMENTS_FILE=requirements.txt + +pip3 install -r $PIP_REQUIREMENTS_FILE +if [ $? -ne 0 ] +then + echo Error installing python3 + exit 1 +fi + + +echo Python3 setup complete +exit 0 diff --git a/scripts/build/build_node/Platform/Linux/requirements.txt b/scripts/build/build_node/Platform/Linux/requirements.txt new file mode 100644 index 0000000000..1a466c03f8 --- /dev/null +++ b/scripts/build/build_node/Platform/Linux/requirements.txt @@ -0,0 +1,43 @@ +boto3==1.16.18 \ + --hash=sha256:51c419d890ae216b9b031be31f3182739dc3deb5b64351f286bffca2818ddb35 \ + --hash=sha256:d70d21ea137d786e84124639a62be42f92f4b09472ebfb761156057c92dc5366 +psutil==5.8.0 \ + --hash=sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64 \ + --hash=sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131 \ + --hash=sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c \ + --hash=sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6 \ + --hash=sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023 \ + --hash=sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df \ + --hash=sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394 \ + --hash=sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4 \ + --hash=sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b \ + --hash=sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2 \ + --hash=sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d \ + --hash=sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65 \ + --hash=sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d \ + --hash=sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef \ + --hash=sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7 \ + --hash=sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60 \ + --hash=sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6 \ + --hash=sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8 \ + --hash=sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b \ + --hash=sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d \ + --hash=sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac \ + --hash=sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935 \ + --hash=sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d \ + --hash=sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28 \ + --hash=sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876 \ + --hash=sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0 \ + --hash=sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3 \ + --hash=sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563 +requests==2.25.1 \ + --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \ + --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e +traceback2==1.4.0 \ + --hash=sha256:05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030 \ + --hash=sha256:8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23 +urllib3==1.26.4 \ + --hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \ + --hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937 +tempfile2==0.1.1 \ + --hash=sha256:77fdd256c16804053d3d588168b79595099ea5e874c3fb171893b0ababd10340