Gems/AWSCore/Code/Include/Framework/AWSApiRequestJob.h (233 lines of code) (raw):
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Component/TickBus.h>
#include <Framework/AWSApiClientJob.h>
#include <Framework/AWSApiRequestJobConfig.h>
namespace Aws
{
namespace Client
{
class AsyncCallerContext;
}
}
namespace AWSCore
{
/// A macro that simplifies the declaration of AwsApiRequestTraits for APIs that have a result.
///
/// \param SERVICE - the name of an Aws service. e.g. Lambda.
///
/// \param REQUEST - the name of an Aws request supported by that
/// service. e.g. Invoke.
#define AWS_API_REQUEST_TRAITS(SERVICE, REQUEST) \
AWSCore::AwsApiRequestTraits< \
Aws::SERVICE::SERVICE##Client, \
Aws::SERVICE::Model::REQUEST##Request, \
Aws::SERVICE::Model::REQUEST##Outcome, \
Aws::SERVICE::Model::REQUEST##OutcomeCallable, \
Aws::SERVICE::REQUEST##ResponseReceivedHandler, \
Aws::SERVICE::Model::REQUEST##Result, \
Aws::Client::AWSError<Aws::SERVICE::SERVICE##Errors>, \
&Aws::SERVICE::SERVICE##Client::REQUEST, \
&Aws::SERVICE::SERVICE##Client::REQUEST##Callable, \
&Aws::SERVICE::SERVICE##Client::REQUEST##Async \
>
/// A macro that simplifies the declaration of AwsApiRequestTraits for APIs that have no result.
///
/// \param SERVICE - the name of an Aws service. e.g. Lambda.
///
/// \param REQUEST - the name of an Aws request supported by that
/// service. e.g. Invoke.
#define AWS_API_REQUEST_TRAITS_NO_RESULT(SERVICE, REQUEST) \
AWSCore::AwsApiRequestTraits< \
Aws::SERVICE::SERVICE##Client, \
Aws::SERVICE::Model::REQUEST##Request, \
Aws::SERVICE::Model::REQUEST##Outcome, \
Aws::SERVICE::Model::REQUEST##OutcomeCallable, \
Aws::SERVICE::REQUEST##ResponseReceivedHandler, \
Aws::NoResult, \
Aws::Client::AWSError<Aws::SERVICE::SERVICE##Errors>, \
&Aws::SERVICE::SERVICE##Client::REQUEST, \
&Aws::SERVICE::SERVICE##Client::REQUEST##Callable, \
&Aws::SERVICE::SERVICE##Client::REQUEST##Async \
>
/// Macro used below to define the traits type. Parameters
/// correspond to those generated by the macro above.
#define AWS_API_REQUEST_TRAITS_TEMPLATE_DEFINITION_HELPER \
template< \
class _ClientType, \
class _RequestType, \
class _OutcomeType, \
class _OutcomeCallableType, \
class _AsyncHandlerType, \
class _ResultType, \
class _ErrorType, \
RequestFunctionType<_ClientType, _OutcomeType, _RequestType> _Function, \
RequestCallableFunctionType<_ClientType, _OutcomeCallableType, _RequestType> _CallableFunction, \
RequestAsyncFunctionType<_ClientType, _RequestType, _AsyncHandlerType> _AsyncFunction \
>
/// Macro used below to define the traits type. Parameters
/// correspond to those generated by the macro above.
#define AWS_API_REQUEST_TRAITS_TEMPLATE_INSTANCE_HELPER \
AwsApiRequestTraits< \
_ClientType, \
_RequestType, \
_OutcomeType, \
_OutcomeCallableType, \
_AsyncHandlerType, \
_ResultType, \
_ErrorType, \
_Function, \
_CallableFunction, \
_AsyncFunction \
>
// const pointer to const sync execution member function of client type
template<class ClientType, class OutcomeType, class RequestType>
using RequestFunctionType = OutcomeType(ClientType::* const)(const RequestType&) const;
// const pointer to const callable execution member function of client type
template<class ClientType, class OutcomeCallableType, class RequestType>
using RequestCallableFunctionType = OutcomeCallableType(ClientType::* const)(const RequestType&) const;
// const pointer to const async execution member function of client type
template<class ClientType, class RequestType, class AsyncHandlerType>
using RequestAsyncFunctionType = void (ClientType::* const)(const RequestType&, const AsyncHandlerType&, const std::shared_ptr<const Aws::Client::AsyncCallerContext>&) const;
/// Encapsulates everything we need to know to make an AWS service
/// request of a particular type. Use the AWS_API_REQUEST_TRAITS macro
/// instead of using this type directly.
AWS_API_REQUEST_TRAITS_TEMPLATE_DEFINITION_HELPER
struct AwsApiRequestTraits
{
using ClientType = _ClientType;
using RequestType = _RequestType;
using OutcomeType = _OutcomeType;
using CallableOutcomeType = _OutcomeCallableType;
using AsyncHandlerType = _AsyncHandlerType;
using ResultType = _ResultType;
using ErrorType = _ErrorType;
using FunctionType = RequestFunctionType<_ClientType, _OutcomeType, _RequestType>;
using CallableFunctionType = RequestCallableFunctionType<_ClientType, _OutcomeCallableType, _RequestType>;
using AsyncFunctionType = RequestAsyncFunctionType<_ClientType, _RequestType, _AsyncHandlerType>;
static FunctionType Function;
static CallableFunctionType CallableFunction;
static AsyncFunctionType AsyncFunction;
};
/// Define storage for AwsApiRequestTraits::Function
AWS_API_REQUEST_TRAITS_TEMPLATE_DEFINITION_HELPER
typename AWS_API_REQUEST_TRAITS_TEMPLATE_INSTANCE_HELPER::FunctionType AWS_API_REQUEST_TRAITS_TEMPLATE_INSTANCE_HELPER::Function = _Function;
/// Define storage for AwsApiRequestTraits::CallableFunction
AWS_API_REQUEST_TRAITS_TEMPLATE_DEFINITION_HELPER
typename AWS_API_REQUEST_TRAITS_TEMPLATE_INSTANCE_HELPER::CallableFunctionType AWS_API_REQUEST_TRAITS_TEMPLATE_INSTANCE_HELPER::CallableFunction = _CallableFunction;
/// Define storage for AwsApiRequestTraits::AsyncFunction
AWS_API_REQUEST_TRAITS_TEMPLATE_DEFINITION_HELPER
typename AWS_API_REQUEST_TRAITS_TEMPLATE_INSTANCE_HELPER::AsyncFunctionType AWS_API_REQUEST_TRAITS_TEMPLATE_INSTANCE_HELPER::AsyncFunction = _AsyncFunction;
/// Macro that simplifies the declaration of an AwsRequestJob that has a result.
#define AWS_API_REQUEST_JOB(SERVICE, REQUEST) AWSCore::AwsApiRequestJob<AWS_API_REQUEST_TRAITS(SERVICE, REQUEST)>
/// Macro that simplifies the declaration of an AwsRequestJob that has no result.
#define AWS_API_REQUEST_JOB_NO_RESULT(SERVICE, REQUEST) AWSCore::AwsApiRequestJob<AWS_API_REQUEST_TRAITS_NO_RESULT(SERVICE, REQUEST)>
/// An Az::Job that that executes a specific AWS request.
template<class RequestTraits>
class AwsApiRequestJob
: public AwsApiClientJob<typename RequestTraits::ClientType>
{
public:
// To use a different allocator, extend this class and use this macro.
AZ_CLASS_ALLOCATOR(AwsApiRequestJob, AZ::SystemAllocator);
using AwsApiRequestJobType = AwsApiRequestJob<RequestTraits>;
using AwsApiClientJobType = AwsApiClientJob<typename RequestTraits::ClientType>;
using ClientType = typename RequestTraits::ClientType;
using RequestType = typename RequestTraits::RequestType;
using OutcomeType = typename RequestTraits::OutcomeType;
using ResultType = typename RequestTraits::ResultType;
using ErrorType = typename RequestTraits::ErrorType;
using IConfig = IAwsApiRequestJobConfig<RequestTraits>;
using Config = AwsApiRequestJobConfig<RequestTraits>;
using OnSuccessFunction = AZStd::function<void(AwsApiRequestJob* job)>;
using OnFailureFunction = AZStd::function<void(AwsApiRequestJob* job)>;
class Function;
template<class Allocator = AZ::SystemAllocator>
static AwsApiRequestJob* Create(OnSuccessFunction onSuccess, OnFailureFunction onFailure = OnFailureFunction{}, IConfig* config = GetDefaultConfig());
static Config* GetDefaultConfig()
{
static AwsApiJobConfigHolder<Config> s_configHolder{};
return s_configHolder.GetConfig(AwsApiClientJobType::GetDefaultConfig());
}
AwsApiRequestJob(bool isAutoDelete, IConfig* config = GetDefaultConfig())
: AwsApiClientJobType(isAutoDelete, config)
{
}
/// Override AZ:Job defined method to reset request state when
/// the job object is reused.
void Reset(bool isClearDependent) override
{
request = RequestType{};
result = ResultType{};
error = ErrorType{};
m_wasSuccess = false;
AwsApiClientJobType::Reset(isClearDependent);
}
/// Determines if the request was successful.
bool WasSuccess()
{
return m_wasSuccess;
}
RequestType request;
ResultType result;
ErrorType error;
protected:
void Process() override
{
// Currently the AWS SDK always executes requests synchronously. Even
// when the Async and Callable versions of the API are used, the
// actual AWS call is made synchronously, blocking whatever thread it
// is executing on. Eventually the SDK may use true non-blocking
// async I/O for requests. When that feature is available, we can
// use the AZ::Job defined IncrementDependentCount method, start the
// async i/o, and call WaitForChildren. When the i/o completes, we
// would call DecrementDependentCount, which would cause WaitFor-
// Children to return. We would then call OnOutcome.
//
// We want to call OnOutcome when the job is in a processing state
// so that it can create and wait for child jobs, or do other
// things that the job system requires it to be in the processing
// state. That means that in the async case it has to be called
// after the call to WaitForChildren returns (otherwise the job's
// state is suspended).
bool ok = PrepareRequest();
if (ok)
{
// Get real pointer from shared pointer, then use with member
// function pointer to call the function.
OutcomeType outcome = (AwsApiClientJobType::m_client.get()->*RequestTraits::Function)(request);
if (outcome.IsSuccess())
{
result = std::move(outcome.GetResultWithOwnership());
m_wasSuccess = true;
OnSuccess();
}
else
{
error = outcome.GetError();
m_wasSuccess = false;
OnFailure();
}
}
}
/// Called by Process to prepare the request. By default no changes
/// are made to the request object. Override to defer the preparation
/// of request data until your running on the job's worker thread,
/// instead of setting the request data before calling Start.
///
/// \return true if the request should be made.
virtual bool PrepareRequest()
{
return true;
}
/// Called when request has completed successfully.
virtual void OnSuccess()
{
}
/// Called when the request fails.
virtual void OnFailure()
{
}
/// Called when request can't process and still requires cleanup (Specifically for the derived class AwsApiRequestJob<RequestTraits>::Function which does not use auto delete)
virtual void DoCleanup()
{
}
bool m_wasSuccess{ false };
};
/// A specialization of AwsApiRequestJob that lets you provide functions
/// that are called on success or failure of the request.
template<class RequestTraits>
class AwsApiRequestJob<RequestTraits>::Function
: public AwsApiRequestJob
{
public:
// To use a different allocator, extend this class and use this macro.
AZ_CLASS_ALLOCATOR(Function, AZ::SystemAllocator);
Function(OnSuccessFunction onSuccess, OnFailureFunction onFailure = OnFailureFunction{}, IConfig* config = GetDefaultConfig())
: AwsApiRequestJobType(
false, config) // No auto delete - we need to perform our callbacks on the main thread so we queue them through tickbus
, m_onSuccess{ onSuccess }
, m_onFailure{ onFailure }
{
}
private:
void OnSuccess() override
{
AZStd::function<void()> callbackHandler = [this]()
{
if (m_onSuccess)
{
m_onSuccess(this);
}
delete this;
};
AZ::TickBus::QueueFunction(callbackHandler);
}
void OnFailure() override
{
AZStd::function<void()> callbackHandler = [this]()
{
if (m_onFailure)
{
m_onFailure(this);
}
delete this;
};
AZ::TickBus::QueueFunction(callbackHandler);
}
// Code doesn't use auto delete - this allows code to make sure things get cleaned up in cases where success or failure can't be
// called.
void DoCleanup() override
{
AZStd::function<void()> callbackHandler = [this]()
{
delete this;
};
AZ::TickBus::QueueFunction(callbackHandler);
}
OnSuccessFunction m_onSuccess;
OnFailureFunction m_onFailure;
};
template<class RequestTraits>
template<class Allocator>
AwsApiRequestJob<RequestTraits>* AwsApiRequestJob<RequestTraits>::Create(
OnSuccessFunction onSuccess, OnFailureFunction onFailure, IConfig* config)
{
return azcreate(Function, (onSuccess, onFailure, config), Allocator);
}
} // namespace AWSCore