You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
304 lines
14 KiB
C++
304 lines
14 KiB
C++
/*
|
|
* 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 <QString>
|
|
#include <QByteArray>
|
|
#include <QHash>
|
|
#include <QObject>
|
|
#include <AzCore/Interface/Interface.h>
|
|
|
|
#include <AzFramework/Asset/AssetSystemTypes.h>
|
|
#include <connection/connectionManager.h>
|
|
#endif
|
|
|
|
namespace AzFramework
|
|
{
|
|
namespace AssetSystem
|
|
{
|
|
class BaseAssetProcessorMessage;
|
|
} // namespace AssetSystem
|
|
} // namespace AzFramework
|
|
|
|
namespace AssetProcessor
|
|
{
|
|
class AssetRequestHandler;
|
|
|
|
template<typename TRequest>
|
|
struct MessageData
|
|
{
|
|
static_assert(AZStd::is_base_of<AzFramework::AssetSystem::BaseAssetProcessorMessage, TRequest>::value, "TRequest must derive from BaseAssetProcessorMessage");
|
|
|
|
AZStd::shared_ptr<TRequest> m_message;
|
|
NetworkRequestID m_key;
|
|
QString m_platform;
|
|
bool m_fencingFailed{ false };
|
|
|
|
MessageData() = default;
|
|
MessageData(AZStd::shared_ptr<TRequest> message, NetworkRequestID key, QString platform, bool fencingFailed = false)
|
|
: m_message(message), m_key(key), m_platform(platform), m_fencingFailed(fencingFailed)
|
|
{}
|
|
|
|
template<typename TOther>
|
|
MessageData(const MessageData<TOther>& rhs)
|
|
{
|
|
m_message = AZStd::rtti_pointer_cast<TRequest>(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<void(unsigned int, unsigned int, QByteArray, QString)> requestHandler)
|
|
: m_requestHandler(AZStd::move(requestHandler))
|
|
{
|
|
AZ::Interface<IRequestRouter>::Register(this);
|
|
}
|
|
|
|
virtual ~IRequestRouter()
|
|
{
|
|
AZ::Interface<IRequestRouter>::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<typename TRequest, typename TResponse, typename TClass>
|
|
void RegisterQueuedCallbackHandler(TClass* obj, TResponse(TClass::* handler)(AssetProcessor::MessageData<TRequest>))
|
|
{
|
|
// 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<TRequest, void>([=](MessageData<TRequest> 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<TRequest, TResponse>([obj, handler](MessageData<TRequest> 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 <class TRequest, class TResponse>
|
|
void RegisterMessageHandler(TResponse(*handler)(MessageData<TRequest> messageData))
|
|
{
|
|
RegisterMessageHandler<TRequest, TResponse>(AZStd::function<TResponse(MessageData<TRequest>)>(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 <class TRequest, class TResponse>
|
|
void RegisterMessageHandler(AZStd::function<TResponse(MessageData<TRequest>)> handler)
|
|
{
|
|
static constexpr unsigned int MessageType = TRequest::MessageType;
|
|
|
|
m_messageHandlers[MessageType] = [handler = AZStd::move(handler)](MessageData<AzFramework::AssetSystem::BaseAssetProcessorMessage> messageData)
|
|
{
|
|
MessageData<TRequest> downcastData = messageData;
|
|
|
|
if (downcastData.m_message)
|
|
{
|
|
IRequestRouter::HandleResponse<TRequest, TResponse>(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<class TRequest>
|
|
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<class TRequest, class TResponse, typename AZStd::enable_if_t<!AZStd::is_void_v<TResponse>>* = nullptr>
|
|
static void HandleResponse(AZStd::function<TResponse(MessageData<TRequest>)> handler, MessageData<TRequest> messageData)
|
|
{
|
|
auto&& response = handler(messageData);
|
|
|
|
ConnectionBus::Event(messageData.m_key.first, &ConnectionBus::Events::SendResponse, messageData.m_key.second, response);
|
|
}
|
|
|
|
template<class TRequest, class TResponse, typename AZStd::enable_if_t<AZStd::is_void_v<TResponse>>* = nullptr>
|
|
static void HandleResponse(AZStd::function<TResponse(MessageData<TRequest>)> handler, MessageData<TRequest> messageData)
|
|
{
|
|
// This template handles void returns which mean no response should be sent
|
|
handler(messageData);
|
|
}
|
|
|
|
using MessageHandler = AZStd::function<void(MessageData<AzFramework::AssetSystem::BaseAssetProcessorMessage>)>;
|
|
|
|
//! Map of messageType to message handler callback
|
|
AZStd::unordered_map<unsigned int /*messageType*/, MessageHandler> m_messageHandlers;
|
|
|
|
//! Parent object callback which will be registered with the ConnectionManager for each message
|
|
AZStd::function<void(unsigned int, unsigned int, QByteArray, QString)> 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<AzFramework::AssetSystem::RequestAssetStatus> 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
|
|
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<typename TRequest, typename TResponse>
|
|
AZStd::function<TResponse(MessageData<TRequest>)> ToFunction(TResponse(AssetRequestHandler::* func)(MessageData<TRequest>))
|
|
{
|
|
using namespace AZStd::placeholders;
|
|
|
|
return AZStd::function<TResponse(MessageData<TRequest>)>(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<AzFramework::AssetSystem::BaseAssetProcessorMessage> message);
|
|
|
|
private:
|
|
|
|
void DeleteFenceFile_Retry(unsigned fenceId, QString fenceFileName, NetworkRequestID key, AZStd::shared_ptr<BaseAssetProcessorMessage> message, QString platform, int retriesRemaining);
|
|
|
|
void SendAssetStatus(NetworkRequestID groupID, unsigned int type, AssetStatus status);
|
|
|
|
void HandleRequestEscalateAsset(MessageData<AzFramework::AssetSystem::RequestEscalateAsset> 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<NetworkRequestID, AssetRequestLine> m_pendingAssetRequests;
|
|
|
|
//! 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<BaseAssetProcessorMessage> message, QString platform)
|
|
:m_requestId(requestId)
|
|
, m_message(AZStd::move(message))
|
|
, m_platform(platform)
|
|
{
|
|
}
|
|
|
|
NetworkRequestID m_requestId{};
|
|
AZStd::shared_ptr<BaseAssetProcessorMessage> m_message{};
|
|
QString m_platform{};
|
|
};
|
|
AZStd::unordered_map<unsigned int, RequestInfo> m_pendingFenceRequestMap;
|
|
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
|