Files
o3de/Gems/AudioEngineWwise/Code/Source/Builder/AudioControlBuilderWorker.cpp
T
lumberyard-employee-dm f648cb1fd8 Update the FileIO Aliases (#4186)
* Update the FileIOAlias naming to make the cache, project root and engine
root paths more clear

The alias of `@root@`, `@assets@`, and `@projectplatformcache@` has been
collapsed to `@projectproductassets@`

The alias of `@devroot@` and `@engroot@` has been collapsed to
`@engroot@`

The alias of `@devassets@` and `@projectroot@` has been collapsed to
`@projectroot@`

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated use of devassets and devroot properties in python

Those properties now use projectroot and engroot

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updating the alias @engroot@ alias path comment in each platform specific LocalFileIO_*.cpp file

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed hardcoded size of 9 for the product asset alias.

The ResolvePath function now just appends the @projectproductassets@
alias with the input path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Remove duplicate @projectproductassets@ check in ProcessFileTreeRequest

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fix for typos in Hydra python test

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated LocalFileIO::Copy call on Windows to use the Unicode aware CopyFileW API

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the AWSMetreicsGemAllocatorFixture to properly suppress asset
cache write errors for Test file creation.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed unneeded call to set the @projectproductasstes@ alias at the bottom of the AssetSeedManagerTest SetUp

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Added a deprecated alias map to the FileIO System

When a deprecated alias is accessed, the FileIO System logs an AZ_Error and indicates the alias that should be used

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated python test scripts to use the projectroot binding

Retrieving the AutomatedTesting project path based on "<devroot>/AutomatedTesting" has been removed.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated references to devroot and devgame within the codebase

The GetAbsoluteDevGameFolderPath functions has been replaced with direct call to AZ::Utils::GetProjectPath
The GetAbsoluteDevRootFolderPath functions has been replaced with direct calls to AZ::Utils::GetEnginePath

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated <engroot>/AutomatedTesting references to projectroot


Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Replaced references that assumes the project path is <engroot>/AutomatedTesting with <projectroot> in the AutomatedTesting python test

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Correct casing in emfxworkspace file


Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed newly added AppendPathParts function
Removed the Path constructors which accepts a PathIterable instance

The PathIterable isn't safe to return to a user of the Path class as it might be referencing temporary paths supplied via PathView arguments

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed unused parameter warning

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Undid change to the LexicallyProximate function to set the path result to the base path.

It needs to return the *this path if the pathResult is empty

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Moved the LocalFileIO ConvertToAbsolutePath implementations to AZ::Utils

Fixed the ConvertToAbsolutePath implementation for Unix platforms to use a buffer that is size PATH_MAX(4096 on all our supported Unix platforms).
Because the buffer before was AZ::IO::MaxPathLength which as a size of 1024, this was resulting in the Unix `realpath` function smashing the stack when it converts a path to an absolute path that is longer than 1024 characters

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the EditorCore.Tests to attach the AZ Environment to the EditorCore shared library that is statically loaded on launch.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed for DeprecatedAlaisesKeyVisitor Visit function causing the non string_view overloads being hidden causing a hidden base class function warning

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Changed the AWSMetricsGemMock to use a temporary for writing test assets

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the LocalFileIO::ResolvePath function to use HasRootPath to determine if a path starts with a root path such as "D:", "D:/" or "/"

IsAbsolute was not the corect check as the path of "D:" is a relative
path.
To be absolute according to the Windows the path must have a root
directory. i.e "D:/" or "D:\"

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed absolute path comment from LocalFile_UnixLike.cpp and LocalFile_Android.cpp FindFiles implementations
Updated the ConvertToAlias to supply an AZ::IO::FixedMaxPath

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Replaced usage of the @projectproductassets@ alias with @engroot@ when referring to the LmbrCentral source folder in the CopyDependencyBuilderTest and the SeedBuilderTests

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the ScriptCanvas Upgrade Tool to output backed up files to the
Project User Path instead of the engine root

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed whitespacing issues in Application.cpp

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Remove unnecessary creation of a FixedMaxPath in the UpgradeTool.cpp

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Modified testSeedFolder variable in the SeedBuilderTests to use the
@engroot@ alias instead of @projectproductassets@/.. alias when
referring to the LmbrCentral Gem source path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated references to the Project Asset Cache in the PythonTests.

Those tests no longer use the logic `azlmbr.paths.projectroot / "Cache" / "pc"` to retrieve a path to the cache root but instead the `azlmbr.paths.projectproductassets` constant

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed the FileIO Deprecated Alias test on Windows

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removing @projectsourceassets@ alias, as it is only used once.

Updated the PhysX EditorSystemComponent.cpp to query the ProjectPath
form the SettingsRegistry.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Replaced @projectproductassets@ alias with @products@

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Rollback changes to the PhysX EditorSystemComponent.cpp

The changes to use the ProjectPath from the SettingsRegistry has been implemented in PR #4497

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-10-05 15:12:53 -05:00

551 lines
26 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Builder/AudioControlBuilderWorker.h>
#include <AzCore/AzCore_Traits_Platform.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/JSON/rapidjson.h>
#include <AzCore/JSON/document.h>
#include <AzCore/PlatformId/PlatformId.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <ATLCommon.h>
#include <Common_wwise.h>
#include <Config_wwise.h>
namespace AudioControlBuilder
{
namespace Internal
{
const char JsonEventsKey[] = "includedEvents";
const char SoundbankDependencyFileExtension[] = ".bankdeps";
const char NodeDoesNotExistMessage[] = "%s node does not exist. Please be sure that you have defined at least one %s for this Audio Control file.\n";
const char MalformedNodeMissingAttributeMessage[] = "%s node is malformed: does not have an attribute %s defined. This is likely the result of manual editing. Please resave the Audio Control file.\n";
const char MalformedNodeMissingChildNodeMessage[] = "%s node does not contain a child %s node. This is likely the result of manual editing. Please resave the Audio Control file.\n";
namespace Legacy
{
using AtlConfigGroupMap = AZStd::unordered_map<AZStd::string, const AZ::rapidxml::xml_node<char>*>;
AZStd::string GetAtlPlatformName(const AZStd::string& requestPlatform)
{
AZStd::string atlPlatform;
AZStd::string platform = requestPlatform;
// When debugging a builder using a debug task, it replaces platform tags with "debug platform". Make sure the builder
// actually uses the host platform identifier when going through this function in this case.
if (platform == "debug platform")
{
atlPlatform = AZ_TRAIT_OS_PLATFORM_NAME;
AZStd::to_lower(atlPlatform.begin(), atlPlatform.end());
}
else
{
if (platform == "pc")
{
atlPlatform = "windows";
}
else if (platform == "android")
{
atlPlatform = "android";
}
else if (platform == "mac")
{
atlPlatform = "mac";
}
else
{
atlPlatform = AZStd::move(platform);
}
}
return AZStd::move(atlPlatform);
}
AZ::Outcome<void, AZStd::string> BuildConfigGroupMap(const AZ::rapidxml::xml_node<char>* preloadRequestNode, AtlConfigGroupMap& configGroupMap)
{
AZ_Assert(preloadRequestNode != nullptr, NodeDoesNotExistMessage, Audio::ATLXmlTags::ATLPreloadRequestTag, "preload request");
auto configGroupNode = preloadRequestNode->first_node(Audio::ATLXmlTags::ATLConfigGroupTag);
while (configGroupNode)
{
// Populate the config group map by iterating over all ATLConfigGroup nodes, and place each one in the map keyed by the group's atl_name attribute...
if (const auto configGroupNameAttr = configGroupNode->first_attribute(Audio::ATLXmlTags::ATLNameAttribute))
{
configGroupMap.emplace(configGroupNameAttr->value(), configGroupNode);
}
else
{
return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
Audio::ATLXmlTags::ATLConfigGroupTag, Audio::ATLXmlTags::ATLNameAttribute));
}
configGroupNode = configGroupNode->next_sibling(Audio::ATLXmlTags::ATLConfigGroupTag);
}
// If no config groups are defined, this is an empty preload request with no banks referenced, which is valid.
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> GetBanksFromAtlPreloads(const AZ::rapidxml::xml_node<char>* preloadsNode, const AZStd::string& atlPlatformIdentifier, AZStd::vector<AZStd::string>& banksReferenced)
{
AZ_Assert(preloadsNode != nullptr, NodeDoesNotExistMessage, Audio::ATLXmlTags::PreloadsNodeTag, "preload request");
auto preloadRequestNode = preloadsNode->first_node(Audio::ATLXmlTags::ATLPreloadRequestTag);
if (!preloadRequestNode)
{
return AZ::Failure(AZStd::string::format(NodeDoesNotExistMessage,
Audio::ATLXmlTags::ATLPreloadRequestTag, "preload request"));
}
// For each preload request in the control file, determine which config group is used for this platform and register each
// bank listed in that preload request as a dependency.
while (preloadRequestNode)
{
AtlConfigGroupMap configGroupMap;
auto configGroupMapResult = BuildConfigGroupMap(preloadRequestNode, configGroupMap);
// If the returned map is empty, there are not banks referenced in the preload request, return the result here.
if (!configGroupMapResult.IsSuccess() || configGroupMap.size() == 0)
{
return configGroupMapResult;
}
const auto platformsNode = preloadRequestNode->first_node(Audio::ATLXmlTags::ATLPlatformsTag);
if (!platformsNode)
{
return AZ::Failure(AZStd::string::format(MalformedNodeMissingChildNodeMessage,
Audio::ATLXmlTags::ATLPreloadRequestTag, Audio::ATLXmlTags::ATLPlatformsTag));
}
auto platformNode = platformsNode->first_node(Audio::ATLXmlTags::PlatformNodeTag);
if (!platformNode)
{
return AZ::Failure(AZStd::string::format(MalformedNodeMissingChildNodeMessage,
Audio::ATLXmlTags::ATLPlatformsTag, Audio::ATLXmlTags::PlatformNodeTag));
}
AZStd::string configGroupName;
// For each platform node in the platform list, check the atl_name to see if it matches the platform the request is
// intended for. If it is, grab the name of the config group that is used for that platform to load it.
while (platformNode)
{
const auto atlNameAttr = platformNode->first_attribute(Audio::ATLXmlTags::ATLNameAttribute);
if (!atlNameAttr)
{
return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
Audio::ATLXmlTags::PlatformNodeTag, Audio::ATLXmlTags::ATLNameAttribute));
}
else if (atlPlatformIdentifier == atlNameAttr->value())
{
// We've found the right platform that matches the request, so grab the group name and stop looking through
// the list
const auto configGroupNameAttr = platformNode->first_attribute(Audio::ATLXmlTags::ATLConfigGroupAttribute);
if (!configGroupNameAttr)
{
return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
Audio::ATLXmlTags::PlatformNodeTag, Audio::ATLXmlTags::ATLConfigGroupAttribute));
}
configGroupName = configGroupNameAttr->value();
break;
}
platformNode = platformNode->next_sibling(Audio::ATLXmlTags::PlatformNodeTag);
}
const AZ::rapidxml::xml_node<char>* configGroupNode = configGroupMap[configGroupName];
if (!configGroupNode)
{
// The config group this platform uses isn't defined in the control file. This might be intentional, so just
// generate a warning and keep going to the next preload node.
AZ_TracePrintf("Audio Control Builder", "%s node for config group %s is not defined, so no banks are referenced.",
Audio::ATLXmlTags::ATLConfigGroupTag, configGroupName.c_str());
}
else
{
auto wwiseFileNode = configGroupNode->first_node(Audio::WwiseXmlTags::WwiseFileTag);
if (!wwiseFileNode)
{
return AZ::Failure(AZStd::string::format(MalformedNodeMissingChildNodeMessage,
Audio::ATLXmlTags::ATLConfigGroupTag, Audio::WwiseXmlTags::WwiseFileTag));
}
// For each WwiseFile (soundbank) referenced in the config group, grab the file name and add it to the reference list
while (wwiseFileNode)
{
const auto bankNameAttribute = wwiseFileNode->first_attribute(Audio::WwiseXmlTags::WwiseNameAttribute);
if (!bankNameAttribute)
{
return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
Audio::WwiseXmlTags::WwiseFileTag, Audio::WwiseXmlTags::WwiseNameAttribute));
}
// Prepend the bank name with the relative path to the wwise sounds folder to get relative path to the bank from
// the @products@ alias and push that into the list of banks referenced.
AZStd::string soundsPrefix = Audio::Wwise::DefaultBanksPath;
banksReferenced.emplace_back(soundsPrefix + bankNameAttribute->value());
wwiseFileNode = wwiseFileNode->next_sibling(Audio::WwiseXmlTags::WwiseFileTag);
}
}
preloadRequestNode = preloadRequestNode->next_sibling(Audio::ATLXmlTags::ATLPreloadRequestTag);
}
return AZ::Success();
}
} // namespace Legacy
AZ::Outcome<void, AZStd::string> BuildAtlEventList(const AZ::rapidxml::xml_node<char>* triggersNode, AZStd::vector<AZStd::string>& eventNames)
{
AZ_Assert(triggersNode != nullptr, NodeDoesNotExistMessage, Audio::ATLXmlTags::TriggersNodeTag, "trigger");
auto triggerNode = triggersNode->first_node(Audio::ATLXmlTags::ATLTriggerTag);
while (triggerNode)
{
// For each audio trigger, push the name of the Wwise event (if assigned) into the list.
// It's okay for an ATLTrigger node to not have a Wwise event associated with it.
if (const auto eventNode = triggerNode->first_node(Audio::WwiseXmlTags::WwiseEventTag))
{
if (const auto eventNameAttr = eventNode->first_attribute(Audio::WwiseXmlTags::WwiseNameAttribute))
{
eventNames.push_back(eventNameAttr->value());
}
else
{
return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
Audio::WwiseXmlTags::WwiseEventTag, Audio::WwiseXmlTags::WwiseNameAttribute));
}
}
triggerNode = triggerNode->next_sibling(Audio::ATLXmlTags::ATLTriggerTag);
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> GetBanksFromAtlPreloads(const AZ::rapidxml::xml_node<char>* preloadsNode, AZStd::vector<AZStd::string>& banksReferenced)
{
AZ_Assert(preloadsNode != nullptr, NodeDoesNotExistMessage, Audio::ATLXmlTags::PreloadsNodeTag, "preload request");
auto preloadRequestNode = preloadsNode->first_node(Audio::ATLXmlTags::ATLPreloadRequestTag);
if (!preloadRequestNode)
{
return AZ::Failure(AZStd::string::format(NodeDoesNotExistMessage, Audio::ATLXmlTags::ATLPreloadRequestTag, "preload request"));
}
// Loop through the ATLPreloadRequest nodes...
// Find any Wwise banks listed and add them to the banksReferenced vector.
while (preloadRequestNode)
{
// Attempt to find the child node in the New Xml format...
if (auto wwiseFileNode = preloadRequestNode->first_node(Audio::WwiseXmlTags::WwiseFileTag))
{
while (wwiseFileNode)
{
const auto bankNameAttr = wwiseFileNode->first_attribute(Audio::WwiseXmlTags::WwiseNameAttribute);
if (bankNameAttr)
{
AZStd::string soundsPrefix = Audio::Wwise::DefaultBanksPath;
banksReferenced.emplace_back(soundsPrefix + bankNameAttr->value());
}
else
{
return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
Audio::WwiseXmlTags::WwiseFileTag, Audio::WwiseXmlTags::WwiseNameAttribute));
}
wwiseFileNode = wwiseFileNode->next_sibling(Audio::WwiseXmlTags::WwiseFileTag);
}
}
else
{
return AZ::Failure(AZStd::string::format("Preloads Xml appears to be in an older format, trying Legacy parsing.\n"));
}
preloadRequestNode = preloadRequestNode->next_sibling(Audio::ATLXmlTags::ATLPreloadRequestTag);
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> GetEventsFromBankMetadata(const rapidjson::Value& rootObject, AZStd::set<AZStd::string>& eventNames)
{
if (!rootObject.IsObject())
{
return AZ::Failure(AZStd::string("The root of the metadata file is not an object. Please regenerate the metadata for this soundbank."));
}
// If the file doesn't define an events field, then there are no events in the bank
if (!rootObject.HasMember(JsonEventsKey))
{
return AZ::Success();
}
const rapidjson::Value& eventsArray = rootObject[JsonEventsKey];
if (!eventsArray.IsArray())
{
return AZ::Failure(AZStd::string("Events field is not an array. Please regenerate the metadata for this soundbank."));
}
for (rapidjson::SizeType eventIndex = 0; eventIndex < eventsArray.Size(); ++eventIndex)
{
eventNames.emplace(eventsArray[eventIndex].GetString());
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> GetEventsFromBank(const AZStd::string& bankMetadataPath, AZStd::set<AZStd::string>& eventNames)
{
if (!AZ::IO::SystemFile::Exists(bankMetadataPath.c_str()))
{
return AZ::Failure(AZStd::string::format("Failed to find the soundbank metadata file %s. Full dependency information cannot be determined without the metadata file. Please regenerate the metadata for this soundbank.", bankMetadataPath.c_str()));
}
AZ::u64 fileSize = AZ::IO::SystemFile::Length(bankMetadataPath.c_str());
if (fileSize == 0)
{
return AZ::Failure(AZStd::string::format("Soundbank metadata file at path %s is an empty file. Please regenerate the metadata for this soundbank.", bankMetadataPath.c_str()));
}
AZStd::vector<char> buffer(fileSize + 1);
buffer[fileSize] = 0;
if (!AZ::IO::SystemFile::Read(bankMetadataPath.c_str(), buffer.data()))
{
return AZ::Failure(AZStd::string::format("Failed to read the soundbank metadata file at path %s. Please make sure the file is not open or being edited by another program.", bankMetadataPath.c_str()));
}
rapidjson::Document bankMetadataDoc;
bankMetadataDoc.Parse(buffer.data());
if (bankMetadataDoc.GetParseError() != rapidjson::ParseErrorCode::kParseErrorNone)
{
return AZ::Failure(AZStd::string::format("Failed to parse soundbank metadata at path %s into JSON. Please regenerate the metadata for this soundbank.", bankMetadataPath.c_str()));
}
return GetEventsFromBankMetadata(bankMetadataDoc, eventNames);
}
} // namespace Internal
AudioControlBuilderWorker::AudioControlBuilderWorker()
: m_globalScopeControlsPath("libs/gameaudio/")
, m_isShuttingDown(false)
{
AZ::StringFunc::Path::Normalize(m_globalScopeControlsPath);
}
void AudioControlBuilderWorker::ShutDown()
{
m_isShuttingDown = true;
}
void AudioControlBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
{
if (m_isShuttingDown)
{
response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
return;
}
for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
{
if (info.m_identifier == "server")
{
continue;
}
AssetBuilderSDK::JobDescriptor descriptor;
descriptor.m_jobKey = "Audio Control";
descriptor.m_critical = true;
descriptor.SetPlatformIdentifier(info.m_identifier.c_str());
descriptor.m_priority = 0;
response.m_createJobOutputs.push_back(descriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
}
void AudioControlBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
{
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "AudioControlBuilderWorker Starting Job.\n");
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;
}
AZStd::string fileName;
AZ::StringFunc::Path::GetFullFileName(request.m_fullPath.c_str(), fileName);
AssetBuilderSDK::JobProduct jobProduct(request.m_fullPath);
if (!ParseProductDependencies(request, jobProduct.m_dependencies, jobProduct.m_pathDependencies))
{
AZ_Error(AssetBuilderSDK::ErrorWindow, false, "Error during parsing product dependencies for asset %s.\n", fileName.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
response.m_outputProducts.push_back(jobProduct);
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
bool AudioControlBuilderWorker::ParseProductDependencies(
const AssetBuilderSDK::ProcessJobRequest& request,
AZStd::vector<AssetBuilderSDK::ProductDependency>& productDependencies,
AssetBuilderSDK::ProductPathDependencySet& pathDependencies)
{
AZ::IO::FileIOStream fileStream;
if (!fileStream.Open(request.m_fullPath.c_str(), AZ::IO::OpenMode::ModeRead))
{
return false;
}
AZ::IO::SizeType length = fileStream.GetLength();
if (length == 0)
{
return false;
}
AZStd::vector<char> charBuffer;
charBuffer.resize_no_construct(length + 1);
fileStream.Read(length, charBuffer.data());
charBuffer.back() = 0;
// Get the XML root node
AZ::rapidxml::xml_document<char> xmlDoc;
if (!xmlDoc.parse<AZ::rapidxml::parse_no_data_nodes>(charBuffer.data()))
{
return false;
}
AZ::rapidxml::xml_node<char>* xmlRootNode = xmlDoc.first_node();
if (!xmlRootNode)
{
return false;
}
ParseProductDependenciesFromXmlFile(xmlRootNode,
request.m_fullPath,
request.m_sourceFile,
request.m_platformInfo.m_identifier,
productDependencies,
pathDependencies);
return true;
}
void AudioControlBuilderWorker::ParseProductDependenciesFromXmlFile(
const AZ::rapidxml::xml_node<char>* node,
const AZStd::string& fullPath,
[[maybe_unused]] const AZStd::string& sourceFile,
const AZStd::string& platformIdentifier,
[[maybe_unused]] AZStd::vector<AssetBuilderSDK::ProductDependency>& productDependencies,
AssetBuilderSDK::ProductPathDependencySet& pathDependencies)
{
AZ_Assert(node != nullptr, "AudioControlBuilderWorker::ParseProductDependenciesFromXmlFile - null xml root node!\n");
const auto preloadsNode = node->first_node(Audio::ATLXmlTags::PreloadsNodeTag);
if (!preloadsNode)
{
// No preloads were defined in this control file, so we can return. If triggers are defined in this preload file, we can't
// validate that they'll be playable because we are unsure of what other control files for the given scope are defined.
return;
}
// Collect any references to soundbanks, initially use the newer parsing format...
AZStd::vector<AZStd::string> banksReferenced;
AZ::Outcome<void, AZStd::string> gatherBankReferencesResult = Internal::GetBanksFromAtlPreloads(preloadsNode, banksReferenced);
if (!gatherBankReferencesResult)
{
// Legacy...
// Convert platform name to platform name that is used by wwise and ATL.
AZStd::string atlPlatformName = AZStd::move(Internal::Legacy::GetAtlPlatformName(platformIdentifier));
gatherBankReferencesResult = Internal::Legacy::GetBanksFromAtlPreloads(preloadsNode, atlPlatformName, banksReferenced);
}
if (!gatherBankReferencesResult)
{
AZ_Warning("Audio Control Builder", false, "Failed to gather product dependencies for Audio Control file %s. %s\n",
sourceFile.c_str(), gatherBankReferencesResult.GetError().c_str());
return;
}
if (banksReferenced.size() == 0)
{
// If there are no banks referenced, then there are no dependencies to register, so return.
return;
}
for (const AZStd::string& relativeBankPath : banksReferenced)
{
pathDependencies.emplace(relativeBankPath, AssetBuilderSDK::ProductPathDependencyType::ProductFile);
}
// For each bank figure out what events are included in the bank, then run through every event referenced in the file and
// make sure it is in the list gathered from the banks.
const auto triggersNode = node->first_node(Audio::ATLXmlTags::TriggersNodeTag);
if (!triggersNode)
{
// No triggers were defined in this file, so we don't need to do any event validation.
return;
}
AZStd::vector<AZStd::string> eventsReferenced;
AZ::Outcome<void, AZStd::string> gatherEventReferencesResult = Internal::BuildAtlEventList(triggersNode, eventsReferenced);
if (!gatherEventReferencesResult.IsSuccess())
{
AZ_Warning("Audio Control Builder", false, "Failed to gather list of events referenced by Audio Control file %s. %s",
sourceFile.c_str(), gatherEventReferencesResult.GetError().c_str());
return;
}
AZStd::string projectSourcePath = fullPath;
AZ::u64 firstSubDirectoryIndex = AZ::StringFunc::Find(projectSourcePath, m_globalScopeControlsPath);
AZ::StringFunc::LKeep(projectSourcePath, firstSubDirectoryIndex);
AZStd::set<AZStd::string> wwiseEventsInReferencedBanks;
// Load all bankdeps files for all banks referenced and aggregate the list of events in those files.
for (const AZStd::string& relativeBankPath : banksReferenced)
{
// Create the full path to the bankdeps file from the bank file.
AZStd::string bankMetadataPath;
AZ::StringFunc::Path::Join(projectSourcePath.c_str(), relativeBankPath.c_str(), bankMetadataPath);
AZ::StringFunc::Path::ReplaceExtension(bankMetadataPath, Internal::SoundbankDependencyFileExtension);
AZ::Outcome<void, AZStd::string> getReferencedEventsResult = Internal::GetEventsFromBank(bankMetadataPath, wwiseEventsInReferencedBanks);
if (!getReferencedEventsResult.IsSuccess())
{
// only warn if we couldn't get info from a bankdeps file. Won't impact registering dependencies, but used to help
// customers potentially debug issues.
AZ_Warning("Audio Control Builder", false, "Failed to gather list of events referenced by soundbank %s. %s", relativeBankPath.c_str(), getReferencedEventsResult.GetError().c_str());
}
}
// Confirm that each event referenced by the file exists in the list of events available from the banks referenced.
for (const AZStd::string& eventInControlFile : eventsReferenced)
{
if (wwiseEventsInReferencedBanks.find(eventInControlFile) == wwiseEventsInReferencedBanks.end())
{
AZ_Warning("Audio Control Builder", false, "Failed to find Wwise event %s in the list of events contained in banks referenced by %s. Event may fail to play properly.", eventInControlFile.c_str(), sourceFile.c_str());
}
}
}
} // namespace AudioControlBuilder