Convert legacy XML handling to rapidxml

Updates the Audio Controls Editor code to use rapidxml instead of legacy
xml apis.  Further makes improvements to path manipulations away from
strings towards PathView apis and similar.

Fixes some issues encountered with memory management when handling xml
data that did not occur previously.

Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>
This commit is contained in:
amzn-phist
2021-07-22 17:37:44 -05:00
parent 4d5a985276
commit 5b148b1f40
15 changed files with 580 additions and 410 deletions
@@ -19,10 +19,7 @@
#include <AudioSystemControl_wwise.h>
#include <Common_wwise.h>
#include <ISystem.h>
#include <CryFile.h>
#include <CryPath.h>
#include <Util/PathUtil.h>
#include <QDir>
void InitWwiseResources()
{
@@ -217,28 +214,34 @@ namespace AudioControls
}
//-------------------------------------------------------------------------------------------//
TConnectionPtr CAudioSystemEditor_wwise::CreateConnectionFromXMLNode(XmlNodeRef node, EACEControlType atlControlType)
TConnectionPtr CAudioSystemEditor_wwise::CreateConnectionFromXMLNode(AZ::rapidxml::xml_node<char>* node, EACEControlType atlControlType)
{
if (node)
{
const AZStd::string tag(node->getTag());
TImplControlType type = TagToType(tag);
AZStd::string_view element(node->name());
TImplControlType type = TagToType(element);
if (type != AUDIO_IMPL_INVALID_TYPE)
{
AZStd::string name(node->getAttr(Audio::WwiseXmlTags::WwiseNameAttribute));
AZStd::string localized(node->getAttr(Audio::WwiseXmlTags::WwiseLocalizedAttribute));
AZStd::string name;
AZStd::string_view localized;
// Legacy Preload support
if (localized.empty())
if (auto nameAttr = node->first_attribute(Audio::WwiseXmlTags::WwiseNameAttribute, 0, false);
nameAttr != nullptr)
{
localized = node->getAttr(Audio::WwiseXmlTags::Legacy::WwiseLocalizedAttribute);
name = nameAttr->value();
}
bool isLocalized = AZ::StringFunc::Equal(localized.c_str(), "true");
if (auto localizedAttr = node->first_attribute(Audio::WwiseXmlTags::WwiseLocalizedAttribute, 0, false);
localizedAttr != nullptr)
{
localized = localizedAttr->value();
}
// If control not found, create a placeholder.
// We want to keep that connection even if it's not in the middleware.
// The user could be using the engine without the wwise project
bool isLocalized = AZ::StringFunc::Equal(localized, "true");
// If the control wasn't found, create a placeholder.
// We want to see that connection even if it's not in the middleware.
// User could be viewing the editor without a middleware project.
IAudioSystemControl* control = GetControlByName(name, isLocalized);
if (!control)
{
@@ -250,27 +253,26 @@ namespace AudioControls
}
}
// If it's a switch we actually connect to one of the states within the switch
// If it's a switch we connect to one of the states within the switch
if (type == eWCT_WWISE_SWITCH_GROUP || type == eWCT_WWISE_GAME_STATE_GROUP)
{
if (node->getChildCount() == 1)
if (auto childNode = node->first_node();
childNode != nullptr)
{
node = node->getChild(0);
if (node)
AZStd::string childName;
if (auto childNameAttr = childNode->first_attribute(Audio::WwiseXmlTags::WwiseNameAttribute, 0, false);
childNameAttr != nullptr)
{
AZStd::string childName(node->getAttr(Audio::WwiseXmlTags::WwiseNameAttribute));
IAudioSystemControl* childControl = GetControlByName(childName, false, control);
if (!childControl)
{
childControl = CreateControl(SControlDef(childName, type == eWCT_WWISE_SWITCH_GROUP ? eWCT_WWISE_SWITCH : eWCT_WWISE_GAME_STATE, false, control));
}
control = childControl;
childName = childNameAttr->value();
}
}
else
{
CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_ERROR, "Audio Controls Editor (Wwise): Error reading connection to Wwise control %s", name.c_str());
IAudioSystemControl* childControl = GetControlByName(childName, false, control);
if (!childControl)
{
childControl = CreateControl(SControlDef(
childName, type == eWCT_WWISE_SWITCH_GROUP ? eWCT_WWISE_SWITCH : eWCT_WWISE_GAME_STATE, false, control));
}
control = childControl;
}
}
@@ -289,16 +291,19 @@ namespace AudioControls
float mult = 1.0f;
float shift = 0.0f;
if (node->haveAttr(Audio::WwiseXmlTags::WwiseMutiplierAttribute))
if (auto multAttr = node->first_attribute(Audio::WwiseXmlTags::WwiseMutiplierAttribute, 0, false);
multAttr != nullptr)
{
const AZStd::string multProperty(node->getAttr(Audio::WwiseXmlTags::WwiseMutiplierAttribute));
mult = AZStd::stof(multProperty);
mult = AZStd::stof(AZStd::string(multAttr->value()));
}
if (node->haveAttr(Audio::WwiseXmlTags::WwiseShiftAttribute))
if (auto shiftAttr = node->first_attribute(Audio::WwiseXmlTags::WwiseShiftAttribute, 0, false);
shiftAttr != nullptr)
{
const AZStd::string shiftProperty(node->getAttr(Audio::WwiseXmlTags::WwiseShiftAttribute));
shift = AZStd::stof(shiftProperty);
shift = AZStd::stof(AZStd::string(shiftAttr->value()));
}
connection->m_mult = mult;
connection->m_shift = shift;
return connection;
@@ -308,11 +313,12 @@ namespace AudioControls
TStateConnectionPtr connection = AZStd::make_shared<CStateToRtpcConnection>(control->GetId());
float value = 0.0f;
if (node->haveAttr(Audio::WwiseXmlTags::WwiseValueAttribute))
if (auto valueAttr = node->first_attribute(Audio::WwiseXmlTags::WwiseValueAttribute, 0, false);
valueAttr != nullptr)
{
const AZStd::string valueProperty(node->getAttr(Audio::WwiseXmlTags::WwiseValueAttribute));
value = AZStd::stof(valueProperty);
value = AZStd::stof(AZStd::string(valueAttr->value()));
}
connection->m_value = value;
return connection;
}
@@ -329,28 +335,50 @@ namespace AudioControls
}
//-------------------------------------------------------------------------------------------//
XmlNodeRef CAudioSystemEditor_wwise::CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType)
AZ::rapidxml::xml_node<char>* CAudioSystemEditor_wwise::CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType)
{
const IAudioSystemControl* control = GetControl(connection->GetID());
if (control)
{
XmlAllocator& xmlAllocator(AudioControls::s_xmlAllocator);
switch (control->GetType())
{
case AudioControls::eWCT_WWISE_SWITCH:
[[fallthrough]];
case AudioControls::eWCT_WWISE_SWITCH_GROUP:
[[fallthrough]];
case AudioControls::eWCT_WWISE_GAME_STATE:
[[fallthrough]];
case AudioControls::eWCT_WWISE_GAME_STATE_GROUP:
{
const IAudioSystemControl* parent = control->GetParent();
if (parent)
{
XmlNodeRef switchNode = GetISystem()->CreateXmlNode(TypeToTag(parent->GetType()).data());
switchNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, parent->GetName().c_str());
AZStd::string_view parentType = TypeToTag(parent->GetType());
auto switchNode = xmlAllocator.allocate_node(
AZ::rapidxml::node_element,
xmlAllocator.allocate_string(parentType.data())
);
XmlNodeRef stateNode = switchNode->createNode(Audio::WwiseXmlTags::WwiseValueTag);
stateNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, control->GetName().c_str());
switchNode->addChild(stateNode);
auto switchNameAttr = xmlAllocator.allocate_attribute(
Audio::WwiseXmlTags::WwiseNameAttribute,
xmlAllocator.allocate_string(parent->GetName().c_str())
);
auto stateNode = xmlAllocator.allocate_node(
AZ::rapidxml::node_element,
Audio::WwiseXmlTags::WwiseValueTag
);
auto stateNameAttr = xmlAllocator.allocate_attribute(
Audio::WwiseXmlTags::WwiseNameAttribute,
xmlAllocator.allocate_string(control->GetName().c_str())
);
switchNode->append_attribute(switchNameAttr);
stateNode->append_attribute(stateNameAttr);
switchNode->append_node(stateNode);
return switchNode;
}
break;
@@ -358,51 +386,98 @@ namespace AudioControls
case AudioControls::eWCT_WWISE_RTPC:
{
XmlNodeRef connectionNode = GetISystem()->CreateXmlNode(TypeToTag(control->GetType()).data());
connectionNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, control->GetName().c_str());
auto connectionNode = xmlAllocator.allocate_node(
AZ::rapidxml::node_element,
xmlAllocator.allocate_string(TypeToTag(control->GetType()).data())
);
auto nameAttr = xmlAllocator.allocate_attribute(
Audio::WwiseXmlTags::WwiseNameAttribute,
xmlAllocator.allocate_string(control->GetName().c_str())
);
connectionNode->append_attribute(nameAttr);
if (atlControlType == eACET_RTPC)
{
AZStd::shared_ptr<const CRtpcConnection> rtpcConnection = AZStd::static_pointer_cast<const CRtpcConnection>(connection);
if (rtpcConnection->m_mult != 1.0f)
if (rtpcConnection->m_mult != 1.f)
{
connectionNode->setAttr(Audio::WwiseXmlTags::WwiseMutiplierAttribute, rtpcConnection->m_mult);
auto multAttr = xmlAllocator.allocate_attribute(
Audio::WwiseXmlTags::WwiseMutiplierAttribute,
xmlAllocator.allocate_string(AZStd::to_string(rtpcConnection->m_mult).c_str())
);
connectionNode->append_attribute(multAttr);
}
if (rtpcConnection->m_shift != 0.0f)
if (rtpcConnection->m_shift != 0.f)
{
connectionNode->setAttr(Audio::WwiseXmlTags::WwiseShiftAttribute, rtpcConnection->m_shift);
auto shiftAttr = xmlAllocator.allocate_attribute(
Audio::WwiseXmlTags::WwiseShiftAttribute,
xmlAllocator.allocate_string(AZStd::to_string(rtpcConnection->m_shift).c_str())
);
connectionNode->append_attribute(shiftAttr);
}
}
else if (atlControlType == eACET_SWITCH_STATE)
{
AZStd::shared_ptr<const CStateToRtpcConnection> stateConnection = AZStd::static_pointer_cast<const CStateToRtpcConnection>(connection);
connectionNode->setAttr(Audio::WwiseXmlTags::WwiseValueAttribute, stateConnection->m_value);
auto valueAttr = xmlAllocator.allocate_attribute(
Audio::WwiseXmlTags::WwiseValueAttribute,
xmlAllocator.allocate_string(AZStd::to_string(stateConnection->m_value).c_str())
);
connectionNode->append_attribute(valueAttr);
}
return connectionNode;
}
case AudioControls::eWCT_WWISE_EVENT:
{
XmlNodeRef connectionNode = GetISystem()->CreateXmlNode(TypeToTag(control->GetType()).data());
connectionNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, control->GetName().c_str());
return connectionNode;
}
[[fallthrough]];
case AudioControls::eWCT_WWISE_AUX_BUS:
{
XmlNodeRef connectionNode = GetISystem()->CreateXmlNode(TypeToTag(control->GetType()).data());
connectionNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, control->GetName().c_str());
auto connectionNode = xmlAllocator.allocate_node(
AZ::rapidxml::node_element,
xmlAllocator.allocate_string(TypeToTag(control->GetType()).data())
);
auto nameAttr = xmlAllocator.allocate_attribute(
Audio::WwiseXmlTags::WwiseNameAttribute,
xmlAllocator.allocate_string(control->GetName().c_str())
);
connectionNode->append_attribute(nameAttr);
return connectionNode;
}
case AudioControls::eWCT_WWISE_SOUND_BANK:
{
XmlNodeRef connectionNode = GetISystem()->CreateXmlNode(TypeToTag(control->GetType()).data());
connectionNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, control->GetName().c_str());
auto connectionNode = xmlAllocator.allocate_node(
AZ::rapidxml::node_element,
xmlAllocator.allocate_string(TypeToTag(control->GetType()).data())
);
auto nameAttr = xmlAllocator.allocate_attribute(
Audio::WwiseXmlTags::WwiseNameAttribute,
xmlAllocator.allocate_string(control->GetName().c_str())
);
connectionNode->append_attribute(nameAttr);
if (control->IsLocalized())
{
connectionNode->setAttr(Audio::WwiseXmlTags::WwiseLocalizedAttribute, "true");
auto locAttr = xmlAllocator.allocate_attribute(
Audio::WwiseXmlTags::WwiseLocalizedAttribute,
xmlAllocator.allocate_string("true")
);
connectionNode->append_attribute(locAttr);
}
return connectionNode;
}
}
@@ -77,8 +77,8 @@ namespace AudioControls
EACEControlType ImplTypeToATLType(TImplControlType type) const override;
TImplControlTypeMask GetCompatibleTypes(EACEControlType atlControlType) const override;
TConnectionPtr CreateConnectionToControl(EACEControlType atlControlType, IAudioSystemControl* middlewareControl) override;
TConnectionPtr CreateConnectionFromXMLNode(XmlNodeRef node, EACEControlType atlControlType) override;
XmlNodeRef CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType) override;
TConnectionPtr CreateConnectionFromXMLNode(AZ::rapidxml::xml_node<char>* node, EACEControlType atlControlType) override;
AZ::rapidxml::xml_node<char>* CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType) override;
const AZStd::string_view GetTypeIcon(TImplControlType type) const override;
const AZStd::string_view GetTypeIconSelected(TImplControlType type) const override;
AZStd::string GetName() const override;
@@ -17,12 +17,6 @@
#include <AudioFileUtils.h>
#include <Config_wwise.h>
#include <ISystem.h>
#include <CryFile.h>
#include <CryPath.h>
#include <Util/PathUtil.h>
using namespace PathUtil;
namespace AudioControls
{
@@ -68,8 +62,7 @@ namespace AudioControls
for (const auto& filePath : foundFiles)
{
AZ_Assert(AZ::IO::FileIOBase::GetInstance()->Exists(filePath.c_str()), "FindFiles found file '%s' but FileIO says it doesn't exist!", filePath.c_str());
AZStd::string fileName;
AZ::StringFunc::Path::GetFullFileName(filePath.c_str(), fileName);
AZ::IO::PathView fileName = filePath.Filename();
if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(filePath.c_str()))
{
@@ -79,15 +72,15 @@ namespace AudioControls
// we load only one as all of them should have the
// same content (in the future we want to have a
// consistency report to highlight if this is not the case)
m_localizationFolder = fileName;
m_localizationFolder.assign(fileName.Native().data(), fileName.Native().size());
LoadSoundBanks(rootFolder, m_localizationFolder, true);
isLocalizedLoaded = true;
}
}
else if (AZ::StringFunc::Find(fileName.c_str(), Audio::Wwise::BankExtension) != AZStd::string::npos
&& !AZ::StringFunc::Equal(fileName.c_str(), Audio::Wwise::InitBank))
else if (fileName.Extension() == Audio::Wwise::BankExtension && !AZ::StringFunc::Equal(fileName.Native(), Audio::Wwise::InitBank))
{
m_audioSystemImpl->CreateControl(SControlDef(fileName, eWCT_WWISE_SOUND_BANK, isLocalized, nullptr, subPath));
m_audioSystemImpl->CreateControl(
SControlDef(AZStd::string{ fileName.Native() }, eWCT_WWISE_SOUND_BANK, isLocalized, nullptr, subPath));
}
}
}
@@ -103,14 +96,14 @@ namespace AudioControls
if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(filePath.c_str()))
{
LoadControlsInFolder(filePath);
LoadControlsInFolder(filePath.Native());
}
else
{
// Open the file, read into an xmlDoc, and call LoadControls with the root xml node...
AZ_TracePrintf("AudioWwiseLoader", "Loading Xml from '%s'", filePath.c_str());
Audio::ScopedXmlLoader xmlFileLoader(filePath);
Audio::ScopedXmlLoader xmlFileLoader(filePath.Native());
if (!xmlFileLoader.HasError())
{
LoadControl(xmlFileLoader.GetRootNode());
@@ -13,6 +13,7 @@
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/string.h>
#include <AzCore/XML/rapidxml.h>
namespace AudioControls
{
@@ -39,4 +40,7 @@ namespace AudioControls
using FilepathSet = AZStd::set<AZStd::string>;
using XmlAllocator = AZ::rapidxml::memory_pool<>;
inline static XmlAllocator s_xmlAllocator;
} // namespace AudioControls
@@ -12,12 +12,10 @@
#include <AzCore/EBus/EBus.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/string/string_view.h>
#include <AzCore/XML/rapidxml.h>
#include <ACETypes.h>
#include <platform.h>
#include <IXml.h>
namespace AudioControls
{
class IAudioSystemEditor;
@@ -117,14 +115,14 @@ namespace AudioControls
//! @param node XML node where the connection is defined.
//! @param atlControlType The type of the ATL control you are connecting to.
//! @return A pointer to the newly created connection.
virtual TConnectionPtr CreateConnectionFromXMLNode(XmlNodeRef node, EACEControlType atlControlType) = 0;
virtual TConnectionPtr CreateConnectionFromXMLNode(AZ::rapidxml::xml_node<char>* node, EACEControlType atlControlType) = 0;
//! When serializing connections between controls this function will be called once per connection to serialize its properties.
//! This function should be in sync with CreateConnectionToControl as whatever it's written here will have to be read there.
//! @param connection Connection to serialize.
//! @param atlControlType Type of the ATL control that has this connection.
//! @return XML node with the connection serialized.
virtual XmlNodeRef CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType) = 0;
virtual AZ::rapidxml::xml_node<char>* CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType) = 0;
//! Whenever a connection is removed from an ATL control this function should be called.
//! To keep the system informed of which controls have been connected and which ones haven't.
@@ -47,6 +47,7 @@ namespace Audio
static constexpr const char* ATLInternalNameAttribute = "atl_internal_name";
static constexpr const char* ATLTypeAttribute = "atl_type";
static constexpr const char* ATLConfigGroupAttribute = "atl_config_group_name";
static constexpr const char* ATLPathAttribute = "path";
static constexpr const char* ATLDataLoadType = "AutoLoad";
@@ -9,6 +9,7 @@
#pragma once
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/string.h>
@@ -20,22 +21,26 @@ namespace Audio
/*!
* FindFilesInPath
*/
static AZStd::vector<AZStd::string> FindFilesInPath(const AZStd::string_view folderPath, const char* filter)
static AZStd::vector<AZ::IO::FixedMaxPath> FindFilesInPath(const AZStd::string_view folderPath, const char* filter)
{
AZStd::vector<AZStd::string> foundFiles;
AZStd::vector<AZ::IO::FixedMaxPath> foundFiles;
AZ::IO::FileIOBase::FindFilesCallbackType findFilesCallback = [&foundFiles](const char* file) -> bool
{
foundFiles.emplace_back(file);
foundFiles.emplace_back(AZ::IO::FixedMaxPath{ file }.LexicallyNormal());
return true;
};
auto fileIO = AZ::IO::FileIOBase::GetInstance();
if (fileIO)
if (auto fileIO = AZ::IO::FileIOBase::GetInstance();
fileIO != nullptr)
{
AZ::IO::Result result = fileIO->FindFiles(folderPath.data(), filter, findFilesCallback);
if (result == AZ::IO::ResultCode::Success)
{
return AZStd::move(foundFiles);
}
}
return foundFiles;
return {};
}
/*!
@@ -11,13 +11,11 @@
#include <ACETypes.h>
#include <AzCore/std/string/string_view.h>
#include <AzCore/XML/rapidxml.h>
#include <IAudioConnection.h>
#include <IAudioSystemControl.h>
#include <ISystem.h>
#include <IXml.h>
namespace AudioControls
{
class CATLControlsModel;
@@ -25,15 +23,52 @@ namespace AudioControls
//-------------------------------------------------------------------------------------------//
struct SRawConnectionData
{
SRawConnectionData(XmlNodeRef node, bool isValid)
: m_xmlNode(node)
, m_isValid(isValid)
{}
SRawConnectionData(AZ::rapidxml::xml_node<char>* node, bool isValid)
{
m_xmlNode = DeepCopyNode(node);
m_isValid = isValid;
}
XmlNodeRef m_xmlNode;
AZ::rapidxml::xml_node<char>* m_xmlNode{ nullptr };
// indicates if the connection is valid for the currently loaded middleware
bool m_isValid;
bool m_isValid{ false };
// Rapid XML provides a 'copy_node' utility that will copy an entire node tree,
// but it only copies pointers of any strings in the node names and values.
// This causes problems with storing raw xml nodes as this class does because strings
// will be pointing into the memory pool of an xml document that has gone out of scope.
// This function is a rewritten version of 'copy_node' that does the deep copy of strings
// into the new destination tree.
static AZ::rapidxml::xml_node<char>* DeepCopyNode(AZ::rapidxml::xml_node<char>* srcNode)
{
AZ::rapidxml::xml_node<char>* destNode = nullptr;
if (srcNode)
{
XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator);
destNode = xmlAlloc.allocate_node(srcNode->type());
destNode->name(xmlAlloc.allocate_string(srcNode->name(), srcNode->name_size()), srcNode->name_size());
destNode->value(xmlAlloc.allocate_string(srcNode->value(), srcNode->value_size()), srcNode->value_size());
for (AZ::rapidxml::xml_node<char>* child = srcNode->first_node(); child != nullptr; child = child->next_sibling())
{
destNode->append_node(DeepCopyNode(child));
}
for (AZ::rapidxml::xml_attribute<char>* attr = srcNode->first_attribute(); attr != nullptr; attr = attr->next_attribute())
{
destNode->append_attribute(xmlAlloc.allocate_attribute(
xmlAlloc.allocate_string(attr->name(), attr->name_size()),
xmlAlloc.allocate_string(attr->value(), attr->value_size()),
attr->name_size(),
attr->value_size()
));
}
}
return destNode;
}
};
using TXmlNodeList = AZStd::vector<SRawConnectionData>;
@@ -10,21 +10,31 @@
#include <AudioControlsEditorWindow.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <ATLControlsModel.h>
#include <ATLControlsPanel.h>
#include <AudioControlsEditorPlugin.h>
#include <AudioControlsEditorUndo.h>
#include <AudioSystemPanel.h>
#include <CryFile.h>
#include <CryPath.h>
#include <DockTitleBarWidget.h>
#include <IAudioSystem.h>
#include <ImplementationManager.h>
#include <InspectorPanel.h>
#include <CryFile.h>
#include <CryPath.h>
#include <ISystem.h>
#include <QAudioControlEditorIcons.h>
#include <Util/PathUtil.h>
#include <QAudioControlEditorIcons.h>
#include <QPaintEvent>
#include <QPushButton>
#include <QApplication>
@@ -318,19 +328,24 @@ namespace AudioControls
// once we can listen to delete messages from Asset system, this can be changed to an EBus handler.
const char* controlsPath = nullptr;
Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath);
AZStd::string sControlsPath(Path::GetEditingGameDataFolder());
AZ::StringFunc::Path::Join(sControlsPath.c_str(), controlsPath, sControlsPath);
Audio::SAudioManagerRequestData<Audio::eAMRT_PARSE_CONTROLS_DATA> oParseGlobalRequestData(sControlsPath.c_str(), Audio::eADS_GLOBAL);
AZ::IO::FixedMaxPath controlsFolder{ controlsPath };
Audio::SAudioManagerRequestData<Audio::eAMRT_PARSE_CONTROLS_DATA> oParseGlobalRequestData(controlsFolder.c_str(), Audio::eADS_GLOBAL);
oConfigDataRequest.pData = &oParseGlobalRequestData;
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequest, oConfigDataRequest);
// parse the AudioSystem level-specific config data
AZStd::string levelName{ GetIEditor()->GetLevelName().toUtf8().data() };
AZ::StringFunc::Path::Join(sControlsPath.c_str(), "levels", sControlsPath);
AZ::StringFunc::Path::Join(sControlsPath.c_str(), levelName.c_str(), sControlsPath);
Audio::SAudioManagerRequestData<Audio::eAMRT_PARSE_CONTROLS_DATA> oParseLevelRequestData(sControlsPath.c_str(), Audio::eADS_LEVEL_SPECIFIC);
oConfigDataRequest.pData = &oParseLevelRequestData;
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequest, oConfigDataRequest);
AZStd::string levelName;
AzToolsFramework::EditorRequestBus::BroadcastResult(levelName, &AzToolsFramework::EditorRequests::GetLevelName);
if (!levelName.empty() && levelName != "Untitled")
{
controlsFolder /= "levels";
controlsFolder /= levelName;
Audio::SAudioManagerRequestData<Audio::eAMRT_PARSE_CONTROLS_DATA> oParseLevelRequestData(controlsFolder.c_str(), Audio::eADS_LEVEL_SPECIFIC);
oConfigDataRequest.pData = &oParseLevelRequestData;
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequest, oConfigDataRequest);
}
// inform the middleware specific plugin that the data has been saved
// to disk (in case it needs to update something)
@@ -11,26 +11,21 @@
#include <AzCore/std/string/conversions.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
#include <ACEEnums.h>
#include <ATLCommon.h>
#include <ATLControlsModel.h>
#include <AudioFileUtils.h>
#include <IAudioSystem.h>
#include <IAudioSystemControl.h>
#include <IAudioSystemEditor.h>
#include <QAudioControlTreeWidget.h>
#include <CryFile.h>
#include <CryPath.h>
#include <IEditor.h>
#include <ISystem.h>
#include <StringUtils.h>
#include <Util/PathUtil.h>
#include <Util/UndoUtil.h>
#include <QStandardItem>
using namespace PathUtil;
namespace AudioControls
{
//-------------------------------------------------------------------------------------------//
@@ -91,100 +86,81 @@ namespace AudioControls
{
const CUndoSuspend suspendUndo;
// Get the partial path (relative under asset root) where the controls live.
// Get the relative path (under asset root) where the controls live.
const char* controlsPath = nullptr;
Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath);
// Get the full path up to asset root.
AZStd::string controlsFullPath(Path::GetEditingGameDataFolder());
AZ::StringFunc::Path::Join(controlsFullPath.c_str(), controlsPath, controlsFullPath);
AZ::IO::FixedMaxPath controlsFullPath = AZ::Utils::GetProjectPath();
controlsFullPath /= controlsPath;
// load the global controls
LoadAllLibrariesInFolder(controlsFullPath, "");
LoadAllLibrariesInFolder(controlsFullPath.Native(), "");
// load the level specific controls
auto cryPak = gEnv->pCryPak;
AZ::IO::FixedMaxPath searchPath = controlsFullPath / LoaderStrings::LevelsSubFolder;
AZStd::string searchMask;
AZ::StringFunc::Path::Join(controlsFullPath.c_str(), LoaderStrings::LevelsSubFolder, searchMask);
AZ::StringFunc::Path::Join(searchMask.c_str(), "*", searchMask, true, false);
AZ::IO::ArchiveFileIterator handle = cryPak->FindFirst(searchMask.c_str());
if (handle)
auto foundFiles = Audio::FindFilesInPath(searchPath.Native(), "*");
for (const auto& file : foundFiles)
{
do
if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(file.c_str()))
{
if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
AZStd::string levelName{ file.Filename().Native() };
LoadAllLibrariesInFolder(controlsFullPath.Native(), levelName);
if (!m_atlControlsModel->ScopeExists(levelName))
{
AZStd::string_view name = handle.m_filename;
if (name != "." && name != "..")
{
LoadAllLibrariesInFolder(controlsFullPath, name);
if (!m_atlControlsModel->ScopeExists(name))
{
// if the control doesn't exist it
// means it is not a real level in the
// project so it is flagged as LocalOnly
m_atlControlsModel->AddScope(name, true);
}
}
// If the scope doesn't exist it means it is not a real
// level in the project so it's flagged as LocalOnly
m_atlControlsModel->AddScope(levelName, true);
}
}
while (handle = cryPak->FindNext(handle));
cryPak->FindClose(handle);
}
CreateDefaultControls();
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsLoader::LoadAllLibrariesInFolder(const AZStd::string_view folderPath, const AZStd::string_view level)
{
AZStd::string path(folderPath);
if (path.back() != AZ_CORRECT_FILESYSTEM_SEPARATOR)
{
path.append(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
}
AZ::IO::FixedMaxPath searchPath{ folderPath };
if (!level.empty())
{
path.append(LoaderStrings::LevelsSubFolder);
path.append(GetSlash());
path.append(level);
path.append(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
searchPath /= LoaderStrings::LevelsSubFolder;
searchPath /= level;
}
AZStd::string searchPath = path + "*.xml";
auto cryPak = gEnv->pCryPak;
AZ::IO::ArchiveFileIterator handle = cryPak->FindFirst(searchPath.c_str());
if (handle)
auto foundFiles = Audio::FindFilesInPath(searchPath.Native(), "*.xml");
for (auto& file : foundFiles)
{
do
Audio::ScopedXmlLoader xmlLoader(file.Native());
if (xmlLoader.HasError())
{
AZStd::string filename = path + AZStd::string{ static_cast<AZStd::string_view>(handle.m_filename) };
AZ::StringFunc::Path::Normalize(filename);
XmlNodeRef root = GetISystem()->LoadXmlFromFile(filename.c_str());
if (root)
AZ_Warning("AudioControlsLoader", false, "Unable to load the xml file '%s'", file.c_str());
continue;
}
auto xmlRootNode = xmlLoader.GetRootNode();
if (xmlRootNode && azstricmp(xmlRootNode->name(), Audio::ATLXmlTags::RootNodeTag) == 0)
{
AZ::IO::PathView fileName = file.Filename();
AZStd::to_lower(file.Native().begin(), file.Native().end());
m_loadedFilenames.insert(file.c_str());
if (auto nameAttr = xmlRootNode->first_attribute(Audio::ATLXmlTags::ATLNameAttribute, 0, false); nameAttr != nullptr)
{
AZStd::string tag = root->getTag();
if (tag == Audio::ATLXmlTags::RootNodeTag)
{
AZStd::to_lower(filename.begin(), filename.end());
m_loadedFilenames.insert(filename.c_str());
AZStd::string file = static_cast<AZStd::string_view>(handle.m_filename);
if (root->haveAttr(Audio::ATLXmlTags::ATLNameAttribute))
{
file = root->getAttr(Audio::ATLXmlTags::ATLNameAttribute);
}
AZ::StringFunc::Path::StripExtension(file);
LoadControlsLibrary(root, folderPath, level, file);
}
fileName = nameAttr->value();
}
else
{
CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_ERROR, "(Audio Controls Editor) Failed parsing ATL Library '%s'", filename.c_str());
fileName = fileName.Stem();
}
} while (handle = cryPak->FindNext(handle));
cryPak->FindClose(handle);
LoadControlsLibrary(xmlRootNode, folderPath, level, fileName.Native());
}
}
}
@@ -233,74 +209,93 @@ namespace AudioControls
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsLoader::LoadControlsLibrary(XmlNodeRef rootNode, [[maybe_unused]] const AZStd::string_view filePath, const AZStd::string_view level, const AZStd::string_view fileName)
void CAudioControlsLoader::LoadControlsLibrary(
const AZ::rapidxml::xml_node<char>* rootNode,
[[maybe_unused]] const AZStd::string_view filePath,
const AZStd::string_view level,
const AZStd::string_view fileName)
{
QStandardItem* rootFolderItem = AddUniqueFolderPath(m_layoutModel->invisibleRootItem(), QString(fileName.data()));
if (rootFolderItem && rootNode)
{
const int numControlTypes = rootNode->getChildCount();
for (int i = 0; i < numControlTypes; ++i)
auto controlTypeNode = rootNode->first_node(); // e.g. "AudioTriggers", "AudioRtpcs", etc
while (controlTypeNode)
{
XmlNodeRef node = rootNode->getChild(i);
const int numControls = node->getChildCount();
for (int j = 0; j < numControls; ++j)
auto controlNode = controlTypeNode->first_node(); // e.g. "ATLTrigger", "ATLRtpc", etc
while (controlNode)
{
LoadControl(node->getChild(j), rootFolderItem, level);
LoadControl(controlNode, rootFolderItem, level);
controlNode = controlNode->next_sibling();
}
controlTypeNode = controlTypeNode->next_sibling();
}
}
}
//-------------------------------------------------------------------------------------------//
CATLControl* CAudioControlsLoader::LoadControl(XmlNodeRef node, QStandardItem* folderItem, const AZStd::string_view scope)
CATLControl* CAudioControlsLoader::LoadControl(AZ::rapidxml::xml_node<char>* node, QStandardItem* folderItem, const AZStd::string_view scope)
{
CATLControl* control = nullptr;
if (node)
AZStd::string controlPath;
if (auto controlPathAttr = node->first_attribute("path", 0, false);
controlPathAttr != nullptr)
{
QStandardItem* parentItem = AddUniqueFolderPath(folderItem, QString(node->getAttr(LoaderStrings::PathAttribute)));
if (parentItem)
controlPath = controlPathAttr->value();
}
QStandardItem* parentItem = AddUniqueFolderPath(folderItem, QString(controlPath.c_str()));
if (parentItem)
{
AZStd::string name;
if (auto nameAttr = node->first_attribute(Audio::ATLXmlTags::ATLNameAttribute, 0, false);
nameAttr != nullptr)
{
const AZStd::string name = node->getAttr(Audio::ATLXmlTags::ATLNameAttribute);
const EACEControlType controlType = TagToType(node->getTag());
name = nameAttr->value();
}
control = m_atlControlsModel->CreateControl(name, controlType);
if (control)
const EACEControlType controlType = TagToType(node->name());
control = m_atlControlsModel->CreateControl(name, controlType);
if (control)
{
QStandardItem* item = new QAudioControlItem(QString(control->GetName().c_str()), control);
if (item)
{
QStandardItem* item = new QAudioControlItem(QString(control->GetName().c_str()), control);
if (item)
{
parentItem->appendRow(item);
}
switch (controlType)
{
case eACET_SWITCH:
{
const int numStates = node->getChildCount();
for (int i = 0; i < numStates; ++i)
{
CATLControl* stateControl = LoadControl(node->getChild(i), item, scope);
if (stateControl)
{
stateControl->SetParent(control);
control->AddChild(stateControl);
}
}
break;
}
case eACET_PRELOAD:
{
LoadPreloadConnections(node, control);
break;
}
default:
{
LoadConnections(node, control);
break;
}
}
control->SetScope(scope);
parentItem->appendRow(item);
}
switch (controlType)
{
case eACET_SWITCH:
{
auto switchStateNode = node->first_node();
while (switchStateNode)
{
CATLControl* stateControl = LoadControl(switchStateNode, item, scope);
if (stateControl)
{
stateControl->SetParent(control);
control->AddChild(stateControl);
}
switchStateNode = switchStateNode->next_sibling();
}
break;
}
case eACET_PRELOAD:
{
LoadPreloadConnections(node, control);
break;
}
default:
{
LoadConnections(node, control);
break;
}
}
control->SetScope(scope);
}
}
@@ -310,44 +305,37 @@ namespace AudioControls
//-------------------------------------------------------------------------------------------//
void CAudioControlsLoader::LoadScopes()
{
AZStd::string levelsFolderPath;
AZ::StringFunc::Path::Join(Path::GetEditingGameDataFolder().c_str(), LoaderStrings::LevelsSubFolder, levelsFolderPath);
LoadScopesImpl(levelsFolderPath);
AZ::IO::FixedMaxPath levelsFolderPath = AZ::Utils::GetProjectPath();
levelsFolderPath /= "Levels";
LoadScopesImpl(levelsFolderPath.Native());
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsLoader::LoadScopesImpl(const AZStd::string_view levelsFolder)
{
AZStd::string search;
AZ::StringFunc::Path::Join(levelsFolder.data(), "*", search, true, false);
auto cryPak = gEnv->pCryPak;
AZ::IO::ArchiveFileIterator handle = cryPak->FindFirst(search.c_str());
if (handle)
auto fileIO = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FixedMaxPath searchPath{ levelsFolder };
auto foundFiles = Audio::FindFilesInPath(searchPath.Native(), "*");
for (auto& file : foundFiles)
{
do
AZ::IO::PathView filePath{ file };
AZ::IO::PathView fileName = filePath.Filename();
if (fileIO->IsDirectory(filePath.Native().data()))
{
AZStd::string name = static_cast<AZStd::string_view>(handle.m_filename);
if (name != "." && name != ".." && !name.empty())
LoadScopesImpl((searchPath / fileName).Native());
}
else
{
AZ::IO::PathView fileExt = filePath.Extension();
if (fileExt == ".ly" || fileExt == ".cry" || fileExt == ".prefab")
{
if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
{
AZ::StringFunc::Path::Join(levelsFolder.data(), name.c_str(), search);
LoadScopesImpl(search);
}
else
{
AZStd::string extension;
AZ::StringFunc::Path::GetExtension(name.c_str(), extension, false);
if (extension.compare("cry") == 0 || extension.compare("ly") == 0)
{
AZ::StringFunc::Path::StripExtension(name);
m_atlControlsModel->AddScope(name);
}
}
AZ::IO::PathView fileStem = filePath.Stem();
// May need to verify that .prefabs are the actual "level" prefab
// i.e. that it matches levels/<levelname>/<levelname>.prefab
m_atlControlsModel->AddScope(fileStem.Native());
}
}
while (handle = cryPak->FindNext(handle));
cryPak->FindClose(handle);
}
}
@@ -475,100 +463,80 @@ namespace AudioControls
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsLoader::LoadConnections(XmlNodeRef rootNode, CATLControl* control)
void CAudioControlsLoader::LoadConnections(AZ::rapidxml::xml_node<char>* rootNode, CATLControl* control)
{
if (!rootNode || !control)
if (control && rootNode && m_audioSystemImpl)
{
return;
}
const int numChildren = rootNode->getChildCount();
for (int i = 0; i < numChildren; ++i)
{
XmlNodeRef node = rootNode->getChild(i);
const AZStd::string tag = node->getTag();
if (m_audioSystemImpl)
auto childNode = rootNode->first_node();
while (childNode)
{
TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(node, control->GetType());
TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(childNode, control->GetType());
if (connection)
{
control->AddConnection(connection);
}
control->m_connectionNodes.push_back(SRawConnectionData(node, connection != nullptr));
control->m_connectionNodes.push_back(SRawConnectionData(childNode, connection != nullptr));
childNode = childNode->next_sibling();
}
}
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsLoader::LoadPreloadConnections(XmlNodeRef node, CATLControl* control)
void CAudioControlsLoader::LoadPreloadConnections(AZ::rapidxml::xml_node<char>* node, CATLControl* control)
{
if (!node || !control)
if (!control || !node || !m_audioSystemImpl)
{
return;
}
AZStd::string type = node->getAttr(Audio::ATLXmlTags::ATLTypeAttribute);
if (type.compare(Audio::ATLXmlTags::ATLDataLoadType) == 0)
AZStd::string type;
if (auto typeAttr = node->first_attribute(Audio::ATLXmlTags::ATLTypeAttribute, 0, false);
typeAttr != nullptr)
{
control->SetAutoLoad(true);
}
else
{
control->SetAutoLoad(false);
type = typeAttr->value();
}
// Legacy Preload XML parsing...
// Read all the platform definitions for this control
XmlNodeRef platformsGroupNode = node->findChild(Audio::ATLXmlTags::ATLPlatformsTag);
if (platformsGroupNode)
control->SetAutoLoad(type == Audio::ATLXmlTags::ATLDataLoadType);
auto platformGroupNode = node->first_node(Audio::ATLXmlTags::ATLPlatformsTag, 0, false);
if (platformGroupNode)
{
// Legacy preload parsing...
// Don't parse the platform groups xml chunk anymore.
// Read the connection information for all connected preloads...
const int numChildren = node->getChildCount();
for (int i = 0; i < numChildren; ++i)
auto configGroupNode = node->first_node(Audio::ATLXmlTags::ATLConfigGroupTag, 0, false);
while (configGroupNode)
{
XmlNodeRef groupNode = node->getChild(i);
const AZStd::string tag = groupNode->getTag();
if (tag.compare(Audio::ATLXmlTags::ATLConfigGroupTag) != 0)
{
continue;
}
const AZStd::string groupName = groupNode->getAttr(Audio::ATLXmlTags::ATLNameAttribute);
const int numConnections = groupNode->getChildCount();
for (int j = 0; j < numConnections; ++j)
{
XmlNodeRef connectionNode = groupNode->getChild(j);
if (connectionNode && m_audioSystemImpl)
{
TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(connectionNode, control->GetType());
if (connection)
{
control->AddConnection(connection);
}
control->m_connectionNodes.push_back(SRawConnectionData(connectionNode, connection != nullptr));
}
}
}
}
else
{
// New Preload XML parsing...
const int numChildren = node->getChildCount();
for (int i = 0; i < numChildren; ++i)
{
XmlNodeRef connectionNode = node->getChild(i);
if (connectionNode && m_audioSystemImpl)
auto connectionNode = configGroupNode->first_node();
while (connectionNode)
{
TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(connectionNode, control->GetType());
if (connection)
{
control->AddConnection(connection);
}
control->m_connectionNodes.push_back(SRawConnectionData(connectionNode, connection != nullptr));
connectionNode = connectionNode->next_sibling();
}
configGroupNode = configGroupNode->next_sibling();
}
}
else
{
// New format preload parsing...
auto connectionNode = node->first_node();
while (connectionNode)
{
TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(connectionNode, control->GetType());
if (connection)
{
control->AddConnection(connection);
}
control->m_connectionNodes.push_back(SRawConnectionData(connectionNode, connection != nullptr));
connectionNode = connectionNode->next_sibling();
}
}
}
@@ -586,15 +554,28 @@ namespace AudioControls
}
//-------------------------------------------------------------------------------------------//
CATLControl* CAudioControlsLoader::CreateInternalSwitchState(CATLControl* parentControl, const AZStd::string& switchName, const AZStd::string& stateName)
CATLControl* CAudioControlsLoader::CreateInternalSwitchState(CATLControl* parentControl, [[maybe_unused]] const AZStd::string& switchName, const AZStd::string& stateName)
{
CATLControl* childControl = m_atlControlsModel->CreateControl(stateName, eACET_SWITCH_STATE, parentControl);
XmlNodeRef requestNode = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::ATLSwitchRequestTag);
requestNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, switchName.c_str());
XmlNodeRef valueNode = requestNode->createNode(Audio::ATLXmlTags::ATLValueTag);
valueNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, stateName.c_str());
requestNode->addChild(valueNode);
XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator);
AZ::rapidxml::xml_node<char>* requestNode =
xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLSwitchRequestTag));
AZ::rapidxml::xml_attribute<char>* switchNameAttr = xmlAlloc.allocate_attribute(
xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(switchName.c_str()));
requestNode->append_attribute(switchNameAttr);
AZ::rapidxml::xml_node<char>* valueNode =
xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLValueTag));
AZ::rapidxml::xml_attribute<char>* stateNameAttr = xmlAlloc.allocate_attribute(
xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(stateName.c_str()));
valueNode->append_attribute(stateNameAttr);
requestNode->append_node(valueNode);
childControl->m_connectionNodes.push_back(SRawConnectionData(requestNode, false));
return childControl;
@@ -10,12 +10,11 @@
#pragma once
#include <AzCore/std/containers/set.h>
#include <AzCore/XML/rapidxml.h>
#include <ACETypes.h>
#include <AudioControl.h>
#include <IXml.h>
#include <QString>
class QStandardItemModel;
@@ -38,11 +37,11 @@ namespace AudioControls
private:
void LoadAllLibrariesInFolder(const AZStd::string_view folderPath, const AZStd::string_view level);
void LoadControlsLibrary(XmlNodeRef rootNode, const AZStd::string_view filePath, const AZStd::string_view level, const AZStd::string_view fileName);
CATLControl* LoadControl(XmlNodeRef node, QStandardItem* folderItem, const AZStd::string_view scope);
void LoadControlsLibrary(const AZ::rapidxml::xml_node<char>* rootNode, const AZStd::string_view filePath, const AZStd::string_view level, const AZStd::string_view fileName);
CATLControl* LoadControl(AZ::rapidxml::xml_node<char>* node, QStandardItem* folderItem, const AZStd::string_view scope);
void LoadPreloadConnections(XmlNodeRef node, CATLControl* control);
void LoadConnections(XmlNodeRef rootNode, CATLControl* control);
void LoadPreloadConnections(AZ::rapidxml::xml_node<char>* node, CATLControl* control);
void LoadConnections(AZ::rapidxml::xml_node<char>* rootNode, CATLControl* control);
void CreateDefaultControls();
QStandardItem* AddControl(CATLControl* control, QStandardItem* folderItem);
@@ -9,29 +9,28 @@
#include <AudioControlsWriter.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/IO/TextStreamWriters.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/XML/rapidxml_print.h>
#include <ACEEnums.h>
#include <ATLControlsModel.h>
#include <CryFile.h>
#include <IAudioSystem.h>
#include <IAudioSystemControl.h>
#include <IAudioSystemEditor.h>
#include <IEditor.h>
#include <Include/IFileUtil.h>
#include <Include/ISourceControl.h>
#include <ISystem.h>
#include <StringUtils.h>
#include <Util/PathUtil.h>
#include <QModelIndex>
#include <QStandardItemModel>
#include <QFileInfo>
using namespace PathUtil;
namespace AudioControls
{
namespace WriterStrings
@@ -80,6 +79,18 @@ namespace AudioControls
index = index.sibling(++i, 0);
}
auto fileIO = AZ::IO::FileIOBase::GetInstance();
AZStd::for_each(
m_foundLibraryPaths.begin(), m_foundLibraryPaths.end(),
[fileIO](AZStd::string& libraryPath) -> void
{
AZStd::optional<AZ::u64> newLength = fileIO->ConvertToAlias(libraryPath.data(), libraryPath.size());
if (newLength)
{
libraryPath.resize_no_construct(*newLength);
}
AZStd::to_lower(libraryPath.begin(), libraryPath.end());
});
// Delete libraries that don't exist anymore from disk
FilepathSet librariesToDelete;
@@ -103,7 +114,10 @@ namespace AudioControls
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::WriteLibrary(const AZStd::string_view libraryName, QModelIndex root)
{
if (root.isValid())
const char* controlsPath = nullptr;
Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath);
if (root.isValid() && controlsPath)
{
TLibraryStorage library;
int i = 0;
@@ -114,68 +128,63 @@ namespace AudioControls
child = root.model()->index(++i, 0, root);
}
const char* controlsPath = nullptr;
Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath);
for (auto& libraryPair : library)
{
AZStd::string libraryPath;
AZ::IO::FixedMaxPath libraryPath{ controlsPath };
const AZStd::string& scope = libraryPair.first;
if (scope.empty())
{
// no scope, file at the root level
libraryPath.append(controlsPath);
AZ::StringFunc::Path::Join(libraryPath.c_str(), libraryName.data(), libraryPath);
libraryPath.append(WriterStrings::LibraryExtension);
libraryPath /= libraryName;
libraryPath.ReplaceExtension(WriterStrings::LibraryExtension);
}
else
{
// with scope, inside level folder
libraryPath.append(controlsPath);
libraryPath.append(WriterStrings::LevelsSubFolder);
AZ::StringFunc::Path::Join(libraryPath.c_str(), scope.c_str(), libraryPath);
AZ::StringFunc::Path::Join(libraryPath.c_str(), libraryName.data(), libraryPath);
libraryPath.append(WriterStrings::LibraryExtension);
libraryPath /= AZ::IO::FixedMaxPath{ WriterStrings::LevelsSubFolder } / scope / libraryName;
libraryPath.ReplaceExtension(WriterStrings::LibraryExtension);
}
// should be able to change this back to GamePathToFullPath once a path normalization bug has been fixed:
AZStd::string fullFilePath;
AZ::StringFunc::Path::Join(Path::GetEditingGameDataFolder().c_str(), libraryPath.c_str(), fullFilePath);
AZStd::to_lower(fullFilePath.begin(), fullFilePath.end());
AZ::IO::FixedMaxPath fullFilePath = AZ::Utils::GetProjectPath();
fullFilePath /= libraryPath;
m_foundLibraryPaths.insert(fullFilePath.c_str());
const SLibraryScope& libScope = libraryPair.second;
if (libScope.m_isDirty)
{
XmlNodeRef fileNode = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::RootNodeTag);
fileNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, libraryName.data());
XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator);
AZ::rapidxml::xml_node<char>* fileNode =
xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(Audio::ATLXmlTags::RootNodeTag));
AZ::rapidxml::xml_attribute<char>* nameAttr = xmlAlloc.allocate_attribute(
xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(libraryName.data()));
fileNode->append_attribute(nameAttr);
for (int ii = 0; ii < eACET_NUM_TYPES; ++ii)
{
if (ii != eACET_SWITCH_STATE) // switch_states are written inside the switches
if (libScope.m_nodes[ii] && libScope.m_nodes[ii]->first_node() != nullptr)
{
if (libScope.m_nodes[ii]->getChildCount() > 0)
{
fileNode->addChild(libScope.m_nodes[ii]);
}
fileNode->append_node(libScope.m_nodes[ii]);
}
}
if (QFileInfo::exists(fullFilePath.c_str()))
if (auto fileInfo = QFileInfo(fullFilePath.c_str());
fileInfo.exists())
{
const DWORD fileAttributes = GetFileAttributes(fullFilePath.c_str());
if (fileAttributes & FILE_ATTRIBUTE_READONLY)
if (!fileInfo.isWritable())
{
// file is read-only
CheckOutFile(fullFilePath);
// file exists and is read-only
CheckOutFile(fullFilePath.Native());
}
fileNode->saveToFile(fullFilePath.c_str());
[[maybe_unused]] bool writeOk = WriteXmlToFile(fullFilePath.Native(), fileNode);
}
else
{
// save the file, CheckOutFile will add it, since it's new
fileNode->saveToFile(fullFilePath.c_str());
CheckOutFile(fullFilePath);
// since it's a new file, save the file first, CheckOutFile will add it
[[maybe_unused]] bool writeOk = WriteXmlToFile(fullFilePath.Native(), fileNode);
CheckOutFile(fullFilePath.Native());
}
}
}
@@ -249,14 +258,63 @@ namespace AudioControls
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::WriteControlToXml(XmlNodeRef node, CATLControl* control, const AZStd::string_view path)
bool CAudioControlsWriter::WriteXmlToFile(const AZStd::string_view filepath, AZ::rapidxml::xml_node<char>* rootNode)
{
if (!rootNode)
{
return false;
}
using namespace AZ::IO;
AZStd::string docString;
ByteContainerStream stringStream(&docString);
AZ::rapidxml::xml_document<char> xmlDoc;
xmlDoc.append_node(rootNode);
RapidXMLStreamWriter streamWriter(&stringStream);
AZ::rapidxml::print(streamWriter.Iterator(), xmlDoc);
streamWriter.FlushCache();
constexpr int openMode =
(SystemFile::SF_OPEN_WRITE_ONLY | SystemFile::SF_OPEN_CREATE | SystemFile::SF_OPEN_CREATE_PATH);
if (SystemFile fileOut;
fileOut.Open(filepath.data(), openMode))
{
auto bytesWritten = fileOut.Write(docString.data(), docString.size());
return (bytesWritten == docString.size());
}
return false;
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::WriteControlToXml(AZ::rapidxml::xml_node<char>* node, CATLControl* control, const AZStd::string_view path)
{
if (!node || !control)
{
return;
}
XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator);
const EACEControlType type = control->GetType();
XmlNodeRef childNode = node->createNode(TypeToTag(type).data());
childNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, control->GetName().c_str());
AZStd::string_view typeName = TypeToTag(type);
AZ::rapidxml::xml_node<char>* childNode =
xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(typeName.data()));
AZ::rapidxml::xml_attribute<char>* nameAttr = xmlAlloc.allocate_attribute(
xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(control->GetName().c_str()));
childNode->append_attribute(nameAttr);
if (!path.empty())
{
childNode->setAttr("path", path.data());
AZ::rapidxml::xml_attribute<char>* pathAttr = xmlAlloc.allocate_attribute(
xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLPathAttribute), xmlAlloc.allocate_string(path.data()));
childNode->append_attribute(pathAttr);
}
if (type == eACET_SWITCH)
@@ -271,7 +329,11 @@ namespace AudioControls
{
if (control->IsAutoLoad())
{
childNode->setAttr(Audio::ATLXmlTags::ATLTypeAttribute, Audio::ATLXmlTags::ATLDataLoadType);
AZ::rapidxml::xml_attribute<char>* loadAttr = xmlAlloc.allocate_attribute(
xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLTypeAttribute),
xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLDataLoadType));
childNode->append_attribute(loadAttr);
}
// New Preloads XML...
@@ -282,38 +344,39 @@ namespace AudioControls
WriteConnectionsToXml(childNode, control);
}
node->addChild(childNode);
node->append_node(childNode);
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::WriteConnectionsToXml(XmlNodeRef node, CATLControl* control)
void CAudioControlsWriter::WriteConnectionsToXml(AZ::rapidxml::xml_node<char>* node, CATLControl* control)
{
if (control && m_audioSystemImpl)
if (node && control && m_audioSystemImpl)
{
TXmlNodeList otherNodes = control->m_connectionNodes;
auto end = AZStd::remove_if(otherNodes.begin(), otherNodes.end(),
[](const SRawConnectionData& node)
auto end = AZStd::remove_if(
otherNodes.begin(), otherNodes.end(),
[](const SRawConnectionData& connection)
{
return node.m_isValid;
return connection.m_isValid;
}
);
otherNodes.erase(end, otherNodes.end());
for (auto& connectionNode : otherNodes)
{
node->addChild(connectionNode.m_xmlNode);
node->append_node(SRawConnectionData::DeepCopyNode(connectionNode.m_xmlNode));
}
const size_t size = control->ConnectionCount();
for (size_t i = 0; i < size; ++i)
{
TConnectionPtr connection = control->GetConnectionAt(i);
if (connection)
if (TConnectionPtr connection = control->GetConnectionAt(i);
connection != nullptr)
{
XmlNodeRef childNode = m_audioSystemImpl->CreateXMLNodeFromConnection(connection, control->GetType());
if (childNode)
if (auto childNode = m_audioSystemImpl->CreateXMLNodeFromConnection(connection, control->GetType());
childNode != nullptr)
{
node->addChild(childNode);
node->append_node(childNode);
control->m_connectionNodes.push_back(SRawConnectionData(childNode, true));
}
}
@@ -322,24 +385,24 @@ namespace AudioControls
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::CheckOutFile(const AZStd::string& filepath)
void CAudioControlsWriter::CheckOutFile(const AZStd::string_view filepath)
{
IEditor* editor = GetIEditor();
IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr;
if (fileUtil)
{
fileUtil->CheckoutFile(filepath.c_str(), nullptr);
fileUtil->CheckoutFile(filepath.data(), nullptr);
}
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::DeleteLibraryFile(const AZStd::string& filepath)
void CAudioControlsWriter::DeleteLibraryFile(const AZStd::string_view filepath)
{
IEditor* editor = GetIEditor();
IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr;
if (fileUtil)
{
fileUtil->DeleteFromSourceControl(filepath.c_str(), nullptr);
fileUtil->DeleteFromSourceControl(filepath.data(), nullptr);
}
}
@@ -16,10 +16,9 @@
#include <ACETypes.h>
#include <ATLCommon.h>
#include <AudioControl.h>
#include <ISystem.h>
#include <QModelIndex>
#include <IXml.h>
class QStandardItemModel;
@@ -33,15 +32,16 @@ namespace AudioControls
{
SLibraryScope()
{
m_nodes[eACET_TRIGGER] = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::TriggersNodeTag);
m_nodes[eACET_RTPC] = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::RtpcsNodeTag);
m_nodes[eACET_SWITCH] = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::SwitchesNodeTag);
XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator);
m_nodes[eACET_TRIGGER] = xmlAlloc.allocate_node(AZ::rapidxml::node_element, Audio::ATLXmlTags::TriggersNodeTag);
m_nodes[eACET_RTPC] = xmlAlloc.allocate_node(AZ::rapidxml::node_element, Audio::ATLXmlTags::RtpcsNodeTag);
m_nodes[eACET_SWITCH] = xmlAlloc.allocate_node(AZ::rapidxml::node_element, Audio::ATLXmlTags::SwitchesNodeTag);
m_nodes[eACET_SWITCH_STATE] = nullptr;
m_nodes[eACET_ENVIRONMENT] = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::EnvironmentsNodeTag);
m_nodes[eACET_PRELOAD] = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::PreloadsNodeTag);
m_nodes[eACET_ENVIRONMENT] = xmlAlloc.allocate_node(AZ::rapidxml::node_element, Audio::ATLXmlTags::EnvironmentsNodeTag);
m_nodes[eACET_PRELOAD] = xmlAlloc.allocate_node(AZ::rapidxml::node_element, Audio::ATLXmlTags::PreloadsNodeTag);
}
XmlNodeRef m_nodes[eACET_NUM_TYPES];
AZ::rapidxml::xml_node<char>* m_nodes[eACET_NUM_TYPES];
bool m_isDirty = false;
};
@@ -56,12 +56,13 @@ namespace AudioControls
private:
void WriteLibrary(const AZStd::string_view libraryName, QModelIndex root);
void WriteItem(QModelIndex index, const AZStd::string& path, TLibraryStorage& library, bool isParentModified);
void WriteControlToXml(XmlNodeRef node, CATLControl* control, const AZStd::string_view path);
void WriteConnectionsToXml(XmlNodeRef node, CATLControl* control);
void WriteControlToXml(AZ::rapidxml::xml_node<char>* node, CATLControl* control, const AZStd::string_view path);
void WriteConnectionsToXml(AZ::rapidxml::xml_node<char>* node, CATLControl* control);
bool IsItemModified(QModelIndex index);
void CheckOutFile(const AZStd::string& filepath);
void DeleteLibraryFile(const AZStd::string& filepath);
bool WriteXmlToFile(const AZStd::string_view filepath, AZ::rapidxml::xml_node<char>* rootNode);
void CheckOutFile(const AZStd::string_view filepath);
void DeleteLibraryFile(const AZStd::string_view filepath);
CATLControlsModel* m_atlModel;
QStandardItemModel* m_layoutModel;
@@ -31,7 +31,7 @@ namespace AudioControls
iconFile = ":/Icons/Switch_Icon.svg";
break;
case AudioControls::eACET_SWITCH_STATE:
iconFile = ":/Icons/Property_Icon.svg";
iconFile = ":/Icons/Property_Icon.png";
break;
case AudioControls::eACET_ENVIRONMENT:
iconFile = ":/Icons/Environment_Icon.svg";
@@ -41,7 +41,7 @@ namespace AudioControls
break;
default:
// should make a "default"/empty icon...
iconFile = ":/Icons/RTPC_Icon.svg";
iconFile = ":/Icons/Unassigned.svg";
}
QIcon icon(iconFile);
@@ -1005,14 +1005,14 @@ namespace Audio
AZStd::string searchPath;
AZ::StringFunc::Path::Join(m_rootPath.c_str(), folderPath, searchPath);
AZStd::vector<AZStd::string> foundFiles = Audio::FindFilesInPath(searchPath, "*.xml");
auto foundFiles = Audio::FindFilesInPath(searchPath, "*.xml");
for (const auto& file : foundFiles)
{
AZ_Assert(AZ::IO::FileIOBase::GetInstance()->Exists(file.c_str()), "FindFiles found file '%s' but FileIO says it doesn't exist!", file.c_str());
g_audioLogger.Log(eALT_ALWAYS, "Loading Audio Controls Library: '%s'", file.c_str());
Audio::ScopedXmlLoader xmlFileLoader(file);
Audio::ScopedXmlLoader xmlFileLoader(file.Native());
if (xmlFileLoader.HasError())
{
continue;
@@ -1053,14 +1053,14 @@ namespace Audio
AZStd::string searchPath;
AZ::StringFunc::Path::Join(m_rootPath.c_str(), folderPath, searchPath);
AZStd::vector<AZStd::string> foundFiles = Audio::FindFilesInPath(searchPath, "*.xml");
auto foundFiles = Audio::FindFilesInPath(searchPath, "*.xml");
for (const auto& file : foundFiles)
{
AZ_Assert(AZ::IO::FileIOBase::GetInstance()->Exists(file.c_str()), "FindFiles found file '%s' but FileIO says it doesn't exist!", file.c_str());
g_audioLogger.Log(eALT_ALWAYS, "Loading Audio Preloads Library: '%s'", file.c_str());
Audio::ScopedXmlLoader xmlFileLoader(file);
Audio::ScopedXmlLoader xmlFileLoader(file.Native());
if (xmlFileLoader.HasError())
{
continue;