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

351 lines
13 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include <AudioControlsWriter.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/StringFunc/StringFunc.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
{
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);
}
// 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)
{
if (root.isValid())
{
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);
}
const char* controlsPath = nullptr;
Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath);
for (auto& libraryPair : library)
{
AZStd::string libraryPath;
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);
}
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);
}
// 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());
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());
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]->getChildCount() > 0)
{
fileNode->addChild(libScope.m_nodes[ii]);
}
}
}
if (QFileInfo::exists(fullFilePath.c_str()))
{
const DWORD fileAttributes = GetFileAttributes(fullFilePath.c_str());
if (fileAttributes & FILE_ATTRIBUTE_READONLY)
{
// file is read-only
CheckOutFile(fullFilePath);
}
fileNode->saveToFile(fullFilePath.c_str());
}
else
{
// save the file, CheckOutFile will add it, since it's new
fileNode->saveToFile(fullFilePath.c_str());
CheckOutFile(fullFilePath);
}
}
}
}
}
//-------------------------------------------------------------------------------------------//
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;
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::WriteControlToXml(XmlNodeRef node, CATLControl* control, const AZStd::string_view path)
{
const EACEControlType type = control->GetType();
XmlNodeRef childNode = node->createNode(TypeToTag(type).data());
childNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, control->GetName().c_str());
if (!path.empty())
{
childNode->setAttr("path", path.data());
}
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())
{
childNode->setAttr(Audio::ATLXmlTags::ATLTypeAttribute, Audio::ATLXmlTags::ATLDataLoadType);
}
// New Preloads XML...
WriteConnectionsToXml(childNode, control);
}
else
{
WriteConnectionsToXml(childNode, control);
}
node->addChild(childNode);
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::WriteConnectionsToXml(XmlNodeRef node, CATLControl* control)
{
if (control && m_audioSystemImpl)
{
TXmlNodeList otherNodes = control->m_connectionNodes;
auto end = AZStd::remove_if(otherNodes.begin(), otherNodes.end(),
[](const SRawConnectionData& node)
{
return node.m_isValid;
}
);
otherNodes.erase(end, otherNodes.end());
for (auto& connectionNode : otherNodes)
{
node->addChild(connectionNode.m_xmlNode);
}
const size_t size = control->ConnectionCount();
for (size_t i = 0; i < size; ++i)
{
TConnectionPtr connection = control->GetConnectionAt(i);
if (connection)
{
XmlNodeRef childNode = m_audioSystemImpl->CreateXMLNodeFromConnection(connection, control->GetType());
if (childNode)
{
node->addChild(childNode);
control->m_connectionNodes.push_back(SRawConnectionData(childNode, true));
}
}
}
}
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::CheckOutFile(const AZStd::string& filepath)
{
IEditor* editor = GetIEditor();
IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr;
if (fileUtil)
{
fileUtil->CheckoutFile(filepath.c_str(), nullptr);
}
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsWriter::DeleteLibraryFile(const AZStd::string& filepath)
{
IEditor* editor = GetIEditor();
IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr;
if (fileUtil)
{
fileUtil->DeleteFromSourceControl(filepath.c_str(), nullptr);
}
}
} // namespace AudioControls