Merge remote-tracking branch 'upstream/development' into compiletime_filerequest_code2

This commit is contained in:
nemerle
2022-01-08 21:25:48 +01:00
785 changed files with 141969 additions and 22455 deletions
@@ -10,7 +10,7 @@
#if defined(PLATFORM_SUPPORTS_AWS_NATIVE_SDK)
#include <AzCore/PlatformDef.h>
#include <AzCore/PlatformIncl.h>
AZ_PUSH_DISABLE_WARNING(4251 4996, "-Wunknown-warning-option")
#include <aws/core/utils/logging/LogSystemInterface.h>
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/PlatformIncl.h>
#if defined(PLATFORM_SUPPORTS_AWS_NATIVE_SDK)
#include <aws/core/utils/memory/MemorySystemInterface.h>
#else
@@ -13,7 +13,7 @@
#include <AzCore/Module/Environment.h>
#if defined(PLATFORM_SUPPORTS_AWS_NATIVE_SDK)
#include <AzCore/PlatformIncl.h>
// The AWS Native SDK AWSAllocator triggers a warning due to accessing members of std::allocator directly.
// AWSAllocator.h(70): warning C4996: 'std::allocator<T>::pointer': warning STL4010: Various members of std::allocator are deprecated in C++17.
// Use std::allocator_traits instead of accessing these members directly.
@@ -37,6 +37,7 @@
#include <AssetBuilderInfo.h>
#include <AzCore/Interface/Interface.h>
#include <Entity/EntityUtilityComponent.h>
#include <AssetBuilderStatic.h>
namespace AssetBuilder
{
@@ -165,6 +166,7 @@ void AssetBuilderApplication::StartCommon(AZ::Entity* systemEntity)
AssetBuilderSDK::InitializeSerializationContext();
AssetBuilderSDK::InitializeBehaviorContext();
AssetBuilder::InitializeSerializationContext();
// the asset builder app never writes source files, only assets, so there is no need to do any kind of asset upgrading
AZ::Data::AssetManager::Instance().SetAssetInfoUpgradingEnabled(false);
@@ -35,6 +35,7 @@
#include <AzCore/Interface/Interface.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <ToolsComponents/ToolsAssetCatalogComponent.h>
#include <AssetBuilderStatic.h>
// Command-line parameter options:
static const char* const s_paramHelp = "help"; // Print help information.
@@ -52,10 +53,10 @@ static const char* const s_paramDebugCreate = "debug_create"; // Debug mode for
static const char* const s_paramDebugProcess = "debug_process"; // Debug mode for the process job of the specified file.
static const char* const s_paramPlatformTags = "tags"; // Additional list of tags to add platform tag list.
static const char* const s_paramPlatform = "platform"; // Platform to use
static const char* const s_paramRegisterBuilders = "register"; // Indicates the AP is starting up and requesting a list of registered builders
// Task modes:
static const char* const s_taskResident = "resident"; // stays up and running indefinitely, accepting jobs via network connection
static const char* const s_taskRegisterBuilder = "register"; // outputs all the builder descriptors
static const char* const s_taskCreateJob = "create"; // runs a builders createJobs function
static const char* const s_taskProcessJob = "process"; // runs processJob function
static const char* const s_taskDebug = "debug"; // runs a one shot job in a fake environment for a specified file.
@@ -205,6 +206,40 @@ void AssetBuilderComponent::Reflect(AZ::ReflectContext* context)
}
}
bool AssetBuilderComponent::DoHelloPing()
{
using namespace AssetBuilder;
BuilderHelloRequest request;
BuilderHelloResponse response;
AZStd::string id;
if (!GetParameter(s_paramId, id))
{
return false;
}
request.m_uuid = AZ::Uuid::CreateString(id.c_str());
AZ_TracePrintf(
"AssetBuilderComponent", "RunInResidentMode: Pinging asset processor with the builder UUID %s\n",
request.m_uuid.ToString<AZStd::string>().c_str());
bool result = AzFramework::AssetSystem::SendRequest(request, response);
AZ_Error("AssetBuilder", result, "Failed to send hello request to Asset Processor");
// This error is only shown if we successfully got a response AND the response explicitly indicates the AP rejected the builder
AZ_Error("AssetBuilder", !result || response.m_accepted, "Asset Processor rejected connection request");
if (result)
{
AZ_TracePrintf("AssetBuilder", "Builder ID: %s\n", response.m_uuid.ToString<AZStd::string>().c_str());
}
return result;
}
bool AssetBuilderComponent::Run()
{
AZ_TracePrintf("AssetBuilderComponent", "Run: Parsing command line.\n");
@@ -218,8 +253,8 @@ bool AssetBuilderComponent::Run()
}
AZStd::string task;
AZStd::string debugFile;
if (GetParameter(s_paramDebug, debugFile, false))
{
task = s_taskDebug;
@@ -257,11 +292,13 @@ bool AssetBuilderComponent::Run()
AZ_TracePrintf("AssetBuilderComponent", "Run: Connecting back to Asset Processor...\n");
bool connectedToAssetProcessor = ConnectToAssetProcessor();
//AP connection is required to access the asset catalog
AZ_Error("AssetBuilder", connectedToAssetProcessor, "Failed to establish a network connection to the AssetProcessor. Use -help for options.");;
AZ_Error("AssetBuilder", connectedToAssetProcessor, "Failed to establish a network connection to the AssetProcessor. Use -help for options.");
bool registerBuilders = commandLine->GetNumSwitchValues(s_paramRegisterBuilders) > 0;
IBuilderApplication* builderApplication = AZ::Interface<IBuilderApplication>::Get();
if(!builderApplication)
if (!builderApplication)
{
AZ_Error("AssetBuilder", false, "Failed to retreive IBuilderApplication interface");
return false;
@@ -275,7 +312,7 @@ bool AssetBuilderComponent::Run()
{
if (task == s_taskResident)
{
result = RunInResidentMode();
result = RunInResidentMode(registerBuilders);
}
else if (task == s_taskDebug)
{
@@ -371,43 +408,46 @@ bool AssetBuilderComponent::ConnectToAssetProcessor()
//////////////////////////////////////////////////////////////////////////
bool AssetBuilderComponent::RunInResidentMode()
bool AssetBuilderComponent::SendRegisteredBuildersToAp()
{
using namespace AssetBuilderSDK;
AssetBuilder::BuilderRegistrationRequest registrationRequest;
for (const auto& [uuid, desc] : m_assetBuilderDescMap)
{
AssetBuilder::BuilderRegistration registration;
registration.m_name = desc->m_name;
registration.m_analysisFingerprint = desc->m_analysisFingerprint;
registration.m_flags = desc->m_flags;
registration.m_flagsByJobKey = desc->m_flagsByJobKey;
registration.m_version = desc->m_version;
registration.m_busId = desc->m_busId;
registration.m_patterns = desc->m_patterns;
registration.m_productsToKeepOnFailure = desc->m_productsToKeepOnFailure;
registrationRequest.m_builders.push_back(AZStd::move(registration));
}
bool result = SendRequest(registrationRequest);
AZ_Error("AssetBuilder", result, "Failed to send builder registration request to Asset Processor");
return result;
}
bool AssetBuilderComponent::RunInResidentMode(bool sendRegistration)
{
using namespace AssetBuilder;
using namespace AZStd::placeholders;
AZ_TracePrintf("AssetBuilderComponent", "RunInResidentMode: Starting resident mode (waiting for commands to arrive)\n");
AZStd::string port, id, builderFolder;
if (!GetParameter(s_paramId, id)
|| !GetParameter(s_paramModule, builderFolder))
{
return false;
}
if (!LoadBuilders(builderFolder))
{
return false;
}
AzFramework::SocketConnection::GetInstance()->AddMessageHandler(CreateJobsNetRequest::MessageType(), AZStd::bind(&AssetBuilderComponent::CreateJobsResidentHandler, this, _1, _2, _3, _4));
AzFramework::SocketConnection::GetInstance()->AddMessageHandler(ProcessJobNetRequest::MessageType(), AZStd::bind(&AssetBuilderComponent::ProcessJobResidentHandler, this, _1, _2, _3, _4));
BuilderHelloRequest request;
BuilderHelloResponse response;
bool result = DoHelloPing() && ((sendRegistration && SendRegisteredBuildersToAp()) || !sendRegistration);
request.m_uuid = AZ::Uuid::CreateString(id.c_str());
AZ_TracePrintf("AssetBuilderComponent", "RunInResidentMode: Pinging asset processor with the builder UUID %s\n", request.m_uuid.ToString<AZStd::string>().c_str());
bool result = AzFramework::AssetSystem::SendRequest(request, response);
AZ_Error("AssetBuilder", result, "Failed to send hello request to Asset Processor");
// This error is only shown if we successfully got a response AND the response explicitly indicates the AP rejected the builder
AZ_Error("AssetBuilder", !result || response.m_accepted, "Asset Processor rejected connection request");
if (result && response.m_accepted)
if (result)
{
m_running = true;
@@ -416,7 +456,6 @@ bool AssetBuilderComponent::RunInResidentMode()
AzFramework::EngineConnectionEvents::Bus::Handler::BusConnect(); // Listen for disconnects
AZ_TracePrintf("AssetBuilder", "Builder ID: %s\n", response.m_uuid.ToString<AZStd::string>().c_str());
AZ_TracePrintf("AssetBuilder", "Resident mode ready\n");
m_mainEvent.acquire();
AZ_TracePrintf("AssetBuilder", "Shutting down\n");
@@ -737,11 +776,7 @@ bool AssetBuilderComponent::RunOneShotTask(const AZStd::string& task)
AZ::StringFunc::Path::Normalize(inputFilePath);
AZ::StringFunc::Path::Normalize(outputFilePath);
if (task == s_taskRegisterBuilder)
{
return HandleRegisterBuilder(inputFilePath, outputFilePath);
}
else if (task == s_taskCreateJob)
if (task == s_taskCreateJob)
{
auto func = [this](const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
{
@@ -897,7 +932,7 @@ void AssetBuilderComponent::JobThread()
{
case JobType::Create:
{
using namespace AssetBuilderSDK;
using namespace AssetBuilder;
auto* netRequest = azrtti_cast<CreateJobsNetRequest*>(job->m_netRequest.get());
auto* netResponse = azrtti_cast<CreateJobsNetResponse*>(job->m_netResponse.get());
@@ -923,7 +958,7 @@ void AssetBuilderComponent::JobThread()
}
case JobType::Process:
{
using namespace AssetBuilderSDK;
using namespace AssetBuilder;
AZ_TracePrintf("AssetBuilder", "Running processJob task\n");
@@ -982,14 +1017,14 @@ void AssetBuilderComponent::JobThread()
void AssetBuilderComponent::CreateJobsResidentHandler(AZ::u32 /*typeId*/, AZ::u32 serial, const void* data, AZ::u32 dataLength)
{
using namespace AssetBuilderSDK;
using namespace AssetBuilder;
ResidentJobHandler<CreateJobsNetRequest, CreateJobsNetResponse>(serial, data, dataLength, JobType::Create);
}
void AssetBuilderComponent::ProcessJobResidentHandler(AZ::u32 /*typeId*/, AZ::u32 serial, const void* data, AZ::u32 dataLength)
{
using namespace AssetBuilderSDK;
using namespace AssetBuilder;
ResidentJobHandler<ProcessJobNetRequest, ProcessJobNetResponse>(serial, data, dataLength, JobType::Process);
}
@@ -1019,18 +1054,6 @@ bool AssetBuilderComponent::HandleTask(const AZStd::string& inputFilePath, const
return true;
}
bool AssetBuilderComponent::HandleRegisterBuilder(const AZStd::string& /*inputFilePath*/, const AZStd::string& outputFilePath) const
{
AssetBuilderSDK::RegisterBuilderResponse response;
for (const auto& pair : m_assetBuilderDescMap)
{
response.m_assetBuilderDescList.push_back(*pair.second);
}
return AZ::Utils::SaveObjectToFile(outputFilePath, AZ::DataStream::ST_XML, &response);
}
void AssetBuilderComponent::UpdateResultCode(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
if (request.m_jobDescription.m_failOnError)
@@ -46,6 +46,7 @@ class AssetBuilderComponent
public:
AZ_COMPONENT(AssetBuilderComponent, "{04332899-5d73-4d41-86b7-b1017d349673}")
static void Reflect(AZ::ReflectContext* context);
bool DoHelloPing();
AssetBuilderComponent() = default;
~AssetBuilderComponent() override = default;
@@ -64,7 +65,7 @@ public:
void RegisterBuilderInformation(const AssetBuilderSDK::AssetBuilderDesc& builderDesc) override;
void RegisterComponentDescriptor(AZ::ComponentDescriptor* descriptor) override;
//EngineConnectionEvents Handler
void Disconnected(AzFramework::SocketConnection* connection) override;
@@ -98,12 +99,13 @@ protected:
static const char* GetLibraryExtension();
bool ConnectToAssetProcessor();
bool SendRegisteredBuildersToAp();
bool LoadBuilders(const AZStd::string& builderFolder);
bool LoadBuilder(const AZStd::string& filePath);
void UnloadBuilders();
//! Hooks up net job request handling and keeps the AssetBuilder running indefinitely
bool RunInResidentMode();
bool RunInResidentMode(bool sendRegistration);
bool RunDebugTask(AZStd::string&& debugFile, bool runCreateJobs, bool runProcessJob);
bool RunOneShotTask(const AZStd::string& task);
@@ -120,9 +122,6 @@ protected:
void ProcessJob(const AssetBuilderSDK::ProcessJobFunction& job, const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& outResponse);
//! Handles a builder registration request
bool HandleRegisterBuilder(const AZStd::string& inputFilePath, const AZStd::string& outputFilePath) const;
//! If needed looks at collected data and updates the result code from the job accordingly.
void UpdateResultCode(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
@@ -141,7 +140,7 @@ protected:
//! Currently loading builder
AssetBuilder::ExternalModuleAssetBuilderInfo* m_currentAssetBuilder = nullptr;
//! Thread for running a job, so we don't block the network thread while doing work
AZStd::thread_desc m_jobThreadDesc;
AZStd::thread m_jobThread;
@@ -153,7 +152,7 @@ protected:
AZStd::binary_semaphore m_mainEvent;
//! Use to signal a new job is ready to be processed
AZStd::binary_semaphore m_jobEvent;
//! Lock for m_queuedJob
AZStd::mutex m_jobMutex;
@@ -0,0 +1,182 @@
/*
* 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
*
*/
#include <AssetBuilderStatic.h>
#include <AzCore/Component/ComponentApplicationBus.h>
namespace AssetBuilder
{
void Reflect(AZ::ReflectContext* context)
{
BuilderRegistrationRequest::Reflect(context);
BuilderHelloRequest::Reflect(context);
BuilderHelloResponse::Reflect(context);
CreateJobsNetRequest::Reflect(context);
CreateJobsNetResponse::Reflect(context);
ProcessJobNetRequest::Reflect(context);
ProcessJobNetResponse::Reflect(context);
}
void InitializeSerializationContext()
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
AZ_Assert(serializeContext, "Unable to retrieve serialize context.");
Reflect(serializeContext);
}
void BuilderHelloRequest::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<BuilderHelloRequest>()->Version(1)->Field("UUID", &BuilderHelloRequest::m_uuid);
}
}
unsigned int BuilderHelloRequest::MessageType()
{
static unsigned int messageType = AZ_CRC("AssetBuilderSDK::BuilderHelloRequest", 0x213a7248);
return messageType;
}
unsigned int BuilderHelloRequest::GetMessageType() const
{
return MessageType();
}
void BuilderHelloResponse::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<BuilderHelloResponse>()
->Version(1)
->Field("Accepted", &BuilderHelloResponse::m_accepted)
->Field("UUID", &BuilderHelloResponse::m_uuid);
}
}
unsigned int BuilderHelloResponse::GetMessageType() const
{
return BuilderHelloRequest::MessageType();
}
//////////////////////////////////////////////////////////////////////////
void CreateJobsNetRequest::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<CreateJobsNetRequest>()->Version(1)->Field("Request", &CreateJobsNetRequest::m_request);
}
}
unsigned int CreateJobsNetRequest::MessageType()
{
static unsigned int messageType = AZ_CRC("AssetBuilderSDK::CreateJobsNetRequest", 0xc48209c0);
return messageType;
}
unsigned int CreateJobsNetRequest::GetMessageType() const
{
return MessageType();
}
void CreateJobsNetResponse::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<CreateJobsNetResponse>()->Version(1)->Field("Response", &CreateJobsNetResponse::m_response);
}
}
unsigned int CreateJobsNetResponse::GetMessageType() const
{
return CreateJobsNetRequest::MessageType();
}
void ProcessJobNetRequest::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<ProcessJobNetRequest>()->Version(1)->Field("Request", &ProcessJobNetRequest::m_request);
}
}
unsigned int ProcessJobNetRequest::MessageType()
{
static unsigned int messageType = AZ_CRC("AssetBuilderSDK::ProcessJobNetRequest", 0x479f340f);
return messageType;
}
unsigned int ProcessJobNetRequest::GetMessageType() const
{
return MessageType();
}
void ProcessJobNetResponse::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<ProcessJobNetResponse>()->Version(1)->Field("Response", &ProcessJobNetResponse::m_response);
}
}
unsigned int ProcessJobNetResponse::GetMessageType() const
{
return ProcessJobNetRequest::MessageType();
}
//---------------------------------------------------------------------
void BuilderRegistration::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<BuilderRegistration>()
->Version(1)
->Field("Name", &BuilderRegistration::m_name)
->Field("Patterns", &BuilderRegistration::m_patterns)
->Field("BusId", &BuilderRegistration::m_busId)
->Field("Version", &BuilderRegistration::m_version)
->Field("AnalysisFingerprint", &BuilderRegistration::m_analysisFingerprint)
->Field("Flags", &BuilderRegistration::m_flags)
->Field("FlagsByJobKey", &BuilderRegistration::m_flagsByJobKey)
->Field("ProductsToKeepOnFailure", &BuilderRegistration::m_productsToKeepOnFailure);
}
}
void BuilderRegistrationRequest::Reflect(AZ::ReflectContext* context)
{
BuilderRegistration::Reflect(context);
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<BuilderRegistrationRequest, BaseAssetProcessorMessage>()->Version(1)->Field(
"Builders", &BuilderRegistrationRequest::m_builders);
}
}
unsigned int BuilderRegistrationRequest::GetMessageType() const
{
return BuilderRegistrationRequest::MessageType;
}
} // namespace AssetBuilder
@@ -0,0 +1,140 @@
/*
* 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 <AzFramework/Asset/AssetProcessorMessages.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
namespace AssetBuilder
{
void Reflect(AZ::ReflectContext* context);
void InitializeSerializationContext();
//! BuilderHelloRequest is sent by an AssetBuilder that is attempting to connect to the AssetProcessor to register itself as a worker
class BuilderHelloRequest : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(BuilderHelloRequest, AZ::OSAllocator, 0);
AZ_RTTI(BuilderHelloRequest, "{5fab5962-a1d8-42a5-bf7a-fb1a8c5a9588}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
static unsigned int MessageType();
unsigned int GetMessageType() const override;
//! Unique ID assigned to this builder to identify it
AZ::Uuid m_uuid = AZ::Uuid::CreateNull();
};
//! BuilderHelloResponse contains the AssetProcessor's response to a builder connection attempt, indicating if it is accepted and the ID
//! that it was assigned
class BuilderHelloResponse : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(BuilderHelloResponse, AZ::OSAllocator, 0);
AZ_RTTI(BuilderHelloResponse, "{5f3d7c11-6639-4c6f-980a-32be546903c2}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
unsigned int GetMessageType() const override;
//! Indicates if the builder was accepted by the AP
bool m_accepted = false;
//! Unique ID assigned to the builder. If the builder isn't a local process, this is the ID assigned by the AP
AZ::Uuid m_uuid = AZ::Uuid::CreateNull();
};
class CreateJobsNetRequest : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(CreateJobsNetRequest, AZ::OSAllocator, 0);
AZ_RTTI(CreateJobsNetRequest, "{97fa717d-3a09-4d21-95c6-b2eafd773f1c}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
static unsigned int MessageType();
unsigned int GetMessageType() const override;
AssetBuilderSDK::CreateJobsRequest m_request;
};
class CreateJobsNetResponse : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(CreateJobsNetResponse, AZ::OSAllocator, 0);
AZ_RTTI(CreateJobsNetResponse, "{b2c7c2d3-b60e-4b27-b699-43e0ba991c33}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
unsigned int GetMessageType() const override;
AssetBuilderSDK::CreateJobsResponse m_response;
};
class ProcessJobNetRequest : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(ProcessJobNetRequest, AZ::OSAllocator, 0);
AZ_RTTI(ProcessJobNetRequest, "{05288de1-020b-48db-b9de-715f17284efa}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
static unsigned int MessageType();
unsigned int GetMessageType() const override;
AssetBuilderSDK::ProcessJobRequest m_request;
};
class ProcessJobNetResponse : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(ProcessJobNetResponse, AZ::OSAllocator, 0);
AZ_RTTI(ProcessJobNetResponse, "{26ddf882-246c-4cfb-912f-9b8e389df4f6}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
unsigned int GetMessageType() const override;
AssetBuilderSDK::ProcessJobResponse m_response;
};
//////////////////////////////////////////////////////////////////////////
struct BuilderRegistration
{
AZ_CLASS_ALLOCATOR(BuilderRegistration, AZ::OSAllocator, 0);
AZ_TYPE_INFO(BuilderRegistration, "{36E785C3-5046-4568-870A-336C8249E453}");
static void Reflect(AZ::ReflectContext* context);
AZStd::string m_name;
AZStd::vector<AssetBuilderSDK::AssetBuilderPattern> m_patterns;
AZ::Uuid m_busId;
int m_version = 0;
AZStd::string m_analysisFingerprint;
AZ::u8 m_flags = 0;
AZStd::unordered_map<AZStd::string, AZ::u8> m_flagsByJobKey;
AZStd::unordered_map<AZStd::string, AZStd::unordered_set<AZ::u32>> m_productsToKeepOnFailure;
};
class BuilderRegistrationRequest : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(BuilderRegistrationRequest, AZ::OSAllocator, 0);
AZ_RTTI(BuilderRegistrationRequest, "{FA9CF2D5-C847-47F3-979D-6C3AE061715C}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
static constexpr unsigned int MessageType = AZ_CRC_CE("AssetSystem::BuilderRegistrationRequest");
BuilderRegistrationRequest() = default;
unsigned int GetMessageType() const override;
AZStd::vector<BuilderRegistration> m_builders;
};
} // namespace AssetBuilder
@@ -6,6 +6,22 @@
#
#
ly_add_target(
NAME AssetBuilder.Static STATIC
NAMESPACE AZ
FILES_CMAKE
asset_builder_static_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
BUILD_DEPENDENCIES
PUBLIC
AZ::AzCore
AZ::AzFramework
AZ::AzToolsFramework
AZ::AssetBuilderSDK
)
ly_add_target(
NAME AssetBuilder EXECUTABLE
NAMESPACE AZ
@@ -17,6 +33,7 @@ ly_add_target(
.
BUILD_DEPENDENCIES
PRIVATE
AssetBuilder.Static
3rdParty::Qt::Core
3rdParty::Qt::Gui
3rdParty::Qt::Network
@@ -0,0 +1,12 @@
#
# 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
#
#
set(FILES
AssetBuilderStatic.h
AssetBuilderStatic.cpp
)
@@ -1172,19 +1172,10 @@ namespace AssetBuilderSDK
JobProduct::Reflect(context);
AssetBuilderDesc::Reflect(context);
RegisterBuilderRequest::Reflect(context);
RegisterBuilderResponse::Reflect(context);
CreateJobsRequest::Reflect(context);
CreateJobsResponse::Reflect(context);
ProcessJobRequest::Reflect(context);
ProcessJobResponse::Reflect(context);
BuilderHelloRequest::Reflect(context);
BuilderHelloResponse::Reflect(context);
CreateJobsNetRequest::Reflect(context);
CreateJobsNetResponse::Reflect(context);
ProcessJobNetRequest::Reflect(context);
ProcessJobNetResponse::Reflect(context);
}
void InitializeSerializationContext()
@@ -1263,24 +1254,6 @@ namespace AssetBuilderSDK
}
}
void RegisterBuilderRequest::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<RegisterBuilderRequest>()->
Version(1)->
Field("FilePath", &RegisterBuilderRequest::m_filePath);
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<RegisterBuilderRequest>("RegisterBuilderRequest")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "asset.builder")
->Property("filePath", BehaviorValueProperty(&RegisterBuilderRequest::m_filePath));
}
}
void AssetBuilderDesc::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
@@ -1313,25 +1286,6 @@ namespace AssetBuilderSDK
}
}
void RegisterBuilderResponse::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<RegisterBuilderResponse>()
->Version(1)
->Field("Asset Builder Desc List", &RegisterBuilderResponse::m_assetBuilderDescList);
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<RegisterBuilderResponse>("RegisterBuilderResponse")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "asset.builder")
->Constructor()
->Property("assetBuilderDescList", BehaviorValueProperty(&RegisterBuilderResponse::m_assetBuilderDescList));
}
}
bool CreateJobsResponse::Succeeded() const
{
return m_result == CreateJobsResultCode::Success;
@@ -1362,128 +1316,6 @@ namespace AssetBuilderSDK
}
}
void BuilderHelloRequest::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<BuilderHelloRequest>()
->Version(1)
->Field("UUID", &BuilderHelloRequest::m_uuid);
}
}
unsigned int BuilderHelloRequest::MessageType()
{
static unsigned int messageType = AZ_CRC("AssetBuilderSDK::BuilderHelloRequest", 0x213a7248);
return messageType;
}
unsigned int BuilderHelloRequest::GetMessageType() const
{
return MessageType();
}
void BuilderHelloResponse::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<BuilderHelloResponse>()
->Version(1)
->Field("Accepted", &BuilderHelloResponse::m_accepted)
->Field("UUID", &BuilderHelloResponse::m_uuid);
}
}
unsigned int BuilderHelloResponse::GetMessageType() const
{
return BuilderHelloRequest::MessageType();
}
//////////////////////////////////////////////////////////////////////////
void CreateJobsNetRequest::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<CreateJobsNetRequest>()
->Version(1)
->Field("Request", &CreateJobsNetRequest::m_request);
}
}
unsigned int CreateJobsNetRequest::MessageType()
{
static unsigned int messageType = AZ_CRC("AssetBuilderSDK::CreateJobsNetRequest", 0xc48209c0);
return messageType;
}
unsigned int CreateJobsNetRequest::GetMessageType() const
{
return MessageType();
}
void CreateJobsNetResponse::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<CreateJobsNetResponse>()
->Version(1)
->Field("Response", &CreateJobsNetResponse::m_response);
}
}
unsigned int CreateJobsNetResponse::GetMessageType() const
{
return CreateJobsNetRequest::MessageType();
}
void ProcessJobNetRequest::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<ProcessJobNetRequest>()
->Version(1)
->Field("Request", &ProcessJobNetRequest::m_request);
}
}
unsigned int ProcessJobNetRequest::MessageType()
{
static unsigned int messageType = AZ_CRC("AssetBuilderSDK::ProcessJobNetRequest", 0x479f340f);
return messageType;
}
unsigned int ProcessJobNetRequest::GetMessageType() const
{
return MessageType();
}
void ProcessJobNetResponse::Reflect(AZ::ReflectContext* context)
{
auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<ProcessJobNetResponse>()
->Version(1)
->Field("Response", &ProcessJobNetResponse::m_response);
}
}
unsigned int ProcessJobNetResponse::GetMessageType() const
{
return ProcessJobNetRequest::MessageType();
}
JobDependency::JobDependency(const AZStd::string& jobKey, const AZStd::string& platformIdentifier, const JobDependencyType& type, const SourceFileDependency& sourceFile)
: m_jobKey(jobKey)
, m_platformIdentifier(platformIdentifier)
@@ -464,35 +464,6 @@ namespace AssetBuilderSDK
AZStd::string m_platformIdentifier;
};
//! RegisterBuilderRequest contains input data that will be sent by the AssetProcessor to the builder during the startup registration phase
struct RegisterBuilderRequest
{
AZ_CLASS_ALLOCATOR(RegisterBuilderRequest, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(RegisterBuilderRequest, "{7C6C5198-4766-42B8-9A1E-48479CE2F5EA}");
AZStd::string m_filePath;
RegisterBuilderRequest() {}
explicit RegisterBuilderRequest(const AZStd::string& filePath)
: m_filePath(filePath)
{
}
static void Reflect(AZ::ReflectContext* context);
};
//! INTERNAL USE ONLY - RegisterBuilderResponse contains registration data that will be sent by the builder to the AssetProcessor in response to RegisterBuilderRequest
struct RegisterBuilderResponse
{
AZ_CLASS_ALLOCATOR(RegisterBuilderResponse, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(RegisterBuilderResponse, "{0AE5583F-C763-410E-BA7F-78BD90546C01}");
AZStd::vector<AssetBuilderDesc> m_assetBuilderDescList;
static void Reflect(AZ::ReflectContext* context);
};
/**
* This tells you about a platform in your CreateJobsRequest or your ProcessJobRequest
*/
@@ -756,99 +727,9 @@ namespace AssetBuilderSDK
static void Reflect(AZ::ReflectContext* context);
};
//! BuilderHelloRequest is sent by an AssetBuilder that is attempting to connect to the AssetProcessor to register itself as a worker
class BuilderHelloRequest : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(BuilderHelloRequest, AZ::OSAllocator, 0);
AZ_RTTI(BuilderHelloRequest, "{5fab5962-a1d8-42a5-bf7a-fb1a8c5a9588}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
static unsigned int MessageType();
unsigned int GetMessageType() const override;
//! Unique ID assigned to this builder to identify it
AZ::Uuid m_uuid = AZ::Uuid::CreateNull();
};
//! BuilderHelloResponse contains the AssetProcessor's response to a builder connection attempt, indicating if it is accepted and the ID that it was assigned
class BuilderHelloResponse : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(BuilderHelloResponse, AZ::OSAllocator, 0);
AZ_RTTI(BuilderHelloResponse, "{5f3d7c11-6639-4c6f-980a-32be546903c2}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
unsigned int GetMessageType() const override;
//! Indicates if the builder was accepted by the AP
bool m_accepted = false;
//! Unique ID assigned to the builder. If the builder isn't a local process, this is the ID assigned by the AP
AZ::Uuid m_uuid = AZ::Uuid::CreateNull();
};
class CreateJobsNetRequest : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(CreateJobsNetRequest, AZ::OSAllocator, 0);
AZ_RTTI(CreateJobsNetRequest, "{97fa717d-3a09-4d21-95c6-b2eafd773f1c}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
static unsigned int MessageType();
unsigned int GetMessageType() const override;
CreateJobsRequest m_request;
};
class CreateJobsNetResponse : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(CreateJobsNetResponse, AZ::OSAllocator, 0);
AZ_RTTI(CreateJobsNetResponse, "{b2c7c2d3-b60e-4b27-b699-43e0ba991c33}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
unsigned int GetMessageType() const override;
CreateJobsResponse m_response;
};
class ProcessJobNetRequest : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(ProcessJobNetRequest, AZ::OSAllocator, 0);
AZ_RTTI(ProcessJobNetRequest, "{05288de1-020b-48db-b9de-715f17284efa}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
static unsigned int MessageType();
unsigned int GetMessageType() const override;
ProcessJobRequest m_request;
};
class ProcessJobNetResponse : public AzFramework::AssetSystem::BaseAssetProcessorMessage
{
public:
AZ_CLASS_ALLOCATOR(ProcessJobNetResponse, AZ::OSAllocator, 0);
AZ_RTTI(ProcessJobNetResponse, "{26ddf882-246c-4cfb-912f-9b8e389df4f6}", BaseAssetProcessorMessage);
static void Reflect(AZ::ReflectContext* context);
unsigned int GetMessageType() const override;
ProcessJobResponse m_response;
};
//! JobCancelListener can be used by builders in their processJob method to listen for job cancellation request.
//! The address of this listener is the jobid which can be found in the process job request.
@@ -935,44 +816,3 @@ namespace AZ
AZ_TYPE_INFO_SPECIALIZE(AssetBuilderSDK::ProductPathDependencyType, "{EF77742B-9627-4072-B431-396AA7183C80}");
AZ_TYPE_INFO_SPECIALIZE(AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType, "{BE9C8805-DB17-4500-944A-EB33FD0BE347}");
}
//! This macro should be used by every AssetBuilder to register itself,
//! AssetProcessor uses these exported function to identify whether a dll is an Asset Builder or not
//! If you want something highly custom you can do these entry points yourself instead of using the macro.
#define REGISTER_ASSETBUILDER \
extern void BuilderOnInit(); \
extern void BuilderDestroy(); \
extern void BuilderRegisterDescriptors(); \
extern void BuilderAddComponents(AZ::Entity * entity); \
extern "C" \
{ \
AZ_DLL_EXPORT int IsAssetBuilder() \
{ \
return 0; \
} \
\
AZ_DLL_EXPORT void InitializeModule(AZ::EnvironmentInstance sharedEnvironment) \
{ \
AZ::Environment::Attach(sharedEnvironment); \
BuilderOnInit(); \
} \
\
AZ_DLL_EXPORT void UninitializeModule() \
{ \
BuilderDestroy(); \
AZ::Environment::Detach(); \
} \
\
AZ_DLL_EXPORT void ModuleRegisterDescriptors() \
{ \
BuilderRegisterDescriptors(); \
} \
\
AZ_DLL_EXPORT void ModuleAddComponents(AZ::Entity * entity) \
{ \
BuilderAddComponents(entity); \
} \
}
// confusion-reducing note: above end-brace is part of the macro, not a namespace
+10 -6
View File
@@ -42,6 +42,7 @@ ly_add_target(
AZ::AzQtComponents
AZ::AzToolsFramework
AZ::AssetBuilderSDK
AZ::AssetBuilder.Static
${additional_dependencies}
RUNTIME_DEPENDENCIES
AZ::AssetBuilder
@@ -52,7 +53,7 @@ get_property(asset_builders GLOBAL PROPERTY LY_ASSET_BUILDERS)
string (REPLACE ";" "," asset_builders "${asset_builders}")
ly_add_source_properties(
SOURCES native/utilities/ApplicationManager.cpp
PROPERTY COMPILE_DEFINITIONS
PROPERTY COMPILE_DEFINITIONS
VALUES LY_ASSET_BUILDERS="${asset_builders}"
)
@@ -147,9 +148,9 @@ endif()
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME AssetProcessor.Tests EXECUTABLE
NAME AssetProcessor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE AZ
AUTOMOC
AUTORCC
@@ -167,12 +168,12 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
)
ly_add_source_properties(
SOURCES native/tests/assetBuilderSDK/assetBuilderSDKTest.cpp
PROPERTY COMPILE_DEFINITIONS
PROPERTY COMPILE_DEFINITIONS
VALUES ${LY_PAL_TOOLS_DEFINES}
)
ly_add_source_properties(
SOURCES native/unittests/AssetProcessorManagerUnitTests.cpp
PROPERTY COMPILE_DEFINITIONS
PROPERTY COMPILE_DEFINITIONS
VALUES LY_CMAKE_BINARY_DIR="${CMAKE_BINARY_DIR}"
)
@@ -266,7 +267,10 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_googletest(
NAME AZ::AssetProcessor.Tests
TEST_COMMAND $<TARGET_FILE:AZ::AssetProcessor.Tests> --unittest --gtest_filter=-*.SUITE_sandbox*
)
ly_add_googlebenchmark(
NAME AZ::AssetProcessor.Benchmarks
TARGET AZ::AssetProcessor.Tests
)
endif()
@@ -8,4 +8,6 @@
set(FILES
native/FileWatcher/FileWatcher_linux.cpp
native/FileWatcher/FileWatcher_linux.h
native/FileWatcher/FileWatcher_platform.h
)
@@ -5,7 +5,10 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/string/fixed_string.h>
#include <native/FileWatcher/FileWatcher.h>
#include <native/FileWatcher/FileWatcher_platform.h>
#include <QDirIterator>
#include <QHash>
@@ -15,155 +18,127 @@
#include <sys/inotify.h>
static constexpr int s_handleToFolderMapLockTimeout = 1000; // 1 sec timeout for obtaining the handle to folder map lock
static constexpr size_t s_iNotifyMaxEntries = 1024 * 16; // Control the maximum number of entries (from inotify) that can be read at one time
static constexpr size_t s_iNotifyEventSize = sizeof(struct inotify_event);
static constexpr size_t s_iNotifyReadBufferSize = s_iNotifyMaxEntries * s_iNotifyEventSize;
static constexpr size_t s_inotifyMaxEntries = 1024 * 16; // Control the maximum number of entries (from inotify) that can be read at one time
static constexpr size_t s_inotifyEventSize = sizeof(struct inotify_event);
static constexpr size_t s_inotifyReadBufferSize = s_inotifyMaxEntries * s_inotifyEventSize;
struct FolderRootWatch::PlatformImplementation
bool FileWatcher::PlatformImplementation::Initialize()
{
PlatformImplementation() = default;
int m_iNotifyHandle = -1;
QMutex m_handleToFolderMapLock;
QHash<int, QString> m_handleToFolderMap;
bool Initialize()
if (m_inotifyHandle < 0)
{
if (m_iNotifyHandle < 0)
{
// The CLOEXEC flag prevents the inotify watchers from copying on fork/exec
m_iNotifyHandle = inotify_init1(IN_CLOEXEC);
}
return (m_iNotifyHandle >= 0);
// The CLOEXEC flag prevents the inotify watchers from copying on fork/exec
m_inotifyHandle = inotify_init1(IN_CLOEXEC);
[[maybe_unused]] const auto err = errno;
[[maybe_unused]] AZStd::fixed_string<255> errorString;
AZ_Warning("FileWatcher", (m_inotifyHandle >= 0), "Unable to initialize inotify, file monitoring will not be available: %s\n", strerror_r(err, errorString.data(), errorString.capacity()));
}
void Finalize()
{
if (m_iNotifyHandle >= 0)
{
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
{
AZ_Error("FileWatcher", false, "Unable to obtain inotify handle lock on thread");
return;
}
QHashIterator<int, QString> iter(m_handleToFolderMap);
while (iter.hasNext())
{
iter.next();
int watchHandle = iter.key();
inotify_rm_watch(m_iNotifyHandle, watchHandle);
}
m_handleToFolderMap.clear();
m_handleToFolderMapLock.unlock();
::close(m_iNotifyHandle);
m_iNotifyHandle = -1;
}
}
void AddWatchFolder(QString folder)
{
if (m_iNotifyHandle >= 0)
{
// Clean up the path before accepting it as a watch folder
QString cleanPath = QDir::cleanPath(folder);
// Add the folder to watch and track it
int watchHandle = inotify_add_watch(m_iNotifyHandle,
cleanPath.toUtf8().constData(),
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE);
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
{
AZ_Error("FileWatcher", false, "Unable to obtain inotify handle lock on thread");
return;
}
m_handleToFolderMap[watchHandle] = cleanPath;
m_handleToFolderMapLock.unlock();
// Add all the subfolders to watch and track them
QDirIterator dirIter(folder, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
while (dirIter.hasNext())
{
QString dirName = dirIter.next();
if (dirName.endsWith("/.") || dirName.endsWith("/.."))
{
continue;
}
int watchHandle = inotify_add_watch(m_iNotifyHandle,
dirName.toUtf8().constData(),
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE);
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
{
AZ_Error("FileWatcher", false, "Unable to obtain inotify handle lock on thread");
return;
}
m_handleToFolderMap[watchHandle] = dirName;
m_handleToFolderMapLock.unlock();
}
}
}
void RemoveWatchFolder(int watchHandle)
{
if (m_iNotifyHandle >= 0)
{
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
{
AZ_Error("FileWatcher", false, "Unable to obtain inotify handle lock on thread");
return;
}
QHash<int, QString>::iterator handleToRemove = m_handleToFolderMap.find(watchHandle);
if (handleToRemove != m_handleToFolderMap.end())
{
inotify_rm_watch(m_iNotifyHandle, watchHandle);
m_handleToFolderMap.erase(handleToRemove);
}
m_handleToFolderMapLock.unlock();
}
}
};
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
FolderRootWatch::FolderRootWatch(const QString rootFolder)
: m_root(rootFolder)
, m_shutdownThreadSignal(false)
, m_fileWatcher(nullptr)
, m_platformImpl(new PlatformImplementation())
{
return (m_inotifyHandle >= 0);
}
FolderRootWatch::~FolderRootWatch()
void FileWatcher::PlatformImplementation::Finalize()
{
// Destructor is required in here since this file contains the definition of struct PlatformImplementation
Stop();
if (m_inotifyHandle < 0)
{
return;
}
delete m_platformImpl;
{
QMutexLocker lock{&m_handleToFolderMapLock};
for (const auto& watchHandle : m_handleToFolderMap.keys())
{
inotify_rm_watch(m_inotifyHandle, watchHandle);
}
m_handleToFolderMap.clear();
}
::close(m_inotifyHandle);
m_inotifyHandle = -1;
}
bool FolderRootWatch::Start()
void FileWatcher::PlatformImplementation::AddWatchFolder(QString folder, bool recursive)
{
if (m_inotifyHandle < 0)
{
return;
}
// Clean up the path before accepting it as a watch folder
QString cleanPath = QDir::cleanPath(folder);
// Add the folder to watch and track it
int watchHandle = inotify_add_watch(m_inotifyHandle,
cleanPath.toUtf8().constData(),
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE);
if (watchHandle < 0)
{
[[maybe_unused]] const auto err = errno;
[[maybe_unused]] AZStd::fixed_string<255> errorString;
AZ_Warning("FileWatcher", false, "inotify_add_watch failed for path %s: %s", cleanPath.toUtf8().constData(), strerror_r(err, errorString.data(), errorString.capacity()));
return;
}
{
QMutexLocker lock{&m_handleToFolderMapLock};
m_handleToFolderMap[watchHandle] = cleanPath;
}
// Add all the contents (files and directories) to watch and track them
QDirIterator dirIter(folder, QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files, (recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags) | QDirIterator::FollowSymlinks);
while (dirIter.hasNext())
{
QString dirName = dirIter.next();
watchHandle = inotify_add_watch(m_inotifyHandle,
dirName.toUtf8().constData(),
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE);
if (watchHandle < 0)
{
[[maybe_unused]] const auto err = errno;
[[maybe_unused]] AZStd::fixed_string<255> errorString;
AZ_Warning("FileWatcher", false, "inotify_add_watch failed for path %s: %s", dirName.toUtf8().constData(), strerror_r(err, errorString.data(), errorString.capacity()));
return;
}
QMutexLocker lock{&m_handleToFolderMapLock};
m_handleToFolderMap[watchHandle] = dirName;
}
}
void FileWatcher::PlatformImplementation::RemoveWatchFolder(int watchHandle)
{
if (m_inotifyHandle < 0)
{
return;
}
QMutexLocker lock{&m_handleToFolderMapLock};
if (m_handleToFolderMap.remove(watchHandle))
{
inotify_rm_watch(m_inotifyHandle, watchHandle);
}
}
bool FileWatcher::PlatformStart()
{
// inotify will be used by linux to monitor file changes within directories under the root folder
if (!m_platformImpl->Initialize())
{
return false;
}
m_platformImpl->AddWatchFolder(m_root);
for (const auto& [directory, recursive] : m_folderWatchRoots)
{
if (QDir(directory).exists())
{
m_platformImpl->AddWatchFolder(directory, recursive);
}
}
m_shutdownThreadSignal = false;
m_thread = std::thread([this]() { WatchFolderLoop(); });
return true;
}
void FolderRootWatch::Stop()
void FileWatcher::PlatformStop()
{
m_shutdownThreadSignal = true;
@@ -172,64 +147,78 @@ void FolderRootWatch::Stop()
if (m_thread.joinable())
{
m_thread.join(); // wait for the thread to finish
m_thread = std::thread(); //destroy
}
}
void FolderRootWatch::WatchFolderLoop()
void FileWatcher::WatchFolderLoop()
{
char eventBuffer[s_iNotifyReadBufferSize];
char eventBuffer[s_inotifyReadBufferSize];
while (!m_shutdownThreadSignal)
{
ssize_t bytesRead = ::read(m_platformImpl->m_iNotifyHandle, eventBuffer, s_iNotifyReadBufferSize);
ssize_t bytesRead = ::read(m_platformImpl->m_inotifyHandle, eventBuffer, s_inotifyReadBufferSize);
if (bytesRead < 0)
{
// Break out of the loop when the notify handle was closed (outside of this thread)
break;
}
else if (bytesRead > 0)
if (!bytesRead)
{
for (size_t index=0; index<bytesRead;)
continue;
}
for (size_t index=0; index<bytesRead;)
{
const auto* event = reinterpret_cast<inotify_event*>(&eventBuffer[index]);
if (event->mask & (IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVE))
{
struct inotify_event *event = ( struct inotify_event * ) &eventBuffer[ index ];
const QString pathStr = QDir(m_platformImpl->m_handleToFolderMap[event->wd]).absoluteFilePath(event->name);
if (event->mask & (IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVE ))
if (event->mask & (IN_CREATE | IN_MOVED_TO))
{
QString pathStr = QString("%1%2%3").arg(m_platformImpl->m_handleToFolderMap[event->wd], QDir::separator(), event->name);
if (event->mask & IN_ISDIR)
{
// New Directory, see if it should be added to the watched directories
// It is only added if it is a child of a recursively watched directory
const auto found = AZStd::find_if(begin(m_folderWatchRoots), end(m_folderWatchRoots), [this, event](const WatchRoot& watchRoot)
{
return watchRoot.m_directory == m_platformImpl->m_handleToFolderMap[event->wd];
});
if (event->mask & (IN_CREATE | IN_MOVED_TO))
{
if ( event->mask & IN_ISDIR )
// If the path is not in m_folderWatchRoots, it must
// be a new subdirectory of a subdirectory of some
// other root that is being watched recursively.
// Maintain the recursive nature of that root.
const bool shouldAddFolder = (found == end(m_folderWatchRoots)) ? true : found->m_recursive;
if (shouldAddFolder)
{
// New Directory, add it to the watch
m_platformImpl->AddWatchFolder(pathStr);
}
else
{
ProcessNewFileEvent(pathStr);
m_platformImpl->AddWatchFolder(pathStr, true);
}
}
else if (event->mask & (IN_DELETE | IN_MOVED_FROM))
else
{
if (event->mask & IN_ISDIR)
{
// Directory Deleted, remove it from the watch
m_platformImpl->RemoveWatchFolder(event->wd);
}
else
{
ProcessDeleteFileEvent(pathStr);
}
}
else if ((event->mask & IN_MODIFY) && ((event->mask & IN_ISDIR) != IN_ISDIR))
{
ProcessModifyFileEvent(pathStr);
rawFileAdded(pathStr, {});
}
}
index += s_iNotifyEventSize + event->len;
else if (event->mask & (IN_DELETE | IN_MOVED_FROM))
{
if (event->mask & IN_ISDIR)
{
// Directory Deleted, remove it from the watch
m_platformImpl->RemoveWatchFolder(event->wd);
}
else
{
rawFileRemoved(pathStr, {});
}
}
else if ((event->mask & IN_MODIFY) && ((event->mask & IN_ISDIR) != IN_ISDIR))
{
rawFileModified(pathStr, {});
}
}
index += s_inotifyEventSize + event->len;
}
}
}
@@ -0,0 +1,26 @@
/*
* 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 <FileWatcher/FileWatcher.h>
#include <QMutex>
#include <QHash>
class FileWatcher::PlatformImplementation
{
public:
bool Initialize();
void Finalize();
void AddWatchFolder(QString folder, bool recursive);
void RemoveWatchFolder(int watchHandle);
int m_inotifyHandle = -1;
QMutex m_handleToFolderMapLock;
QHash<int, QString> m_handleToFolderMap;
};
@@ -0,0 +1,11 @@
/*
* 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 <native/FileWatcher/FileWatcher_linux.h>
@@ -8,4 +8,6 @@
set(FILES
native/FileWatcher/FileWatcher_macos.cpp
native/FileWatcher/FileWatcher_mac.h
native/FileWatcher/FileWatcher_platform.h
)
@@ -0,0 +1,20 @@
/*
* 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 <native/FileWatcher/FileWatcher.h>
#include <CoreServices/CoreServices.h>
class FileWatcher::PlatformImplementation
{
public:
FSEventStreamRef m_stream = nullptr;
CFRunLoopRef m_runLoop = nullptr;
};
@@ -6,47 +6,23 @@
*
*/
#include <native/FileWatcher/FileWatcher.h>
#include <native/FileWatcher/FileWatcher_platform.h>
#include <native/utilities/BatchApplicationManager.h>
#include <AzCore/Debug/Trace.h>
#include <CoreServices/CoreServices.h>
void FileEventStreamCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);
struct FolderRootWatch::PlatformImplementation
{
PlatformImplementation() : m_stream(nullptr), m_runLoop(nullptr) { }
FSEventStreamRef m_stream;
CFRunLoopRef m_runLoop;
QString m_renameFileDirectory;
};
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
FolderRootWatch::FolderRootWatch(const QString rootFolder)
: m_root(rootFolder)
, m_shutdownThreadSignal(false)
, m_fileWatcher(nullptr)
, m_platformImpl(new PlatformImplementation())
{
}
FolderRootWatch::~FolderRootWatch()
{
// Destructor is required in here since this file contains the definition of struct PlatformImplementation
Stop();
delete m_platformImpl;
}
bool FolderRootWatch::Start()
bool FileWatcher::PlatformStart()
{
m_shutdownThreadSignal = false;
CFStringRef rootPath = CFStringCreateWithCString(kCFAllocatorDefault, m_root.toStdString().data(), kCFStringEncodingMacRoman);
CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&rootPath, 1, NULL);
CFMutableArrayRef pathsToWatch = CFArrayCreateMutable(nullptr, this->m_folderWatchRoots.size(), nullptr);
for (const auto& root : this->m_folderWatchRoots)
{
CFArrayAppendValue(pathsToWatch, root.m_directory.toCFString());
}
// The larger this number, the larger the delay between the kernel knowing a file changed
// and us actually consuming the event. It is very important for asset processor to deal with
@@ -60,11 +36,12 @@ bool FolderRootWatch::Start()
// Set ourselves as the value for the context info field so that in the callback
// we get passed into it and the callback can call our public API to handle
// the file change events
FSEventStreamContext streamContext;
::memset(&streamContext, 0, sizeof(streamContext));
streamContext.info = this;
FSEventStreamContext streamContext{
/*.version =*/ 0,
/*.info =*/ this,
};
m_platformImpl->m_stream = FSEventStreamCreate(NULL,
m_platformImpl->m_stream = FSEventStreamCreate(nullptr,
FileEventStreamCallback,
&streamContext,
pathsToWatch,
@@ -72,24 +49,25 @@ bool FolderRootWatch::Start()
timeBetweenKernelUpdateAndNotification,
kFSEventStreamCreateFlagFileEvents);
AZ_Error("FileWatcher", (m_platformImpl->m_stream != nullptr), "FSEventStreamCreate returned a nullptr. No file events will be reported for %s", m_root.toStdString().c_str());
m_thread = std::thread(std::bind(&FolderRootWatch::WatchFolderLoop, this));
AZ_Error("FileWatcher", (m_platformImpl->m_stream != nullptr), "FSEventStreamCreate returned a nullptr. No file events will be reported.");
const CFIndex pathCount = CFArrayGetCount(pathsToWatch);
for(CFIndex i = 0; i < pathCount; ++i)
{
CFRelease(CFArrayGetValueAtIndex(pathsToWatch, i));
}
CFRelease(pathsToWatch);
CFRelease(rootPath);
return (m_platformImpl->m_stream != nullptr);
return m_platformImpl->m_stream != nullptr;
}
void FolderRootWatch::Stop()
void FileWatcher::PlatformStop()
{
m_shutdownThreadSignal = true;
if (m_thread.joinable())
{
m_thread.join(); // wait for the thread to finish
m_thread = std::thread(); //destroy
}
FSEventStreamStop(m_platformImpl->m_stream);
@@ -97,7 +75,7 @@ void FolderRootWatch::Stop()
FSEventStreamRelease(m_platformImpl->m_stream);
}
void FolderRootWatch::WatchFolderLoop()
void FileWatcher::WatchFolderLoop()
{
// Use a half second timeout interval so that we can check if
// m_shutdownThreadSignal has been changed while we were running the RunLoop
@@ -117,14 +95,14 @@ void FolderRootWatch::WatchFolderLoop()
void FileEventStreamCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[])
{
FolderRootWatch* watcher = reinterpret_cast<FolderRootWatch*>(clientCallBackInfo);
auto* watcher = reinterpret_cast<FileWatcher*>(clientCallBackInfo);
const char** filePaths = reinterpret_cast<const char**>(eventPaths);
for (int i = 0; i < numEvents; ++i)
{
QFileInfo fileInfo(QDir::cleanPath(filePaths[i]));
QString fileAndPath = fileInfo.absoluteFilePath();
const QFileInfo fileInfo(QDir::cleanPath(filePaths[i]));
const QString fileAndPath = fileInfo.absoluteFilePath();
if (!fileInfo.isHidden())
{
@@ -133,38 +111,38 @@ void FileEventStreamCallback(ConstFSEventStreamRef streamRef, void *clientCallBa
// so check for all of them
if (eventFlags[i] & kFSEventStreamEventFlagItemCreated)
{
watcher->ProcessNewFileEvent(fileAndPath);
watcher->rawFileAdded(fileAndPath, {});
}
if (eventFlags[i] & kFSEventStreamEventFlagItemModified)
{
watcher->ProcessModifyFileEvent(fileAndPath);
watcher->rawFileModified(fileAndPath, {});
}
if (eventFlags[i] & kFSEventStreamEventFlagItemRemoved)
{
watcher->ProcessDeleteFileEvent(fileAndPath);
watcher->rawFileRemoved(fileAndPath, {});
}
if (eventFlags[i] & kFSEventStreamEventFlagItemRenamed)
{
if (fileInfo.exists())
{
watcher->ProcessNewFileEvent(fileAndPath);
watcher->rawFileAdded(fileAndPath, {});
// macOS does not send out an event for the directory being
// modified when a file has been renamed but the FileWatcher
// API expects it so send out the modification event ourselves.
watcher->ProcessModifyFileEvent(fileInfo.absolutePath());
watcher->rawFileModified(fileInfo.absolutePath(), {});
}
else
{
watcher->ProcessDeleteFileEvent(fileAndPath);
watcher->rawFileRemoved(fileAndPath, {});
// macOS does not send out an event for the directory being
// modified when a file has been renamed but the FileWatcher
// API expects it so send out the modification event ourselves.
watcher->ProcessModifyFileEvent(fileInfo.absolutePath());
watcher->rawFileModified(fileInfo.absolutePath(), {});
}
}
}
@@ -0,0 +1,11 @@
/*
* 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 <native/FileWatcher/FileWatcher_mac.h>
@@ -1,130 +0,0 @@
/*
* 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
*
*/
#include <native/FileWatcher/FileWatcher.h>
#include <AzCore/PlatformIncl.h>
struct FolderRootWatch::PlatformImplementation
{
PlatformImplementation() : m_directoryHandle(nullptr), m_ioHandle(nullptr) { }
HANDLE m_directoryHandle;
HANDLE m_ioHandle;
};
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
FolderRootWatch::FolderRootWatch(const QString rootFolder)
: m_root(rootFolder)
, m_shutdownThreadSignal(false)
, m_fileWatcher(nullptr)
, m_platformImpl(new PlatformImplementation())
{
}
FolderRootWatch::~FolderRootWatch()
{
// Destructor is required in here since this file contains the definition of struct PlatformImplementation
Stop();
delete m_platformImpl;
}
bool FolderRootWatch::Start()
{
m_platformImpl->m_directoryHandle = ::CreateFileW(m_root.toStdWString().data(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
if (m_platformImpl->m_directoryHandle != INVALID_HANDLE_VALUE)
{
m_platformImpl->m_ioHandle = ::CreateIoCompletionPort(m_platformImpl->m_directoryHandle, nullptr, 1, 0);
if (m_platformImpl->m_ioHandle != INVALID_HANDLE_VALUE)
{
m_shutdownThreadSignal = false;
m_thread = std::thread(std::bind(&FolderRootWatch::WatchFolderLoop, this));
return true;
}
}
return false;
}
void FolderRootWatch::Stop()
{
m_shutdownThreadSignal = true;
CloseHandle(m_platformImpl->m_ioHandle);
m_platformImpl->m_ioHandle = nullptr;
if (m_thread.joinable())
{
m_thread.join(); // wait for the thread to finish
m_thread = std::thread(); //destroy
}
CloseHandle(m_platformImpl->m_directoryHandle);
m_platformImpl->m_directoryHandle = nullptr;
}
void FolderRootWatch::WatchFolderLoop()
{
FILE_NOTIFY_INFORMATION aFileNotifyInformationList[50000];
QString path;
OVERLAPPED aOverlapped;
LPOVERLAPPED pOverlapped;
DWORD dwByteCount;
ULONG_PTR ulKey;
while (!m_shutdownThreadSignal)
{
::memset(aFileNotifyInformationList, 0, sizeof(aFileNotifyInformationList));
::memset(&aOverlapped, 0, sizeof(aOverlapped));
if (::ReadDirectoryChangesW(m_platformImpl->m_directoryHandle, aFileNotifyInformationList, sizeof(aFileNotifyInformationList), true, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_FILE_NAME, nullptr, &aOverlapped, nullptr))
{
//wait for up to a second for I/O to signal
dwByteCount = 0;
if (::GetQueuedCompletionStatus(m_platformImpl->m_ioHandle, &dwByteCount, &ulKey, &pOverlapped, INFINITE))
{
//if we are signaled to shutdown bypass
if (!m_shutdownThreadSignal && ulKey)
{
if (dwByteCount)
{
int offset = 0;
FILE_NOTIFY_INFORMATION* pFileNotifyInformation = aFileNotifyInformationList;
do
{
pFileNotifyInformation = (FILE_NOTIFY_INFORMATION*)((char*)pFileNotifyInformation + offset);
path.clear();
path.append(m_root);
path.append(QString::fromWCharArray(pFileNotifyInformation->FileName, pFileNotifyInformation->FileNameLength / 2));
QString file = QDir::toNativeSeparators(QDir::cleanPath(path));
switch (pFileNotifyInformation->Action)
{
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME:
ProcessNewFileEvent(file);
break;
case FILE_ACTION_REMOVED:
case FILE_ACTION_RENAMED_OLD_NAME:
ProcessDeleteFileEvent(file);
break;
case FILE_ACTION_MODIFIED:
ProcessModifyFileEvent(file);
break;
}
offset = pFileNotifyInformation->NextEntryOffset;
} while (offset);
}
}
}
}
}
}
@@ -7,6 +7,8 @@
#
set(FILES
native/FileWatcher/FileWatcher_win.cpp
native/FileWatcher/FileWatcher_platform.h
native/FileWatcher/FileWatcher_windows.cpp
native/FileWatcher/FileWatcher_windows.h
native/resource.h
)
@@ -0,0 +1,11 @@
/*
* 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 <native/FileWatcher/FileWatcher_windows.h>
@@ -1,130 +0,0 @@
/*
* 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
*
*/
#include <native/FileWatcher/FileWatcher.h>
#include <AzCore/PlatformIncl.h>
struct FolderRootWatch::PlatformImplementation
{
PlatformImplementation() : m_directoryHandle(nullptr), m_ioHandle(nullptr) { }
HANDLE m_directoryHandle;
HANDLE m_ioHandle;
};
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
FolderRootWatch::FolderRootWatch(const QString rootFolder)
: m_root(rootFolder)
, m_shutdownThreadSignal(false)
, m_fileWatcher(nullptr)
, m_platformImpl(new PlatformImplementation())
{
}
FolderRootWatch::~FolderRootWatch()
{
// Destructor is required in here since this file contains the definition of struct PlatformImplementation
Stop();
delete m_platformImpl;
}
bool FolderRootWatch::Start()
{
m_platformImpl->m_directoryHandle = ::CreateFileW(m_root.toStdWString().data(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
if (m_platformImpl->m_directoryHandle != INVALID_HANDLE_VALUE)
{
m_platformImpl->m_ioHandle = ::CreateIoCompletionPort(m_platformImpl->m_directoryHandle, nullptr, 1, 0);
if (m_platformImpl->m_ioHandle != INVALID_HANDLE_VALUE)
{
m_shutdownThreadSignal = false;
m_thread = std::thread(std::bind(&FolderRootWatch::WatchFolderLoop, this));
return true;
}
}
return false;
}
void FolderRootWatch::Stop()
{
m_shutdownThreadSignal = true;
CloseHandle(m_platformImpl->m_ioHandle);
m_platformImpl->m_ioHandle = nullptr;
if (m_thread.joinable())
{
m_thread.join(); // wait for the thread to finish
m_thread = std::thread(); //destroy
}
CloseHandle(m_platformImpl->m_directoryHandle);
m_platformImpl->m_directoryHandle = nullptr;
}
void FolderRootWatch::WatchFolderLoop()
{
FILE_NOTIFY_INFORMATION aFileNotifyInformationList[50000];
QString path;
OVERLAPPED aOverlapped;
LPOVERLAPPED pOverlapped;
DWORD dwByteCount;
ULONG_PTR ulKey;
while (!m_shutdownThreadSignal)
{
::memset(aFileNotifyInformationList, 0, sizeof(aFileNotifyInformationList));
::memset(&aOverlapped, 0, sizeof(aOverlapped));
if (::ReadDirectoryChangesW(m_platformImpl->m_directoryHandle, aFileNotifyInformationList, sizeof(aFileNotifyInformationList), true, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_FILE_NAME, nullptr, &aOverlapped, nullptr))
{
//wait for up to a second for I/O to signal
dwByteCount = 0;
if (::GetQueuedCompletionStatus(m_platformImpl->m_ioHandle, &dwByteCount, &ulKey, &pOverlapped, INFINITE))
{
//if we are signaled to shutdown bypass
if (!m_shutdownThreadSignal && ulKey)
{
if (dwByteCount)
{
int offset = 0;
FILE_NOTIFY_INFORMATION* pFileNotifyInformation = aFileNotifyInformationList;
do
{
pFileNotifyInformation = (FILE_NOTIFY_INFORMATION*)((char*)pFileNotifyInformation + offset);
path.clear();
path.append(m_root);
path.append(QString::fromWCharArray(pFileNotifyInformation->FileName, pFileNotifyInformation->FileNameLength / 2));
QString file = QDir::toNativeSeparators(QDir::cleanPath(path));
switch (pFileNotifyInformation->Action)
{
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME:
ProcessNewFileEvent(file);
break;
case FILE_ACTION_REMOVED:
case FILE_ACTION_RENAMED_OLD_NAME:
ProcessDeleteFileEvent(file);
break;
case FILE_ACTION_MODIFIED:
ProcessModifyFileEvent(file);
break;
}
offset = pFileNotifyInformation->NextEntryOffset;
} while (offset);
}
}
}
}
}
}
@@ -0,0 +1,153 @@
/*
* 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
*
*/
#include <AzCore/std/tuple.h>
#include <AzCore/std/utils.h>
#include <native/FileWatcher/FileWatcher.h>
#include <native/FileWatcher/FileWatcher_platform.h>
#include <QDir>
bool FileWatcher::PlatformStart()
{
m_shutdownThreadSignal = false;
bool allSucceeded = true;
for (const auto& [directory, recursive] : m_folderWatchRoots)
{
if (QDir(directory).exists())
{
allSucceeded &= m_platformImpl->AddWatchFolder(directory, recursive);
}
}
return allSucceeded;
}
bool FileWatcher::PlatformImplementation::AddWatchFolder(QString root, bool recursive)
{
HandleUniquePtr directoryHandle{::CreateFileW(
root.toStdWString().data(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
nullptr
)};
if (directoryHandle.get() == INVALID_HANDLE_VALUE)
{
AZ_Warning("FileWatcher", false, "Failed to start watching %s", root.toUtf8().constData());
return false;
}
// Associate this file handle with our existing io completion port handle
if (!::CreateIoCompletionPort(directoryHandle.get(), m_ioHandle.get(), /*CompletionKey =*/ static_cast<ULONG_PTR>(PlatformImplementation::EventType::FileRead), 1))
{
return false;
}
auto id = AZStd::make_unique<OVERLAPPED>();
auto* idp = id.get();
const auto& [folderWatch, inserted] = m_folderRootWatches.emplace(AZStd::piecewise_construct, AZStd::forward_as_tuple(idp),
AZStd::forward_as_tuple(AZStd::move(id), AZStd::move(directoryHandle), root, recursive));
if (!inserted)
{
return false;
}
return folderWatch->second.ReadChanges();
}
bool FileWatcher::PlatformImplementation::FolderRootWatch::ReadChanges()
{
// Register to get directory change notifications for our directory handle
return ::ReadDirectoryChangesW(
m_directoryHandle.get(),
&m_fileNotifyInformationList,
sizeof(m_fileNotifyInformationList),
m_recursive,
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_FILE_NAME,
nullptr,
m_overlapped.get(),
nullptr
);
}
void FileWatcher::PlatformStop()
{
m_shutdownThreadSignal = true;
// Send a special signal to the child thread, that is blocked in a GetQueuedCompletionStatus call, with a completion
// key set to Shutdown. The child thread will stop its processing when it receives this value for the completion key
PostQueuedCompletionStatus(m_platformImpl->m_ioHandle.get(), 0, /*CompletionKey =*/ static_cast<ULONG_PTR>(PlatformImplementation::EventType::Shutdown), nullptr);
if (m_thread.joinable())
{
m_thread.join(); // wait for the thread to finish
}
}
void FileWatcher::WatchFolderLoop()
{
LPOVERLAPPED directoryId = nullptr;
ULONG_PTR completionKey = 0;
while (!m_shutdownThreadSignal)
{
DWORD dwByteCount = 0;
if (::GetQueuedCompletionStatus(m_platformImpl->m_ioHandle.get(), &dwByteCount, &completionKey, &directoryId, INFINITE))
{
if (m_shutdownThreadSignal || completionKey == static_cast<ULONG_PTR>(PlatformImplementation::EventType::Shutdown))
{
break;
}
if (dwByteCount == 0)
{
continue;
}
const auto foundFolderRoot = m_platformImpl->m_folderRootWatches.find(directoryId);
if (foundFolderRoot == end(m_platformImpl->m_folderRootWatches))
{
continue;
}
PlatformImplementation::FolderRootWatch& folderRoot = foundFolderRoot->second;
// Initialize offset to 1 to ensure that the first iteration is always processed
DWORD offset = 1;
for (
const FILE_NOTIFY_INFORMATION* pFileNotifyInformation = reinterpret_cast<const FILE_NOTIFY_INFORMATION*>(&folderRoot.m_fileNotifyInformationList);
offset;
pFileNotifyInformation = reinterpret_cast<const FILE_NOTIFY_INFORMATION*>(reinterpret_cast<const char*>(pFileNotifyInformation) + offset)
){
const QString file = QDir::toNativeSeparators(QDir(folderRoot.m_directoryRoot)
.filePath(QString::fromWCharArray(pFileNotifyInformation->FileName, pFileNotifyInformation->FileNameLength / 2)));
switch (pFileNotifyInformation->Action)
{
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME:
rawFileAdded(file, {});
break;
case FILE_ACTION_REMOVED:
case FILE_ACTION_RENAMED_OLD_NAME:
rawFileRemoved(file, {});
break;
case FILE_ACTION_MODIFIED:
rawFileModified(file, {});
break;
}
offset = pFileNotifyInformation->NextEntryOffset;
}
folderRoot.ReadChanges();
}
}
}
@@ -0,0 +1,65 @@
/*
* 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/std/containers/unordered_map.h>
#include <AzCore/std/typetraits/aligned_storage.h>
#include <AzCore/std/typetraits/remove_pointer.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <native/FileWatcher/FileWatcher.h>
#include <AzCore/PlatformIncl.h>
struct HandleDeleter
{
void operator()(HANDLE handle)
{
if (handle && handle != INVALID_HANDLE_VALUE)
{
CloseHandle(handle);
}
}
};
using HandleUniquePtr = AZStd::unique_ptr<AZStd::remove_pointer_t<HANDLE>, HandleDeleter>;
class FileWatcher::PlatformImplementation
{
public:
bool AddWatchFolder(QString folder, bool recursive);
struct FolderRootWatch
{
FolderRootWatch(AZStd::unique_ptr<OVERLAPPED>&& overlapped, HandleUniquePtr&& directoryHandle, QString root, bool recursive)
: m_overlapped(AZStd::move(overlapped))
, m_directoryHandle(AZStd::move(directoryHandle))
, m_directoryRoot(AZStd::move(root))
, m_recursive(recursive)
{
}
bool ReadChanges();
AZStd::unique_ptr<OVERLAPPED> m_overlapped; // Identifies this root watch
HandleUniquePtr m_directoryHandle;
QString m_directoryRoot;
bool m_recursive;
AZStd::aligned_storage_t<64 * 1024, sizeof(DWORD)> m_fileNotifyInformationList{};
};
enum class EventType
{
FileRead,
Shutdown
};
AZStd::unordered_map<LPOVERLAPPED, FolderRootWatch> m_folderRootWatches;
HandleUniquePtr m_ioHandle{CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, /*CompletionKey =*/ static_cast<ULONG_PTR>(EventType::FileRead), 1)};
};
@@ -44,7 +44,6 @@ set(FILES
native/FileProcessor/FileProcessor.h
native/FileWatcher/FileWatcher.cpp
native/FileWatcher/FileWatcher.h
native/FileWatcher/FileWatcherAPI.h
native/InternalBuilders/SettingsRegistryBuilder.cpp
native/InternalBuilders/SettingsRegistryBuilder.h
native/resourcecompiler/JobsModel.cpp
@@ -63,13 +62,6 @@ set(FILES
native/resourcecompiler/RCJobSortFilterProxyModel.h
native/resourcecompiler/RCQueueSortModel.cpp
native/resourcecompiler/RCQueueSortModel.h
native/shadercompiler/shadercompilerjob.cpp
native/shadercompiler/shadercompilerjob.h
native/shadercompiler/shadercompilerManager.cpp
native/shadercompiler/shadercompilerManager.h
native/shadercompiler/shadercompilerMessages.h
native/shadercompiler/shadercompilerModel.cpp
native/shadercompiler/shadercompilerModel.h
native/utilities/ApplicationManagerAPI.h
native/utilities/ApplicationManager.cpp
native/utilities/ApplicationManager.h
@@ -67,8 +67,6 @@ set(FILES
native/unittests/PlatformConfigurationUnitTests.h
native/unittests/RCcontrollerUnitTests.cpp
native/unittests/RCcontrollerUnitTests.h
native/unittests/ShaderCompilerUnitTests.cpp
native/unittests/ShaderCompilerUnitTests.h
native/unittests/UnitTestRunner.cpp
native/unittests/UnitTestRunner.h
native/unittests/UtilitiesUnitTests.cpp
@@ -6,154 +6,119 @@
*
*/
#include "FileWatcher.h"
#include "AzCore/std/containers/vector.h"
#include <native/assetprocessor.h>
#include <native/FileWatcher/FileWatcher_platform.h>
#include <QFileInfo>
//////////////////////////////////////////////////////////////////////////////
/// FolderWatchRoot
void FolderRootWatch::ProcessNewFileEvent(const QString& file)
//! IsSubfolder(folderA, folderB)
//! returns whether folderA is a subfolder of folderB
//! assumptions: absolute paths
static bool IsSubfolder(const QString& folderA, const QString& folderB)
{
FileChangeInfo info;
info.m_action = FileAction::FileAction_Added;
info.m_filePath = file;
const bool invoked = QMetaObject::invokeMethod(m_fileWatcher, "AnyFileChange", Qt::QueuedConnection, Q_ARG(FileChangeInfo, info));
Q_ASSERT(invoked);
}
// lets avoid allocating or messing with memory - this is a MAJOR hotspot as it is called for any file change even in the cache!
if (folderA.length() <= folderB.length())
{
return false;
}
void FolderRootWatch::ProcessDeleteFileEvent(const QString& file)
{
FileChangeInfo info;
info.m_action = FileAction::FileAction_Removed;
info.m_filePath = file;
const bool invoked = QMetaObject::invokeMethod(m_fileWatcher, "AnyFileChange", Qt::QueuedConnection, Q_ARG(FileChangeInfo, info));
Q_ASSERT(invoked);
}
using AZStd::begin;
using AZStd::end;
void FolderRootWatch::ProcessModifyFileEvent(const QString& file)
{
FileChangeInfo info;
info.m_action = FileAction::FileAction_Modified;
info.m_filePath = file;
const bool invoked = QMetaObject::invokeMethod(m_fileWatcher, "AnyFileChange", Qt::QueuedConnection, Q_ARG(FileChangeInfo, info));
Q_ASSERT(invoked);
constexpr auto isSlash = [](const QChar c) constexpr
{
return c == AZ::IO::WindowsPathSeparator || c == AZ::IO::PosixPathSeparator;
};
const auto firstPathSeparator = AZStd::find_if(begin(folderB), end(folderB), [&isSlash](const QChar c)
{
return isSlash(c);
});
// Follow the convention used by AZ::IO::Path, and use a case-sensitive comparison on Posix paths
const bool useCaseSensitiveCompare = (firstPathSeparator == end(folderB)) ? true : (*firstPathSeparator == AZ::IO::PosixPathSeparator);
return AZStd::equal(begin(folderB), end(folderB), begin(folderA), [isSlash, useCaseSensitiveCompare](const QChar charAtB, const QChar charAtA)
{
if (isSlash(charAtA))
{
return isSlash(charAtB);
}
if (useCaseSensitiveCompare)
{
return charAtA == charAtB;
}
return charAtA.toLower() == charAtB.toLower();
});
}
//////////////////////////////////////////////////////////////////////////
/// FileWatcher
FileWatcher::FileWatcher()
: m_nextHandle(0)
: m_platformImpl(AZStd::make_unique<PlatformImplementation>())
{
qRegisterMetaType<FileChangeInfo>("FileChangeInfo");
auto makeFilter = [this](auto signal)
{
return [this, signal](QString path)
{
const auto foundWatchRoot = AZStd::find_if(begin(m_folderWatchRoots), end(m_folderWatchRoots), [path](const WatchRoot& watchRoot)
{
return Filter(path, watchRoot);
});
if (foundWatchRoot == end(m_folderWatchRoots))
{
return;
}
AZStd::invoke(signal, this, path);
};
};
// The rawFileAdded signals are emitted by the watcher thread. Use a queued
// connection so that the consumers of the notification process the
// notification on the main thread.
connect(this, &FileWatcher::rawFileAdded, this, makeFilter(&FileWatcher::fileAdded), Qt::QueuedConnection);
connect(this, &FileWatcher::rawFileRemoved, this, makeFilter(&FileWatcher::fileRemoved), Qt::QueuedConnection);
connect(this, &FileWatcher::rawFileModified, this, makeFilter(&FileWatcher::fileModified), Qt::QueuedConnection);
}
FileWatcher::~FileWatcher()
{
disconnect();
StopWatching();
}
int FileWatcher::AddFolderWatch(FolderWatchBase* pFolderWatch)
void FileWatcher::AddFolderWatch(QString directory, bool recursive)
{
if (!pFolderWatch)
// Search for an already monitored root that is a parent of `directory`,
// that is already watching subdirectories recursively
const auto found = AZStd::find_if(begin(m_folderWatchRoots), end(m_folderWatchRoots), [directory](const WatchRoot& root)
{
return -1;
return root.m_recursive && IsSubfolder(directory, root.m_directory);
});
if (found != end(m_folderWatchRoots))
{
// This directory is already watched
return;
}
FolderRootWatch* pFolderRootWatch = nullptr;
//create a new root and start listening for changes
m_folderWatchRoots.push_back({directory, recursive});
//see if this a sub folder of an already watched root
for (auto rootsIter = m_folderWatchRoots.begin(); !pFolderRootWatch && rootsIter != m_folderWatchRoots.end(); ++rootsIter)
//since we created a new root, see if the new root is a super folder
//of other roots, if it is then then fold those roots into the new super root
if (recursive)
{
if (FolderWatchBase::IsSubfolder(pFolderWatch->m_folder, (*rootsIter)->m_root))
AZStd::erase_if(m_folderWatchRoots, [directory](const WatchRoot& root)
{
pFolderRootWatch = *rootsIter;
}
return IsSubfolder(root.m_directory, directory);
});
}
bool bCreatedNewRoot = false;
//if its not a sub folder
if (!pFolderRootWatch)
{
//create a new root and start listening for changes
pFolderRootWatch = new FolderRootWatch(pFolderWatch->m_folder);
//make sure the folder watcher(s) get deleted before this
pFolderRootWatch->setParent(this);
bCreatedNewRoot = true;
}
pFolderRootWatch->m_fileWatcher = this;
QObject::connect(this, &FileWatcher::AnyFileChange, pFolderWatch, &FolderWatchBase::OnAnyFileChange);
if (bCreatedNewRoot)
{
if (m_startedWatching)
{
pFolderRootWatch->Start();
}
//since we created a new root, see if the new root is a super folder
//of other roots, if it is then then fold those roots into the new super root
for (auto rootsIter = m_folderWatchRoots.begin(); rootsIter != m_folderWatchRoots.end(); )
{
if (FolderWatchBase::IsSubfolder((*rootsIter)->m_root, pFolderWatch->m_folder))
{
//union the sub folder map over to the new root
pFolderRootWatch->m_subFolderWatchesMap.insert((*rootsIter)->m_subFolderWatchesMap);
//clear the old root sub folders map so they don't get deleted when we
//delete the old root as they are now pointed to by the new root
(*rootsIter)->m_subFolderWatchesMap.clear();
//delete the empty old root, deleting a root will call Stop()
//automatically which kills the thread
delete *rootsIter;
//remove the old root pointer form the watched list
rootsIter = m_folderWatchRoots.erase(rootsIter);
}
else
{
++rootsIter;
}
}
//add the new root to the watched roots
m_folderWatchRoots.push_back(pFolderRootWatch);
}
//add to the root
pFolderRootWatch->m_subFolderWatchesMap.insert(m_nextHandle, pFolderWatch);
m_nextHandle++;
return m_nextHandle - 1;
}
void FileWatcher::RemoveFolderWatch(int handle)
void FileWatcher::ClearFolderWatches()
{
for (auto rootsIter = m_folderWatchRoots.begin(); rootsIter != m_folderWatchRoots.end(); )
{
//find an element by the handle
auto foundIter = (*rootsIter)->m_subFolderWatchesMap.find(handle);
if (foundIter != (*rootsIter)->m_subFolderWatchesMap.end())
{
//remove the element
(*rootsIter)->m_subFolderWatchesMap.erase(foundIter);
//we removed a folder watch, if it's empty then there is no reason to keep watching it.
if ((*rootsIter)->m_subFolderWatchesMap.empty())
{
delete(*rootsIter);
rootsIter = m_folderWatchRoots.erase(rootsIter);
}
else
{
++rootsIter;
}
}
else
{
++rootsIter;
}
}
m_folderWatchRoots.clear();
}
void FileWatcher::StartWatching()
@@ -164,12 +129,18 @@ void FileWatcher::StartWatching()
return;
}
for (FolderRootWatch* root : m_folderWatchRoots)
if (PlatformStart())
{
root->Start();
m_thread = AZStd::thread({/*.name=*/ "AssetProcessor FileWatcher thread"}, [this]{
WatchFolderLoop();
});
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "File Change Monitoring started.\n");
}
else
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "File Change Monitoring failed to start.\n");
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "File Change Monitoring started.\n");
m_startedWatching = true;
}
@@ -177,17 +148,35 @@ void FileWatcher::StopWatching()
{
if (!m_startedWatching)
{
AZ_Warning("FileWatcher", false, "StartWatching() called when is not watching for file changes.");
AZ_Warning("FileWatcher", false, "StopWatching() called when is not watching for file changes.");
return;
}
for (FolderRootWatch* root : m_folderWatchRoots)
{
root->Stop();
}
PlatformStop();
m_startedWatching = false;
}
#include "native/FileWatcher/moc_FileWatcher.cpp"
#include "native/FileWatcher/moc_FileWatcherAPI.cpp"
bool FileWatcher::Filter(QString path, const WatchRoot& watchRoot)
{
if (!IsSubfolder(path, watchRoot.m_directory))
{
return false;
}
if (!watchRoot.m_recursive)
{
// filter out subtrees too.
QStringRef subRef = path.rightRef(path.length() - watchRoot.m_directory.length());
if ((subRef.indexOf('/') != -1) || (subRef.indexOf('\\') != -1))
{
return false; // filter this out.
}
// we don't care about subdirs. IsDir is more expensive so we do it after the above filter.
if (QFileInfo(path).isDir())
{
return false;
}
}
return true;
}
@@ -5,62 +5,21 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef FILEWATCHER_COMPONENT_H
#define FILEWATCHER_COMPONENT_H
//////////////////////////////////////////////////////////////////////////
#pragma once
#if !defined(Q_MOC_RUN)
#include "FileWatcherAPI.h"
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzCore/std/parallel/thread.h>
#include <QMap>
#include <QVector>
#include <QString>
#include <QObject>
#include <thread>
#endif
class FileWatcher;
//////////////////////////////////////////////////////////////////////////
//! FolderRootWatch
/*! Class used for holding a point in the files system from which file changes are tracked.
* */
class FolderRootWatch
: public QObject
{
Q_OBJECT
friend class FileWatcher;
public:
FolderRootWatch(const QString rootFolder);
virtual ~FolderRootWatch();
void ProcessNewFileEvent(const QString& file);
void ProcessDeleteFileEvent(const QString& file);
void ProcessModifyFileEvent(const QString& file);
void ProcessRenameFileEvent(const QString& fileOld, const QString& fileNew);
public Q_SLOTS:
bool Start();
void Stop();
private:
void WatchFolderLoop();
private:
std::thread m_thread;
QString m_root;
QMap<int, FolderWatchBase*> m_subFolderWatchesMap;
volatile bool m_shutdownThreadSignal;
FileWatcher* m_fileWatcher;
// Can't use unique_ptr because this is a QObject and Qt's magic sauce is
// unable to determine the size of the unique_ptr and so fails to compile
struct PlatformImplementation;
PlatformImplementation* m_platformImpl;
};
//////////////////////////////////////////////////////////////////////////
//! FileWatcher
/*! Class that handles creation and deletion of FolderRootWatches based on
@@ -73,23 +32,47 @@ class FileWatcher
public:
FileWatcher();
virtual ~FileWatcher();
~FileWatcher() override;
//////////////////////////////////////////////////////////////////////////
virtual int AddFolderWatch(FolderWatchBase* pFolderWatch);
virtual void RemoveFolderWatch(int handle);
void AddFolderWatch(QString directory, bool recursive = true);
void ClearFolderWatches();
//////////////////////////////////////////////////////////////////////////
void StartWatching();
void StopWatching();
Q_SIGNALS:
void AnyFileChange(FileChangeInfo info);
// These signals are emitted when a file under a watched path changes
void fileAdded(QString filePath);
void fileRemoved(QString filePath);
void fileModified(QString filePath);
// These signals are emitted by the platform implementations when files
// change. Some platforms' file watch APIs do not support non-recursive
// watches, so the signals are filtered before being forwarded to the
// non-"raw" fileAdded/Removed/Modified signals above.
void rawFileAdded(QString filePath, QPrivateSignal);
void rawFileRemoved(QString filePath, QPrivateSignal);
void rawFileModified(QString filePath, QPrivateSignal);
private:
int m_nextHandle;
AZStd::vector<FolderRootWatch*> m_folderWatchRoots;
bool m_startedWatching = false;
};
bool PlatformStart();
void PlatformStop();
void WatchFolderLoop();
#endif//FILEWATCHER_COMPONENT_H
class PlatformImplementation;
friend class PlatformImplementation;
struct WatchRoot
{
QString m_directory;
bool m_recursive;
};
static bool Filter(QString path, const WatchRoot& watchRoot);
AZStd::unique_ptr<PlatformImplementation> m_platformImpl;
AZStd::vector<WatchRoot> m_folderWatchRoots;
AZStd::thread m_thread;
bool m_startedWatching = false;
AZStd::atomic_bool m_shutdownThreadSignal = false;
};
@@ -1,222 +0,0 @@
/*
* 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
*
*/
#ifndef FILEWATCHERAPI_H
#define FILEWATCHERAPI_H
#include <QString>
#include <QObject>
#include <QDir>
//////////////////////////////////////////////////////////////////////////
//! FileAction
/*! Enum for which file changes are tracked.
* */
enum FileAction
{
FileAction_None = 0x00,
FileAction_Added = 0x01,
FileAction_Removed = 0x02,
FileAction_Modified = 0x04,
FileAction_Any = 0xFF,
};
inline FileAction operator | (FileAction a, FileAction b)
{
return static_cast<FileAction>(static_cast<int>(a) | static_cast<int>(b));
}
inline FileAction operator & (FileAction a, FileAction b)
{
return static_cast<FileAction>(static_cast<int>(a) & static_cast<int>(b));
}
//////////////////////////////////////////////////////////////////////////
//! FileChangeInfo
/*! Struct for passing along information about file changes.
* */
struct FileChangeInfo
{
FileChangeInfo()
: m_action(FileAction::FileAction_None)
{}
FileChangeInfo(const FileChangeInfo& rhs)
: m_action(rhs.m_action)
, m_filePath(rhs.m_filePath)
, m_filePathOld(rhs.m_filePathOld)
{
}
FileAction m_action;
QString m_filePath;
QString m_filePathOld;
};
Q_DECLARE_METATYPE(FileChangeInfo)
//////////////////////////////////////////////////////////////////////////
//! FolderWatchBase
/*! Class for filtering file changes generated from a root watch. Define your own
*! custom filtering by deriving from this base class and implement your own
*! custom code for what to do when receiving a file change notification.
* */
class FolderWatchBase
: public QObject
{
Q_OBJECT
public:
FolderWatchBase(const QString strFolder, bool bWatchSubtree = true, FileAction fileAction = FileAction::FileAction_Any)
: m_folder(strFolder)
, m_watchSubtree(bWatchSubtree)
, m_fileAction(fileAction)
{
m_folder = QDir::toNativeSeparators(QDir::cleanPath(m_folder) + "/");
}
//! IsSubfolder(folderA, folderB)
//! returns whether folderA is a subfolder of folderB
//! assumptions: absolute paths, case insensitive
static bool IsSubfolder(const QString& folderA, const QString& folderB)
{
// lets avoid allocating or messing with memory - this is a MAJOR hotspot as it is called for any file change even in the cache!
int sizeB = folderB.length();
int sizeA = folderA.length();
if (sizeA <= sizeB)
{
return false;
}
QChar slash1 = QChar('\\');
QChar slash2 = QChar('/');
int posA = 0;
// A is going to be the longer one, so use B:
for (int idx = 0; idx < sizeB; ++idx)
{
QChar charAtA = folderA.at(posA);
QChar charAtB = folderB.at(idx);
if ((charAtB == slash1) || (charAtB == slash2))
{
if ((charAtA != slash1) && (charAtA != slash2))
{
return false;
}
++posA;
}
else
{
if (charAtA.toLower() != charAtB.toLower())
{
return false;
}
++posA;
}
}
return true;
}
QString m_folder;
bool m_watchSubtree;
FileAction m_fileAction;
public Q_SLOTS:
void OnAnyFileChange(FileChangeInfo info)
{
//if they set a file action then respect it by rejecting non matching file actions
if (info.m_action & m_fileAction)
{
//is the file is in the folder or subtree (if specified) then call OnFileChange
if (FolderWatchBase::IsSubfolder(info.m_filePath, m_folder))
{
OnFileChange(info);
}
}
}
virtual void OnFileChange(const FileChangeInfo& info) = 0;
};
//////////////////////////////////////////////////////////////////////////
//! FolderWatchCallbackEx
/*! Class implements a more complex filtering that can optionally filter for file
*! extension and call different callback for different kinds of file changes
*! generated from a root watch.
*! Notes:
*! - empty extension "" catches all file changes
*! - extension should not include the leading "."
* */
class FolderWatchCallbackEx
: public FolderWatchBase
{
Q_OBJECT
public:
FolderWatchCallbackEx(const QString strFolder, const QString extension, bool bWatchSubtree)
: FolderWatchBase(strFolder, bWatchSubtree)
, m_extension(extension)
{
}
QString m_extension;
//on file change call the change callback if passes extension then route
//to specific file action type callback
virtual void OnFileChange(const FileChangeInfo& info)
{
//if they set an extension to watch for only let matching extensions through
QFileInfo fileInfo(info.m_filePath);
if (!m_watchSubtree)
{
// filter out subtrees too.
QStringRef subRef = info.m_filePath.rightRef(info.m_filePath.length() - m_folder.length());
if ((subRef.indexOf('/') != -1) || (subRef.indexOf('\\') != -1))
{
return; // filter this out.
}
// we don't care about subdirs. IsDir is more expensive so we do it after the above filter.
if (fileInfo.isDir())
{
return;
}
}
if (m_extension.isEmpty() || fileInfo.completeSuffix().compare(m_extension, Qt::CaseInsensitive) == 0)
{
if (info.m_action & FileAction::FileAction_Any)
{
Q_EMIT fileChange(info);
}
if (info.m_action & FileAction::FileAction_Added)
{
Q_EMIT fileAdded(info.m_filePath);
}
if (info.m_action & FileAction::FileAction_Removed)
{
Q_EMIT fileRemoved(info.m_filePath);
}
if (info.m_action & FileAction::FileAction_Modified)
{
Q_EMIT fileModified(info.m_filePath);
}
}
}
Q_SIGNALS:
void fileChange(FileChangeInfo info);
void fileAdded(QString filePath);
void fileRemoved(QString filePath);
void fileModified(QString filePath);
};
#endif//FILEWATCHERAPI_H
@@ -85,7 +85,7 @@ namespace AssetProcessor
enum AssetCatalogStatus
{
RequiresSaving,
RequiresSaving,
UpToDate
};
@@ -213,11 +213,11 @@ namespace AssetProcessor
bool m_critical = false;
int m_priority = -1;
// indicates whether we need to check the server first for the outputs of this job
// indicates whether we need to check the server first for the outputs of this job
// before we start processing locally
bool m_checkServer = false;
// Indicates whether this job needs to be processed irrespective of whether its fingerprint got modified or not.
// Indicates whether this job needs to be processed irrespective of whether its fingerprint got modified or not.
bool m_autoProcessJob = false;
AssetBuilderSDK::AssetBuilderDesc m_assetBuilderDesc;
@@ -251,9 +251,9 @@ namespace AssetProcessor
JobDetails() = default;
};
//! JobDesc struct is used for identifying jobs that need to be processed again
//! because of job dependency declared on them by other jobs
//! JobDesc struct is used for identifying jobs that need to be processed again
//! because of job dependency declared on them by other jobs
struct JobDesc
{
AZStd::string m_databaseSourceName;
@@ -283,7 +283,7 @@ namespace AssetProcessor
}
};
//! JobIndentifier is an internal structure that store all the data that can uniquely identify a job
//! JobIndentifier is an internal structure that store all the data that can uniquely identify a job
struct JobIndentifier
{
JobDesc m_jobDesc;
@@ -1,162 +0,0 @@
/*
* 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
*
*/
#include "shadercompilerManager.h"
#include "shadercompilerjob.h"
#include <QThreadPool>
#include "native/utilities/assetUtils.h"
ShaderCompilerManager::ShaderCompilerManager(QObject* parent)
: QObject(parent)
, m_isUnitTesting(false)
, m_numberOfJobsStarted(0)
, m_numberOfJobsEnded(0)
, m_numberOfErrors(0)
{
}
ShaderCompilerManager::~ShaderCompilerManager()
{
}
void ShaderCompilerManager::process(unsigned int connID, unsigned int type, unsigned int serial, QByteArray payload)
{
(void)type;
(void)serial;
Q_ASSERT(AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest") == type);
decodeShaderCompilerRequest(connID, payload);
}
void ShaderCompilerManager::decodeShaderCompilerRequest(unsigned int connID, QByteArray payload)
{
if (payload.length() < sizeof(unsigned int) + sizeof(unsigned int) + 2 + sizeof(unsigned short))
{
QString error = "Payload size is too small";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
return;
}
unsigned char* data_end = reinterpret_cast<unsigned char*>(payload.data() + payload.size());
unsigned int* requestId = reinterpret_cast<unsigned int*>(data_end - sizeof(unsigned int));
unsigned int* serverListSizePtr = reinterpret_cast<unsigned int*>(data_end - sizeof(unsigned int) - sizeof(unsigned int));
unsigned short* serverPortPtr = reinterpret_cast<unsigned short*>(data_end - sizeof(unsigned int) - sizeof(unsigned int) - sizeof(unsigned short));
ShaderCompilerRequestMessage msg;
QString error;
msg.requestId = *requestId;
msg.serverListSize = *serverListSizePtr;
msg.serverPort = *serverPortPtr;
if ((msg.serverListSize <= 0) || (msg.serverListSize > 100000))
{
error = "Shader Compiler Server List is wrong";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
return;
}
if (msg.serverPort == 0)
{
error = "Shader Compiler port is wrong";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
return;
}
char* position_of_first_null = reinterpret_cast<char*>(serverPortPtr) - 1;// -1 for null
if ((*position_of_first_null) != '\0')
{
error = "Shader Compiler payload is corrupt,position is not null";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
return;
}
char* beginning_of_serverList = position_of_first_null - msg.serverListSize;
char* position_of_second_null = beginning_of_serverList - 1;//-1 for null
if ((*position_of_second_null) != '\0')
{
error = "Shader Compiler payload is corrupt,position is not null";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
return;
}
unsigned int originalPayloadSize = static_cast<unsigned int>(payload.size()) - sizeof(unsigned int) - sizeof(unsigned int) - sizeof(unsigned short) - static_cast<unsigned int>(msg.serverListSize) - 2;
msg.serverList = beginning_of_serverList;
msg.originalPayload.insert(0, payload.data(), static_cast<unsigned int>(originalPayloadSize));
ShaderCompilerJob* shaderCompilerJob = new ShaderCompilerJob();
shaderCompilerJob->initialize(this, msg);
shaderCompilerJob->setIsUnitTesting(m_isUnitTesting);
m_shaderCompilerJobMap[msg.requestId] = connID;
shaderCompilerJob->setAutoDelete(true);
QThreadPool* threadPool = QThreadPool::globalInstance();
threadPool->start(shaderCompilerJob);
}
void ShaderCompilerManager::OnShaderCompilerJobComplete(QByteArray payload, unsigned int requestId)
{
auto iterator = m_shaderCompilerJobMap.find(requestId);
if (iterator != m_shaderCompilerJobMap.end())
{
sendResponse(iterator.value(), AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyResponse"), 0, payload);
}
else
{
QString error = "Shader Compiler cannot find the connection id";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
}
}
void ShaderCompilerManager::sendResponse(unsigned int connId, unsigned int /*type*/, unsigned int /*serial*/, QByteArray payload)
{
EBUS_EVENT_ID(connId, AssetProcessor::ConnectionBus, SendRaw, AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyResponse"), 0, payload);
}
void ShaderCompilerManager::shaderCompilerError(QString errorMessage, QString server, QString timestamp, QString payload)
{
m_numberOfErrors++;
emit numberOfErrorsChanged();
emit sendErrorMessageFromShaderJob(errorMessage, server, timestamp, payload);
}
void ShaderCompilerManager::jobStarted()
{
m_numberOfJobsStarted++;
emit numberOfJobsStartedChanged();
}
void ShaderCompilerManager::jobEnded()
{
m_numberOfJobsEnded++;
numberOfJobsEndedChanged();
}
void ShaderCompilerManager::setIsUnitTesting(bool isUnitTesting)
{
m_isUnitTesting = isUnitTesting;
}
int ShaderCompilerManager::numberOfJobsStarted()
{
return m_numberOfJobsStarted;
}
int ShaderCompilerManager::numberOfJobsEnded()
{
return m_numberOfJobsEnded;
}
int ShaderCompilerManager::numberOfErrors()
{
return m_numberOfErrors;
}
@@ -1,67 +0,0 @@
/*
* 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
*
*/
#ifndef SHADERCOMPILERMANAGER_H
#define SHADERCOMPILERMANAGER_H
#if !defined(Q_MOC_RUN)
#include <QObject>
#include <QHash>
#include <QString>
#include <QByteArray>
#endif
typedef QHash<unsigned int, unsigned int> ShaderCompilerJobMap;
/**
* The Shader Compiler Manager class receive a shader compile request
* and starts a shader compiler job for it
*/
class ShaderCompilerManager
: public QObject
{
Q_OBJECT
Q_PROPERTY(int numberOfJobsStarted READ numberOfJobsStarted NOTIFY numberOfJobsStartedChanged)
Q_PROPERTY(int numberOfJobsEnded READ numberOfJobsEnded NOTIFY numberOfJobsEndedChanged)
Q_PROPERTY(int numberOfErrors READ numberOfErrors NOTIFY numberOfErrorsChanged)
public:
explicit ShaderCompilerManager(QObject* parent = 0);
virtual ~ShaderCompilerManager();
void process(unsigned int connID, unsigned int type, unsigned int serial, QByteArray payload);
void decodeShaderCompilerRequest(unsigned int connID, QByteArray payload);
void setIsUnitTesting(bool isUnitTesting);
int numberOfJobsStarted();
int numberOfJobsEnded();
int numberOfErrors();
virtual void sendResponse(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
signals:
void sendErrorMessage(QString errorMessage);
void sendErrorMessageFromShaderJob(QString errorMessage, QString server, QString timestamp, QString payload);
void numberOfJobsStartedChanged();
void numberOfJobsEndedChanged();
void numberOfErrorsChanged();
public slots:
void OnShaderCompilerJobComplete(QByteArray payload, unsigned int requestId);
void shaderCompilerError(QString errorMessage, QString server, QString timestamp, QString payload);
void jobStarted();
void jobEnded();
private:
ShaderCompilerJobMap m_shaderCompilerJobMap;
bool m_isUnitTesting;
int m_numberOfJobsStarted;
int m_numberOfJobsEnded;
int m_numberOfErrors;
};
#endif // SHADERCOMPILERMANAGER_H
@@ -1,24 +0,0 @@
/*
* 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
*
*/
#ifndef SHADERCOMPILERMESSAGES_H
#define SHADERCOMPILERMESSAGES_H
#include <QByteArray>
#include <QString>
struct ShaderCompilerRequestMessage
{
QByteArray originalPayload;
QString serverList;
unsigned short serverPort;
unsigned int serverListSize;
unsigned int requestId;
};
#endif //SHADERCOMPILERMESSAGES_H
@@ -1,150 +0,0 @@
/*
* 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
*
*/
#include "shadercompilerModel.h"
namespace
{
ShaderCompilerModel* s_singleton = nullptr;
}
ShaderCompilerModel::ShaderCompilerModel(QObject* parent)
: QAbstractItemModel(parent)
{
Q_ASSERT(s_singleton == nullptr);
s_singleton = this;
}
ShaderCompilerModel::~ShaderCompilerModel()
{
s_singleton = nullptr;
}
ShaderCompilerModel* ShaderCompilerModel::Get()
{
return s_singleton;
}
QVariant ShaderCompilerModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
int row = index.row();
if (row < 0)
{
return QVariant();
}
if (row >= m_shaderErrorInfoList.count())
{
return QVariant();
}
switch (role)
{
case TimeStampRole:
return m_shaderErrorInfoList[row].m_shaderTimestamp;
case ServerRole:
return m_shaderErrorInfoList[row].m_shaderServerName;
case ErrorRole:
return m_shaderErrorInfoList[row].m_shaderError;
case OriginalRequestRole:
return m_shaderErrorInfoList[row].m_shaderOriginalPayload;
case Qt::DisplayRole:
switch (index.column())
{
case ColumnTimeStamp:
return m_shaderErrorInfoList[row].m_shaderTimestamp;
case ColumnServer:
return m_shaderErrorInfoList[row].m_shaderServerName;
case ColumnError:
return m_shaderErrorInfoList[row].m_shaderServerName;
}
}
return QVariant();
}
Qt::ItemFlags ShaderCompilerModel::flags(const QModelIndex& index) const
{
(void)index;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
int ShaderCompilerModel::rowCount(const QModelIndex& parent) const
{
(void)parent;
return m_shaderErrorInfoList.count();
}
QModelIndex ShaderCompilerModel::parent(const QModelIndex&) const
{
return QModelIndex();
}
QModelIndex ShaderCompilerModel::index(int row, int column, const QModelIndex& parent) const
{
if (row >= rowCount(parent) || column >= columnCount(parent))
{
return QModelIndex();
}
return createIndex(row, column);
}
int ShaderCompilerModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : Column::Max;
}
QVariant ShaderCompilerModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch (section)
{
case ColumnTimeStamp:
return tr("Time Stamp");
case ColumnServer:
return tr("Server");
case ColumnError:
return tr("Error");
default:
break;
}
}
return QAbstractItemModel::headerData(section, orientation, role);
}
QHash<int, QByteArray> ShaderCompilerModel::roleNames() const
{
QHash<int, QByteArray> result;
result[TimeStampRole] = "timestamp";
result[ServerRole] = "server";
result[ErrorRole] = "error";
result[OriginalRequestRole] = "originalRequest";
return result;
}
void ShaderCompilerModel::addShaderErrorInfoEntry(QString errorMessage, QString timestamp, QString payload, QString server)
{
ShaderCompilerErrorInfo shaderCompileErrorInfo(errorMessage, timestamp, payload, server);
beginInsertRows(QModelIndex(), m_shaderErrorInfoList.size(), m_shaderErrorInfoList.size());
m_shaderErrorInfoList.append(shaderCompileErrorInfo);
endInsertRows();
}
@@ -1,93 +0,0 @@
/*
* 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
*
*/
#ifndef SHADERCOMPILERMODEL_H
#define SHADERCOMPILERMODEL_H
#if !defined(Q_MOC_RUN)
#include <QAbstractItemModel>
#include <QList>
#include <QVariant>
#include <QHash>
#include <QByteArray>
#include <QString>
#endif
class QModelIndex;
class QObject;
struct ShaderCompilerErrorInfo
{
QString m_shaderError;
QString m_shaderTimestamp;
QString m_shaderOriginalPayload;
QString m_shaderServerName;
ShaderCompilerErrorInfo(QString shaderError, QString shaderTimestamp, QString shaderOriginalPayload, QString shaderServerName)
: m_shaderError(shaderError)
, m_shaderTimestamp(shaderTimestamp)
, m_shaderOriginalPayload(shaderOriginalPayload)
, m_shaderServerName(shaderServerName)
{
}
};
/** The Shader Compiler model is responsible for capturing error requests
*/
class ShaderCompilerModel
: public QAbstractItemModel
{
Q_OBJECT
public:
enum DataRoles
{
TimeStampRole = Qt::UserRole + 1,
ServerRole,
ErrorRole,
OriginalRequestRole,
};
enum Column
{
ColumnTimeStamp,
ColumnServer,
ColumnError,
Max
};
/// standard Qt constructor
explicit ShaderCompilerModel(QObject* parent = 0);
virtual ~ShaderCompilerModel();
// singleton pattern
static ShaderCompilerModel* Get();
/// QAbstractListModel interface
QModelIndex parent(const QModelIndex&) const override;
QModelIndex index(int row, int column, const QModelIndex& parent) const override;
int columnCount(const QModelIndex&) const override;
virtual int rowCount(const QModelIndex& parent) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
virtual QVariant data(const QModelIndex& index, int role) const override;
virtual QHash<int, QByteArray> roleNames() const override;
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
public slots:
void addShaderErrorInfoEntry(QString errorMessage, QString timestamp, QString payload, QString server);
private:
QList<ShaderCompilerErrorInfo> m_shaderErrorInfoList;
};
#endif // SHADERCOMPILERMODEL_H
@@ -1,194 +0,0 @@
/*
* 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
*
*/
#include "shadercompilerjob.h"
#include "native/assetprocessor.h"
#include <QTcpSocket>
ShaderCompilerJob::ShaderCompilerJob()
: m_isUnitTesting(false)
, m_manager(nullptr)
{
}
ShaderCompilerJob::~ShaderCompilerJob()
{
m_manager = nullptr;
}
ShaderCompilerRequestMessage ShaderCompilerJob::ShaderCompilerMessage() const
{
return m_ShaderCompilerMessage;
}
void ShaderCompilerJob::initialize(QObject* pManager, const ShaderCompilerRequestMessage& ShaderCompilerMessage)
{
m_manager = pManager;
m_ShaderCompilerMessage = ShaderCompilerMessage;
}
QString ShaderCompilerJob::getServerAddress()
{
if (isServerListEmpty())
{
return QString();
}
QString serverAddress;
if (!m_ShaderCompilerMessage.serverList.contains(","))
{
serverAddress = m_ShaderCompilerMessage.serverList;
m_ShaderCompilerMessage.serverList.clear();
return serverAddress;
}
QStringList serverList = m_ShaderCompilerMessage.serverList.split(",");
serverAddress = serverList.takeAt(0);
m_ShaderCompilerMessage.serverList = serverList.join(",");
return serverAddress;
}
bool ShaderCompilerJob::isServerListEmpty()
{
return m_ShaderCompilerMessage.serverList.isEmpty();
}
bool ShaderCompilerJob::attemptDelivery(QString serverAddress, QByteArray& payload)
{
QTcpSocket socket;
QString error;
int waitingTime = 8000; // 8 sec timeout for sending.
int jobCompileMaxTime = 1000 * 60; // 60 sec timeout for compilation
if (m_isUnitTesting)
{
waitingTime = 500;
jobCompileMaxTime = 500;
}
socket.connectToHost(serverAddress, m_ShaderCompilerMessage.serverPort, QIODevice::ReadWrite);
if (socket.waitForConnected(waitingTime))
{
qint64 bytesWritten = 0;
qint64 payloadSize = static_cast<qint64>(m_ShaderCompilerMessage.originalPayload.size());
// send payload size to server
while (bytesWritten != sizeof(qint64))
{
qint64 currentWrite = socket.write(reinterpret_cast<char*>(&payloadSize) + bytesWritten,
sizeof(qint64) - bytesWritten);
if (currentWrite == -1)
{
//It is important to note that we are only outputting the error to debugchannel only here because
//we are forwarding these error messages upstream to the manager,who will take the appropriate action
error = "Connection Lost:Unable to send data";
AZ_TracePrintf(AssetProcessor::DebugChannel, error.toUtf8().data());
QMetaObject::invokeMethod(m_manager, "shaderCompilerError", Qt::QueuedConnection, Q_ARG(QString, error), Q_ARG(QString, QDateTime::currentDateTime().toString()), Q_ARG(QString, QString(m_ShaderCompilerMessage.originalPayload)), Q_ARG(QString, serverAddress));
return false;
}
socket.flush();
bytesWritten += currentWrite;
}
bytesWritten = 0;
//send actual payload to server
while (bytesWritten != m_ShaderCompilerMessage.originalPayload.size())
{
qint64 currentWrite = socket.write(m_ShaderCompilerMessage.originalPayload.data() + bytesWritten,
m_ShaderCompilerMessage.originalPayload.size() - bytesWritten);
if (currentWrite == -1)
{
error = "Connection Lost:Unable to send data";
AZ_TracePrintf(AssetProcessor::DebugChannel, error.toUtf8().data());
QMetaObject::invokeMethod(m_manager, "shaderCompilerError", Qt::QueuedConnection, Q_ARG(QString, error), Q_ARG(QString, QDateTime::currentDateTime().toString()), Q_ARG(QString, QString(m_ShaderCompilerMessage.originalPayload)), Q_ARG(QString, serverAddress));
}
socket.flush();
bytesWritten += currentWrite;
}
}
else
{
error = "Unable to connect to IP Address " + serverAddress;
AZ_TracePrintf(AssetProcessor::DebugChannel, error.toUtf8().data());
QMetaObject::invokeMethod(m_manager, "shaderCompilerError", Qt::QueuedConnection, Q_ARG(QString, error), Q_ARG(QString, QDateTime::currentDateTime().toString()), Q_ARG(QString, QString(m_ShaderCompilerMessage.originalPayload)), Q_ARG(QString, serverAddress));
return false;
}
unsigned int expectedBytes = sizeof(unsigned int) + sizeof(qint8);
unsigned int bytesReadTotal = 0;
unsigned int messageSize = 0;
bool isMessageSizeKnown = false;
//read the entire payload
while ((bytesReadTotal < expectedBytes + messageSize))
{
if (socket.bytesAvailable() == 0)
{
if (!socket.waitForReadyRead(jobCompileMaxTime))
{
error = "Remote IP is taking too long to respond: " + serverAddress;
AZ_TracePrintf(AssetProcessor::DebugChannel, error.toUtf8().data());
QMetaObject::invokeMethod(m_manager, "shaderCompilerError", Qt::QueuedConnection, Q_ARG(QString, error), Q_ARG(QString, QDateTime::currentDateTime().toString()), Q_ARG(QString, QString(m_ShaderCompilerMessage.originalPayload)), Q_ARG(QString, serverAddress));
payload.clear();
return false;
}
}
qint64 bytesAvailable = socket.bytesAvailable();
if (bytesAvailable >= expectedBytes && !isMessageSizeKnown)
{
socket.peek(reinterpret_cast<char*>(&messageSize), sizeof(unsigned int));
payload.resize(expectedBytes + messageSize);
isMessageSizeKnown = true;
}
if (bytesAvailable > 0)
{
qint64 bytesRead = socket.read(payload.data() + bytesReadTotal, bytesAvailable);
if (bytesRead <= 0)
{
error = "Connection closed by remote IP Address " + serverAddress;
AZ_TracePrintf(AssetProcessor::DebugChannel, error.toUtf8().data());
QMetaObject::invokeMethod(m_manager, "shaderCompilerError", Qt::QueuedConnection, Q_ARG(QString, error), Q_ARG(QString, QDateTime::currentDateTime().toString()), Q_ARG(QString, QString(m_ShaderCompilerMessage.originalPayload)), Q_ARG(QString, serverAddress));
payload.clear();
return false;
}
bytesReadTotal = aznumeric_cast<uint32_t>(bytesReadTotal + bytesRead);
}
}
return true; // payload successfully send
}
void ShaderCompilerJob::run()
{
QMetaObject::invokeMethod(m_manager, "jobStarted", Qt::QueuedConnection);
QByteArray payload;
//until server list is empty, keep trying
while (!isServerListEmpty())
{
QString serverAddress = getServerAddress();
//attempt to send payload
if (attemptDelivery(serverAddress, payload))
{
break;
}
}
//we are appending request id at the end of every payload,
//therefore in the case of any errors also
//we will be sending atleast four bytes to the game
payload.append(reinterpret_cast<char*>(&m_ShaderCompilerMessage.requestId), sizeof(unsigned int));
QMetaObject::invokeMethod(m_manager, "OnShaderCompilerJobComplete", Qt::QueuedConnection, Q_ARG(QByteArray, payload), Q_ARG(unsigned int, m_ShaderCompilerMessage.requestId));
QMetaObject::invokeMethod(m_manager, "jobEnded", Qt::QueuedConnection);
}
void ShaderCompilerJob::setIsUnitTesting(bool isUnitTesting)
{
m_isUnitTesting = isUnitTesting;
}
@@ -1,44 +0,0 @@
/*
* 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
*
*/
#ifndef SHADERCOMPILERJOB_H
#define SHADERCOMPILERJOB_H
#include <QRunnable>
#include "shadercompilerMessages.h"
class QByteArray;
class QObject;
/**
* This class is responsible for connecting to the shader compiler server
* and getting back the response to the shader compiler manager
*/
class ShaderCompilerJob
: public QRunnable
{
public:
explicit ShaderCompilerJob();
virtual ~ShaderCompilerJob();
ShaderCompilerRequestMessage ShaderCompilerMessage() const;
void initialize(QObject* pManager, const ShaderCompilerRequestMessage& ShaderCompilerMessage);
QString getServerAddress();
bool isServerListEmpty();
virtual void run() override;
void setIsUnitTesting(bool isUnitTesting);
bool attemptDelivery(QString serverAddress, QByteArray& payload);
private:
ShaderCompilerRequestMessage m_ShaderCompilerMessage;
QObject* m_manager;
bool m_isUnitTesting;
};
#endif // SHADERCOMPILERJOB_H
@@ -18,8 +18,6 @@
#include <QCoreApplication>
AZ_UNIT_TEST_HOOK(new BaseAssetProcessorTestEnvironment)
namespace AssetProcessor
{
class UnitTestAppManager : public BatchApplicationManager
@@ -35,7 +33,7 @@ namespace AssetProcessor
{
return false;
}
// tests which use the builder bus plug in their own mock version, so disconnect ours.
AssetProcessor::AssetBuilderInfoBus::Handler::BusDisconnect();
@@ -59,7 +57,7 @@ namespace AssetProcessor
void SetUp() override
{
AssetProcessorTest::SetUp();
static int numParams = 1;
static char processName[] = {"AssetProcessorBatch"};
static char* namePtr = &processName[0];
@@ -147,7 +145,7 @@ namespace AssetProcessor
time.start();
actualTest->StartTest();
while (!testIsComplete)
{
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
@@ -5,76 +5,8 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "utilities/BatchApplicationManager.h"
#include <AzTest/AzTest.h>
#include <AzTest/Utils.h>
#include <native/tests/BaseAssetProcessorTest.h>
DECLARE_AZ_UNIT_TEST_MAIN()
int RunUnitTests(int argc, char* argv[], bool& ranUnitTests)
{
ranUnitTests = true;
INVOKE_AZ_UNIT_TEST_MAIN(nullptr); // nullptr turns off default test environment used to catch stray asserts
// This looks a bit weird, but the macro returns conditionally, so *if* we get here, it means the unit tests didn't run
ranUnitTests = false;
return 0;
}
int main(int argc, char* argv[])
{
qputenv("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM", "1");
AZ::Debug::Trace::HandleExceptions(true);
AZ::Test::ApplyGlobalParameters(&argc, argv);
// If "--unittest" is present on the command line, run unit testing
// and return immediately. Otherwise, continue as normal.
AZ::Test::addTestEnvironment(new BaseAssetProcessorTestEnvironment());
bool pauseOnComplete = false;
if (AZ::Test::ContainsParameter(argc, argv, "--pause-on-completion"))
{
pauseOnComplete = true;
}
bool ranUnitTests;
int result = RunUnitTests(argc, argv, ranUnitTests);
if (ranUnitTests)
{
if (pauseOnComplete)
{
system("pause");
}
return result;
}
BatchApplicationManager applicationManager(&argc, &argv);
setvbuf(stdout, NULL, _IONBF, 0); // Disabling output buffering to fix test failures due to incomplete logs
ApplicationManager::BeforeRunStatus status = applicationManager.BeforeRun();
if (status != ApplicationManager::BeforeRunStatus::Status_Success)
{
if (status == ApplicationManager::BeforeRunStatus::Status_Restarting)
{
//AssetProcessor will restart
return 0;
}
else
{
//Initialization failed
return 1;
}
}
return applicationManager.Run() ? 0 : 1;
}
AZ_UNIT_TEST_HOOK(new BaseAssetProcessorTestEnvironment)
@@ -29,14 +29,16 @@ namespace AssetProcessor
AssetTreeItem::AssetTreeItem(
AZStd::shared_ptr<AssetTreeItemData> data,
QIcon errorIcon,
QIcon folderIcon,
QIcon fileIcon,
AssetTreeItem* parentItem) :
m_data(data),
m_parent(parentItem),
m_errorIcon(errorIcon), // QIcon is implicitily shared.
m_folderIcon(QIcon(QStringLiteral(":/Gallery/Asset_Folder.svg"))),
m_fileIcon(QIcon(QStringLiteral(":/Gallery/Asset_File.svg")))
m_errorIcon(errorIcon), // QIcon is implicitly shared.
m_folderIcon(folderIcon),
m_fileIcon(fileIcon)
{
m_folderIcon.addFile(QStringLiteral(":/Gallery/Asset_Folder.svg"), QSize(), QIcon::Selected);
}
AssetTreeItem::~AssetTreeItem()
@@ -45,7 +47,7 @@ namespace AssetProcessor
AssetTreeItem* AssetTreeItem::CreateChild(AZStd::shared_ptr<AssetTreeItemData> data)
{
m_childItems.emplace_back(new AssetTreeItem(data, m_errorIcon, this));
m_childItems.emplace_back(new AssetTreeItem(data, m_errorIcon, m_folderIcon, m_fileIcon, this));
return m_childItems.back().get();
}
@@ -50,6 +50,8 @@ namespace AssetProcessor
explicit AssetTreeItem(
AZStd::shared_ptr<AssetTreeItemData> data,
QIcon errorIcon,
QIcon folderIcon,
QIcon fileIcon,
AssetTreeItem* parentItem = nullptr);
virtual ~AssetTreeItem();
@@ -16,9 +16,12 @@ namespace AssetProcessor
AssetTreeModel::AssetTreeModel(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> sharedDbConnection, QObject *parent) :
QAbstractItemModel(parent),
m_sharedDbConnection(sharedDbConnection),
m_errorIcon(QStringLiteral(":/stylesheet/img/logging/error.svg"))
m_sharedDbConnection(sharedDbConnection)
, m_errorIcon(QStringLiteral(":/stylesheet/img/logging/error.svg"))
, m_folderIcon(QIcon(QStringLiteral(":/Gallery/Asset_Folder.svg")))
, m_fileIcon(QIcon(QStringLiteral(":/Gallery/Asset_File.svg")))
{
m_folderIcon.addFile(QStringLiteral(":/Gallery/Asset_Folder.svg"), QSize(), QIcon::Selected);
ApplicationManagerNotifications::Bus::Handler::BusConnect();
AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Handler::BusConnect();
}
@@ -40,7 +43,7 @@ namespace AssetProcessor
void AssetTreeModel::Reset()
{
beginResetModel();
m_root.reset(new AssetTreeItem(AZStd::make_shared<AssetTreeItemData>("", "", true, AZ::Uuid::CreateNull()), m_errorIcon));
m_root.reset(new AssetTreeItem(AZStd::make_shared<AssetTreeItemData>("", "", true, AZ::Uuid::CreateNull()), m_errorIcon, m_folderIcon, m_fileIcon));
ResetModel();
@@ -54,5 +54,7 @@ namespace AssetProcessor
AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> m_sharedDbConnection;
QIcon m_errorIcon;
QIcon m_folderIcon;
QIcon m_fileIcon;
};
}
@@ -37,7 +37,6 @@
#include "../connection/connection.h"
#include "../resourcecompiler/rccontroller.h"
#include "../resourcecompiler/RCJobSortFilterProxyModel.h"
#include "../shadercompiler/shadercompilerModel.h"
#include <QClipboard>
@@ -148,7 +147,6 @@ void MainWindow::Activate()
ui->buttonList->addTab(QStringLiteral("Jobs"));
ui->buttonList->addTab(QStringLiteral("Assets"));
ui->buttonList->addTab(QStringLiteral("Logs"));
ui->buttonList->addTab(QStringLiteral("Shaders"));
ui->buttonList->addTab(QStringLiteral("Connections"));
ui->buttonList->addTab(QStringLiteral("Tools"));
@@ -167,7 +165,7 @@ void MainWindow::Activate()
ui->connectionTreeView->header()->resizeSection(ConnectionManager::PortColumn, 60);
ui->connectionTreeView->header()->resizeSection(ConnectionManager::PlatformColumn, 60);
ui->connectionTreeView->header()->resizeSection(ConnectionManager::AutoConnectColumn, 60);
ui->connectionTreeView->header()->setStretchLastSection(false);
connect(ui->connectionTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::OnConnectionSelectionChanged);
@@ -191,12 +189,12 @@ void MainWindow::Activate()
ui->allowListAllowedListConnectionsListView->setModel(&m_allowedListAddresses);
connect(ui->allowedListRejectedConnectionsListView, &QListView::clicked, this, &MainWindow::OnRejectedConnectionsListViewClicked);
ui->allowedListRejectedConnectionsListView->setModel(&m_rejectedAddresses);
connect(ui->allowedListEnableCheckBox, &QCheckBox::toggled, this, &MainWindow::OnAllowedListCheckBoxToggled);
connect(ui->allowedListAddHostNameToolButton, &QToolButton::clicked, this, &MainWindow::OnAddHostNameAllowedListButtonClicked);
connect(ui->allowedListAddIPToolButton, &QPushButton::clicked, this, &MainWindow::OnAddIPAllowedListButtonClicked);
connect(ui->allowedListToAllowedListToolButton, &QPushButton::clicked, this, &MainWindow::OnToAllowedListButtonClicked);
connect(ui->allowedListToRejectedListToolButton, &QToolButton::clicked, this, &MainWindow::OnToRejectedListButtonClicked);
@@ -206,7 +204,7 @@ void MainWindow::Activate()
QRegExpValidator* hostNameValidator = new QRegExpValidator(validHostName, this);
ui->allowedListAddHostNameLineEdit->setValidator(hostNameValidator);
QRegExpValidator* ipValidator = new QRegExpValidator(validIP, this);
ui->allowedListAddIPLineEdit->setValidator(ipValidator);
@@ -237,7 +235,7 @@ void MainWindow::Activate()
m_logSortFilterProxy->setSourceModel(m_logsModel);
m_logSortFilterProxy->setFilterKeyColumn(AzToolsFramework::Logging::LogTableModel::ColumnMessage);
m_logSortFilterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
ui->jobLogTableView->setModel(m_logSortFilterProxy);
ui->jobLogTableView->setItemDelegate(new AzToolsFramework::Logging::LogTableItemDelegate(ui->jobLogTableView));
ui->jobLogTableView->setExpandOnSelection();
@@ -317,14 +315,6 @@ void MainWindow::Activate()
connect(ui->jobFilteredSearchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged,
this, writeJobFilterSettings);
//Shader view
ui->shaderTreeView->setModel(m_guiApplicationManager->GetShaderCompilerModel());
ui->shaderTreeView->header()->resizeSection(ShaderCompilerModel::ColumnTimeStamp, 80);
ui->shaderTreeView->header()->resizeSection(ShaderCompilerModel::ColumnServer, 40);
ui->shaderTreeView->header()->resizeSection(ShaderCompilerModel::ColumnError, 220);
ui->shaderTreeView->header()->setSectionResizeMode(ShaderCompilerModel::ColumnError, QHeaderView::Stretch);
ui->shaderTreeView->header()->setStretchLastSection(false);
// Asset view
m_sourceAssetTreeFilterModel = new AssetProcessor::AssetTreeFilterModel(this);
m_sourceModel = new AssetProcessor::SourceAssetTreeModel(m_sharedDbConnection, this);
@@ -410,7 +400,7 @@ void MainWindow::Activate()
bool zeroAnalysisModeFromSettings = settings.value("EnableZeroAnalysis", QVariant(true)).toBool();
settings.endGroup();
QObject::connect(ui->modtimeSkippingCheckBox, &QCheckBox::stateChanged, this,
QObject::connect(ui->modtimeSkippingCheckBox, &QCheckBox::stateChanged, this,
[this](int newCheckState)
{
bool newOption = newCheckState == Qt::Checked ? true : false;
@@ -553,7 +543,7 @@ void MainWindow::OnAddConnection(bool /*checked*/)
m_guiApplicationManager->GetConnectionManager()->addUserConnection();
}
void MainWindow::OnAllowedListConnectionsListViewClicked()
void MainWindow::OnAllowedListConnectionsListViewClicked()
{
ui->allowedListRejectedConnectionsListView->clearSelection();
}
@@ -563,7 +553,7 @@ void MainWindow::OnRejectedConnectionsListViewClicked()
ui->allowListAllowedListConnectionsListView->clearSelection();
}
void MainWindow::OnAllowedListCheckBoxToggled()
void MainWindow::OnAllowedListCheckBoxToggled()
{
if (!ui->allowedListEnableCheckBox->isChecked())
{
@@ -598,7 +588,7 @@ void MainWindow::OnAllowedListCheckBoxToggled()
ui->allowedListToAllowedListToolButton->setEnabled(true);
ui->allowedListToRejectedListToolButton->setEnabled(true);
}
m_guiApplicationManager->GetConnectionManager()->AllowedListingEnabled(ui->allowedListEnableCheckBox->isChecked());
}
@@ -868,7 +858,7 @@ void MainWindow::OnAssetProcessorStatusChanged(const AssetProcessor::AssetProces
text = tr("Working, analyzing jobs remaining %1, processing jobs remaining %2...").arg(m_createJobCount).arg(m_processJobsCount);
ui->timerContainerWidget->setVisible(false);
ui->productAssetDetailsPanel->SetScanQueueEnabled(false);
IntervalAssetTabFilterRefresh();
}
else
@@ -887,7 +877,7 @@ void MainWindow::OnAssetProcessorStatusChanged(const AssetProcessor::AssetProces
break;
case AssetProcessorStatus::Processing_Jobs:
CheckStartProcessTimers();
m_processJobsCount = entry.m_count;
m_processJobsCount = entry.m_count;
if (m_processJobsCount + m_createJobCount > 0)
{
@@ -993,7 +983,7 @@ void MainWindow::ApplyConfig()
ui->jobLogTableView->header()->resizeSection(AzToolsFramework::Logging::LogTableModel::ColumnType, m_config.logTypeColumnWidth);
}
MainWindow::LogSortFilterProxy::LogSortFilterProxy(QObject* parentOjbect) : QSortFilterProxyModel(parentOjbect)
MainWindow::LogSortFilterProxy::LogSortFilterProxy(QObject* parentOjbect) : QSortFilterProxyModel(parentOjbect)
{
}
@@ -1312,7 +1302,7 @@ void MainWindow::ShowJobViewContextMenu(const QPoint& pos)
ui->sourceAssetDetailsPanel->GoToSource(item->m_elementId.GetInputAssetName().toUtf8().constData());
});
QString productMenuTitle(tr("View product asset..."));
QString productMenuTitle(tr("View product asset..."));
if (item->m_jobState != AzToolsFramework::AssetSystem::JobStatus::Completed)
{
QString disabledActionTooltip(tr("Only completed jobs are available in the Assets tab."));
@@ -1620,7 +1610,7 @@ void MainWindow::ShowProductAssetContextMenu(const QPoint& pos)
{
AzQtComponents::ShowFileOnDesktop(pathToProduct.GetValue());
}
});
QString fileOrFolder(cachedAsset->getChildCount() > 0 ? tr("folder") : tr("file"));
@@ -65,7 +65,6 @@ public:
Jobs,
Assets,
Logs,
Shaders,
Connections,
Tools
};
@@ -763,59 +763,6 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="shaderDialog">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="shaderLabel">
<property name="text">
<string>Shaders</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="shaderInfoPane" native="true">
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="AzQtComponents::TableView" name="shaderTreeView"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="connectionsDialog">
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="spacing">
@@ -24,14 +24,12 @@ void FileWatcherUnitTestRunner::StartTest()
FileWatcher fileWatcher;
FolderWatchCallbackEx folderWatch(tempPath, "", true);
fileWatcher.AddFolderWatch(&folderWatch);
fileWatcher.AddFolderWatch(tempPath);
fileWatcher.StartWatching();
{ // test a single file create/write
bool foundFile = false;
auto connection = QObject::connect(&folderWatch, &FolderWatchCallbackEx::fileAdded, this, [&](QString filename)
auto connection = QObject::connect(&fileWatcher, &FileWatcher::fileAdded, this, [&](QString filename)
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Single file test Found asset: %s.\n", filename.toUtf8().data());
foundFile = true;
@@ -66,7 +64,7 @@ void FileWatcherUnitTestRunner::StartTest()
const unsigned long maxFiles = 10000;
QSet<QString> outstandingFiles;
auto connection = QObject::connect(&folderWatch, &FolderWatchCallbackEx::fileAdded, this, [&](QString filename)
auto connection = QObject::connect(&fileWatcher, &FileWatcher::fileAdded, this, [&](QString filename)
{
outstandingFiles.remove(filename);
});
@@ -122,7 +120,7 @@ void FileWatcherUnitTestRunner::StartTest()
{ // test deletion
bool foundFile = false;
auto connection = QObject::connect(&folderWatch, &FolderWatchCallbackEx::fileRemoved, this, [&](QString filename)
auto connection = QObject::connect(&fileWatcher, &FileWatcher::fileRemoved, this, [&](QString filename)
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Deleted asset: %s...\n", filename.toUtf8().data());
foundFile = true;
@@ -155,7 +153,7 @@ void FileWatcherUnitTestRunner::StartTest()
{
bool fileAddCalled = false;
QString fileAddName;
auto connectionAdd = QObject::connect(&folderWatch, &FolderWatchCallbackEx::fileAdded, this, [&](QString filename)
auto connectionAdd = QObject::connect(&fileWatcher, &FileWatcher::fileAdded, this, [&](QString filename)
{
fileAddCalled = true;
fileAddName = filename;
@@ -163,7 +161,7 @@ void FileWatcherUnitTestRunner::StartTest()
bool fileRemoveCalled = false;
QString fileRemoveName;
auto connectionRemove = QObject::connect(&folderWatch, &FolderWatchCallbackEx::fileRemoved, this, [&](QString filename)
auto connectionRemove = QObject::connect(&fileWatcher, &FileWatcher::fileRemoved, this, [&](QString filename)
{
fileRemoveCalled = true;
fileRemoveName = filename;
@@ -171,7 +169,7 @@ void FileWatcherUnitTestRunner::StartTest()
QStringList fileModifiedNames;
bool fileModifiedCalled = false;
auto connectionModified = QObject::connect(&folderWatch, &FolderWatchCallbackEx::fileModified, this, [&](QString filename)
auto connectionModified = QObject::connect(&fileWatcher, &FileWatcher::fileModified, this, [&](QString filename)
{
fileModifiedCalled = true;
fileModifiedNames.append(filename);
@@ -1,188 +0,0 @@
/*
* 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
*
*/
#include "ShaderCompilerUnitTests.h"
#include "native/connection/connectionManager.h"
#include "native/connection/connection.h"
#include "native/utilities/assetUtils.h"
#define UNIT_TEST_CONNECT_PORT 12125
ShaderCompilerUnitTest::ShaderCompilerUnitTest()
{
m_connectionManager = ConnectionManager::Get();
connect(this, SIGNAL(StartUnitTestForGoodShaderCompiler()), this, SLOT(UnitTestForGoodShaderCompiler()));
connect(this, SIGNAL(StartUnitTestForFirstBadShaderCompiler()), this, SLOT(UnitTestForFirstBadShaderCompiler()));
connect(this, SIGNAL(StartUnitTestForSecondBadShaderCompiler()), this, SLOT(UnitTestForSecondBadShaderCompiler()));
connect(this, SIGNAL(StartUnitTestForThirdBadShaderCompiler()), this, SLOT(UnitTestForThirdBadShaderCompiler()));
connect(&m_shaderCompilerManager, SIGNAL(sendErrorMessageFromShaderJob(QString, QString, QString, QString)), this, SLOT(ReceiveShaderCompilerErrorMessage(QString, QString, QString, QString)));
m_shaderCompilerManager.setIsUnitTesting(true);
m_connectionManager->RegisterService(AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), AZStd::bind(&ShaderCompilerManager::process, &m_shaderCompilerManager, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4));
ContructPayloadForShaderCompilerServer(m_testPayload);
}
ShaderCompilerUnitTest::~ShaderCompilerUnitTest()
{
m_connectionManager->removeConnection(m_connectionId);
}
void ShaderCompilerUnitTest::ContructPayloadForShaderCompilerServer(QByteArray& payload)
{
QString testString = "This is a test string";
QString testServerList = "127.0.0.3,198.51.100.0,127.0.0.1"; // note - 198.51.100.0 is in the 'test' range that will never be assigned to anyone.
unsigned int testServerListLength = static_cast<unsigned int>(testServerList.size());
unsigned short testServerPort = 12348;
unsigned int testRequestId = 1;
qint64 testStringLength = static_cast<qint64>(testString.size());
payload.resize(static_cast<unsigned int>(testStringLength));
memcpy(payload.data(), (testString.toStdString().c_str()), testStringLength);
unsigned int payloadSize = payload.size();
payload.resize(payloadSize + 1 + static_cast<unsigned int>(testServerListLength) + 1 + sizeof(unsigned short) + sizeof(unsigned int) + sizeof(unsigned int));
char* dataStart = payload.data() + payloadSize;
*dataStart = 0;// null
memcpy(payload.data() + payloadSize + 1, (testServerList.toStdString().c_str()), testServerListLength);
dataStart += 1 + testServerListLength;
*dataStart = 0; //null
memcpy(payload.data() + payloadSize + 1 + testServerListLength + 1, reinterpret_cast<char*>(&testServerPort), sizeof(unsigned short));
memcpy(payload.data() + payloadSize + 1 + testServerListLength + 1 + sizeof(unsigned short), reinterpret_cast<char*>(&testServerListLength), sizeof(unsigned int));
memcpy(payload.data() + payloadSize + 1 + testServerListLength + 1 + sizeof(unsigned short) + sizeof(unsigned int), reinterpret_cast<char*>(&testRequestId), sizeof(unsigned int));
}
void ShaderCompilerUnitTest::StartTest()
{
m_connectionId = m_connectionManager->addConnection();
Connection* connection = m_connectionManager->getConnection(m_connectionId);
connection->SetPort(UNIT_TEST_CONNECT_PORT);
connection->SetIpAddress("127.0.0.1");
connection->SetAutoConnect(true);
UnitTestForGoodShaderCompiler();
}
int ShaderCompilerUnitTest::UnitTestPriority() const
{
return -4;
}
void ShaderCompilerUnitTest::UnitTestForGoodShaderCompiler()
{
AZ_TracePrintf("ShaderCompilerUnitTest", " ... Starting test of 'good' shader compiler...\n");
m_shaderCompilerManager.m_sendResponseCallbackFn = AZStd::bind(&ShaderCompilerUnitTest::VerifyPayloadForGoodShaderCompiler, this , AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4);
m_server.Init("127.0.0.1", 12348);
m_server.setServerStatus(UnitTestShaderCompilerServer::GoodServer);
m_connectionManager->SendMessageToService(m_connectionId, AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), 0, m_testPayload);
}
void ShaderCompilerUnitTest::UnitTestForFirstBadShaderCompiler()
{
AZ_TracePrintf("ShaderCompilerUnitTest", " ... Starting test of 'bad' shader compiler... (Incomplete Payload)\n");
m_shaderCompilerManager.m_sendResponseCallbackFn = AZStd::bind(&ShaderCompilerUnitTest::VerifyPayloadForFirstBadShaderCompiler, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4);
m_server.setServerStatus(UnitTestShaderCompilerServer::BadServer_SendsIncompletePayload);
m_connectionManager->SendMessageToService(m_connectionId, AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), 0, m_testPayload);
}
void ShaderCompilerUnitTest::UnitTestForSecondBadShaderCompiler()
{
AZ_TracePrintf("ShaderCompilerUnitTest", " ... Starting test of 'bad' shader compiler... (Payload followed by disconnection)\n");
m_shaderCompilerManager.m_sendResponseCallbackFn = AZStd::bind(&ShaderCompilerUnitTest::VerifyPayloadForSecondBadShaderCompiler, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4);
m_server.setServerStatus(UnitTestShaderCompilerServer::BadServer_ReadsPayloadAndDisconnect);
m_connectionManager->SendMessageToService(m_connectionId, AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), 0, m_testPayload);
}
void ShaderCompilerUnitTest::UnitTestForThirdBadShaderCompiler()
{
AZ_TracePrintf("ShaderCompilerUnitTest", " ... Starting test of 'bad' shader compiler... (Connect but disconnect without data)\n");
m_shaderCompilerManager.m_sendResponseCallbackFn = AZStd::bind(&ShaderCompilerUnitTest::VerifyPayloadForThirdBadShaderCompiler, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4);
m_server.setServerStatus(UnitTestShaderCompilerServer::BadServer_DisconnectAfterConnect);
m_server.startServer();
m_connectionManager->SendMessageToService(m_connectionId, AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), 0, m_testPayload);
}
void ShaderCompilerUnitTest::VerifyPayloadForGoodShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload)
{
(void) connId;
(void) type;
(void) serial;
m_shaderCompilerManager.m_sendResponseCallbackFn = nullptr;
unsigned int messageSize;
quint8 status;
QByteArray payloadToCheck;
unsigned int requestId;
memcpy((&messageSize), payload.data(), sizeof(unsigned int));
memcpy((&status), payload.data() + sizeof(unsigned int), sizeof(unsigned char));
payloadToCheck.resize(messageSize);
memcpy((payloadToCheck.data()), payload.data() + sizeof(unsigned int) + sizeof(unsigned char), messageSize);
memcpy((&requestId), payload.data() + sizeof(unsigned int) + sizeof(unsigned char) + messageSize, sizeof(unsigned int));
QString outgoingTestString = "Test string validated";
if (QString::compare(QString(payloadToCheck), outgoingTestString, Qt::CaseSensitive) != 0)
{
Q_EMIT UnitTestFailed("Unit Test for Good Shader Compiler Failed");
return;
}
Q_EMIT StartUnitTestForFirstBadShaderCompiler();
}
void ShaderCompilerUnitTest::VerifyPayloadForFirstBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload)
{
(void) connId;
(void) type;
(void) serial;
m_shaderCompilerManager.m_sendResponseCallbackFn = nullptr;
QString error = "Remote IP is taking too long to respond: 127.0.0.1";
if ((payload.size() != 4) || (QString::compare(m_lastShaderCompilerErrorMessage, error, Qt::CaseSensitive) != 0))
{
Q_EMIT UnitTestFailed("Unit Test for First Bad Shader Compiler Failed");
return;
}
m_lastShaderCompilerErrorMessage.clear();
Q_EMIT StartUnitTestForSecondBadShaderCompiler();
}
void ShaderCompilerUnitTest::VerifyPayloadForSecondBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload)
{
(void) connId;
(void) type;
(void) serial;
m_shaderCompilerManager.m_sendResponseCallbackFn = nullptr;
QString error = "Remote IP is taking too long to respond: 127.0.0.1";
if ((payload.size() != 4) || (QString::compare(m_lastShaderCompilerErrorMessage, error, Qt::CaseSensitive) != 0))
{
Q_EMIT UnitTestFailed("Unit Test for Second Bad Shader Compiler Failed");
return;
}
m_lastShaderCompilerErrorMessage.clear();
Q_EMIT StartUnitTestForThirdBadShaderCompiler();
}
void ShaderCompilerUnitTest::VerifyPayloadForThirdBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload)
{
(void) connId;
(void) type;
(void) serial;
m_shaderCompilerManager.m_sendResponseCallbackFn = nullptr;
QString error = "Remote IP is taking too long to respond: 127.0.0.1";
if ((payload.size() != 4) || (QString::compare(m_lastShaderCompilerErrorMessage, error, Qt::CaseSensitive) != 0))
{
Q_EMIT UnitTestFailed("Unit Test for Third Bad Shader Compiler Failed");
return;
}
m_lastShaderCompilerErrorMessage.clear();
Q_EMIT UnitTestPassed();
}
void ShaderCompilerUnitTest::ReceiveShaderCompilerErrorMessage(QString error, QString server, QString timestamp, QString payload)
{
(void) server;
(void) timestamp;
(void) payload;
m_lastShaderCompilerErrorMessage = error;
}
REGISTER_UNIT_TEST(ShaderCompilerUnitTest)
@@ -1,81 +0,0 @@
/*
* 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
*
*/
#ifndef SHADERCOMPILERUNITTEST_H
#define SHADERCOMPILERUNITTEST_H
#if !defined(Q_MOC_RUN)
#include "UnitTestRunner.h"
#include <AzCore/std/functional.h>
#include "native/shadercompiler/shadercompilerManager.h"
//#include "native/shadercompiler/shadercompilerMessages.h"
#include "native/utilities/UnitTestShaderCompilerServer.h"
#include <QString>
#include <QByteArray>
#endif
class ConnectionManager;
class ShaderCompilerManagerForUnitTest : public ShaderCompilerManager
{
public:
explicit ShaderCompilerManagerForUnitTest(QObject* parent = 0) : ShaderCompilerManager(parent) {};
// for this test, we override sendResponse and make it so that it just calls a callback instead of actually sending it to the connection manager.
void sendResponse(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload) override
{
if (m_sendResponseCallbackFn)
{
m_sendResponseCallbackFn(connId, type, serial, payload);
}
}
AZStd::function<void(unsigned int, unsigned int, unsigned int, QByteArray)> m_sendResponseCallbackFn;
};
class ShaderCompilerUnitTest
: public UnitTestRun
{
Q_OBJECT
public:
ShaderCompilerUnitTest();
~ShaderCompilerUnitTest();
virtual void StartTest() override;
virtual int UnitTestPriority() const override;
void ContructPayloadForShaderCompilerServer(QByteArray& payload);
Q_SIGNALS:
void StartUnitTestForGoodShaderCompiler();
void StartUnitTestForFirstBadShaderCompiler();
void StartUnitTestForSecondBadShaderCompiler();
void StartUnitTestForThirdBadShaderCompiler();
public Q_SLOTS:
void UnitTestForGoodShaderCompiler();
void UnitTestForFirstBadShaderCompiler();
void UnitTestForSecondBadShaderCompiler();
void UnitTestForThirdBadShaderCompiler();
void VerifyPayloadForGoodShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void VerifyPayloadForFirstBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void VerifyPayloadForSecondBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void VerifyPayloadForThirdBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ReceiveShaderCompilerErrorMessage(QString error, QString server, QString timestamp, QString payload);
private:
UnitTestShaderCompilerServer m_server;
ShaderCompilerManagerForUnitTest m_shaderCompilerManager;
ConnectionManager* m_connectionManager;
QByteArray m_testPayload;
QString m_lastShaderCompilerErrorMessage;
unsigned int m_connectionId = 0;
};
#endif // SHADERCOMPILERUNITTEST_H
@@ -231,44 +231,6 @@ bool ApplicationManager::InitiatedShutdown() const
return m_duringShutdown;
}
void ApplicationManager::GetExternalBuilderFileList(QStringList& externalBuilderModules)
{
externalBuilderModules.clear();
static const char* builder_folder_name = "Builders";
// LY_ASSET_BUILDERS is defined by the CMakeLists.txt. The asset builders add themselves to a variable that
// is populated to allow selective building of those asset builder targets.
// This allows left over Asset builders in the output directory to not be loaded by the AssetProcessor
#if !defined(LY_ASSET_BUILDERS)
#error LY_ASSET_BUILDERS was not defined for ApplicationManager.cpp
#endif
QDir builderDir = QDir::toNativeSeparators(QString(this->m_frameworkApp.GetExecutableFolder()));
builderDir.cd(QString(builder_folder_name));
if (builderDir.exists())
{
AZStd::vector<AZStd::string> tokens;
AZ::StringFunc::Tokenize(AZStd::string_view(LY_ASSET_BUILDERS), tokens, ',');
AZStd::string builderLibrary;
for (const AZStd::string& token : tokens)
{
QString assetBuilderPath(token.c_str());
if (builderDir.exists(assetBuilderPath))
{
externalBuilderModules.push_back(builderDir.absoluteFilePath(assetBuilderPath));
}
}
}
if (externalBuilderModules.empty())
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "AssetProcessor was unable to locate any external builders\n");
}
}
QDir ApplicationManager::GetSystemRoot() const
{
return m_systemRoot;
@@ -459,15 +421,6 @@ void ApplicationManager::PopulateApplicationDependencies()
m_filesOfInterest.push_back(dir.absoluteFilePath(pathWithPlatformExtension));
}
// Get the external builder modules to add to the files of interest
QStringList builderModuleFileList;
GetExternalBuilderFileList(builderModuleFileList);
for (const QString& builderModuleFile : builderModuleFileList)
{
m_filesOfInterest.push_back(builderModuleFile);
}
QDir assetRoot;
AssetUtilities::ComputeAssetRoot(assetRoot);
@@ -21,7 +21,6 @@
#include "native/assetprocessor.h"
#endif
class FolderWatchCallbackEx;
class QCoreApplication;
namespace AZ
@@ -139,7 +138,7 @@ protected:
void RegisterObjectForQuit(QObject* source, bool insertInFront = false);
bool NeedRestart() const;
void addRunningThread(AssetProcessor::ThreadWorker* thread);
template<class BuilderClass>
void RegisterInternalBuilder(const QString& builderName);
@@ -151,9 +150,6 @@ protected:
bool m_duringStartup = true;
AssetProcessorAZApplication m_frameworkApp;
QCoreApplication* m_qApp = nullptr;
//! Get the list of external builder files for this asset processor
void GetExternalBuilderFileList(QStringList& externalBuilderModules);
virtual void Reflect() = 0;
virtual const char* GetLogBaseName() = 0;
@@ -25,6 +25,7 @@
#include <native/InternalBuilders/SettingsRegistryBuilder.h>
#include <AzToolsFramework/Application/Ticker.h>
#include <AzToolsFramework/ToolsFileUtils/ToolsFileUtils.h>
#include <AssetBuilder/AssetBuilderStatic.h>
#include <iostream>
@@ -32,9 +33,6 @@
#include <QElapsedTimer>
//! Amount of time to wait between checking the status of the AssetBuilder process
static const int s_MaximumSleepTimeMS = 10;
//! CreateJobs will wait up to 2 minutes before timing out
//! This shouldn't need to be so high but very large slices can take a while to process currently
//! This should be reduced down to something more reasonable after slice jobs are sped up
@@ -64,6 +62,7 @@ ApplicationManagerBase::~ApplicationManagerBase()
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
AssetProcessor::AssetBuilderRegistrationBus::Handler::BusDisconnect();
AssetBuilderSDK::AssetBuilderBus::Handler::BusDisconnect();
AssetProcessor::AssetBuilderInfoBus::Handler::BusDisconnect();
if (m_settingsRegistryBuilder)
{
@@ -192,7 +191,7 @@ void ApplicationManagerBase::InitAssetProcessorManager()
{
m_assetProcessorManager->SetEnableModtimeSkippingFeature(true);
}
if (commandLine->HasSwitch(Command_enableQueryLogging.m_switch))
{
m_assetProcessorManager->SetQueryLogging(true);
@@ -206,7 +205,7 @@ void ApplicationManagerBase::InitAssetProcessorManager()
{
m_dependencyScanPattern = commandLine->GetSwitchValue(Command_dsp.m_switch, 0).c_str();
}
m_fileDependencyScanPattern = "*";
if (commandLine->HasSwitch(Command_fileDependencyScanPattern.m_switch))
@@ -327,7 +326,7 @@ void ApplicationManagerBase::InitAssetCatalog()
AssetProcessor::AssetCatalog* catalog = new AssetCatalog(assetCatalogHelper, m_platformConfiguration);
// Using a direct connection so we know the catalog has been updated before continuing on with code might depend on the asset being in the catalog
connect(m_assetProcessorManager, &AssetProcessorManager::AssetMessage, catalog, &AssetCatalog::OnAssetMessage, Qt::DirectConnection);
connect(m_assetProcessorManager, &AssetProcessorManager::AssetMessage, catalog, &AssetCatalog::OnAssetMessage, Qt::DirectConnection);
connect(m_assetProcessorManager, &AssetProcessorManager::SourceQueued, catalog, &AssetCatalog::OnSourceQueued);
connect(m_assetProcessorManager, &AssetProcessorManager::SourceFinished, catalog, &AssetCatalog::OnSourceFinished);
connect(m_assetProcessorManager, &AssetProcessorManager::PathDependencyResolved, catalog, &AssetCatalog::OnDependencyResolved);
@@ -379,12 +378,12 @@ void ApplicationManagerBase::InitAssetScanner()
QObject::connect(m_assetScanner, &AssetScanner::FilesFound, [this](QSet<AssetFileInfo> files) { m_fileStateCache->AddInfoSet(files); });
QObject::connect(m_assetScanner, &AssetScanner::FoldersFound, [this](QSet<AssetFileInfo> files) { m_fileStateCache->AddInfoSet(files); });
QObject::connect(m_assetScanner, &AssetScanner::ExcludedFound, [this](QSet<AssetFileInfo> files) { m_fileStateCache->AddInfoSet(files); });
// file table
QObject::connect(m_assetScanner, &AssetScanner::AssetScanningStatusChanged, m_fileProcessor.get(), &FileProcessor::OnAssetScannerStatusChange);
QObject::connect(m_assetScanner, &AssetScanner::FilesFound, m_fileProcessor.get(), &FileProcessor::AssessFilesFromScanner);
QObject::connect(m_assetScanner, &AssetScanner::FoldersFound, m_fileProcessor.get(), &FileProcessor::AssessFoldersFromScanner);
}
void ApplicationManagerBase::DestroyAssetScanner()
@@ -434,63 +433,77 @@ void ApplicationManagerBase::DestroyPlatformConfiguration()
void ApplicationManagerBase::InitFileMonitor()
{
m_folderWatches.reserve(m_platformConfiguration->GetScanFolderCount());
m_watchHandles.reserve(m_platformConfiguration->GetScanFolderCount());
for (int folderIdx = 0; folderIdx < m_platformConfiguration->GetScanFolderCount(); ++folderIdx)
{
const AssetProcessor::ScanFolderInfo& info = m_platformConfiguration->GetScanFolderAt(folderIdx);
FolderWatchCallbackEx* newFolderWatch = new FolderWatchCallbackEx(info.ScanPath(), "", info.RecurseSubFolders());
// hook folder watcher to assess files on add/modify
// relevant files will be sent to resource compiler
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded,
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::AssessAddedFile);
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileModified,
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::AssessModifiedFile);
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved,
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::AssessDeletedFile);
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded, [this](QString path) { m_fileStateCache->AddFile(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileModified, [this](QString path) { m_fileStateCache->UpdateFile(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved, [this](QString path) { m_fileStateCache->RemoveFile(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded, [](QString path) { AZ::Interface<AssetProcessor::ExcludedFolderCacheInterface>::Get()->FileAdded(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded,
m_fileProcessor.get(), &AssetProcessor::FileProcessor::AssessAddedFile);
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved,
m_fileProcessor.get(), &AssetProcessor::FileProcessor::AssessDeletedFile);
m_folderWatches.push_back(AZStd::unique_ptr<FolderWatchCallbackEx>(newFolderWatch));
m_watchHandles.push_back(m_fileWatcher.AddFolderWatch(newFolderWatch));
m_fileWatcher.AddFolderWatch(info.ScanPath(), info.RecurseSubFolders());
}
// also hookup monitoring for the cache (output directory)
QDir cacheRoot;
if (AssetUtilities::ComputeProjectCacheRoot(cacheRoot))
{
FolderWatchCallbackEx* newFolderWatch = new FolderWatchCallbackEx(cacheRoot.absolutePath(), "", true);
m_fileWatcher.AddFolderWatch(cacheRoot.absolutePath(), true);
}
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded, [this](QString path) { m_fileStateCache->AddFile(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileModified, [this](QString path) { m_fileStateCache->UpdateFile(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved, [this](QString path) { m_fileStateCache->RemoveFile(path); });
if (m_platformConfiguration->GetScanFolderCount() || !cacheRoot.path().isEmpty())
{
const auto cachePath = QDir::toNativeSeparators(cacheRoot.absolutePath());
// we only care about cache root deletions.
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved,
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::AssessDeletedFile);
const auto OnFileAdded = [this, cachePath](QString path)
{
const bool isCacheRoot = path.startsWith(cachePath);
if (isCacheRoot)
{
m_fileStateCache->AddFile(path);
}
else
{
m_assetProcessorManager->AssessAddedFile(path);
m_fileStateCache->AddFile(path);
AZ::Interface<AssetProcessor::ExcludedFolderCacheInterface>::Get()->FileAdded(path);
m_fileProcessor->AssetProcessor::FileProcessor::AssessAddedFile(path);
}
};
m_folderWatches.push_back(AZStd::unique_ptr<FolderWatchCallbackEx>(newFolderWatch));
m_watchHandles.push_back(m_fileWatcher.AddFolderWatch(newFolderWatch));
const auto OnFileModified = [this, cachePath](QString path)
{
const bool isCacheRoot = path.startsWith(cachePath);
if (isCacheRoot)
{
m_assetProcessorManager->AssessModifiedFile(path);
}
else
{
m_assetProcessorManager->AssessModifiedFile(path);
m_fileStateCache->UpdateFile(path);
}
};
const auto OnFileRemoved = [this, cachePath](QString path)
{
const bool isCacheRoot = path.startsWith(cachePath);
if (isCacheRoot)
{
m_fileStateCache->RemoveFile(path);
m_assetProcessorManager->AssessDeletedFile(path);
}
else
{
m_assetProcessorManager->AssessDeletedFile(path);
m_fileStateCache->RemoveFile(path);
m_fileProcessor->AssessDeletedFile(path);
}
};
connect(&m_fileWatcher, &FileWatcher::fileAdded, OnFileAdded);
connect(&m_fileWatcher, &FileWatcher::fileModified, OnFileModified);
connect(&m_fileWatcher, &FileWatcher::fileRemoved, OnFileRemoved);
}
}
void ApplicationManagerBase::DestroyFileMonitor()
{
for (int watchHandle : m_watchHandles)
{
m_fileWatcher.RemoveFolderWatch(watchHandle);
}
m_folderWatches.resize(0);
m_fileWatcher.ClearFolderWatches();
}
void ApplicationManagerBase::DestroyApplicationServer()
@@ -591,6 +604,51 @@ void ApplicationManagerBase::InitConnectionManager()
}, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4)
);
m_connectionManager->RegisterService(
AssetBuilder::BuilderRegistrationRequest::MessageType,
[this](unsigned int /*connId*/, unsigned int /*type*/, unsigned int /*serial*/, QByteArray payload, QString)
{
AssetBuilder::BuilderRegistrationRequest registrationRequest;
if (m_builderRegistrationComplete)
{
return;
}
m_builderRegistrationComplete = true;
if (AssetProcessor::UnpackMessage(payload, registrationRequest))
{
for (const auto& builder : registrationRequest.m_builders)
{
AssetBuilderSDK::AssetBuilderDesc desc;
desc.m_name = builder.m_name;
desc.m_patterns = builder.m_patterns;
desc.m_version = builder.m_version;
desc.m_analysisFingerprint = builder.m_analysisFingerprint;
desc.m_flags = builder.m_flags;
desc.m_busId = builder.m_busId;
desc.m_flagsByJobKey = builder.m_flagsByJobKey;
desc.m_productsToKeepOnFailure = builder.m_productsToKeepOnFailure;
// Builders registered this way are always external builders
desc.m_builderType = AssetBuilderSDK::AssetBuilderDesc::AssetBuilderType::External;
RegisterBuilderInformation(desc);
}
QTimer::singleShot(
0, this,
[this]()
{
if (!PostActivate())
{
QuitRequested();
}
});
}
});
//You can get Asset Processor Current State
using AzFramework::AssetSystem::RequestAssetProcessorStatus;
auto GetState = [this](unsigned int connId, unsigned int, unsigned int serial, QByteArray payload, QString)
@@ -633,11 +691,11 @@ void ApplicationManagerBase::InitConnectionManager()
AssetProcessorPlatformStatusRequest requestMessage;
if (AssetProcessor::UnpackMessage(payload, requestMessage))
{
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(responseMessage.m_isPlatformEnabled,
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(responseMessage.m_isPlatformEnabled,
&AzToolsFramework::AssetSystemRequestBus::Events::IsAssetPlatformEnabled, requestMessage.m_platform.c_str());
}
AssetProcessor::ConnectionBus::Event(connId,
AssetProcessor::ConnectionBus::Event(connId,
&AssetProcessor::ConnectionBus::Events::SendResponse, serial, responseMessage);
});
@@ -653,11 +711,11 @@ void ApplicationManagerBase::InitConnectionManager()
if (AssetProcessor::UnpackMessage(payload, requestMessage))
{
const char* platformIdentifier = requestMessage.m_platform.c_str();
responseMessage.m_numberOfPendingJobs =
responseMessage.m_numberOfPendingJobs =
GetRCController()->NumberOfPendingJobsPerPlatform(platformIdentifier);
}
AssetProcessor::ConnectionBus::Event(connId,
AssetProcessor::ConnectionBus::Event(connId,
&AssetProcessor::ConnectionBus::Events::SendResponse, serial, responseMessage);
});
}
@@ -696,7 +754,7 @@ void ApplicationManagerBase::InitAssetRequestHandler(AssetProcessor::AssetReques
QObject::connect(GetAssetProcessorManager(), &AssetProcessorManager::SendAssetExistsResponse, m_assetRequestHandler, &AssetRequestHandler::OnRequestAssetExistsResponse);
QObject::connect(GetAssetProcessorManager(), &AssetProcessorManager::FenceFileDetected, m_assetRequestHandler, &AssetRequestHandler::OnFenceFileDetected);
// connect the Asset Request Handler to RC:
QObject::connect(m_assetRequestHandler, &AssetRequestHandler::RequestCompileGroup, GetRCController(), &RCController::OnRequestCompileGroup);
QObject::connect(m_assetRequestHandler, &AssetRequestHandler::RequestEscalateAssetBySearchTerm, GetRCController(), &RCController::OnEscalateJobsBySearchTerm);
@@ -754,8 +812,6 @@ ApplicationManager::BeforeRunStatus ApplicationManagerBase::BeforeRun()
qRegisterMetaType<AzFramework::AssetSystem::AssetStatus>("AzFramework::AssetSystem::AssetStatus");
qRegisterMetaType<AzFramework::AssetSystem::AssetStatus>("AssetStatus");
qRegisterMetaType<FileChangeInfo>("FileChangeInfo");
qRegisterMetaType<AssetProcessor::AssetScanningStatus>("AssetScanningStatus");
qRegisterMetaType<AssetProcessor::NetworkRequestID>("NetworkRequestID");
@@ -840,14 +896,6 @@ bool ApplicationManagerBase::Run()
return false;
}
bool startedSuccessfully = true;
if (!PostActivate())
{
QuitRequested();
startedSuccessfully = false;
}
AZ_Printf(AssetProcessor::ConsoleChannel, "Asset Processor Batch Processing Started.\n");
AZ_Printf(AssetProcessor::ConsoleChannel, "-----------------------------------------\n");
QElapsedTimer allAssetsProcessingTimer;
@@ -867,7 +915,7 @@ bool ApplicationManagerBase::Run()
RemoveOldTempFolders();
Destroy();
return (startedSuccessfully && FailedAssetsCount() == 0);
return FailedAssetsCount() == 0;
}
void ApplicationManagerBase::HandleFileRelocation() const
@@ -899,7 +947,7 @@ void ApplicationManagerBase::HandleFileRelocation() const
while(!m_sourceControlReady)
{
// We need to wait for source control to be ready before continuing
if (printCounter % 10 == 0)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Waiting for Source Control connection\n");
@@ -966,18 +1014,18 @@ void ApplicationManagerBase::HandleFileRelocation() const
AZ_Printf(AssetProcessor::ConsoleChannel, "SETTING: Preview file move. Run again with --%s to actually make changes\n", ConfirmCommand);
}
auto* interface = AZ::Interface<AssetProcessor::ISourceFileRelocation>::Get();
auto* relocationInterface = AZ::Interface<AssetProcessor::ISourceFileRelocation>::Get();
if(interface)
if(relocationInterface)
{
auto result = interface->Move(source, destination, previewOnly, allowBrokenDependencies, !leaveEmptyFolders, updateReferences, excludeMetaDataFiles);
auto result = relocationInterface->Move(source, destination, previewOnly, allowBrokenDependencies, !leaveEmptyFolders, updateReferences, excludeMetaDataFiles);
if (result.IsSuccess())
{
AssetProcessor::RelocationSuccess success = result.TakeValue();
// The report can be too long for the AZ_Printf buffer, so split it into individual lines
AZStd::string report = interface->BuildReport(success.m_relocationContainer, success.m_updateTasks, true, updateReferences);
AZStd::string report = relocationInterface->BuildReport(success.m_relocationContainer, success.m_updateTasks, true, updateReferences);
AZStd::vector<AZStd::string> lines;
AzFramework::StringFunc::Tokenize(report.c_str(), lines, "\n");
@@ -1051,18 +1099,18 @@ void ApplicationManagerBase::HandleFileRelocation() const
AZ_Printf(AssetProcessor::ConsoleChannel, "SETTING: Preview file delete. Run again with --%s to actually make changes\n", ConfirmCommand);
}
auto* interface = AZ::Interface<AssetProcessor::ISourceFileRelocation>::Get();
auto* relocationInterface = AZ::Interface<AssetProcessor::ISourceFileRelocation>::Get();
if (interface)
if (relocationInterface)
{
auto result = interface->Delete(source, previewOnly, allowBrokenDependencies, !leaveEmptyFolders, excludeMetaDataFiles);
auto result = relocationInterface->Delete(source, previewOnly, allowBrokenDependencies, !leaveEmptyFolders, excludeMetaDataFiles);
if (result.IsSuccess())
{
AssetProcessor::RelocationSuccess success = result.TakeValue();
// The report can be too long for the AZ_Printf buffer, so split it into individual lines
AZStd::string report = interface->BuildReport(success.m_relocationContainer, success.m_updateTasks, false, updateReferences);
AZStd::string report = relocationInterface->BuildReport(success.m_relocationContainer, success.m_updateTasks, false, updateReferences);
AZStd::vector<AZStd::string> lines;
AzFramework::StringFunc::Tokenize(report.c_str(), lines, "\n");
@@ -1129,7 +1177,7 @@ void ApplicationManagerBase::CheckForIdle()
TryScanProductDependencies();
TryHandleFileRelocation();
// since we are shutting down, we save the registry and then we quit.
AZ_Printf(AssetProcessor::ConsoleChannel, "No assets remain in the build queue. Saving the catalog, and then shutting down.\n");
// stop accepting any further idle messages, as we will shut down - don't want this function to repeat!
@@ -1173,7 +1221,7 @@ void ApplicationManagerBase::InitBuilderManager()
{
m_builderManager->ConnectionLost(connId);
});
}
void ApplicationManagerBase::ShutdownBuilderManager()
@@ -1207,7 +1255,7 @@ void ApplicationManagerBase::ShutDownAssetDatabase()
AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Handler::BusDisconnect();
}
void ApplicationManagerBase::InitFileProcessor()
void ApplicationManagerBase::InitFileProcessor()
{
AssetProcessor::ThreadController<AssetProcessor::FileProcessor>* fileProcessorHelper = new AssetProcessor::ThreadController<AssetProcessor::FileProcessor>();
@@ -1298,22 +1346,13 @@ bool ApplicationManagerBase::Activate()
}
InitBuilderConfiguration();
m_isCurrentlyLoadingGems = true;
if (!ActivateModules())
{
// ActivateModules reports any errors it encounters.
m_isCurrentlyLoadingGems = false;
return false;
}
m_isCurrentlyLoadingGems = false;
PopulateApplicationDependencies();
InitAssetProcessorManager();
AssetBuilderSDK::InitializeSerializationContext();
AssetBuilderSDK::InitializeBehaviorContext();
AssetBuilder::InitializeSerializationContext();
InitFileStateCache();
InitFileProcessor();
@@ -1341,7 +1380,7 @@ bool ApplicationManagerBase::Activate()
RegisterObjectForQuit(m_rcController);
m_connectionsToRemoveOnShutdown << QObject::connect(
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState,
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::AssetProcessorManagerIdleState,
this, [this](bool state)
{
if (state)
@@ -1362,7 +1401,7 @@ bool ApplicationManagerBase::Activate()
});
m_connectionsToRemoveOnShutdown << QObject::connect(
this, &ApplicationManagerBase::CheckAssetProcessorManagerIdleState,
this, &ApplicationManagerBase::CheckAssetProcessorManagerIdleState,
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::CheckAssetProcessorIdleState);
MakeActivationConnections();
@@ -1376,6 +1415,22 @@ bool ApplicationManagerBase::Activate()
return false;
}
}
AssetProcessor::AssetProcessorStatusEntry entry(AssetProcessor::AssetProcessorStatus::Initializing_Builders, 0, QString());
Q_EMIT AssetProcessorStatusChanged(entry);
AZStd::thread_desc desc;
desc.m_name = "Builder Component Registration";
AZStd::thread builderRegistrationThread(
desc,
[]()
{
AssetProcessor::BuilderRef builder;
AssetProcessor::BuilderManagerBus::BroadcastResult(builder, &AssetProcessor::BuilderManagerBus::Events::GetBuilder, true);
});
builderRegistrationThread.detach();
return true;
}
@@ -1384,11 +1439,6 @@ bool ApplicationManagerBase::PostActivate()
m_connectionManager->LoadConnections();
InitializeInternalBuilders();
if (!InitializeExternalBuilders())
{
AZ_Error("AssetProcessor", false, "AssetProcessor is closing. Failed to initialize and load all the external builders. Please ensure that Builders_Temp directory is not read-only. Please see log for more information.\n");
return false;
}
Q_EMIT OnBuildersRegistered();
@@ -1401,7 +1451,7 @@ bool ApplicationManagerBase::PostActivate()
AZ::SystemTickBus::Broadcast(&AZ::SystemTickEvents::OnSystemTick);
});
// now that everything is up and running, we start scanning. Before this, we don't want file events to start percolating through the
// now that everything is up and running, we start scanning. Before this, we don't want file events to start percolating through the
// asset system.
GetAssetScanner()->StartScan();
@@ -1425,124 +1475,20 @@ bool ApplicationManagerBase::InitializeInternalBuilders()
return result;
}
bool ApplicationManagerBase::InitializeExternalBuilders()
{
AssetProcessor::AssetProcessorStatusEntry entry(AssetProcessor::AssetProcessorStatus::Initializing_Builders);
Q_EMIT AssetProcessorStatusChanged(entry);
QCoreApplication::processEvents(QEventLoop::AllEvents);
// Get the list of external build modules (full paths)
QStringList fileList;
GetExternalBuilderFileList(fileList);
for (const QString& filePath : fileList)
{
if (QLibrary::isLibrary(filePath))
{
AssetProcessor::ExternalModuleAssetBuilderInfo* externalAssetBuilderInfo = new AssetProcessor::ExternalModuleAssetBuilderInfo(filePath);
AssetProcessor::AssetBuilderType assetBuilderType = externalAssetBuilderInfo->Load();
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "AssetProcessor is loading library %s\n", filePath.toUtf8().data());
if (assetBuilderType == AssetProcessor::AssetBuilderType::None)
{
AZ_Warning(AssetProcessor::DebugChannel, false, "Non-builder DLL was found in Builders directory %s, skipping. \n", filePath.toUtf8().data());
delete externalAssetBuilderInfo;
continue;
}
if (assetBuilderType == AssetProcessor::AssetBuilderType::Invalid)
{
AZ_Warning(AssetProcessor::DebugChannel, false, "AssetProcessor was not able to load the library: %s\n", filePath.toUtf8().data());
delete externalAssetBuilderInfo;
return false;
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Initializing and registering builder %s\n", externalAssetBuilderInfo->GetName().toUtf8().data());
m_currentExternalAssetBuilder = externalAssetBuilderInfo;
externalAssetBuilderInfo->Initialize();
m_currentExternalAssetBuilder = nullptr;
m_externalAssetBuilders.push_back(externalAssetBuilderInfo);
}
}
// Also init external builders which may be inside of Gems
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
&AzToolsFramework::ToolsApplicationRequests::CreateAndAddEntityFromComponentTags,
AZStd::vector<AZ::Crc32>({ AssetBuilderSDK::ComponentTags::AssetBuilder }), "AssetBuilders Entity");
return true;
}
bool ApplicationManagerBase::WaitForBuilderExit(AzFramework::ProcessWatcher* processWatcher, AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds)
{
AZ::u32 exitCode = 0;
bool finishedOK = false;
QElapsedTimer ticker;
ProcessCommunicatorTracePrinter tracer(processWatcher->GetCommunicator(), "AssetBuilder");
ticker.start();
while (!finishedOK)
{
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(s_MaximumSleepTimeMS));
tracer.Pump();
if (ticker.elapsed() > processTimeoutLimitInSeconds * 1000 || (jobCancelListener && jobCancelListener->IsCancelled()))
{
break;
}
if (!processWatcher->IsProcessRunning(&exitCode))
{
finishedOK = true; // we either cant wait for it, or it finished.
break;
}
}
tracer.Pump(); // empty whats left if possible.
if (processWatcher->IsProcessRunning(&exitCode))
{
processWatcher->TerminateProcess(1);
}
if (exitCode != 0)
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "AssetBuilder exited with error code %d", exitCode);
return false;
}
else if (jobCancelListener && jobCancelListener->IsCancelled())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "AssetBuilder was terminated. There was a request to cancel the job.\n");
return false;
}
else if (!finishedOK)
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "AssetBuilder failed to terminate within %d seconds", processTimeoutLimitInSeconds);
return false;
}
return true;
}
void ApplicationManagerBase::RegisterBuilderInformation(const AssetBuilderSDK::AssetBuilderDesc& builderDesc)
{
// Create Job Function validation
AZ_Error(AssetProcessor::ConsoleChannel,
builderDesc.m_createJobFunction,
"Create Job Function (m_createJobFunction) for %s builder is empty.\n",
builderDesc.m_name.c_str());
if (!builderDesc.IsExternalBuilder())
{
// Create Job Function validation
AZ_Error(
AssetProcessor::ConsoleChannel, builderDesc.m_createJobFunction,
"Create Job Function (m_createJobFunction) for %s builder is empty.\n", builderDesc.m_name.c_str());
// Process Job Function validation
AZ_Error(AssetProcessor::ConsoleChannel,
builderDesc.m_processJobFunction,
"Process Job Function (m_processJobFunction) for %s builder is empty.\n",
builderDesc.m_name.c_str());
// Process Job Function validation
AZ_Error(
AssetProcessor::ConsoleChannel, builderDesc.m_processJobFunction,
"Process Job Function (m_processJobFunction) for %s builder is empty.\n", builderDesc.m_name.c_str());
}
// Bus ID validation
AZ_Error(AssetProcessor::ConsoleChannel,
@@ -1550,67 +1496,66 @@ void ApplicationManagerBase::RegisterBuilderInformation(const AssetBuilderSDK::A
"Bus ID for %s builder is empty.\n",
builderDesc.m_name.c_str());
// This is an external builder registering, we will want to track its builder desc since it can register multiple ones
AZStd::string builderFilePath;
if (m_currentExternalAssetBuilder)
{
m_currentExternalAssetBuilder->RegisterBuilderDesc(builderDesc.m_busId);
builderFilePath = m_currentExternalAssetBuilder->GetModuleFullPath().toUtf8().data();
}
AssetBuilderSDK::AssetBuilderDesc modifiedBuilderDesc = builderDesc;
// Allow for overrides defined in a BuilderConfig.ini file to update our code defined default values
AssetProcessor::BuilderConfigurationRequestBus::Broadcast(&AssetProcessor::BuilderConfigurationRequests::UpdateBuilderDescriptor, builderDesc.m_name, modifiedBuilderDesc);
if (builderDesc.IsExternalBuilder())
{
// We're going to override the createJob function so we can run it externally in AssetBuilder, rather than having it run inside the AP
modifiedBuilderDesc.m_createJobFunction = [builderFilePath](const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
// We're going to override the createJob function so we can run it externally in AssetBuilder, rather than having it run
// inside the AP
modifiedBuilderDesc.m_createJobFunction =
[](const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
{
AssetProcessor::BuilderRef builderRef;
AssetProcessor::BuilderManagerBus::BroadcastResult(builderRef, &AssetProcessor::BuilderManagerBusTraits::GetBuilder, false);
if (builderRef)
{
AssetProcessor::BuilderRef builderRef;
AssetProcessor::BuilderManagerBus::BroadcastResult(builderRef, &AssetProcessor::BuilderManagerBusTraits::GetBuilder);
int retryCount = 0;
AssetProcessor::BuilderRunJobOutcome result;
if (builderRef)
do
{
int retryCount = 0;
AssetProcessor::BuilderRunJobOutcome result;
do
{
retryCount++;
result = builderRef->RunJob<AssetBuilderSDK::CreateJobsNetRequest, AssetBuilderSDK::CreateJobsNetResponse>(request, response, s_MaximumCreateJobsTimeSeconds, "create", builderFilePath, nullptr);
} while (result == AssetProcessor::BuilderRunJobOutcome::LostConnection && retryCount <= AssetProcessor::RetriesForJobNetworkError);
}
else
{
AZ_Error("AssetProcessor", false, "Failed to retrieve a valid builder to process job");
}
};
retryCount++;
result = builderRef->RunJob<AssetBuilder::CreateJobsNetRequest, AssetBuilder::CreateJobsNetResponse>(
request, response, s_MaximumCreateJobsTimeSeconds, "create", "", nullptr);
} while (result == AssetProcessor::BuilderRunJobOutcome::LostConnection &&
retryCount <= AssetProcessor::RetriesForJobNetworkError);
}
else
{
AZ_Error("AssetProcessor", false, "Failed to retrieve a valid builder to process job");
}
};
// Also override the processJob function to run externally
modifiedBuilderDesc.m_processJobFunction = [builderFilePath](const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
modifiedBuilderDesc.m_processJobFunction =
[](const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
{
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
AssetProcessor::BuilderRef builderRef;
AssetProcessor::BuilderManagerBus::BroadcastResult(builderRef, &AssetProcessor::BuilderManagerBusTraits::GetBuilder, false);
if (builderRef)
{
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
int retryCount = 0;
AssetProcessor::BuilderRunJobOutcome result;
AssetProcessor::BuilderRef builderRef;
AssetProcessor::BuilderManagerBus::BroadcastResult(builderRef, &AssetProcessor::BuilderManagerBusTraits::GetBuilder);
if (builderRef)
do
{
int retryCount = 0;
AssetProcessor::BuilderRunJobOutcome result;
do
{
retryCount++;
result = builderRef->RunJob<AssetBuilderSDK::ProcessJobNetRequest, AssetBuilderSDK::ProcessJobNetResponse>(request, response, s_MaximumProcessJobsTimeSeconds, "process", builderFilePath, &jobCancelListener, request.m_tempDirPath);
} while (result == AssetProcessor::BuilderRunJobOutcome::LostConnection && retryCount <= AssetProcessor::RetriesForJobNetworkError);
}
else
{
AZ_Error("AssetProcessor", false, "Failed to retrieve a valid builder to process job");
}
};
retryCount++;
result = builderRef->RunJob<AssetBuilder::ProcessJobNetRequest, AssetBuilder::ProcessJobNetResponse>(
request, response, s_MaximumProcessJobsTimeSeconds, "process", "", &jobCancelListener, request.m_tempDirPath);
} while (result == AssetProcessor::BuilderRunJobOutcome::LostConnection &&
retryCount <= AssetProcessor::RetriesForJobNetworkError);
}
else
{
AZ_Error("AssetProcessor", false, "Failed to retrieve a valid builder to process job");
}
};
}
if (m_builderDescMap.find(modifiedBuilderDesc.m_busId) != m_builderDescMap.end())
@@ -1768,7 +1713,7 @@ bool ApplicationManagerBase::CheckSufficientDiskSpace(const QString& savePath, q
[[maybe_unused]] bool result = AzToolsFramework::ToolsFileUtils::GetFreeDiskSpace(savePath, bytesFree);
AZ_Assert(result, "Unable to determine the amount of free space on drive containing path (%s).", savePath.toUtf8().constData());
if (bytesFree < requiredSpace + s_ReservedDiskSpaceInBytes)
{
if (shutdownIfInsufficient)
@@ -1806,8 +1751,8 @@ void ApplicationManagerBase::RemoveOldTempFolders()
return;
}
// We will remove old temp folders if either their modified time is older than the cutoff time or
// if the total number of temp folders have exceeded the maximum number of temp folders.
// We will remove old temp folders if either their modified time is older than the cutoff time or
// if the total number of temp folders have exceeded the maximum number of temp folders.
QFileInfoList entries = root.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time); // sorting by modification time
int folderCount = 0;
bool removeFolder = false;
@@ -1821,9 +1766,9 @@ void ApplicationManagerBase::RemoveOldTempFolders()
// Since we are sorting the folders list from latest to oldest, we will either be in a state where we have to delete all the remaining folders or not
// because either we have reached the folder limit or reached the cutoff date limit.
removeFolder = removeFolder || (folderCount++ >= s_MaximumTempFolders) ||
removeFolder = removeFolder || (folderCount++ >= s_MaximumTempFolders) ||
(entry.lastModified() < cutoffTime);
if (removeFolder)
{
QDir dir(entry.absoluteFilePath());
@@ -1837,8 +1782,6 @@ void ApplicationManagerBase::ConnectivityStateChanged(const AzToolsFramework::So
Q_EMIT SourceControlReady();
}
void ApplicationManagerBase::OnAssetProcessorManagerIdleState(bool isIdle)
{
// these can come in during shutdown.
@@ -47,7 +47,6 @@ namespace AssetProcessor
class ApplicationServer;
class ConnectionManager;
class FolderWatchCallbackEx;
class ControlRequestHandler;
class ApplicationManagerBase
@@ -149,7 +148,6 @@ protected:
void CreateQtApplication() override;
bool InitializeInternalBuilders();
bool InitializeExternalBuilders();
void InitBuilderManager();
void ShutdownBuilderManager();
bool InitAssetDatabase();
@@ -173,8 +171,6 @@ protected:
AssetProcessor::AssetCatalog* GetAssetCatalog() const { return m_assetCatalog; }
static bool WaitForBuilderExit(AzFramework::ProcessWatcher* processWatcher, AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds);
ApplicationServer* m_applicationServer = nullptr;
ConnectionManager* m_connectionManager = nullptr;
@@ -195,9 +191,7 @@ protected:
bool m_sourceControlReady = false;
bool m_fullIdle = false;
AZStd::vector<AZStd::unique_ptr<FolderWatchCallbackEx> > m_folderWatches;
FileWatcher m_fileWatcher;
AZStd::vector<int> m_watchHandles;
AssetProcessor::PlatformConfiguration* m_platformConfiguration = nullptr;
AssetProcessor::AssetProcessorManager* m_assetProcessorManager = nullptr;
AssetProcessor::AssetCatalog* m_assetCatalog = nullptr;
@@ -218,6 +212,8 @@ protected:
AZStd::shared_ptr<AssetProcessor::InternalRecognizerBasedBuilder> m_internalBuilder;
AZStd::shared_ptr<AssetProcessor::SettingsRegistryBuilder> m_settingsRegistryBuilder;
bool m_builderRegistrationComplete = false;
// Builder description map based on the builder id
AZStd::unordered_map<AZ::Uuid, AssetBuilderSDK::AssetBuilderDesc> m_builderDescMap;
@@ -231,7 +227,7 @@ protected:
AZStd::list<AssetProcessor::ExternalModuleAssetBuilderInfo*> m_externalAssetBuilders;
AssetProcessor::ExternalModuleAssetBuilderInfo* m_currentExternalAssetBuilder = nullptr;
QAtomicInt m_connectionsAwaitingAssetCatalogSave = 0;
int m_remainingAPMJobs = 0;
bool m_assetProcessorManagerIsReady = false;
@@ -23,7 +23,6 @@
#include <native/resourcecompiler/RCBuilder.h>
#include <AssetBuilder/AssetBuilderInfo.h>
class FolderWatchCallbackEx;
class QCoreApplication;
namespace AssetProcessor
@@ -16,6 +16,7 @@
#include <native/utilities/AssetBuilderInfo.h>
#include <QCoreApplication>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AssetBuilder/AssetBuilderStatic.h>
namespace AssetProcessor
{
@@ -138,7 +139,7 @@ namespace AssetProcessor
}
}
bool Builder::Start()
bool Builder::Start(bool doRegistration)
{
// Get the current BinXXX folder based on the current running AP
QString applicationDir = QCoreApplication::instance()->applicationDirPath();
@@ -155,7 +156,7 @@ namespace AssetProcessor
return false;
}
const AZStd::vector<AZStd::string> params = BuildParams("resident", buildersFolder.c_str(), UuidString(), "", "");
const AZStd::vector<AZStd::string> params = BuildParams("resident", buildersFolder.c_str(), UuidString(), "", "", doRegistration);
m_processWatcher = LaunchProcess(fullExePathString.c_str(), params);
@@ -179,7 +180,7 @@ namespace AssetProcessor
return !m_processWatcher || (m_processWatcher && m_processWatcher->IsProcessRunning(exitCode));
}
AZStd::vector<AZStd::string> Builder::BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile) const
AZStd::vector<AZStd::string> Builder::BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile, bool doRegistration) const
{
QDir projectCacheRoot;
AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot);
@@ -200,6 +201,11 @@ namespace AssetProcessor
params.emplace_back(AZStd::string::format(R"(-engine-path="%s")", enginePath.c_str()));
params.emplace_back(AZStd::string::format("-port=%d", portNumber));
if(doRegistration)
{
params.emplace_back("--register");
}
if (moduleFilePath && moduleFilePath[0])
{
params.emplace_back(AZStd::string::format(R"(-module="%s")", moduleFilePath));
@@ -232,7 +238,7 @@ namespace AssetProcessor
{
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_processExecutableString = fullExePath;
AZStd::vector<AZStd::string> commandLineArray{ fullExePath };
commandLineArray.insert(commandLineArray.end(), params.begin(), params.end());
processLaunchInfo.m_commandlineParameters = AZStd::move(commandLineArray);
@@ -350,17 +356,19 @@ namespace AssetProcessor
BuilderManager::BuilderManager(ConnectionManager* connectionManager)
{
using namespace AZStd::placeholders;
connectionManager->RegisterService(AssetBuilderSDK::BuilderHelloRequest::MessageType(), AZStd::bind(&BuilderManager::IncomingBuilderPing, this, _1, _2, _3, _4, _5));
connectionManager->RegisterService(AssetBuilder::BuilderHelloRequest::MessageType(), AZStd::bind(&BuilderManager::IncomingBuilderPing, this, _1, _2, _3, _4, _5));
// Setup a background thread to pump the idle builders so they don't get blocked trying to output to stdout/err
m_pollingThread = AZStd::thread([this]()
AZStd::thread_desc desc;
desc.m_name = "BuilderManager Idle Pump";
m_pollingThread = AZStd::thread(desc, [this]()
{
while (!m_quitListener.WasQuitRequested())
{
while (!m_quitListener.WasQuitRequested())
{
PumpIdleBuilders();
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(s_IdleBuilderPumpingDelayMS));
}
});
PumpIdleBuilders();
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(s_IdleBuilderPumpingDelayMS));
}
});
m_quitListener.BusConnect();
BusConnect();
@@ -399,8 +407,8 @@ namespace AssetProcessor
void BuilderManager::IncomingBuilderPing(AZ::u32 connId, AZ::u32 /*type*/, AZ::u32 serial, QByteArray payload, QString platform)
{
AssetBuilderSDK::BuilderHelloRequest requestPing;
AssetBuilderSDK::BuilderHelloResponse responsePing;
AssetBuilder::BuilderHelloRequest requestPing;
AssetBuilder::BuilderHelloResponse responsePing;
if (!AZ::Utils::LoadObjectFromBufferInPlace(payload.data(), payload.length(), requestPing))
{
@@ -476,7 +484,7 @@ namespace AssetProcessor
return builder;
}
BuilderRef BuilderManager::GetBuilder()
BuilderRef BuilderManager::GetBuilder(bool doRegistration)
{
AZStd::shared_ptr<Builder> newBuilder;
BuilderRef builderRef;
@@ -484,27 +492,30 @@ namespace AssetProcessor
{
AZStd::unique_lock<AZStd::mutex> lock(m_buildersMutex);
for (auto itr = m_builders.begin(); itr != m_builders.end(); )
if (!doRegistration)
{
auto& builder = itr->second;
if (!builder->m_busy)
for (auto itr = m_builders.begin(); itr != m_builders.end();)
{
builder->PumpCommunicator();
auto& builder = itr->second;
if (builder->IsValid())
if (!builder->m_busy)
{
return BuilderRef(builder);
builder->PumpCommunicator();
if (builder->IsValid())
{
return BuilderRef(builder);
}
else
{
itr = m_builders.erase(itr);
}
}
else
{
itr = m_builders.erase(itr);
++itr;
}
}
else
{
++itr;
}
}
AZ_TracePrintf("BuilderManager", "Starting new builder for job request\n");
@@ -516,7 +527,7 @@ namespace AssetProcessor
builderRef = BuilderRef(newBuilder);
}
if (!newBuilder->Start())
if (!newBuilder->Start(doRegistration))
{
AZ_Error("BuilderManager", false, "Builder failed to start");
@@ -39,7 +39,7 @@ namespace AssetProcessor
virtual ~BuilderManagerBusTraits() = default;
//! Returns a builder for doing work
virtual BuilderRef GetBuilder() = 0;
virtual BuilderRef GetBuilder(bool doRegistration) = 0;
};
using BuilderManagerBus = AZ::EBus<BuilderManagerBusTraits>;
@@ -98,12 +98,12 @@ namespace AssetProcessor
private:
//! Starts the builder process and waits for it to connect
bool Start();
bool Start(bool doRegistration);
//! Sets the connection id and signals that the builder has connected
void SetConnection(AZ::u32 connId);
AZStd::vector<AZStd::string> BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile) const;
AZStd::vector<AZStd::string> BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile, bool doRegistration) const;
AZStd::unique_ptr<AzFramework::ProcessWatcher> LaunchProcess(const char* fullExePath, const AZStd::vector<AZStd::string>& params) const;
//! Waits for the builder exe to send the job response and pumps stdout/err
@@ -169,7 +169,7 @@ namespace AssetProcessor
void ConnectionLost(AZ::u32 connId);
//BuilderManagerBus
BuilderRef GetBuilder() override;
BuilderRef GetBuilder(bool doRegistration) override;
private:
@@ -50,7 +50,7 @@ namespace AssetProcessor
if (!netResponse.m_response.Succeeded() || s_createRequestFileForSuccessfulJob)
{
// we write the request out to disk for failure or debugging
// we write the request out to disk for failure or debugging
if (!DebugWriteRequestFile(tempFolderPath.c_str(), request, task, modulePath))
{
return BuilderRunJobOutcome::FailedToWriteDebugRequest;
@@ -83,7 +83,7 @@ namespace AssetProcessor
return false;
}
auto params = BuildParams(task.c_str(), modulePath.c_str(), "", jobRequestFile, jobResponseFile);
auto params = BuildParams(task.c_str(), modulePath.c_str(), "", jobRequestFile, jobResponseFile, false);
AZStd::string paramString;
AZ::StringFunc::Join(paramString, params.begin(), params.end(), " ");
@@ -12,8 +12,6 @@
#include "native/resourcecompiler/rccontroller.h"
#include "native/FileServer/fileServer.h"
#include "native/AssetManager/assetScanner.h"
#include "native/shadercompiler/shadercompilerManager.h"
#include "native/shadercompiler/shadercompilerModel.h"
#include <QApplication>
#include <QDialogButtonBox>
@@ -55,7 +53,7 @@ namespace
{
moduleFileInfo.setFile(executableDirectory);
}
QDir binaryDir = moduleFileInfo.absoluteDir();
// strip extension
QString applicationBase = moduleFileInfo.completeBaseName();
@@ -70,7 +68,7 @@ namespace
binaryDir.remove(tempFile);
}
}
}
@@ -145,8 +143,6 @@ void GUIApplicationManager::Destroy()
DestroyIniConfiguration();
DestroyFileServer();
DestroyShaderCompilerManager();
DestroyShaderCompilerModel();
}
@@ -192,7 +188,7 @@ bool GUIApplicationManager::Run()
wrapper->enableSaveRestoreGeometry(GetOrganizationName(), GetApplicationName(), "MainWindow", restoreOnFirstShow);
AzQtComponents::StyleManager::setStyleSheet(m_mainWindow, QStringLiteral("style:AssetProcessor.qss"));
auto refreshStyleSheets = [styleManager]()
{
styleManager->Refresh();
@@ -322,19 +318,11 @@ bool GUIApplicationManager::Run()
qApp->setQuitOnLastWindowClosed(false);
QTimer::singleShot(0, this, [this]()
{
if (!PostActivate())
{
QuitRequested();
m_startedSuccessfully = false;
}
});
m_duringStartup = false;
m_startedSuccessfully = true;
int resultCode = qApp->exec(); // this blocks until the last window is closed.
if(!InitiatedShutdown())
{
// if we are here it implies that AP did not stop the Qt event loop and is shutting down prematurely
@@ -427,7 +415,7 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message)
return true;
}
// If we're the main thread, then consider showing the message box directly.
// If we're the main thread, then consider showing the message box directly.
// note that all other threads will PAUSE if they emit a message while the main thread is showing this box
// due to the way the trace system EBUS is mutex-protected.
Qt::ConnectionType connection = Qt::DirectConnection;
@@ -470,7 +458,7 @@ bool GUIApplicationManager::Activate()
AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot);
m_localUserSettings.Load(projectCacheRoot.filePath("AssetProcessorUserSettings.xml").toUtf8().data(), context);
m_localUserSettings.Activate(AZ::UserSettings::CT_LOCAL);
InitIniConfiguration();
InitFileServer();
@@ -479,9 +467,6 @@ bool GUIApplicationManager::Activate()
{
return false;
}
InitShaderCompilerModel();
InitShaderCompilerManager();
return true;
}
@@ -490,6 +475,7 @@ bool GUIApplicationManager::PostActivate()
{
if (!ApplicationManagerBase::PostActivate())
{
m_startedSuccessfully = false;
return false;
}
@@ -606,7 +592,7 @@ void GUIApplicationManager::InitConnectionManager()
QObject::connect(m_fileServer, SIGNAL(AddRenameRequest(unsigned int, bool)), m_connectionManager, SLOT(AddRenameRequest(unsigned int, bool)));
QObject::connect(m_fileServer, SIGNAL(AddFindFileNamesRequest(unsigned int, bool)), m_connectionManager, SLOT(AddFindFileNamesRequest(unsigned int, bool)));
QObject::connect(m_fileServer, SIGNAL(UpdateConnectionMetrics()), m_connectionManager, SLOT(UpdateConnectionMetrics()));
m_connectionManager->RegisterService(ShowAssetProcessorRequest::MessageType,
std::bind([this](unsigned int /*connId*/, unsigned int /*type*/, unsigned int /*serial*/, QByteArray /*payload*/)
{
@@ -661,40 +647,6 @@ void GUIApplicationManager::DestroyFileServer()
}
}
void GUIApplicationManager::InitShaderCompilerManager()
{
m_shaderCompilerManager = new ShaderCompilerManager();
//Shader compiler stuff
m_connectionManager->RegisterService(AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), std::bind(&ShaderCompilerManager::process, m_shaderCompilerManager, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
QObject::connect(m_shaderCompilerManager, SIGNAL(sendErrorMessageFromShaderJob(QString, QString, QString, QString)), m_shaderCompilerModel, SLOT(addShaderErrorInfoEntry(QString, QString, QString, QString)));
}
void GUIApplicationManager::DestroyShaderCompilerManager()
{
if (m_shaderCompilerManager)
{
delete m_shaderCompilerManager;
m_shaderCompilerManager = nullptr;
}
}
void GUIApplicationManager::InitShaderCompilerModel()
{
m_shaderCompilerModel = new ShaderCompilerModel();
}
void GUIApplicationManager::DestroyShaderCompilerModel()
{
if (m_shaderCompilerModel)
{
delete m_shaderCompilerModel;
m_shaderCompilerModel = nullptr;
}
}
IniConfiguration* GUIApplicationManager::GetIniConfiguration() const
{
return m_iniConfiguration;
@@ -704,14 +656,6 @@ FileServer* GUIApplicationManager::GetFileServer() const
{
return m_fileServer;
}
ShaderCompilerManager* GUIApplicationManager::GetShaderCompilerManager() const
{
return m_shaderCompilerManager;
}
ShaderCompilerModel* GUIApplicationManager::GetShaderCompilerModel() const
{
return m_shaderCompilerModel;
}
void GUIApplicationManager::ShowTrayIconErrorMessage(QString msg)
{
@@ -24,8 +24,6 @@ class ConnectionManager;
class IniConfiguration;
class ApplicationServer;
class FileServer;
class ShaderCompilerManager;
class ShaderCompilerModel;
namespace AssetProcessor
{
@@ -47,8 +45,6 @@ public:
ApplicationManager::BeforeRunStatus BeforeRun() override;
IniConfiguration* GetIniConfiguration() const;
FileServer* GetFileServer() const;
ShaderCompilerManager* GetShaderCompilerManager() const;
ShaderCompilerModel* GetShaderCompilerModel() const;
bool Run() override;
////////////////////////////////////////////////////
@@ -72,10 +68,6 @@ private:
void DestroyIniConfiguration();
void InitFileServer();
void DestroyFileServer();
void InitShaderCompilerManager();
void DestroyShaderCompilerManager();
void InitShaderCompilerModel();
void DestroyShaderCompilerModel();
void Destroy() override;
Q_SIGNALS:
@@ -99,8 +91,7 @@ private:
IniConfiguration* m_iniConfiguration = nullptr;
FileServer* m_fileServer = nullptr;
ShaderCompilerManager* m_shaderCompilerManager = nullptr;
ShaderCompilerModel* m_shaderCompilerModel = nullptr;
QFileSystemWatcher m_qtFileWatcher;
AZ::UserSettingsProvider m_localUserSettings;
bool m_messageBoxIsVisible = false;
@@ -140,7 +140,7 @@ void DHBreakpointsWidget::CreateBreakpoint(const AZStd::string& debugName, int l
QTableWidgetItem* newItem = new QTableWidgetItem(debugName.c_str());
newItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
setItem(newRow, 1, newItem);
newItem = new QTableWidgetItem(QString().setNum(lineNumber + 1)); // +1 offset to match editor numbering
newItem = new QTableWidgetItem(QString().setNum(lineNumber));
newItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
setItem(newRow, 0, newItem);
}
@@ -1433,7 +1433,8 @@ namespace LUAEditor
return;
}
AZStd::to_lower(const_cast<AZStd::string&>(assetId).begin(), const_cast<AZStd::string&>(assetId).end());
AZStd::string assetIdLower(assetId);
AZStd::to_lower(assetIdLower.begin(), assetIdLower.end());
ShowLUAEditorView();
@@ -1446,11 +1447,11 @@ namespace LUAEditor
// * we need to load that lua panel with the document's data, initializing it.
// are we already tracking it?
auto it = m_documentInfoMap.find(assetId);
auto it = m_documentInfoMap.find(assetIdLower);
if (it != m_documentInfoMap.end())
{
// tell the view that it needs to focus that document!
mostRecentlyOpenedDocumentView = assetId;
mostRecentlyOpenedDocumentView = assetIdLower;
if (m_queuedOpenRecent)
{
return;
@@ -1482,14 +1483,14 @@ namespace LUAEditor
// Register the script into the asset catalog
AZ::Data::AssetType assetType = AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid();
AZ::Data::AssetId catalogAssetId;
EBUS_EVENT_RESULT(catalogAssetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, assetId.c_str(), assetType, true);
EBUS_EVENT_RESULT(catalogAssetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, assetIdLower.c_str(), assetType, true);
uint64_t modTime = m_fileIO->ModificationTime(assetId.c_str());
DocumentInfo info;
info.m_assetName = assetId;
info.m_assetName = assetIdLower;
AzFramework::StringFunc::Path::GetFullFileName(assetId.c_str(), info.m_displayName);
info.m_assetId = assetId;
info.m_assetId = assetIdLower;
info.m_bSourceControl_BusyGettingStats = true;
info.m_bSourceControl_BusyGettingStats = false;
info.m_bSourceControl_CanWrite = true;
@@ -1532,7 +1533,7 @@ namespace LUAEditor
luaFile.Close();
}
DataLoadDoneCallback(isLoaded, assetId);
DataLoadDoneCallback(isLoaded, assetIdLower);
//////////////////////////////////////////////////////////////////////////
if (m_queuedOpenRecent)
@@ -1545,7 +1546,7 @@ namespace LUAEditor
m_pLUAEditorMainWindow->IgnoreFocusEvents(false);
}
mostRecentlyOpenedDocumentView = assetId;
mostRecentlyOpenedDocumentView = assetIdLower;
EBUS_QUEUE_FUNCTION(AZ::SystemTickBus, &Context::OpenMostRecentDocumentView, this);
}
@@ -346,7 +346,11 @@ namespace LUAEditor
{
auto selectedAsset = selectedAssets.front();
const AZStd::string filePath = selectedAsset->GetFullPath();
EBUS_EVENT(Context_DocumentManagement::Bus, OnLoadDocument, filePath, true);
auto entryType = selectedAsset->GetEntryType();
if (entryType == AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntryType::Source)
{
EBUS_EVENT(Context_DocumentManagement::Bus, OnLoadDocument, filePath, true);
}
}
});
}
@@ -372,9 +376,17 @@ namespace LUAEditor
StringFilter* stringFilter = new StringFilter();
stringFilter->SetFilterPropagation(AssetTypeFilter::PropagateDirection::Up);
connect(m_gui->m_assetBrowserSearchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, this, [stringFilter](const QString& newString)
connect(m_gui->m_assetBrowserSearchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, this, [&, stringFilter](const QString& newString)
{
stringFilter->SetFilterString(newString);
if (newString.isEmpty())
{
m_gui->m_assetBrowserTreeView->collapseAll();
}
else
{
m_gui->m_assetBrowserTreeView->expandAll();
}
});
// Construct the final filter where they are all and'd together
@@ -1280,7 +1292,7 @@ namespace LUAEditor
// go to that line of the selected file.
lineNumber = dlg.getLineNumber();
currentView->SetCursorPosition(lineNumber - 1, 0);
currentView->SetCursorPosition(lineNumber, 0);
}
}
@@ -2141,24 +2153,7 @@ namespace LUAEditor
if (event->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Control)
{
TrackedLUACtrlTabOrder::iterator tabIter = m_CtrlTabOrder.begin();
while (tabIter != m_CtrlTabOrder.end())
{
if (*tabIter == m_lastFocusedAssetId)
{
// store the visible top window and make it the list's topmost
m_StoredTabAssetId = m_lastFocusedAssetId;
m_CtrlTabOrder.erase(tabIter);
m_CtrlTabOrder.push_front(m_lastFocusedAssetId);
break;
}
++tabIter;
}
}
else if (keyEvent->key() == Qt::Key_C && (keyEvent->modifiers() & Qt::ControlModifier))
if (keyEvent->key() == Qt::Key_C && (keyEvent->modifiers() & Qt::ControlModifier))
{
OnEditMenuCopy();
return true;
@@ -2174,32 +2169,6 @@ namespace LUAEditor
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Control)
{
// reconfigure the ctrl+tab stack to set the next document to be the stored guid
// which was recorded when Ctrl was first pressed
TrackedLUACtrlTabOrder::iterator tabIter = m_CtrlTabOrder.begin();
while (tabIter != m_CtrlTabOrder.end())
{
if (*tabIter == m_StoredTabAssetId)
{
m_CtrlTabOrder.erase(tabIter);
tabIter = m_CtrlTabOrder.begin();
++tabIter;
if (tabIter != m_CtrlTabOrder.end())
{
m_CtrlTabOrder.insert(tabIter, m_StoredTabAssetId);
}
else
{
m_CtrlTabOrder.push_back(m_StoredTabAssetId);
}
break;
}
++tabIter;
}
m_StoredTabAssetId = "";
}
}
@@ -2210,46 +2179,67 @@ namespace LUAEditor
void LUAEditorMainWindow::OnTabForwards()
{
// pop the first entry and push it to the last spot
TrackedLUACtrlTabOrder::iterator tabIter = m_CtrlTabOrder.begin();
if (tabIter != m_CtrlTabOrder.end())
{
AZStd::string assetId = *tabIter;
m_CtrlTabOrder.pop_front();
m_CtrlTabOrder.push_back(assetId);
// then grab the new first entry and pass it on to the widgetry
tabIter = m_CtrlTabOrder.begin();
TrackedLUAViewMap::iterator viewInfoIter = m_dOpenLUAView.find(*tabIter);
if (viewInfoIter != m_dOpenLUAView.end())
while (tabIter != m_CtrlTabOrder.end())
{
if (*tabIter == m_lastFocusedAssetId)
{
viewInfoIter->second.luaDockWidget()->show();
viewInfoIter->second.luaDockWidget()->raise();
viewInfoIter->second.luaViewWidget()->setFocus();
break;
}
tabIter++;
}
if (tabIter == m_CtrlTabOrder.begin())
{
tabIter = m_CtrlTabOrder.end();
--tabIter;
}
else
{
--tabIter;
}
TrackedLUAViewMap::iterator viewInfoIter = m_dOpenLUAView.find(*tabIter);
if (viewInfoIter != m_dOpenLUAView.end())
{
viewInfoIter->second.luaDockWidget()->show();
viewInfoIter->second.luaDockWidget()->raise();
viewInfoIter->second.luaViewWidget()->setFocus();
m_lastFocusedAssetId = *tabIter;
}
}
void LUAEditorMainWindow::OnTabBackwards()
{
// pop the last entry and push it to the first spot
TrackedLUACtrlTabOrder::iterator tabIter = m_CtrlTabOrder.end();
--tabIter;
if (tabIter != m_CtrlTabOrder.end())
{
TrackedLUACtrlTabOrder::iterator tabIter = m_CtrlTabOrder.begin();
while (tabIter != m_CtrlTabOrder.end())
{
AZStd::string assetId = *tabIter;
m_CtrlTabOrder.pop_back();
m_CtrlTabOrder.push_front(assetId);
// then grab the new first entry and pass it on to the widgetry
tabIter = m_CtrlTabOrder.begin();
TrackedLUAViewMap::iterator viewInfoIter = m_dOpenLUAView.find(*tabIter);
if (viewInfoIter != m_dOpenLUAView.end())
if (*tabIter == m_lastFocusedAssetId)
{
viewInfoIter->second.luaDockWidget()->show();
viewInfoIter->second.luaDockWidget()->raise();
viewInfoIter->second.luaViewWidget()->setFocus();
break;
}
tabIter++;
}
if (tabIter == m_CtrlTabOrder.end())
{
return;
}
tabIter++;
if (tabIter == m_CtrlTabOrder.end())
{
tabIter = m_CtrlTabOrder.begin();
}
TrackedLUAViewMap::iterator viewInfoIter = m_dOpenLUAView.find(*tabIter);
if (viewInfoIter != m_dOpenLUAView.end())
{
viewInfoIter->second.luaDockWidget()->show();
viewInfoIter->second.luaDockWidget()->raise();
viewInfoIter->second.luaViewWidget()->setFocus();
m_lastFocusedAssetId = *tabIter;
}
}
+22 -11
View File
@@ -964,9 +964,11 @@ namespace LUAEditor
int endLine;
auto newText = AcumulateSelectedLines(startLine, endLine, callable);
SetSelection(startLine, 0, endLine + 1, 0);
SetSelection(startLine + 1, 0, endLine + 2, 0);
RemoveSelectedText();
SetCursorPosition(startLine + 1, 0);
ReplaceSelectedText(newText);
SetSelection(startLine, 0, endLine + 1, 0);
SetSelection(startLine + 1, 0, endLine + 1, INT_MAX);
}
void LUAViewWidget::CommentSelectedLines()
@@ -1002,21 +1004,30 @@ namespace LUAEditor
{
int startLine;
int endLine;
auto newText = AcumulateSelectedLines(startLine, endLine, [&](QString& newText, QTextBlock& block)
auto currText = AcumulateSelectedLines(startLine, endLine, [&](QString& newText, QTextBlock& block)
{
newText.append(block.text());
newText.append("\n");
});
currText.remove(currText.count() - 1, 1);
if (startLine == 0)
{
return;
}
SetSelection(startLine - 1, INT_MAX, endLine, INT_MAX);
auto upText = GetLineText(startLine -1);
SetSelection(startLine, 0, startLine, INT_MAX);
RemoveSelectedText();
SetCursorPosition(startLine - 1, 0);
ReplaceSelectedText(newText);
SetSelection(startLine - 1, 0, endLine - 1, INT_MAX);
SetSelection(startLine + 1, 0, endLine + 1, INT_MAX);
RemoveSelectedText();
SetCursorPosition(startLine , 0);
ReplaceSelectedText(currText);
SetCursorPosition(endLine + 1, 0);
ReplaceSelectedText(upText);
SetSelection(startLine, 0, endLine, INT_MAX);
}
void LUAViewWidget::MoveSelectedLinesDn()
@@ -1040,11 +1051,11 @@ namespace LUAEditor
newText.prepend("\n");
}
SetSelection(startLine, 0, endLine + 1, 0);
SetSelection(startLine + 1, 0, endLine + 2, 0);
RemoveSelectedText();
SetCursorPosition(startLine + 1, 0);
SetCursorPosition(startLine + 2, 0);
ReplaceSelectedText(newText);
SetSelection(startLine + 1, 0, endLine + 1, INT_MAX);
SetSelection(startLine + 2, 0, endLine + 2, INT_MAX);
}
void LUAViewWidget::SetReadonly(bool readonly)
@@ -8,4 +8,4 @@
#pragma once
#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS false
#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS true
@@ -19,6 +19,7 @@ namespace O3DE::ProjectManager
QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix);
return AZ::Success(QStringList{ ProjectCMakeCommand,
"-G", "Visual Studio 16 2019",
"-B", targetBuildPath,
"-S", m_projectInfo.m_path,
QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath) } );
@@ -77,7 +77,7 @@ namespace O3DE::ProjectManager
if (vsWhereFile.exists() && vsWhereFile.isFile())
{
QStringList vsWhereBaseArguments = QStringList{"-version",
"16.9.2",
"[16.9.2,17)",
"-latest",
"-requires",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64"};
@@ -106,10 +106,8 @@ namespace O3DE::ProjectManager
}
return AZ::Failure(QObject::tr("Visual Studio 2019 version 16.9.2 or higher not found.<br><br>"
"Visual Studio 2019 is required to build this project."
" Install any edition of <a href='https://visualstudio.microsoft.com/downloads/'>Visual Studio 2019</a>"
" or update to a newer version before proceeding to the next step."
" While installing configure Visual Studio with these <a href='https://o3de.org/docs/welcome-guide/setup/requirements/#visual-studio-configuration'>workloads</a>."));
"A compatible version of Visual Studio is required to build this project.<br>"
"Refer to the <a href='https://o3de.org/docs/welcome-guide/requirements/#microsoft-visual-studio'>Visual Studio requirements</a> for more information."));
}
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath)
@@ -284,13 +284,14 @@ namespace O3DE::ProjectManager
{
currentButton = projectButtonIter.value();
currentButton->RestoreDefaultState();
m_projectsFlowLayout->addWidget(currentButton);
}
}
// Check whether project manager has successfully built the project
if (currentButton)
{
m_projectsFlowLayout->addWidget(currentButton);
bool projectBuiltSuccessfully = false;
SettingsInterface::Get()->GetProjectBuiltSuccessfully(projectBuiltSuccessfully, project);
@@ -341,6 +342,7 @@ namespace O3DE::ProjectManager
}
m_stack->setCurrentWidget(m_projectsContent);
m_projectsFlowLayout->update();
}
ProjectManagerScreen ProjectsScreen::GetScreenEnum()
@@ -399,7 +401,6 @@ namespace O3DE::ProjectManager
{
if (ProjectUtils::AddProjectDialog(this))
{
ResetProjectsContent();
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
}
}
@@ -490,7 +491,6 @@ namespace O3DE::ProjectManager
// Open file dialog and choose location for copied project then register copy with O3DE
if (ProjectUtils::CopyProjectDialog(projectInfo.m_path, newProjectInfo, this))
{
ResetProjectsContent();
emit NotifyBuildProject(newProjectInfo);
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
}
@@ -503,7 +503,6 @@ namespace O3DE::ProjectManager
// Unregister Project from O3DE and reload projects
if (ProjectUtils::UnregisterProject(projectPath))
{
ResetProjectsContent();
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
}
}
@@ -17,6 +17,12 @@ namespace AZ
{
namespace DataTypes
{
enum class ScriptProcessorFallbackLogic
{
FailBuild, // this will log error & fail the build
ContinueBuild // this will log the errors but continue the build logic
};
class IScriptProcessorRule
: public IRule
{
@@ -26,6 +32,8 @@ namespace AZ
virtual ~IScriptProcessorRule() override = default;
virtual const AZStd::string& GetScriptFilename() const = 0;
virtual ScriptProcessorFallbackLogic GetScriptProcessorFallbackLogic() const = 0;
};
} // DataTypes
} // SceneAPI
@@ -173,10 +173,13 @@ namespace AZ::SceneAPI::Behaviors
UnloadPython();
}
bool ScriptProcessorRuleBehavior::LoadPython(const AZ::SceneAPI::Containers::Scene& scene, AZStd::string& scriptPath)
bool ScriptProcessorRuleBehavior::LoadPython(const AZ::SceneAPI::Containers::Scene& scene, AZStd::string& scriptPath, Events::ProcessingResult& fallbackResult)
{
using namespace AZ::SceneAPI;
fallbackResult = Events::ProcessingResult::Failure;
int scriptDiscoveryAttempts = 0;
const AZ::SceneAPI::Containers::SceneManifest& manifest = scene.GetManifest();
const Containers::SceneManifest& manifest = scene.GetManifest();
auto view = Containers::MakeDerivedFilterView<DataTypes::IScriptProcessorRule>(manifest.GetValueStorage());
for (const auto& scriptItem : view)
{
@@ -188,6 +191,8 @@ namespace AZ::SceneAPI::Behaviors
}
++scriptDiscoveryAttempts;
fallbackResult = (scriptItem.GetScriptProcessorFallbackLogic() == DataTypes::ScriptProcessorFallbackLogic::ContinueBuild) ?
Events::ProcessingResult::Ignored : Events::ProcessingResult::Failure;
// check for file exist via absolute path
if (!IO::FileIOBase::GetInstance()->Exists(scriptFilename.c_str()))
@@ -301,7 +306,8 @@ namespace AZ::SceneAPI::Behaviors
}
};
if (LoadPython(context.GetScene(), scriptPath))
[[maybe_unused]] Events::ProcessingResult fallbackResult;
if (LoadPython(context.GetScene(), scriptPath, fallbackResult))
{
EditorPythonConsoleNotificationHandler logger;
m_editorPythonEventsInterface->ExecuteWithLock(executeCallback);
@@ -333,8 +339,9 @@ namespace AZ::SceneAPI::Behaviors
return Events::ProcessingResult::Ignored;
}
Events::ProcessingResult fallbackResult;
AZStd::string scriptPath;
if (LoadPython(scene, scriptPath))
if (LoadPython(scene, scriptPath, fallbackResult))
{
AZStd::string manifestUpdate;
auto executeCallback = [&scene, &manifestUpdate, &scriptPath]()
@@ -349,6 +356,12 @@ namespace AZ::SceneAPI::Behaviors
EditorPythonConsoleNotificationHandler logger;
m_editorPythonEventsInterface->ExecuteWithLock(executeCallback);
// if the returned scene manifest is empty then ignore the script update
if (manifestUpdate.empty())
{
return Events::ProcessingResult::Ignored;
}
EntityUtilityBus::Broadcast(&EntityUtilityBus::Events::ResetEntityContext);
AZ::Interface<Prefab::PrefabSystemComponentInterface>::Get()->RemoveAllTemplates();
@@ -364,6 +377,11 @@ namespace AZ::SceneAPI::Behaviors
}
return Events::ProcessingResult::Success;
}
else
{
// if the manifest was not updated by the script, then return back the fallback result
return fallbackResult;
}
}
return Events::ProcessingResult::Ignored;
}
@@ -54,7 +54,7 @@ namespace AZ::SceneAPI::Behaviors
SCENE_DATA_API void GetManifestDependencyPaths(AZStd::vector<AZStd::string>& paths) override;
protected:
bool LoadPython(const AZ::SceneAPI::Containers::Scene& scene, AZStd::string& scriptPath);
bool LoadPython(const AZ::SceneAPI::Containers::Scene& scene, AZStd::string& scriptPath, Events::ProcessingResult& fallbackResult);
void UnloadPython();
bool DoPrepareForExport(Events::PreExportEventContext& context);
@@ -14,6 +14,9 @@
namespace AZ
{
// Enum types must have a TypeId tied to it in order for the reflection to succeed.
AZ_TYPE_INFO_SPECIALIZE(SceneAPI::DataTypes::ScriptProcessorFallbackLogic, "{3DCABF3D-E8EF-43E7-B3C7-373E05825F60}");
namespace SceneAPI
{
namespace SceneData
@@ -23,13 +26,23 @@ namespace AZ
return m_scriptFilename;
}
DataTypes::ScriptProcessorFallbackLogic ScriptProcessorRule::GetScriptProcessorFallbackLogic() const
{
return m_fallbackLogic;
}
void ScriptProcessorRule::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<ScriptProcessorRule, DataTypes::IScriptProcessorRule>()->Version(1)
->Field("scriptFilename", &ScriptProcessorRule::m_scriptFilename);
serializeContext->Class<ScriptProcessorRule, DataTypes::IScriptProcessorRule>()->Version(2)
->Field("scriptFilename", &ScriptProcessorRule::m_scriptFilename)
->Field("fallbackLogic", &ScriptProcessorRule::m_fallbackLogic);
serializeContext->Enum<DataTypes::ScriptProcessorFallbackLogic>()
->Value("FailBuild", DataTypes::ScriptProcessorFallbackLogic::FailBuild)
->Value("ContinueBuild", DataTypes::ScriptProcessorFallbackLogic::ContinueBuild);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
@@ -35,10 +35,13 @@ namespace AZ
m_scriptFilename = AZStd::move(scriptFilename);
}
DataTypes::ScriptProcessorFallbackLogic GetScriptProcessorFallbackLogic() const override;
static void Reflect(ReflectContext* context);
protected:
AZStd::string m_scriptFilename;
DataTypes::ScriptProcessorFallbackLogic m_fallbackLogic = DataTypes::ScriptProcessorFallbackLogic::FailBuild;
};
} // SceneData
} // SceneAPI
@@ -11,6 +11,7 @@
#include <SceneAPI/SceneCore/Containers/SceneManifest.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/IScriptProcessorRule.h>
#include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
#include <SceneAPI/SceneData/ReflectionRegistrar.h>
#include <SceneAPI/SceneData/Rules/CoordinateSystemRule.h>
#include <SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h>
@@ -271,5 +272,79 @@ namespace AZ
auto update = scriptProcessorRuleBehavior.UpdateManifest(scene, AssetImportRequest::Update, AssetImportRequest::Generic);
EXPECT_EQ(update, ProcessingResult::Ignored);
}
TEST_F(SceneManifest_JSON, ScriptProcessorRule_DefaultFallbackLogic_Works)
{
using namespace AZ::SceneAPI;
constexpr const char* defaultJson = { R"JSON(
{
"values": [
{
"$type": "ScriptProcessorRule",
"scriptFilename": "foo.py"
}
]
})JSON" };
auto scene = Containers::Scene("mock");
auto result = scene.GetManifest().LoadFromString(defaultJson, m_serializeContext.get(), m_jsonRegistrationContext.get());
EXPECT_TRUE(result.IsSuccess());
EXPECT_FALSE(scene.GetManifest().IsEmpty());
ASSERT_EQ(scene.GetManifest().GetEntryCount(), 1);
auto view = Containers::MakeDerivedFilterView<DataTypes::IScriptProcessorRule>(scene.GetManifest().GetValueStorage());
EXPECT_EQ(view.begin()->GetScriptProcessorFallbackLogic(), DataTypes::ScriptProcessorFallbackLogic::FailBuild);
}
TEST_F(SceneManifest_JSON, ScriptProcessorRule_ExplicitFallbackLogic_Works)
{
using namespace AZ::SceneAPI;
constexpr const char* fallbackLogicJson = { R"JSON(
{
"values": [
{
"$type": "ScriptProcessorRule",
"scriptFilename": "foo.py",
"fallbackLogic": "FailBuild"
}
]
})JSON" };
auto scene = Containers::Scene("mock");
auto result = scene.GetManifest().LoadFromString(fallbackLogicJson, m_serializeContext.get(), m_jsonRegistrationContext.get());
EXPECT_TRUE(result.IsSuccess());
EXPECT_FALSE(scene.GetManifest().IsEmpty());
ASSERT_EQ(scene.GetManifest().GetEntryCount(), 1);
auto view = Containers::MakeDerivedFilterView<DataTypes::IScriptProcessorRule>(scene.GetManifest().GetValueStorage());
EXPECT_EQ(view.begin()->GetScriptProcessorFallbackLogic(), DataTypes::ScriptProcessorFallbackLogic::FailBuild);
}
TEST_F(SceneManifest_JSON, ScriptProcessorRule_ContinueBuildFallbackLogic_Works)
{
using namespace AZ::SceneAPI;
constexpr const char* fallbackLogicJson = { R"JSON(
{
"values": [
{
"$type": "ScriptProcessorRule",
"scriptFilename": "foo.py",
"fallbackLogic": "ContinueBuild"
}
]
})JSON" };
auto scene = Containers::Scene("mock");
auto result = scene.GetManifest().LoadFromString(fallbackLogicJson, m_serializeContext.get(), m_jsonRegistrationContext.get());
EXPECT_TRUE(result.IsSuccess());
EXPECT_FALSE(scene.GetManifest().IsEmpty());
ASSERT_EQ(scene.GetManifest().GetEntryCount(), 1);
auto view = Containers::MakeDerivedFilterView<DataTypes::IScriptProcessorRule>(scene.GetManifest().GetValueStorage());
EXPECT_EQ(view.begin()->GetScriptProcessorFallbackLogic(), DataTypes::ScriptProcessorFallbackLogic::ContinueBuild);
}
}
}