/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #pragma once #if !defined(Q_MOC_RUN) #include "native/assetprocessor.h" #include "native/utilities/assetUtils.h" #include #include #include #include #include #include #include #endif namespace AzFramework { namespace AssetSystem { class BaseAssetProcessorMessage; } // namespace AssetSystem } // namespace AzFramework namespace AssetProcessor { class AssetRequestHandler; template struct MessageData { static_assert(AZStd::is_base_of::value, "TRequest must derive from BaseAssetProcessorMessage"); AZStd::shared_ptr m_message; NetworkRequestID m_key; QString m_platform; bool m_fencingFailed{ false }; MessageData() = default; MessageData(AZStd::shared_ptr message, NetworkRequestID key, QString platform, bool fencingFailed = false) : m_message(message), m_key(key), m_platform(platform), m_fencingFailed(fencingFailed) {} template MessageData(const MessageData& rhs) { m_message = AZStd::rtti_pointer_cast(rhs.m_message); m_key = rhs.m_key; m_platform = rhs.m_platform; m_fencingFailed = rhs.m_fencingFailed; } }; struct IRequestRouter { friend class AssetRequestHandler; AZ_RTTI(IRequestRouter, "{FC7F875C-2CD1-4CD2-AC63-71097DF612AC}"); IRequestRouter(AZStd::function requestHandler) : m_requestHandler(AZStd::move(requestHandler)) { AZ::Interface::Register(this); } virtual ~IRequestRouter() { AZ::Interface::Unregister(this); } //! Registers a QT object callback as a handler for a TRequest type of message. //! The callback function will be run on obj's thread //! If the return value of the handler is void, no response will be sent. //! Not thread-safe, do not call after AP initialization stage template void RegisterQueuedCallbackHandler(TClass* obj, TResponse(TClass::* handler)(AssetProcessor::MessageData)) { AZ_Assert(obj, "Programmer Error - Handler object is null"); // Return type is set to void here since the response needs to be delayed along with the handler call // HandleResponse gets called twice in this whole chain but the first time won't attempt to send a response because of this void RegisterMessageHandler([=](MessageData messageData) { QMetaObject::invokeMethod(obj, [=]() { // This will run on the obj's thread and handle sending the response now that we're ready to process HandleResponse([obj, handler](MessageData messageData) -> TResponse { return (obj->*handler)(messageData); }, messageData); }, Qt::ConnectionType::QueuedConnection); }); } //! Registers a callback as a handler for a TRequest type of message. //! If the return value of the handler is void, no response will be sent. //! Not thread-safe, do not call after AP initialization stage template void RegisterMessageHandler(TResponse(*handler)(MessageData messageData)) { RegisterMessageHandler(AZStd::function)>(AZStd::move(handler))); } //! Registers a callback as a handler for a TRequest type of message. //! If the return value of the handler is void, no response will be sent. //! Not thread-safe, do not call after AP initialization stage template void RegisterMessageHandler(AZStd::function)> handler) { static constexpr unsigned int MessageType = TRequest::MessageType; m_messageHandlers[MessageType] = [handler = AZStd::move(handler)](MessageData messageData) { MessageData downcastData = messageData; if (downcastData.m_message) { IRequestRouter::HandleResponse(AZStd::move(handler), downcastData); } else { AZ_TracePrintf(AssetProcessor::DebugChannel, "Expected message type (%d) but incoming message type is %d.\n", MessageType, messageData.m_message->GetMessageType()); } }; using namespace AZStd::placeholders; ConnectionManagerRequestBus::Broadcast(&ConnectionManagerRequestBus::Events::RegisterService, MessageType, AZStd::bind(m_requestHandler, _1, _3, _4, _5)); } template void UnregisterMessageHandler() { static constexpr unsigned int MessageType = TRequest::MessageType; auto messageItr = m_messageHandlers.find(MessageType); if(messageItr != m_messageHandlers.end()) { m_messageHandlers.erase(messageItr); } } AZ_DISABLE_COPY_MOVE(IRequestRouter); protected: //! Helper to handle sending a response for a message if one is needed. template>* = nullptr> static void HandleResponse(AZStd::function)> handler, MessageData messageData) { auto&& response = handler(messageData); ConnectionBus::Event(messageData.m_key.first, &ConnectionBus::Events::SendResponse, messageData.m_key.second, response); } template>* = nullptr> static void HandleResponse(AZStd::function)> handler, MessageData messageData) { // This template handles void returns which mean no response should be sent handler(messageData); } using MessageHandler = AZStd::function)>; //! Map of messageType to message handler callback AZStd::unordered_map m_messageHandlers; //! Parent object callback which will be registered with the ConnectionManager for each message AZStd::function m_requestHandler; }; //! AssetRequestHandler //! this exists to handle requests from outside sources to compile assets. //! or to get the status of groups of assets. class AssetRequestHandler : public QObject { using AssetStatus = AzFramework::AssetSystem::AssetStatus; using BaseAssetProcessorMessage = AzFramework::AssetSystem::BaseAssetProcessorMessage; Q_OBJECT public: AssetRequestHandler(); protected: //! This function creates a fence file. //! It will return the fencefile path if it succeeds, otherwise it returns an empty string virtual QString CreateFenceFile(unsigned int fenceId); //! This function delete a fence file. //! it will return true if it succeeds, otherwise it returns false. virtual bool DeleteFenceFile(QString fenceFileName); Q_SIGNALS: //! Request that a compile group is created for all assets that match that platform and search term. //! emitting this signal will ultimately result in OnCompileGroupCreated and OnCompileGroupFinished being executed //! at some later time with the same groupID. void RequestCompileGroup(NetworkRequestID groupID, QString platform, QString searchTerm, AZ::Data::AssetId assetId, bool isStatusRequest, int searchType); //! This request goes out to ask the system in general whether an asset can be found (as a product). void RequestAssetExists(NetworkRequestID groupID, QString platform, QString searchTerm, AZ::Data::AssetId assetId, int searchType); void RequestEscalateAssetByUuid(QString platform, AZ::Uuid escalatedAssetUUID); void RequestEscalateAssetBySearchTerm(QString platform, QString escalatedSearchTerm); public Q_SLOTS: //! ProcessGetAssetStatus - someone on the network wants to know about the status of an asset. //! isStatusRequest will be TRUE if its a status request. If its false it means its a compile request void ProcessAssetRequest(MessageData messageData); //! OnCompileGroupCreated is invoked in response to asking for a compile group to be created. //! Its status will either be Unknown if no assets are queued or in flight that match that pattern //! or it will be Queued or Compiling if some were matched. //! If you get a Queued or Compiling, you will eventually get a OnCompileGroupFinished with the same group ID. void OnCompileGroupCreated(NetworkRequestID groupID, AssetStatus status); //! OnCompileGroupFinished is expected to be called when a compile group completes or fails. //! the status is expected to be either Compiled or Failed. void OnCompileGroupFinished(NetworkRequestID groupID, AssetStatus status); //! Called from the outside in response to a RequestAssetExists. void OnRequestAssetExistsResponse(NetworkRequestID groupID, bool exists); void OnFenceFileDetected(unsigned int fenceId); //! This will get called for every asset related messages or messages that require fencing virtual void OnNewIncomingRequest(unsigned int connId, unsigned int serial, QByteArray payload, QString platform); public: //! Just return how many in flight requests there are. int GetNumOutstandingAssetRequests() const; protected: template AZStd::function)> ToFunction(TResponse(AssetRequestHandler::* func)(MessageData)) { using namespace AZStd::placeholders; return AZStd::function)>(AZStd::bind(func, this, _1)); } // Invokes the appropriate handler and returns true if the message should be deleted by the caller and false if the request handler is responsible for deleting the message virtual bool InvokeHandler(MessageData message); //! This is an internal struct that is used for storing all the necessary information for requests that require fencing struct RequestInfo { RequestInfo() = default; RequestInfo(NetworkRequestID requestId, AZStd::shared_ptr message, QString platform) : m_requestId(requestId) , m_message(AZStd::move(message)) , m_platform(platform) { } NetworkRequestID m_requestId{}; AZStd::shared_ptr m_message{}; QString m_platform{}; }; AZStd::unordered_map m_pendingFenceRequestMap; protected: void DeleteFenceFile_Retry(unsigned fenceId, QString fenceFileName, NetworkRequestID key, AZStd::shared_ptr message, QString platform, int retriesRemaining); void SendAssetStatus(NetworkRequestID groupID, unsigned int type, AssetStatus status); void HandleRequestEscalateAsset(MessageData messageData); // we keep state about a request in this class: class AssetRequestLine { public: AssetRequestLine(QString platform, QString searchTerm, const AZ::Data::AssetId& assetId, bool isStatusRequest, int searchType); bool IsStatusRequest() const; QString GetPlatform() const; QString GetSearchTerm() const; const AZ::Data::AssetId& GetAssetId() const; QString GetDisplayString() const; int GetSearchType() const; private: bool m_isStatusRequest; QString m_platform; QString m_searchTerm; AZ::Data::AssetId m_assetId; int m_searchType{ 0 }; }; // this map keeps track of whether a request was for a compile (FALSE), or a status (TRUE) QHash m_pendingAssetRequests; unsigned int m_fenceId = 0; IRequestRouter m_requestRouter{ [this](unsigned int connId, unsigned int serial, QByteArray payload, QString platform) {OnNewIncomingRequest(connId, serial, payload, platform); } }; }; } // namespace AssetProcessor