2
0
Эх сурвалжийг харах

{lyn13480} Adding Shared Cache tab to the Asset Processor (#10565)

* config updates for ACS

Signed-off-by: Allen Jackson <[email protected]>

* Fixing some merge compile errors

Signed-off-by: Allen Jackson <[email protected]>

* cleaning up merge compile errors in tests

Signed-off-by: Allen Jackson <[email protected]>

* small fix for a unit test

Signed-off-by: Allen Jackson <[email protected]>

* new API for setting up the ACS mode

Signed-off-by: Allen Jackson <[email protected]>

* * AssetServerHandlerUnitTest handles the new test logic to enable the Asset Cache Server. so removed GetServerAddress_ReadFromConfig_Valid
* updated AssetCacheServer logic to serialize settings as JSON

Signed-off-by: Allen Jackson <[email protected]>

* Signed-off-by: Allen Jackson <[email protected]>

* Signed-off-by: Allen Jackson <[email protected]>

* removing lines

Signed-off-by: Allen Jackson <[email protected]>

* minor updates

Signed-off-by: Allen Jackson <[email protected]>

* adding UX for ACS

Signed-off-by: Allen Jackson <[email protected]>

* adding configuration UX updates based on a UX review

- now a status bar/message area
- added some native icons
- added default asset types to cache
- open folder dialog
-

Signed-off-by: Allen Jackson <[email protected]>

* fixed odd UTF-8 characters

Signed-off-by: Allen Jackson <[email protected]>

* simplified the platform config code
fixed a duplicate fields issue

Signed-off-by: Allen Jackson <[email protected]>

* defaulting all the patterns to "enablecd"

Signed-off-by: Allen Jackson <[email protected]>

* code clean up and bug fixes using the Shared Cache tab widgets

Signed-off-by: Allen Jackson <[email protected]>

* moved CacheServerData into its own code module

Signed-off-by: Allen Jackson <[email protected]>

* Changed the key to /O3DE/AssetProcessor/Settings/Server

Signed-off-by: Allen Jackson <[email protected]>

* Moving to O3DE base root key for all settings

Signed-off-by: Allen Jackson <[email protected]>

* update unit tests

Signed-off-by: Allen Jackson <[email protected]>
Allen Jackson 3 жил өмнө
parent
commit
05d0759a04

+ 32 - 0
AutomatedTesting/Registry/asset_cache_server_settings.setreg

@@ -0,0 +1,32 @@
+{
+    "O3DE": {
+        "AssetProcessor": {
+            "Settings": {
+                "Server": {
+                    "cacheServerAddress": "",
+                    "assetCacheServerMode": "inactive",
+                    "ACS Atom Image Builder": {
+                        "name": "Atom Image Builder",
+                        "glob": "*.tiff",
+                        "checkServer": true
+                    },
+                    "ACS Precompiled Shader Builder": {
+                        "name": "Precompiled Shader Builder",
+                        "glob": "*.precompiledshader",
+                        "checkServer": true
+                    },
+                    "ACS Scene Builder": {
+                        "name": "Scene Builder",
+                        "glob": "*.fbx",
+                        "checkServer": true
+                    },
+                    "ACS Shader Asset Builder": {
+                        "name": "Shader Asset Builder",
+                        "glob": "*.shader",
+                        "checkServer": true
+                    }
+                }
+            }
+        }
+    }
+}

+ 2 - 0
Code/Tools/AssetProcessor/assetprocessor_gui_files.cmake

@@ -23,6 +23,8 @@ set(FILES
     native/ui/AssetTreeModel.cpp
     native/ui/AssetTreeItem.h
     native/ui/AssetTreeItem.cpp
+    native/ui/CacheServerData.h
+    native/ui/CacheServerData.cpp
     native/ui/ConnectionEditDialog.h
     native/ui/ConnectionEditDialog.cpp
     native/ui/GoToButton.h

+ 1 - 1
Code/Tools/AssetProcessor/native/resourcecompiler/RCBuilder.cpp

@@ -76,7 +76,7 @@ namespace AssetProcessor
 
             descriptor.m_additionalFingerprintInfo = extraInformationForFingerprinting;
 
-            bool isCopyJob = (platformSpec == AssetInternalSpec::Copy);
+            const bool isCopyJob = (platformSpec == AssetInternalSpec::Copy);
 
             // Temporary solution to get around the fact that we don't have job dependencies
             if (isCopyJob)

+ 126 - 0
Code/Tools/AssetProcessor/native/ui/CacheServerData.cpp

@@ -0,0 +1,126 @@
+/*
+ * 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 <native/ui/CacheServerData.h>
+#include <native/utilities/AssetServerHandler.h>
+
+#include <AzCore/IO/Path/Path.h>
+#include <AzCore/JSON/document.h>
+#include <AzCore/JSON/stringbuffer.h>
+#include <AzCore/JSON/prettywriter.h>
+#include <AzCore/JSON/pointer.h>
+
+#include <QFile>
+#include <QTextStream>
+
+namespace AssetProcessor
+{
+    void CacheServerData::Reset()
+    {
+        auto* recognizerConfiguration = AZ::Interface<AssetProcessor::RecognizerConfiguration>::Get();
+        if (recognizerConfiguration)
+        {
+            m_patternContainer = recognizerConfiguration->GetAssetCacheRecognizerContainer();
+        }
+
+        using namespace AssetProcessor;
+        AssetServerBus::BroadcastResult(m_cachingMode, &AssetServerBus::Events::GetRemoteCachingMode);
+        AssetServerBus::BroadcastResult(m_serverAddress, &AssetServerBus::Events::GetServerAddress);
+        m_dirty = false;
+    }
+
+    bool CacheServerData::Save(const AZ::IO::Path& projectPath)
+    {
+        // construct JSON for writing out a .setreg for current settings
+        rapidjson::Document doc;
+        doc.SetObject();
+
+        // this builds up the JSON doc for each key inside AssetProcessorSettingsKey
+        AZStd::string path;
+        AZ::StringFunc::TokenizeVisitor(
+            AssetProcessor::AssetProcessorServerKey,
+            [&doc, &path](AZStd::string_view elem)
+            {
+                auto key = rapidjson::StringRef(elem.data(), elem.size());
+                rapidjson::Value value(rapidjson::kObjectType);
+                if (path.empty())
+                {
+                    doc.AddMember(key, value, doc.GetAllocator());
+                }
+                else
+                {
+                    auto* node = rapidjson::Pointer(path.c_str(), path.size()).Get(doc);
+                    node->AddMember(key, value, doc.GetAllocator());
+                }
+                path += "/";
+                path += elem;
+            },
+            '/');
+
+        // creates a rapidjson doc to hold the shared cache server settings
+        rapidjson::Value value(rapidjson::kObjectType);
+        auto server = rapidjson::Pointer(AssetProcessor::AssetProcessorServerKey).Get(doc);
+
+        server->AddMember(
+            rapidjson::StringRef(AssetProcessor::CacheServerAddressKey),
+            rapidjson::StringRef(m_serverAddress.c_str()),
+            doc.GetAllocator());
+
+        server->AddMember(
+            rapidjson::StringRef(AssetProcessor::AssetCacheServerModeKey),
+            rapidjson::StringRef(AssetProcessor::AssetServerHandler::GetAssetServerModeText(m_cachingMode)),
+            doc.GetAllocator());
+
+        // add the cache patterns
+        AZStd::string jsonText;
+        AssetProcessor::PlatformConfiguration::ConvertToJson(m_patternContainer, jsonText);
+        if (!jsonText.empty())
+        {
+            rapidjson::Document recognizerDoc;
+            recognizerDoc.Parse(jsonText.c_str());
+            for (auto member = recognizerDoc.MemberBegin(); member != recognizerDoc.MemberEnd(); ++member)
+            {
+                rapidjson::Value valuePattern;
+                valuePattern.CopyFrom(member->value, doc.GetAllocator(), true);
+                rapidjson::Value valueKey;
+                valueKey.CopyFrom(member->name, doc.GetAllocator(), true);
+                server->AddMember(AZStd::move(valueKey), AZStd::move(valuePattern), doc.GetAllocator());
+            }
+        }
+
+        // get project folder and construct Registry project folder
+        const char* assetCacheServerSettings = "asset_cache_server_settings.setreg";
+        AZ::IO::Path fullpath( projectPath );
+        fullpath /= "Registry";
+        fullpath /= assetCacheServerSettings;
+
+        QFile file(fullpath.c_str());
+        bool result = file.open(QIODevice::ReadWrite);
+        if (result)
+        {
+            rapidjson::StringBuffer buffer;
+            rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
+            doc.Accept(writer);
+
+            // rewrite the file contents
+            file.resize(0);
+            QTextStream stream(&file);
+            stream.setCodec("UTF-8");
+            stream << buffer.GetString();
+
+            m_errorLevel = ErrorLevel::Notice;
+            m_errorMessage = AZStd::string::format("Updated settings file (%s)", fullpath.c_str());
+        }
+        else
+        {
+            m_errorLevel = ErrorLevel::Error;
+            m_errorMessage = AZStd::string::format("**Error**: Could not write settings file (%s)", fullpath.c_str());
+        }
+        file.close();
+        return result;
+    }
+}

+ 38 - 0
Code/Tools/AssetProcessor/native/ui/CacheServerData.h

@@ -0,0 +1,38 @@
+/*
+ * 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
+ *
+ */
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include <native/utilities/AssetUtilEBusHelper.h>
+#include <native/utilities/PlatformConfiguration.h>
+#include <AzCore/IO/Path/Path.h>
+#endif
+
+namespace AssetProcessor
+{
+    struct CacheServerData
+    {
+        enum class ErrorLevel
+        {
+            None,
+            Notice,
+            Warning,
+            Error
+        };
+
+        bool m_dirty = false;
+        AssetServerMode m_cachingMode = AssetServerMode::Inactive;
+        AZStd::string m_serverAddress = "";
+        RecognizerContainer m_patternContainer;
+        ErrorLevel m_errorLevel = ErrorLevel::None;
+        AZStd::string m_errorMessage;
+
+        void Reset();
+        bool Save(const AZ::IO::Path& projectPath);
+    };
+}

+ 297 - 1
Code/Tools/AssetProcessor/native/ui/MainWindow.cpp

@@ -18,6 +18,9 @@
 
 #include <AzFramework/Asset/AssetSystemBus.h>
 
+#include <AzCore/JSON/stringbuffer.h>
+#include <AzCore/JSON/prettywriter.h>
+#include <AzCore/JSON/pointer.h>
 
 #include <AzQtComponents/AzQtComponentsAPI.h>
 #include <AzQtComponents/Components/ConfigHelpers.h>
@@ -30,7 +33,7 @@
 #include <native/resourcecompiler/JobsModel.h>
 #include "native/ui/ui_MainWindow.h"
 #include "native/ui/JobTreeViewItemDelegate.h"
-
+#include <native/utilities/AssetServerHandler.h>
 
 #include "../utilities/GUIApplicationManager.h"
 #include "../utilities/ApplicationServer.h"
@@ -47,6 +50,7 @@
 #include <QUrl>
 #include <QWidgetAction>
 #include <QKeyEvent>
+#include <QFileDialog>
 
 static const char* g_showContextDetailsKey = "ShowContextDetailsTable";
 static const QString g_jobFilteredSearchWidgetState = QStringLiteral("jobFilteredSearchWidget");
@@ -211,6 +215,7 @@ void MainWindow::Activate()
     ui->buttonList->addTab(QStringLiteral("Connections"));
     ui->buttonList->addTab(QStringLiteral("Builders"));
     ui->buttonList->addTab(QStringLiteral("Tools"));
+    ui->buttonList->addTab(QStringLiteral("Shared Cache"));
 
     connect(ui->buttonList, &AzQtComponents::SegmentBar::currentChanged, ui->dialogStack, &QStackedWidget::setCurrentIndex);
     const int startIndex = static_cast<int>(DialogStackIndex::Jobs);
@@ -527,6 +532,9 @@ void MainWindow::Activate()
 
     m_guiApplicationManager->GetAssetProcessorManager()->SetBuilderDebugFlag(enableBuilderDebugFlag);
     ui->debugOutputCheckBox->setCheckState(enableBuilderDebugFlag ? Qt::Checked : Qt::Unchecked);
+
+    // Shared Cache tab:
+    SetupAssetServerTab();
 }
 
 void MainWindow::BuilderTabSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/)
@@ -570,6 +578,294 @@ void MainWindow::BuilderTabSelectionChanged(const QItemSelection& selected, cons
     }
 }
 
+namespace MainWindowInternal
+{
+    enum class PatternColumns
+    {
+        Enabled = 0,
+        Name = 1,
+        Type = 2,
+        Pattern = 3,
+        Remove = 4
+    };
+}
+
+void MainWindow::SetupAssetServerTab()
+{
+    using namespace AssetProcessor;
+    using namespace MainWindowInternal;
+
+    m_cacheServerData.Reset();
+
+    ui->serverCacheModeOptions->addItem(QString("Inactive"), aznumeric_cast<int>(AssetProcessor::AssetServerMode::Inactive));
+    ui->serverCacheModeOptions->addItem(QString("Server"), aznumeric_cast<int>(AssetProcessor::AssetServerMode::Server));
+    ui->serverCacheModeOptions->addItem(QString("Client"), aznumeric_cast<int>(AssetProcessor::AssetServerMode::Client));
+
+    QObject::connect(ui->serverCacheModeOptions,
+        static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+        this,
+        [this](int newIndex)
+        {
+            AssetServerMode inputAssetServerMode = aznumeric_cast<AssetServerMode>(newIndex);
+            AssetServerBus::BroadcastResult(this->m_cacheServerData.m_cachingMode, &AssetServerBus::Events::GetRemoteCachingMode);
+            if (this->m_cacheServerData.m_cachingMode != inputAssetServerMode)
+            {
+                this->m_cacheServerData.m_dirty = true;
+                this->m_cacheServerData.m_cachingMode = inputAssetServerMode;
+                this->CheckAssetServerStates();
+            }
+        });
+
+    ui->serverAddressButton->setIcon(QIcon(":/PropertyEditor/Resources/Browse_on.png"));
+    QObject::connect(ui->serverAddressButton, &QPushButton::clicked, this,
+        [this]()
+        {
+            auto path = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Choose remote folder.")));
+            if (!path.isEmpty())
+            {
+                ui->serverAddressText->setPlainText(path);
+            }
+        });
+
+    QObject::connect(ui->serverAddressText, &QPlainTextEdit::textChanged, this,
+        [this]()
+        {
+            SetServerAddress(this->ui->serverAddressText->toPlainText().toUtf8().data());
+        });
+
+    QObject::connect(ui->sharedCacheSubmitButton, &QPushButton::clicked, this,
+        [this]()
+        {
+            if (this->m_cacheServerData.m_dirty)
+            {
+                bool changedServerAddress = false;
+                AssembleAssetPatterns();
+                AssetServerBus::BroadcastResult(changedServerAddress, &AssetServerBus::Events::SetServerAddress, this->m_cacheServerData.m_serverAddress);
+                if (changedServerAddress)
+                {
+                    AZ::IO::Path projectPath(m_guiApplicationManager->GetProjectPath().toUtf8().data());
+                    if (this->m_cacheServerData.Save(projectPath))
+                    {
+                        AssetServerBus::Broadcast(&AssetServerBus::Events::SetRemoteCachingMode, this->m_cacheServerData.m_cachingMode);
+                        this->m_cacheServerData.Reset();
+
+                        // Clear the save message after a few moments
+                        QTimer::singleShot(1000 * 5, this, [this] {
+                                this->m_cacheServerData.m_errorLevel = CacheServerData::ErrorLevel::None;
+                                this->m_cacheServerData.m_errorMessage.clear();
+                                this->CheckAssetServerStates();
+                            });
+                    }
+                }
+                else if (this->m_cacheServerData.m_cachingMode != AssetServerMode::Inactive)
+                {
+                    this->m_cacheServerData.m_errorLevel = CacheServerData::ErrorLevel::Error;
+                    this->m_cacheServerData.m_errorMessage = AZStd::string::format("**Error**: Invalid server address!");
+                }
+                this->CheckAssetServerStates();
+            }
+        });
+
+    QObject::connect(ui->sharedCacheDiscardButton, &QPushButton::clicked, this,
+        [this]()
+        {
+            this->m_cacheServerData.Reset();
+            this->ResetAssetServerView();
+        });
+
+    // setting up the patterns table
+    QObject::connect(ui->sharedCacheAddPattern, &QPushButton::clicked, this,
+        [this]()
+        {
+            AddPatternRow("New Name", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard, "", true);
+            this->m_cacheServerData.m_dirty = true;
+            this->CheckAssetServerStates();
+        });
+
+    ui->sharedCacheTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+    ui->sharedCacheTable->horizontalHeader()->setSectionResizeMode(aznumeric_cast<int>(PatternColumns::Enabled), QHeaderView::Fixed);
+    ui->sharedCacheTable->horizontalHeader()->setSectionResizeMode(aznumeric_cast<int>(PatternColumns::Remove), QHeaderView::Fixed);
+
+    ResetAssetServerView();
+    CheckAssetServerStates();
+}
+
+void MainWindow::AddPatternRow(AZStd::string_view name, AssetBuilderSDK::AssetBuilderPattern::PatternType type, AZStd::string_view pattern, bool enable)
+{
+    using namespace AssetBuilderSDK;
+    using namespace MainWindowInternal;
+
+    int row = ui->sharedCacheTable->rowCount();
+    ui->sharedCacheTable->insertRow(row);
+
+    auto updateStatus = [this](int)
+    {
+        this->m_cacheServerData.m_dirty = true;
+        this->CheckAssetServerStates();
+    };
+
+    QObject::connect(ui->sharedCacheTable, &QTableWidget::cellChanged, this,
+        [this](int, int)
+        {
+            this->m_cacheServerData.m_dirty = true;
+            this->CheckAssetServerStates();
+        });
+
+    // Enabled check mark
+    auto* enableChackmark = new QCheckBox();
+    enableChackmark->setChecked(enable);
+    QObject::connect(enableChackmark, &QCheckBox::stateChanged, ui->sharedCacheTable, updateStatus);
+    ui->sharedCacheTable->setCellWidget(row, aznumeric_cast<int>(PatternColumns::Enabled), enableChackmark);
+    ui->sharedCacheTable->setColumnWidth(aznumeric_cast<int>(PatternColumns::Enabled), 8);
+
+    // Name
+    auto* nameWidgetItem = new QTableWidgetItem(name.data());
+    ui->sharedCacheTable->setItem(row, aznumeric_cast<int>(PatternColumns::Name), nameWidgetItem);
+
+    // Type combo
+    auto* combo = new QComboBox();
+    QObject::connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), ui->sharedCacheTable, updateStatus);
+    combo->addItem("Wildcard", QVariant(AssetBuilderPattern::PatternType::Wildcard));
+    combo->addItem("Regex", QVariant(AssetBuilderPattern::PatternType::Regex));
+    combo->setCurrentIndex(aznumeric_cast<int>(type));
+    ui->sharedCacheTable->setCellWidget(row, aznumeric_cast<int>(PatternColumns::Type), combo);
+
+    // Pattern
+    auto* patternWidgetItem = new QTableWidgetItem(pattern.data());
+    ui->sharedCacheTable->setItem(row, aznumeric_cast<int>(PatternColumns::Pattern), patternWidgetItem);
+
+    // Remove button
+    auto* button = new QPushButton();
+    button->setIcon(QIcon(":/PropertyEditor/Resources/trash-small.png"));
+    ui->sharedCacheTable->setCellWidget(row, aznumeric_cast<int>(PatternColumns::Remove), button);
+    ui->sharedCacheTable->setColumnWidth(aznumeric_cast<int>(PatternColumns::Remove), 16);
+    QObject::connect(button, &QPushButton::clicked, this,
+        [this]()
+        {
+            this->ui->sharedCacheTable->removeRow(this->ui->sharedCacheTable->currentRow());
+            this->m_cacheServerData.m_dirty = true;
+            this->CheckAssetServerStates();
+        });
+}
+
+void MainWindow::AssembleAssetPatterns()
+{
+    using namespace AssetBuilderSDK;
+    using namespace MainWindowInternal;
+
+    AssetProcessor::RecognizerContainer patternContainer;
+    int row = 0;
+    for(; row < ui->sharedCacheTable->rowCount(); ++row)
+    {
+        auto pattern = AZStd::pair<AZStd::string, AssetProcessor::AssetRecognizer>();
+
+        auto* itemName = ui->sharedCacheTable->item(row, aznumeric_cast<int>(PatternColumns::Name));
+        auto* itemPattern = ui->sharedCacheTable->item(row, aznumeric_cast<int>(PatternColumns::Pattern));
+        auto* itemType = qobject_cast<QComboBox*>(ui->sharedCacheTable->cellWidget(row, aznumeric_cast<int>(PatternColumns::Type)));
+        auto* itemCheck = qobject_cast<QCheckBox*>(ui->sharedCacheTable->cellWidget(row, aznumeric_cast<int>(PatternColumns::Enabled)));
+
+        pattern.first = itemName->text().toUtf8().data();
+
+        AZStd::string filePattern { itemPattern->text().toUtf8().data() };
+        AssetBuilderPattern::PatternType patternType{};
+        auto typeData = itemType->itemData(itemType->currentIndex());
+        if (typeData.toInt() == aznumeric_cast<int>(AssetBuilderPattern::PatternType::Regex))
+        {
+            patternType = AssetBuilderPattern::PatternType::Regex;
+        }
+        pattern.second.m_patternMatcher = { filePattern, patternType };
+        pattern.second.m_checkServer = (itemCheck->checkState() == Qt::CheckState::Checked);
+
+        patternContainer.emplace(AZStd::move(pattern));
+    }
+
+    m_cacheServerData.m_patternContainer = AZStd::move(patternContainer);
+}
+
+void MainWindow::CheckAssetServerStates()
+{
+    using namespace AssetProcessor;
+
+    if (m_cacheServerData.m_dirty)
+    {
+        ui->sharedCacheSubmitButton->setEnabled(true);
+        ui->sharedCacheDiscardButton->setEnabled(true);
+    }
+    else
+    {
+        ui->sharedCacheSubmitButton->setEnabled(false);
+        ui->sharedCacheDiscardButton->setEnabled(false);
+    }
+
+    switch (m_cacheServerData.m_errorLevel)
+    {
+        case CacheServerData::ErrorLevel::None:
+        {
+            ui->sharedCacheStatus->setText("");
+            break;
+        }
+        case CacheServerData::ErrorLevel::Notice:
+        {
+            ui->sharedCacheStatus->setStyleSheet("font-weight: normal; color: green");
+            ui->sharedCacheStatus->setText(m_cacheServerData.m_errorMessage.c_str());
+            break;
+        }
+        case CacheServerData::ErrorLevel::Warning:
+        {
+            ui->sharedCacheStatus->setStyleSheet("font-weight: medium; color: yellow");
+            ui->sharedCacheStatus->setText(m_cacheServerData.m_errorMessage.c_str());
+            break;
+        }
+        case CacheServerData::ErrorLevel::Error:
+        {
+            ui->sharedCacheStatus->setStyleSheet("font-weight: bold; color: red");
+            ui->sharedCacheStatus->setText(m_cacheServerData.m_errorMessage.c_str());
+            break;
+        }
+        default:
+            break;
+    }
+}
+
+void MainWindow::ResetAssetServerView()
+{
+    using namespace AssetProcessor;
+
+    ui->serverCacheModeOptions->setCurrentIndex(aznumeric_cast<int>(m_cacheServerData.m_cachingMode));
+    ui->serverAddressText->setPlainText(QString(m_cacheServerData.m_serverAddress.c_str()));
+
+    ui->sharedCacheTable->setRowCount(0);
+    for (const auto& pattern : m_cacheServerData.m_patternContainer)
+    {
+        AddPatternRow(
+            pattern.second.m_name,
+            pattern.second.m_patternMatcher.GetBuilderPattern().m_type,
+            pattern.second.m_patternMatcher.GetBuilderPattern().m_pattern,
+            pattern.second.m_checkServer);
+    }
+
+    m_cacheServerData.m_dirty = false;
+    m_cacheServerData.m_errorLevel = CacheServerData::ErrorLevel::None;
+    m_cacheServerData.m_errorMessage.clear();
+    CheckAssetServerStates();
+}
+
+void MainWindow::SetServerAddress(AZStd::string_view serverAddress)
+{
+    using namespace AssetProcessor;
+
+    AssetServerBus::BroadcastResult(
+        this->m_cacheServerData.m_serverAddress,
+        &AssetServerBus::Events::GetServerAddress);
+
+    if (this->m_cacheServerData.m_serverAddress != serverAddress)
+    {
+        this->m_cacheServerData.m_dirty = true;
+        this->m_cacheServerData.m_serverAddress = serverAddress;
+        this->CheckAssetServerStates();
+    }
+}
+
 void MainWindow::SetupAssetSelectionCaching()
 {
     // Connect the source model resetting to preserve selection and restore it after the model is reset.

+ 11 - 0
Code/Tools/AssetProcessor/native/ui/MainWindow.h

@@ -17,6 +17,9 @@
 #include <AzQtComponents/Components/FilteredSearchWidget.h>
 #include <QElapsedTimer>
 #include <ui/BuilderListModel.h>
+#include <native/utilities/AssetUtilEBusHelper.h>
+#include <native/utilities/PlatformConfiguration.h>
+#include <native/ui/CacheServerData.h>
 #endif
 
 namespace AzToolsFramework
@@ -150,6 +153,7 @@ private:
     Config m_config;
     BuilderListModel* m_builderList;
     BuilderListSortFilterProxy* m_builderListSortFilterProxy;
+    AssetProcessor::CacheServerData m_cacheServerData;
 
     void SetContextLogDetailsVisible(bool visible);
     void SetContextLogDetails(const QMap<QString, QString>& details);
@@ -211,6 +215,13 @@ private:
     /// Fires off one final refresh before invalidating the filter refresh timer.
     void ShutdownAssetTabFilterRefresh();
 
+    void SetupAssetServerTab();
+    void AddPatternRow(AZStd::string_view name, AssetBuilderSDK::AssetBuilderPattern::PatternType type, AZStd::string_view pattern, bool enable);
+    void AssembleAssetPatterns();
+    void CheckAssetServerStates();
+    void ResetAssetServerView();
+    void SetServerAddress(AZStd::string_view serverAddress);
+
     void SetupAssetSelectionCaching();
 
     QElapsedTimer m_scanTimer;

+ 275 - 5
Code/Tools/AssetProcessor/native/ui/MainWindow.ui

@@ -685,7 +685,6 @@
            </item>
           </layout>
          </widget>
-			
         </widget>
         <widget class="QWidget" name="LogDialog">
          <layout class="QVBoxLayout" name="verticalLayout_6">
@@ -1329,7 +1328,7 @@
           <item>
            <layout class="QHBoxLayout" name="horizontalLayout_6">
             <item>
-             <spacer name="indentSpacer1">
+             <spacer name="indentSpacer2">
               <property name="orientation">
                <enum>Qt::Horizontal</enum>
               </property>
@@ -1360,7 +1359,7 @@
            </layout>
           </item>
           <item>
-           <spacer name="gapSpacer1">
+           <spacer name="gapSpacer2">
             <property name="orientation">
              <enum>Qt::Vertical</enum>
             </property>
@@ -1388,7 +1387,7 @@
           <item>
            <layout class="QHBoxLayout" name="horizontalLayout_12">
             <item>
-             <spacer name="indentSpacer2">
+             <spacer name="indentSpacer3">
               <property name="orientation">
                <enum>Qt::Horizontal</enum>
               </property>
@@ -1465,7 +1464,7 @@
            </layout>
           </item>
           <item>
-           <spacer name="gapSpacer2">
+           <spacer name="gapSpacer3">
             <property name="orientation">
              <enum>Qt::Vertical</enum>
             </property>
@@ -1479,6 +1478,277 @@
           </item>
          </layout>
         </widget>
+        <widget class="QWidget" name="page">
+         <layout class="QGridLayout" name="gridLayout_2">
+          <property name="leftMargin">
+           <number>0</number>
+          </property>
+          <property name="topMargin">
+           <number>0</number>
+          </property>
+          <property name="rightMargin">
+           <number>0</number>
+          </property>
+          <property name="bottomMargin">
+           <number>0</number>
+          </property>
+          <item row="0" column="0">
+           <widget class="QWidget" name="widget" native="true">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0,0,0">
+             <property name="sizeConstraint">
+              <enum>QLayout::SetDefaultConstraint</enum>
+             </property>
+             <property name="leftMargin">
+              <number>10</number>
+             </property>
+             <property name="rightMargin">
+              <number>10</number>
+             </property>
+             <property name="bottomMargin">
+              <number>10</number>
+             </property>
+             <item row="5" column="4">
+              <widget class="QPushButton" name="serverAddressButton">
+               <property name="maximumSize">
+                <size>
+                 <width>16</width>
+                 <height>16777215</height>
+                </size>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+              </widget>
+             </item>
+             <item row="1" column="0">
+              <widget class="Line" name="line">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="4" column="0">
+              <widget class="QLabel" name="label_3">
+               <property name="text">
+                <string>Step 2: Select a remote folder</string>
+               </property>
+              </widget>
+             </item>
+             <item row="8" column="0" colspan="5">
+              <widget class="QTableWidget" name="sharedCacheTable">
+               <property name="wordWrap">
+                <bool>false</bool>
+               </property>
+               <property name="rowCount">
+                <number>3</number>
+               </property>
+               <property name="columnCount">
+                <number>5</number>
+               </property>
+               <attribute name="horizontalHeaderCascadingSectionResizes">
+                <bool>false</bool>
+               </attribute>
+               <attribute name="horizontalHeaderStretchLastSection">
+                <bool>false</bool>
+               </attribute>
+               <attribute name="verticalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+               <row>
+                <property name="text">
+                 <string>New Row</string>
+                </property>
+               </row>
+               <row/>
+               <row/>
+               <column>
+                <property name="text">
+                 <string/>
+                </property>
+                <property name="toolTip">
+                 <string>Toggle pattern activity.</string>
+                </property>
+               </column>
+               <column>
+                <property name="text">
+                 <string>Name</string>
+                </property>
+               </column>
+               <column>
+                <property name="text">
+                 <string>Type</string>
+                </property>
+                <property name="toolTip">
+                 <string extracomment="Choose how the pattern text will be used. Wildcard or regular expression supported. "/>
+                </property>
+               </column>
+               <column>
+                <property name="text">
+                 <string>Pattern</string>
+                </property>
+               </column>
+               <column>
+                <property name="text">
+                 <string/>
+                </property>
+                <property name="toolTip">
+                 <string>Remove the pattern.</string>
+                </property>
+               </column>
+              </widget>
+             </item>
+             <item row="5" column="0" colspan="4">
+              <widget class="QPlainTextEdit" name="serverAddressText">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="maximumSize">
+                <size>
+                 <width>16777215</width>
+                 <height>32</height>
+                </size>
+               </property>
+               <property name="font">
+                <font>
+                 <family>Amazon Ember Mono</family>
+                 <pointsize>9</pointsize>
+                </font>
+               </property>
+               <property name="toolTip">
+                <string>The remote folder should be accessible to the entire team.</string>
+               </property>
+               <property name="inputMethodHints">
+                <set>Qt::ImhNone</set>
+               </property>
+               <property name="plainText">
+                <string>c:/transfer/stuff</string>
+               </property>
+              </widget>
+             </item>
+             <item row="0" column="0">
+              <widget class="QLabel" name="sharedCacheTitle">
+               <property name="font">
+                <font>
+                 <pointsize>10</pointsize>
+                 <weight>75</weight>
+                 <bold>true</bold>
+                </font>
+               </property>
+               <property name="text">
+                <string># Shared Cache Settings</string>
+               </property>
+               <property name="textFormat">
+                <enum>Qt::MarkdownText</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="9" column="4">
+              <widget class="QPushButton" name="sharedCacheDiscardButton">
+               <property name="maximumSize">
+                <size>
+                 <width>64</width>
+                 <height>16777215</height>
+                </size>
+               </property>
+               <property name="text">
+                <string>Discard</string>
+               </property>
+              </widget>
+             </item>
+             <item row="7" column="0">
+              <widget class="QPushButton" name="sharedCacheAddPattern">
+               <property name="toolTip">
+                <string>Add an asset pattern to cache to the remote folder.</string>
+               </property>
+               <property name="text">
+                <string>+ Add Pattern</string>
+               </property>
+              </widget>
+             </item>
+             <item row="6" column="0">
+              <widget class="QLabel" name="label_4">
+               <property name="text">
+                <string>Step 3: Manage shared cache asset patterns</string>
+               </property>
+              </widget>
+             </item>
+             <item row="2" column="0">
+              <widget class="QLabel" name="label_2">
+               <property name="text">
+                <string>Step 1: Set shared cache mode</string>
+               </property>
+              </widget>
+             </item>
+             <item row="9" column="3">
+              <widget class="QPushButton" name="sharedCacheSubmitButton">
+               <property name="maximumSize">
+                <size>
+                 <width>128</width>
+                 <height>16777215</height>
+                </size>
+               </property>
+               <property name="text">
+                <string>Save Changes</string>
+               </property>
+              </widget>
+             </item>
+             <item row="3" column="0">
+              <widget class="QComboBox" name="serverCacheModeOptions">
+               <property name="maximumSize">
+                <size>
+                 <width>128</width>
+                 <height>16777215</height>
+                </size>
+               </property>
+               <property name="toolTip">
+                <string>Select the mode the project should use to cache asset products.</string>
+               </property>
+               <property name="currentText">
+                <string>Placeholder</string>
+               </property>
+               <property name="placeholderText">
+                <string>Placeholder</string>
+               </property>
+              </widget>
+             </item>
+             <item row="0" column="2" colspan="3">
+              <widget class="QLabel" name="sharedCacheStatus">
+               <property name="font">
+                <font>
+                 <pointsize>10</pointsize>
+                </font>
+               </property>
+               <property name="frameShape">
+                <enum>QFrame::StyledPanel</enum>
+               </property>
+               <property name="frameShadow">
+                <enum>QFrame::Sunken</enum>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+               <property name="textFormat">
+                <enum>Qt::MarkdownText</enum>
+               </property>
+               <property name="margin">
+                <number>5</number>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </widget>
+          </item>
+         </layout>
+        </widget>
        </widget>
       </item>
      </layout>

+ 2 - 2
Code/Tools/AssetProcessor/native/unittests/AssetCacheServerUnitTests.cpp

@@ -71,7 +71,7 @@ namespace UnitTest
         {
             auto getString = [this](AZStd::string& result, AZStd::string_view input) -> bool
             {
-                if (input == "/Amazon/AssetProcessor/Settings/Server/cacheServerAddress")
+                if (input == "/O3DE/AssetProcessor/Settings/Server/cacheServerAddress")
                 {
                     result = this->m_tempFolder.toUtf8().toStdString().c_str();
                 }
@@ -81,7 +81,7 @@ namespace UnitTest
 
             auto getBool = [this](bool& result, AZStd::string_view input) -> bool
             {
-                if (input == "/Amazon/AssetProcessor/Settings/Server/enableCacheServer")
+                if (input == "/O3DE/AssetProcessor/Settings/Server/enableCacheServer")
                 {
                     result = this->m_enableServer;
                 }

+ 24 - 6
Code/Tools/AssetProcessor/native/utilities/AssetServerHandler.cpp

@@ -36,15 +36,16 @@ namespace AssetProcessor
         if (settingsRegistry)
         {
             bool enableAssetCacheServerMode = false;
-            AZ::SettingsRegistryInterface::FixedValueString key(AssetProcessor::AssetProcessorSettingsKey);
-            if (settingsRegistry->Get(enableAssetCacheServerMode, key + "/Server/enableCacheServer"))
+            AZ::SettingsRegistryInterface::FixedValueString key(AssetProcessor::AssetProcessorServerKey);
+            key += "/";
+            if (settingsRegistry->Get(enableAssetCacheServerMode, key + "enableCacheServer"))
             {
                 enableCacheServerMode = enableAssetCacheServerMode ? AssetServerMode::Server : AssetServerMode::Client;
                 AZ_Warning(AssetProcessor::DebugChannel, false, "The 'enableCacheServer' key is deprecated. Please swith to 'assetCacheServerMode'");
             }
 
             AZStd::string assetCacheServerModeValue;
-            if (settingsRegistry->Get(assetCacheServerModeValue, key + "/Server/assetCacheServerMode"))
+            if (settingsRegistry->Get(assetCacheServerModeValue, key + AssetCacheServerModeKey))
             {
                 AZStd::to_lower(assetCacheServerModeValue.begin(), assetCacheServerModeValue.end());
 
@@ -73,7 +74,9 @@ namespace AssetProcessor
         {
             AZStd::string address;
             if (settingsRegistry->Get(address,
-                AZ::SettingsRegistryInterface::FixedValueString(AssetProcessor::AssetProcessorSettingsKey) + "/Server/cacheServerAddress"))
+                AZ::SettingsRegistryInterface::FixedValueString(AssetProcessor::AssetProcessorServerKey)
+                + "/"
+                + CacheServerAddressKey))
             {
                 AZ_TracePrintf(AssetProcessor::DebugChannel, "Server Address: %s\n", address.c_str());
                 return AZStd::move(address);
@@ -114,10 +117,23 @@ namespace AssetProcessor
         return QString();
     }
 
+    const char* AssetServerHandler::GetAssetServerModeText(AssetServerMode mode)
+    {
+        switch (mode)
+        {
+            case AssetServerMode::Inactive: return "inactive";
+            case AssetServerMode::Server: return "server";
+            case AssetServerMode::Client: return "client";
+            default:
+                break;
+        }
+        return "unknown";
+    }
+
     AssetServerHandler::AssetServerHandler()
     {
-        SetServerAddress(CheckServerAddress());
         SetRemoteCachingMode(CheckServerMode());
+        SetServerAddress(CheckServerAddress());
         AssetServerBus::Handler::BusConnect();
     }
 
@@ -216,7 +232,7 @@ namespace AssetProcessor
         return m_serverAddress;
     }
 
-    void AssetServerHandler::SetServerAddress(const AZStd::string& address)
+    bool AssetServerHandler::SetServerAddress(const AZStd::string& address)
     {
         AZStd::string previousServerAddress = m_serverAddress;
         m_serverAddress = address;
@@ -228,7 +244,9 @@ namespace AssetProcessor
                 "Server address (%.*s) is invalid! Reverting back to (%.*s)",
                 AZ_STRING_ARG(address),
                 AZ_STRING_ARG(previousServerAddress));
+            return false;
         }
+        return true;
     }
 
     bool AssetServerHandler::RetrieveJobResult(const AssetProcessor::BuilderParams& builderParams)

+ 7 - 1
Code/Tools/AssetProcessor/native/utilities/AssetServerHandler.h

@@ -13,10 +13,16 @@
 
 namespace AssetProcessor
 {
+    inline constexpr const char* AssetCacheServerModeKey{ "assetCacheServerMode" };
+    inline constexpr const char* CacheServerAddressKey{ "cacheServerAddress" };
+
     //! AssetServerHandler is implementing asset server using network share.
     class AssetServerHandler
         : public AssetServerBus::Handler
     {
+    public:
+        static const char* GetAssetServerModeText(AssetServerMode mode);
+
     public:
         AssetServerHandler();
         virtual ~AssetServerHandler();
@@ -37,7 +43,7 @@ namespace AssetProcessor
         //! Retrieve the remote folder location for the shared cache 
         const AZStd::string& GetServerAddress() const override;
         //! Store the remote folder location for the shared cache 
-        void SetServerAddress(const AZStd::string& address) override;
+        bool SetServerAddress(const AZStd::string& address) override;
     protected:
         //! Source files intended to be copied into the cache don't go through out temp folder so they need
         //! to be added to the Archive in an additional step

+ 1 - 1
Code/Tools/AssetProcessor/native/utilities/AssetUtilEBusHelper.h

@@ -256,7 +256,7 @@ namespace AssetProcessor
         //! Retrieve the remote folder location for the shared cache 
         virtual const AZStd::string& GetServerAddress() const = 0;
         //! Store the remote folder location for the shared cache 
-        virtual void SetServerAddress(const AZStd::string& address) = 0;
+        virtual bool SetServerAddress(const AZStd::string& address) = 0;
     };
     using AssetServerBus = AZ::EBus<AssetServerBusTraits>;
 

+ 34 - 42
Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp

@@ -701,12 +701,11 @@ namespace AssetProcessor
         AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type)
     {
         constexpr AZStd::string_view ACSNamePrefix = "ACS ";
-        constexpr AZ::SettingsRegistryInterface::FixedValueString key(AssetProcessorSettingsKey);
         switch (action)
         {
         case AZ::SettingsRegistryInterface::VisitAction::Begin:
         {
-            if (jsonPath == key + "/Server")
+            if (jsonPath == AssetProcessorServerKey)
             {
                 return AZ::SettingsRegistryInterface::VisitResponse::Continue;
             }
@@ -724,7 +723,7 @@ namespace AssetProcessor
         {
             if (valueName.starts_with(ACSNamePrefix))
             {
-                AZ_Assert(!m_nameStack.empty(), "RC name stack should not be empty. More stack pops, than pushes");
+                AZ_Assert(!m_nameStack.empty(), "Name stack should not be empty. More stack pops, than pushes");
                 m_nameStack.pop();
             }
         }
@@ -1236,6 +1235,18 @@ namespace AssetProcessor
         AZ_CLASS_ALLOCATOR(AssetCacheServerMatcher, AZ::SystemAllocator, 0);
         AZ_TYPE_INFO(AssetCacheServerMatcher, "{329A59C9-755E-4FA9-AADB-05C50AC62FD5}");
 
+        static void Reflect(AZ::SerializeContext* serializeContext)
+        {
+            serializeContext->Class<AssetCacheServerMatcher>()->Version(0)
+                ->Field("name", &AssetCacheServerMatcher::m_name)
+                ->Field("glob", &AssetCacheServerMatcher::m_glob)
+                ->Field("pattern", &AssetCacheServerMatcher::m_pattern)
+                ->Field("productAssetType", &AssetCacheServerMatcher::m_productAssetType)
+                ->Field("checkServer", &AssetCacheServerMatcher::m_checkServer);
+
+            serializeContext->RegisterGenericType<AZStd::unordered_map<AZStd::string, AssetCacheServerMatcher>>();
+        }
+
         AZStd::string m_name;
         AZStd::string m_glob;
         AZStd::string m_pattern;
@@ -1245,10 +1256,6 @@ namespace AssetProcessor
 
     bool PlatformConfiguration::ConvertToJson(const RecognizerContainer& recognizerContainer, AZStd::string& jsonText)
     {
-        AZ::JsonSerializerSettings settings;
-        AZ::ComponentApplicationBus::BroadcastResult(settings.m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
-        settings.m_registrationContext = nullptr;
-
         AZStd::unordered_map<AZStd::string, AssetCacheServerMatcher> assetCacheServerMatcherMap;
         
         for (const auto& recognizer : recognizerContainer)
@@ -1266,9 +1273,13 @@ namespace AssetProcessor
             {
                 matcher.m_pattern = recognizer.second.m_patternMatcher.GetBuilderPattern().m_pattern;
             }
-            assetCacheServerMatcherMap.insert({"ACS " + recognizer.first, matcher});
+            assetCacheServerMatcherMap.insert({"ACS " + matcher.m_name, matcher});
         }
 
+        AZ::JsonSerializerSettings settings;
+        AZ::ComponentApplicationBus::BroadcastResult(settings.m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
+        settings.m_registrationContext = nullptr;
+
         rapidjson::Document jsonDocument;
         auto jsonResult = AZ::JsonSerialization::Store(jsonDocument, jsonDocument.GetAllocator(), assetCacheServerMatcherMap, settings);
         if (jsonResult.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
@@ -1289,12 +1300,12 @@ namespace AssetProcessor
             return false;
         }
 
-        AZ::JsonSerializerSettings settings;
+        AZ::JsonDeserializerSettings settings;
         AZ::ComponentApplicationBus::BroadcastResult(settings.m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
         settings.m_registrationContext = nullptr;
 
         AZStd::unordered_map<AZStd::string, AssetCacheServerMatcher> assetCacheServerMatcherMap;
-        auto resultCode = AZ::JsonSerialization::Load(assetCacheServerMatcherMap, assetCacheServerMatcherDoc, {});        
+        auto resultCode = AZ::JsonSerialization::Load(assetCacheServerMatcherMap, assetCacheServerMatcherDoc, settings);
         if (!resultCode.HasDoneWork())
         {
             return false;
@@ -1316,7 +1327,7 @@ namespace AssetProcessor
             {
                 assetRecognizer.m_patternMatcher = { matcher.second.m_pattern , AssetBuilderSDK::AssetBuilderPattern::Regex };
             }
-            recognizerContainer.insert({ "ACS " + assetRecognizer.m_name, assetRecognizer });
+            recognizerContainer.insert({ "ACS " + matcher.second.m_name, assetRecognizer });
         }
 
         return !recognizerContainer.empty();
@@ -1326,34 +1337,7 @@ namespace AssetProcessor
     {
         if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
         {
-            serializeContext->Class<AssetBuilderSDK::FilePatternMatcher>()->Version(0);
-
-            serializeContext->Class<AssetInternalSpec>()->Version(0);                
-
-            // needs to serialize in/out 'glob' and 'pattern'
-            serializeContext->Class<AssetRecognizer>()->Version(0)
-                ->Field("checkServer", &AssetRecognizer::m_checkServer)
-                ->Field("isCritical", &AssetRecognizer::m_isCritical)
-                ->Field("name", &AssetRecognizer::m_name)
-                ->Field("outputProductDependencies", &AssetRecognizer::m_outputProductDependencies)
-                ->Field("patternMatcher", &AssetRecognizer::m_patternMatcher)
-                ->Field("platformSpecs", &AssetRecognizer::m_platformSpecs)
-                ->Field("priority", &AssetRecognizer::m_priority)
-                ->Field("productAssetType", &AssetRecognizer::m_productAssetType)
-                ->Field("supportsCreateJobs", &AssetRecognizer::m_supportsCreateJobs)
-                ->Field("testLockSource", &AssetRecognizer::m_testLockSource)
-                ->Field("version", &AssetRecognizer::m_version);
-
-            serializeContext->Class<AssetCacheServerMatcher>()->Version(0)
-                ->Field("name", &AssetCacheServerMatcher::m_name)
-                ->Field("glob", &AssetCacheServerMatcher::m_glob)
-                ->Field("pattern", &AssetCacheServerMatcher::m_pattern)
-                ->Field("productAssetType", &AssetCacheServerMatcher::m_productAssetType)
-                ->Field("checkServer", &AssetCacheServerMatcher::m_checkServer);
-
-            serializeContext->RegisterGenericType<AZStd::unordered_map<AZStd::string, AssetRecognizer>>();
-            serializeContext->RegisterGenericType<AZStd::unordered_map<AZStd::string, AssetInternalSpec>>();
-            serializeContext->RegisterGenericType<AZStd::unordered_map<AZStd::string, AssetCacheServerMatcher>>();
+            AssetCacheServerMatcher::Reflect(serializeContext);
         }
     }
 
@@ -1515,8 +1499,7 @@ namespace AssetProcessor
         }
 
         ACSVisitor acsVistor;
-        AZ::SettingsRegistryInterface::FixedValueString key(AssetProcessor::AssetProcessorSettingsKey);
-        settingsRegistry->Visit(acsVistor, key + "/Server");
+        settingsRegistry->Visit(acsVistor, AssetProcessorServerKey);
         for (auto&& acsRecognizer : acsVistor.m_assetRecognizers)
         {
             m_assetCacheServerRecognizers[acsRecognizer.m_name] = AZStd::move(acsRecognizer);
@@ -2131,7 +2114,16 @@ namespace AssetProcessor
 
     bool PlatformConfiguration::AddAssetCacheRecognizerContainer(const RecognizerContainer& recognizerContainer)
     {
-        m_assetCacheServerRecognizers.insert(recognizerContainer.begin(), recognizerContainer.end());
+        bool addedEntries = false;
+        for (const auto& recognizer : recognizerContainer)
+        {
+            auto entryIter = m_assetCacheServerRecognizers.find(recognizer.first);
+            if (entryIter != m_assetCacheServerRecognizers.end())
+            {
+                m_assetCacheServerRecognizers.insert(recognizer);
+                addedEntries = true;
+            }
+        }
         return true;
     }
 

+ 1 - 0
Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.h

@@ -35,6 +35,7 @@ namespace AZ
 namespace AssetProcessor
 {
     inline constexpr const char* AssetProcessorSettingsKey{ "/Amazon/AssetProcessor/Settings" };
+    inline constexpr const char* AssetProcessorServerKey{ "/O3DE/AssetProcessor/Settings/Server" };
     class PlatformConfiguration;
     class ScanFolderInfo;
     extern const char AssetConfigPlatformDir[];