You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/AudioSystem/Code/Source/Editor/AudioControlsWriter.cpp

404 lines
15 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 <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 <IAudioSystem.h>
#include <IAudioSystemControl.h>
#include <IAudioSystemEditor.h>
#include <IEditor.h>
#include <Include/IFileUtil.h>
#include <Include/ISourceControl.h>
#include <QModelIndex>
#include <QStandardItemModel>
#include <QFileInfo>
namespace AudioControls
{
namespace WriterStrings
{
static constexpr const char* LevelsSubFolder = "levels";
static constexpr const char* LibraryExtension = ".xml";
} // namespace WriterStrings
//-------------------------------------------------------------------------------------------//
AZStd::string_view TypeToTag(EACEControlType type)
{
switch (type)
{
case eACET_RTPC:
return Audio::ATLXmlTags::ATLRtpcTag;
case eACET_TRIGGER:
return Audio::ATLXmlTags::ATLTriggerTag;
case eACET_SWITCH:
return Audio::ATLXmlTags::ATLSwitchTag;
case eACET_SWITCH_STATE:
return Audio::ATLXmlTags::ATLSwitchStateTag;
case eACET_PRELOAD:
return Audio::ATLXmlTags::ATLPreloadRequestTag;
case eACET_ENVIRONMENT:
return Audio::ATLXmlTags::ATLEnvironmentTag;
}
return "";
}
//-------------------------------------------------------------------------------------------//
CAudioControlsWriter::CAudioControlsWriter(CATLControlsModel* atlModel, QStandardItemModel* layoutModel, IAudioSystemEditor* audioSystemImpl, FilepathSet& previousLibraryPaths)
: m_atlModel(atlModel)
, m_layoutModel(layoutModel)
, m_audioSystemImpl(audioSystemImpl)
{
if (m_atlModel && m_layoutModel && m_audioSystemImpl)
{
m_layoutModel->blockSignals(true);
int i = 0;
QModelIndex index = m_layoutModel->index(i, 0);
while (index.isValid())
{
WriteLibrary(index.data(Qt::DisplayRole).toString().toUtf8().data(), index);
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
{
if (auto newPathOpt = fileIO->ConvertToAlias(AZ::IO::PathView{ libraryPath });
newPathOpt.has_value())
{
libraryPath = newPathOpt.value().Native();
}
AZStd::to_lower(libraryPath.begin(), libraryPath.end());
});
// Delete libraries that don't exist anymore from disk
FilepathSet librariesToDelete;
AZStd::set_difference(
previousLibraryPaths.begin(), previousLibraryPaths.end(),
m_foundLibraryPaths.begin(), m_foundLibraryPaths.end(),
AZStd::inserter(librariesToDelete, librariesToDelete.begin())
);
for (auto it = librariesToDelete.begin(); it != librariesToDelete.end(); ++it)
{
DeleteLibraryFile((*it).c_str());
}
previousLibraryPaths = m_foundLibraryPaths;
m_layoutModel->blockSignals(false);
}
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::WriteLibrary(const AZStd::string_view libraryName, QModelIndex root)
{
const char* controlsPath = nullptr;
Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath);
if (root.isValid() && controlsPath)
{
TLibraryStorage library;
int i = 0;
QModelIndex child = root.model()->index(i, 0, root);
while (child.isValid())
{
WriteItem(child, "", library, root.data(eDR_MODIFIED).toBool());
child = root.model()->index(++i, 0, root);
}
for (auto& libraryPair : library)
{
AZ::IO::FixedMaxPath libraryPath{ controlsPath };
const AZStd::string& scope = libraryPair.first;
if (scope.empty())
{
// no scope, file at the root level
libraryPath /= libraryName;
libraryPath.ReplaceExtension(WriterStrings::LibraryExtension);
}
else
{
// with scope, inside level folder
libraryPath /= AZ::IO::FixedMaxPath{ WriterStrings::LevelsSubFolder } / scope / libraryName;
libraryPath.ReplaceExtension(WriterStrings::LibraryExtension);
}
AZ::IO::FixedMaxPath fullFilePath = AZ::Utils::GetProjectPath();
fullFilePath /= libraryPath;
m_foundLibraryPaths.insert(fullFilePath.c_str());
const SLibraryScope& libScope = libraryPair.second;
if (libScope.m_isDirty)
{
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 (libScope.m_nodes[ii] && libScope.m_nodes[ii]->first_node() != nullptr)
{
fileNode->append_node(libScope.m_nodes[ii]);
}
}
if (auto fileInfo = QFileInfo(fullFilePath.c_str());
fileInfo.exists())
{
if (!fileInfo.isWritable())
{
// file exists and is read-only
CheckOutFile(fullFilePath.Native());
}
[[maybe_unused]] bool writeOk = WriteXmlToFile(fullFilePath.Native(), fileNode);
}
else
{
// 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());
}
}
}
}
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::WriteItem(QModelIndex index, const AZStd::string& path, TLibraryStorage& library, bool isParentModified)
{
if (index.isValid())
{
if (index.data(eDR_TYPE) == eIT_FOLDER)
{
int i = 0;
QModelIndex child = index.model()->index(i, 0, index);
while (child.isValid())
{
AZStd::string newPath = path.empty() ? "" : path + "/";
newPath += index.data(Qt::DisplayRole).toString().toUtf8().data();
WriteItem(child, newPath, library, index.data(eDR_MODIFIED).toBool() || isParentModified);
child = index.model()->index(++i, 0, index);
}
QStandardItem* item = m_layoutModel->itemFromIndex(index);
if (item)
{
item->setData(false, eDR_MODIFIED);
}
}
else
{
CATLControl* control = m_atlModel->GetControlByID(index.data(eDR_ID).toUInt());
if (control)
{
SLibraryScope& scope = library[control->GetScope()];
if (IsItemModified(index) || isParentModified)
{
scope.m_isDirty = true;
QStandardItem* item = m_layoutModel->itemFromIndex(index);
if (item)
{
item->setData(false, eDR_MODIFIED);
}
}
WriteControlToXml(scope.m_nodes[control->GetType()], control, path);
}
}
}
}
//-------------------------------------------------------------------------------------------//
bool CAudioControlsWriter::IsItemModified(QModelIndex index)
{
if (index.data(eDR_MODIFIED).toBool() == true)
{
return true;
}
int i = 0;
QModelIndex child = index.model()->index(i, 0, index);
while (child.isValid())
{
if (IsItemModified(child))
{
return true;
}
child = index.model()->index(++i, 0, index);
}
return false;
}
//-------------------------------------------------------------------------------------------//
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();
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())
{
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)
{
const size_t size = control->ChildCount();
for (size_t i = 0; i < size; ++i)
{
WriteControlToXml(childNode, control->GetChild(i), "");
}
}
else if (type == eACET_PRELOAD)
{
if (control->IsAutoLoad())
{
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...
WriteConnectionsToXml(childNode, control);
}
else
{
WriteConnectionsToXml(childNode, control);
}
node->append_node(childNode);
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::WriteConnectionsToXml(AZ::rapidxml::xml_node<char>* node, CATLControl* control)
{
if (node && control && m_audioSystemImpl)
{
for (auto& connectionNode : control->m_connectionNodes)
{
if (!connectionNode.m_isValid)
{
auto nodeCopy = SRawConnectionData::DeepCopyNode(connectionNode.m_xmlNode.get());
node->append_node(nodeCopy.release());
}
}
const size_t size = control->ConnectionCount();
for (size_t i = 0; i < size; ++i)
{
if (TConnectionPtr connection = control->GetConnectionAt(i);
connection != nullptr)
{
if (auto childNode = m_audioSystemImpl->CreateXMLNodeFromConnection(connection, control->GetType());
childNode != nullptr)
{
node->append_node(childNode);
control->m_connectionNodes.push_back(SRawConnectionData(childNode, true));
}
}
}
}
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::CheckOutFile(const AZStd::string_view filepath)
{
IEditor* editor = GetIEditor();
IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr;
if (fileUtil)
{
fileUtil->CheckoutFile(AZ::IO::FixedMaxPath{ filepath }.c_str(), nullptr);
}
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::DeleteLibraryFile(const AZStd::string_view filepath)
{
IEditor* editor = GetIEditor();
IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr;
if (fileUtil)
{
fileUtil->DeleteFromSourceControl(AZ::IO::FixedMaxPath{ filepath }.c_str(), nullptr);
}
}
} // namespace AudioControls