Files
o3de/Gems/CustomAssetExample/Code/Source/CustomAssetExample/Builder/CustomAssetExampleBuilderWorker.cpp
T
2021-03-08 14:30:57 -08:00

250 lines
15 KiB
C++

/*
* All or portions of this file Copyright(c) Amazon.com, Inc.or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
*or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <CustomAssetExample/Builder/CustomAssetExampleBuilderWorker.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace CustomAssetExample
{
// Note - Shutdown will be called on a different thread than your process job thread
void ExampleBuilderWorker::ShutDown()
{
m_isShuttingDown = true;
}
// CreateJobs will be called early on in the file scanning pass from the Asset Processor.
// You should create the same jobs, and avoid checking whether the job is up to date or not. The Asset Processor will manage this for you
void ExampleBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
{
if (m_isShuttingDown)
{
response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
return;
}
AZStd::string ext;
AzFramework::StringFunc::Path::GetExtension(request.m_sourceFile.c_str(), ext, false);
// Our *.example extension details a source file with NO dependencies.
// Here we simply create the JobDescriptors for each enabled platform in order to process the source file.
if (AzFramework::StringFunc::Equal(ext.c_str(), "example"))
{
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
// We create a simple job here which only contains the identifying job key and the platform to process the file on
AssetBuilderSDK::JobDescriptor descriptor;
descriptor.m_jobKey = "Compile Example";
descriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
// Note that there are additional parameters for the JobDescriptor which may be beneficial in your use case.
// Notable ones include:
// * m_critical - a boolean that flags this job as one which must complete before the Editor will start up.
// * m_priority - an integer where larger values signify that the job should be processed with higher priority than those with lower values.
// Please see the JobDescriptor for the full complement of configuration parameters.
response.m_createJobOutputs.push_back(descriptor);
// One builder can make multiple jobs for the same source file, for the same platform, as long as it emits a different job key per job.
// This allows you to break large compilations up into smaller jobs. Jobs emitted in this manner may be run in parallel
descriptor.m_jobKey = "Second Compile Example";
// Custom parameters that you may need to know about when the job processes can be added to m_jobParameters
descriptor.m_jobParameters[AZ_CRC("hello", 0x3610a686)] = "World";
response.m_createJobOutputs.push_back(descriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
// Our *.examplesource extension details a source file with dependencies.
// Here we declare source file dependencies and forward the info to the Asset Processor
// This example creates the following dependencies:
// * the source file .../test.examplesource depends on the source file .../test.exampleinclude
// * the source file .../test.exampleinclude depends on the source file .../common.exampleinclude
// * the source file .../common.exampleinclude depends on the non-source file .../common.examplefile
// Note - both file extensions "exampleinclude" and "examplesource" are handled by this builder class.
// However, files with extension "exampleinclude" do not create JobDescriptors, so they are not actually being processed by this builder.
// We are only collecting their dependencies here.
AssetBuilderSDK::SourceFileDependency sourceFileDependencyInfo;
AZStd::string fullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullPath, false);
AzFramework::StringFunc::Path::Normalize(fullPath);
AZStd::string relPath = request.m_sourceFile;
// Source files in this example generate dependencies on a file with the same name, but having "exampleinclude" extensions
if (AzFramework::StringFunc::Equal(ext.c_str(), "examplesource"))
{
AzFramework::StringFunc::Path::ReplaceExtension(relPath, "exampleinclude");
// Declare and add the dependency on the *.exampleinclude file:
sourceFileDependencyInfo.m_sourceFileDependencyPath = relPath;
response.m_sourceFileDependencyList.push_back(sourceFileDependencyInfo);
// Since we're a source file, we also add a job to do the actual compilation (for each enabled platform)
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
AssetBuilderSDK::JobDescriptor descriptor;
descriptor.m_jobKey = "Compile Example";
descriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
// you can also place whatever parameters you want to save for later into this map:
descriptor.m_jobParameters[AZ_CRC("hello", 0x3610a686)] = "World";
response.m_createJobOutputs.push_back(descriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
if (AzFramework::StringFunc::Equal(ext.c_str(), "exampleinclude"))
{
if (AzFramework::StringFunc::Find(request.m_sourceFile.c_str(), "common.exampleinclude") != AZStd::string::npos)
{
// Add any dependencies that common.exampleinclude would like to depend on here, we can also add a non source file as a dependency like we are doing here
AzFramework::StringFunc::Path::ReplaceFullName(fullPath, "common.examplefile");
sourceFileDependencyInfo.m_sourceFileDependencyPath = fullPath;
}
else
{
AzFramework::StringFunc::Path::ReplaceFullName(fullPath, "common.exampleinclude");
// Assigning full path to sourceFileDependency path
sourceFileDependencyInfo.m_sourceFileDependencyPath = fullPath;
}
response.m_sourceFileDependencyList.push_back(sourceFileDependencyInfo);
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
// This example shows how you would be able to declare job dependencies on source files inside a builder and
// forward that info to the asset processor.
// Basically here we are creating a job dependency such that the job with source file ./test.examplejob and
// jobKey "Compile Example" depends on the fingerprint of the job with source file ./test.examplesource and jobkey "Compile Example".
else if (AzFramework::StringFunc::Equal(ext.c_str(), "examplejob"))
{
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
AssetBuilderSDK::JobDescriptor descriptor;
descriptor.m_jobKey = "Compile Example";
descriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
AssetBuilderSDK::SourceFileDependency sourceFile;
sourceFile.m_sourceFileDependencyPath = "test.examplesource";
AssetBuilderSDK::JobDependency jobDependency("Compile Example", platformInfo.m_identifier.c_str(), AssetBuilderSDK::JobDependencyType::Fingerprint, sourceFile);
descriptor.m_jobDependencyList.push_back(jobDependency);
response.m_createJobOutputs.push_back(descriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
AZ_Assert(false, "Unhandled extension type in CustomExampleAssetBuilderWorker.");
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
}
// In this example builder class, we just copy the source file to a modified destination path in the temp directory.
// Later on, this function will be called for jobs the Asset Processor has determined need to be run.
// The request will contain the CreateJobResponse you constructed earlier, including any key value pairs you placed into m_jobParameters
void ExampleBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
{
// This is the most basic example of handling for cancellation requests.
// If possible, you should listen for cancellation requests and then cancel processing work to facilitate faster shutdown of the Asset Processor
// If you need to do more things such as signal a semaphore or other threading work, derive from the Job Cancel Listener and reimplement Cancel()
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
// Use AZ_TracePrintf to communicate job details. The logging system will automatically file the text under the appropriate log file and category.
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Starting Job.\n");
AZStd::string fileName;
AzFramework::StringFunc::Path::GetFullFileName(request.m_fullPath.c_str(), fileName);
AZStd::string ext;
AzFramework::StringFunc::Path::GetExtension(request.m_sourceFile.c_str(), ext, false);
if (AzFramework::StringFunc::Equal(ext.c_str(), "example"))
{
if (AzFramework::StringFunc::Equal(request.m_jobDescription.m_jobKey.c_str(), "Compile Example"))
{
AzFramework::StringFunc::Path::ReplaceExtension(fileName, "example1");
}
else if (AzFramework::StringFunc::Equal(request.m_jobDescription.m_jobKey.c_str(), "Second Compile Example"))
{
AzFramework::StringFunc::Path::ReplaceExtension(fileName, "example2");
}
}
else if (AzFramework::StringFunc::Equal(ext.c_str(), "examplesource"))
{
AzFramework::StringFunc::Path::ReplaceExtension(fileName, "examplesourceprocessed");
}
else if (AzFramework::StringFunc::Equal(ext.c_str(), "examplejob"))
{
AzFramework::StringFunc::Path::ReplaceExtension(fileName, "examplejobprocessed");
}
// All your work should happen inside the tempDirPath.
// The Asset Processor will handle taking the completed files you specify in JobProduct.m_outputProducts from the temp directory into the cache.
AZStd::string destPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), fileName.c_str(), destPath, true);
// Check if we are cancelled or shutting down before doing intensive processing on this source file
if (jobCancelListener.IsCancelled())
{
AZ_TracePrintf(AssetBuilderSDK::WarningWindow, "Cancel was requested for job %s.\n", request.m_fullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
if (m_isShuttingDown)
{
AZ_TracePrintf(AssetBuilderSDK::WarningWindow, "Cancelled job %s because shutdown was requested.\n", request.m_fullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
AZ::IO::LocalFileIO fileIO;
if (fileIO.Copy(request.m_fullPath.c_str(), destPath.c_str()) != AZ::IO::ResultCode::Success)
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Error during processing job %s.\n", request.m_fullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// Push all products successfully built into the JobProduct.m_outputProducts.
// Filepaths can be absolute, or relative to your temporary directory.
// The job request struct has the temp directory, so it will be properly reconstructed to an absolute path later.
AssetBuilderSDK::JobProduct jobProduct(fileName);
// Note - you must also add the asset type to the JobProduct.
// If you have direct access to the type within your gem, you can grab the asset type directly:
// jobProduct.m_productAssetType = return AZ::AzTypeInfo<CustomAssetExample>::Uuid();
// If you need to cross a gem boundary, you can use the AssetTypeInfo EBus and EBusFindAssetTypeByName:
// EBusFindAssetTypeByName assetType("customassetexample");
// AssetTypeInfoBus::BroadcastResult(assetType, &AssetTypeInfo::GetAssetType);
// jobProduct.m_productAssetType = assetType.GetAssetType();
// You should also pick a unique "SubID" for each product. The final address of an asset (the AssetId) is the combination
// of the subId you choose here and the source file's UUID, so if it is not unique, errors will be generated since your
// assets will shadow each other's address, and not be accessible via AssetId.
// you can pick whatever scheme you want but you should ensure stablility in your choice.
// For example, do not use random numbers - ideally no matter what happens, each time you run this process, the same
// subIds are chosen for the same logical asset (even if your builder starts emitting more or different assets out of the same source)
// You can use AssetBuilderSDK::ConstructSubID(...) helper function if you want to use various bits of the subID for things like LOD level
// or you can come up with your own scheme to ensure stability, using the 32-bit address space as you see fit. It only has to be unique
// and stable within the confines of a single source file, it is not globally unique.
jobProduct.m_productSubID = 0;
// once you've filled up the details of the product in jobProduct, add it to the result list:
response.m_outputProducts.push_back(jobProduct);
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
}