Merge branch 'main' into ly-as-sdk/LYN-2948

This commit is contained in:
phistere
2021-05-16 11:51:00 -05:00
549 changed files with 5207 additions and 43781 deletions
@@ -269,13 +269,13 @@ namespace AZ
{
for (auto& [assetId, dependentAsset] : m_dependencies)
{
if (dependentAsset->IsReady())
if (dependentAsset->IsReady() || dependentAsset->IsError())
{
HandleReadyAsset(dependentAsset);
}
}
}
if (auto asset = m_rootAsset.GetStrongReference(); asset.IsReady())
if (auto asset = m_rootAsset.GetStrongReference(); asset.IsReady() || asset.IsError())
{
HandleReadyAsset(asset);
}
@@ -496,10 +496,10 @@ namespace AZ
m_waitingCount -= 1;
disconnectEbus = true;
if (m_waitingAssets.empty())
{
allReady = true;
}
}
if (m_waitingAssets.empty())
{
allReady = true;
}
}
@@ -510,8 +510,15 @@ namespace AZ
}
}
if (allReady && m_initComplete)
// If there are no assets left to be loaded, trigger the final AssetContainer notification (ready or canceled).
// We guard against prematurely sending it (m_initComplete) because it's possible for assets to get removed from our waiting
// list *while* we're still building up the list, so the list would appear to be empty too soon.
// We also guard against sending it multiple times (m_finalNotificationSent), because in some error conditions, it may be
// possible to try to remove the same asset multiple times, which if it's the last asset, it could trigger multiple
// notifications.
if (allReady && m_initComplete && !m_finalNotificationSent)
{
m_finalNotificationSent = true;
if (m_rootAsset)
{
AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetContainerReady, this);
@@ -137,6 +137,7 @@ namespace AZ
AZStd::atomic_int m_invalidDependencies{ 0 };
AZStd::unordered_set<AZ::Data::AssetId> m_unloadedDependencies;
AZStd::atomic_bool m_initComplete{ false };
AZStd::atomic_bool m_finalNotificationSent{false};
mutable AZStd::recursive_mutex m_preloadMutex;
// AssetId -> List of assets it is still waiting on
@@ -477,7 +477,7 @@ namespace AZ
m_console = AZ::Interface<AZ::IConsole>::Get();
if (m_console == nullptr)
{
m_console = aznew AZ::Console();
m_console = aznew AZ::Console(*m_settingsRegistry);
AZ::Interface<AZ::IConsole>::Register(m_console);
m_ownsConsole = true;
m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead());
@@ -916,27 +916,49 @@ namespace AZ
SetSettingsRegistrySpecializations(specializations);
AZStd::vector<char> scratchBuffer;
// Retrieves the list gem module build targets that the active project depends on
SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry,
AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
// In development builds apply the o3de registry and the command line to allow early overrides. This will
// allow developers to override things like default paths or Asset Processor connection settings. Any additional
// values will be replaced by later loads, so this step will happen again at the end of loading.
SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
// Project User Registry is merged after the command line here to allow make sure the any command line override of the project path
// is used for merging the project's user registry
SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
#endif
//! Retrieves the list gem targets that the project has load dependencies on
//! This populates the /Amazon/Gems/<GemName>/SourcePaths array entries which is required
//! by the MergeSettingsToRegistry_GemRegistry() function below to locate the gem's root folder
//! and merge in the gem's registry files.
//! But when running from a pre-built app from the O3DE SDK(Editor/AssetProcessor), the projects binary
//! directory is needed in order to located the load dependency registry files
//! That project binary folder is generated with the <ProjectRoot>/user/Registry when CMake is configured
//! for the project
//! Therefore the order of merging must be as follows
//! 1. MergeSettingsToRegistry_ProjectUserRegistry - Populates the /Amazon/Project/Settings/Build/project_build_path
//! which contains the path to the project binary directory
//! 2. MergeSettingsToRegistry_TargetBuildDependencyRegistry - Loads the cmake_dependencies.<project_name>.<application_name>.setreg
//! file from the locations in order of
//! 1. <executable_directory>/Registry
//! 2. <cache_root>/Registry
//! 3. <project_build_path>/bin/$<CONFIG>/Registry
//! 3. MergeSettingsToRegistry_GemRegistries - Merges the settings registry files from each gem's <GemRoot>/Registry directory
SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry,
AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_EngineRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_GemRegistries(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, true);
#endif
// Update the Runtime file paths in case the "{BootstrapSettingsRootKey}/assets" key was overriden by a setting registry
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
}
void ComponentApplication::SetSettingsRegistrySpecializations(SettingsRegistryInterface::Specializations& specializations)
+176 -38
View File
@@ -13,7 +13,9 @@
#include <AzCore/Console/Console.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/Json/JsonSerializationSettings.h>
#include <AzCore/Settings/CommandLine.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/IO/FileIO.h>
@@ -43,6 +45,12 @@ namespace AZ
{
}
Console::Console(AZ::SettingsRegistryInterface& settingsRegistryInterface)
: Console()
{
RegisterCommandInvokerWithSettingsRegistry(settingsRegistryInterface);
}
Console::~Console()
{
// on console destruction relink the console functors back to the deferred head
@@ -111,51 +119,51 @@ namespace AZ
void Console::ExecuteConfigFile(AZStd::string_view configFileName)
{
IO::FixedMaxPath filePathFixed = configFileName;
if (AZ::IO::FileIOBase* fileIOBase = AZ::IO::FileIOBase::GetInstance())
auto settingsRegistry = AZ::SettingsRegistry::Get();
// If the config file is a settings registry file use the SettingsRegistryInterface MergeSettingsFile function
// otherwise use the SettingsRegistryMergeUtils MergeSettingsToRegistry_ConfigFile function to merge an INI-style
// file to the settings registry
AZ::IO::PathView configFile(configFileName);
if (configFile.Extension() == ".setreg")
{
fileIOBase->ResolvePath(filePathFixed, configFileName);
settingsRegistry->MergeSettingsFile(configFile.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch);
}
IO::SystemFile file;
if (!file.Open(filePathFixed.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY))
else if (configFile.Extension() == ".setregpatch")
{
AZLOG_ERROR("Failed to load '%s'. File could not be opened.", filePathFixed.c_str());
return;
settingsRegistry->MergeSettingsFile(configFile.Native(), AZ::SettingsRegistryInterface::Format::JsonPatch);
}
const IO::SizeType length = file.Length();
if (length == 0)
else
{
AZLOG_ERROR("Failed to load '%s'. File is empty.", filePathFixed.c_str());
return;
}
file.Seek(0, IO::SystemFile::SF_SEEK_BEGIN);
AZStd::string fileBuffer;
fileBuffer.resize(length);
IO::SizeType bytesRead = file.Read(length, fileBuffer.data());
file.Close();
// Resize again just in case bytesRead is less than length for some reason
fileBuffer.resize(bytesRead);
AZLOG_INFO("Loading config file %s", filePathFixed.c_str());
AZStd::vector<AZStd::string_view> separatedCommands;
auto BreakCommandsByLine = [&separatedCommands](AZStd::string_view token)
{
separatedCommands.emplace_back(token);
};
StringFunc::TokenizeVisitor(fileBuffer, BreakCommandsByLine, "\n\r");
for (const auto& commandView : separatedCommands)
{
ConsoleCommandContainer commandArgsView;
auto ConvertCommandStringToArray = [&commandArgsView](AZStd::string_view token)
AZ::SettingsRegistryMergeUtils::ConfigParserSettings configParserSettings;
configParserSettings.m_registryRootPointerPath = "/Amazon/AzCore/Runtime/ConsoleCommands";
configParserSettings.m_commandLineSettings.m_delimiterFunc = [](AZStd::string_view line)
{
commandArgsView.emplace_back(token);
SettingsRegistryInterface::CommandLineArgumentSettings::JsonPathValue pathValue;
AZStd::string_view parsedLine = line;
// Splits the line based on the <equal> or <colon>
if (auto path = AZ::StringFunc::TokenizeNext(parsedLine, "=:"); path.has_value())
{
pathValue.m_path = AZ::StringFunc::StripEnds(*path);
pathValue.m_value = AZ::StringFunc::StripEnds(parsedLine);
}
// If the value is empty, then the line either contained an equal sign followed only by whitespace or the line was empty
// 1. line="testInit=", pathValue.m_path="testInit", pathValue.m_value=""
// 2. line="testInit 1", pathValue.m_path="testInit 1", pathValue.m_value=""
// Therefore the path is split the path on whitespace in order to retrieve a value
if (pathValue.m_value.empty())
{
parsedLine = pathValue.m_path;
if (auto path = AZ::StringFunc::TokenizeNext(parsedLine, " \t"); path.has_value())
{
pathValue.m_path = AZ::StringFunc::StripEnds(*path);
pathValue.m_value = AZ::StringFunc::StripEnds(parsedLine);
}
}
return pathValue;
};
constexpr AZStd::string_view commandSeparators = " =";
StringFunc::TokenizeVisitor(commandView, ConvertCommandStringToArray, commandSeparators);
PerformCommand(commandArgsView, ConsoleSilentMode::NotSilent, ConsoleInvokedFrom::AzConsole, ConsoleFunctorFlags::Null, ConsoleFunctorFlags::Null);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ConfigFile(*settingsRegistry, configFile.Native(), configParserSettings);
}
}
@@ -447,4 +455,134 @@ namespace AZ
return result;
}
struct ConsoleCommandKeyNotificationHandler
{
ConsoleCommandKeyNotificationHandler(AZ::SettingsRegistryInterface& registry, Console& console)
: m_settingsRegistry(registry)
, m_console(console)
{
}
// Responsible for using the Json Serialization Issue Callback system
// to determine when a JSON Patch or JSON Merge Patch modifies a value
// at a path underneath the IConsole::ConsoleRootCommandKey JSON pointer
JsonSerializationResult::ResultCode operator()(AZStd::string_view message,
JsonSerializationResult::ResultCode result, AZStd::string_view path)
{
AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator };
AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator };
if (result.GetTask() == JsonSerializationResult::Tasks::Merge
&& result.GetProcessing() == JsonSerializationResult::Processing::Completed
&& inputKey.IsRelativeTo(consoleRootCommandKey))
{
if (auto type = m_settingsRegistry.GetType(path); type != SettingsRegistryInterface::Type::NoType)
{
operator()(path, type);
}
}
// This is the default issue reporting, that logs using the warning category
if (result.GetProcessing() != JsonSerializationResult::Processing::Completed)
{
scratchBuffer.append(message.begin(), message.end());
scratchBuffer.append("\n Reason: ");
result.AppendToString(scratchBuffer, path);
scratchBuffer.append(".");
AZ_Warning("JSON Serialization", false, "%s", scratchBuffer.c_str());
scratchBuffer.clear();
}
return result;
}
void operator()(AZStd::string_view path, SettingsRegistryInterface::Type type)
{
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator };
AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator };
if (inputKey.IsRelativeTo(consoleRootCommandKey))
{
FixedValueString command = inputKey.LexicallyRelative(consoleRootCommandKey).Native();
ConsoleCommandContainer commandArgs;
// Argument string which stores the value from the Settings Registry long enough
// to pass into the PerformCommand. The ConsoleCommandContainer stores string_views
// and therefore doesn't own the memory.
FixedValueString commandArgString;
if (type == SettingsRegistryInterface::Type::String)
{
if (m_settingsRegistry.Get(commandArgString, path))
{
auto ConvertCommandArgumentToArray = [&commandArgs](AZStd::string_view token)
{
commandArgs.emplace_back(token);
};
constexpr AZStd::string_view commandSeparators = " \t\n\r";
StringFunc::TokenizeVisitor(commandArgString, ConvertCommandArgumentToArray, commandSeparators);
}
}
else if (type == SettingsRegistryInterface::Type::Boolean)
{
bool commandArgBool{};
if (m_settingsRegistry.Get(commandArgBool, path))
{
commandArgString = commandArgBool ? "true" : "false";
commandArgs.emplace_back(commandArgString);
}
}
else if (type == SettingsRegistryInterface::Type::Integer)
{
// Try converting to a signed 64-bit number first and then an unsigned 64-bit number
AZ::s64 commandArgInt{};
AZ::u64 commandArgUInt{};
if (m_settingsRegistry.Get(commandArgInt, path))
{
AZStd::to_string(commandArgString, commandArgInt);
commandArgs.emplace_back(commandArgString);
}
else if (m_settingsRegistry.Get(commandArgUInt, path))
{
AZStd::to_string(commandArgString, commandArgUInt);
commandArgs.emplace_back(commandArgString);
}
}
else if (type == SettingsRegistryInterface::Type::FloatingPoint)
{
double commandArgFloat{};
if (m_settingsRegistry.Get(commandArgFloat, path))
{
AZStd::to_string(commandArgString, commandArgFloat);
commandArgs.emplace_back(commandArgString);
}
}
CVarFixedString commandTrace(command);
for (AZStd::string_view commandArg : commandArgs)
{
commandTrace.push_back(' ');
commandTrace += commandArg;
}
m_console.PerformCommand(command, commandArgs, ConsoleSilentMode::NotSilent, ConsoleInvokedFrom::AzConsole, ConsoleFunctorFlags::Null, ConsoleFunctorFlags::Null);
}
}
AZ::Console& m_console;
AZ::SettingsRegistryInterface& m_settingsRegistry;
AZStd::string scratchBuffer;
};
void Console::RegisterCommandInvokerWithSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry)
{
// Make sure the there is a JSON object at the path of AZ::IConsole::ConsoleRootCommandKey
// So that JSON Patch is able to add values underneath that object (JSON Patch doesn't create intermediate objects)
settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } }}})",
SettingsRegistryInterface::Format::JsonMergePatch);
m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this });
JsonApplyPatchSettings applyPatchSettings;
applyPatchSettings.m_reporting = ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this };
settingsRegistry.SetApplyPatchSettings(applyPatchSettings);
}
}
@@ -14,6 +14,7 @@
#include <AzCore/Console/IConsole.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/containers/unordered_map.h>
@@ -29,6 +30,9 @@ namespace AZ
AZ_CLASS_ALLOCATOR(Console, AZ::OSAllocator, 0);
Console();
//! Constructor overload which registers a notifier with the Settings Registry that will execute
//! a console command whenever a key is set under the AZ::IConsole::ConsoleCommandRootKey JSON object
explicit Console(AZ::SettingsRegistryInterface& settingsRegistry);
~Console() override;
//! IConsole interface
@@ -67,6 +71,7 @@ namespace AZ
void RegisterFunctor(ConsoleFunctorBase* functor) override;
void UnregisterFunctor(ConsoleFunctorBase* functor) override;
void LinkDeferredFunctors(ConsoleFunctorBase*& deferredHead) override;
void RegisterCommandInvokerWithSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry) override;
//! @}
private:
@@ -96,6 +101,7 @@ namespace AZ
ConsoleFunctorBase* m_head;
using CommandMap = AZStd::unordered_map<CVarFixedString, AZStd::vector<ConsoleFunctorBase*>>;
CommandMap m_commands;
AZ::SettingsRegistryInterface::NotifyEventHandler m_consoleCommandKeyHandler;
friend class ConsoleFunctorBase;
};
@@ -148,7 +148,15 @@ namespace AZ
{
AZ::CVarFixedString convertCandidate{ arguments.front() };
char* endPtr = nullptr;
MAX_TYPE value = static_cast<MAX_TYPE>(strtoll(convertCandidate.c_str(), &endPtr, 0));
MAX_TYPE value;
if constexpr (AZStd::is_unsigned_v<MAX_TYPE>)
{
value = aznumeric_cast<MAX_TYPE>(strtoull(convertCandidate.c_str(), &endPtr, 0));
}
else
{
value = aznumeric_cast<MAX_TYPE>(strtoll(convertCandidate.c_str(), &endPtr, 0));
}
if (endPtr == convertCandidate.c_str())
{
@@ -22,8 +22,10 @@
namespace AZ
{
class SettingsRegistryInterface;
class CommandLine;
//! @class IConsole
//! A simple console class for providing text based variable and process interaction.
class IConsole
@@ -33,6 +35,8 @@ namespace AZ
using FunctorVisitor = AZStd::function<void(ConsoleFunctorBase*)>;
inline static constexpr AZStd::string_view ConsoleRootCommandKey = "/Amazon/AzCore/Runtime/ConsoleCommands";
IConsole() = default;
virtual ~IConsole() = default;
@@ -145,6 +149,12 @@ namespace AZ
//! Returns the AZ::Event<> invoked whenever a console command could not be found.
DispatchCommandNotFoundEvent& GetDispatchCommandNotFoundEvent();
//! Register a notification event handler with the Settings Registry
//! That is responsible for updating console commands whenever
//! a key is found underneath the "/Amazon/AzCore/Runtime/ConsoleCommands" JSON entry
//! @param Settings Registry reference to register notifier with
virtual void RegisterCommandInvokerWithSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry) = 0;
AZ_DISABLE_COPY_MOVE(IConsole);
protected:
@@ -24,9 +24,7 @@ namespace AZ
AZ_TYPE_INFO_SPECIALIZE(AZStd::chrono::system_clock::time_point, "{5C48FD59-7267-405D-9C06-1EA31379FE82}");
/**
* Wrapper that reflects a AZStd::chrono::system_clock::time_point to script.
*/
//! Wrapper that reflects a AZStd::chrono::system_clock::time_point to script.
class ScriptTimePoint
{
public:
@@ -38,33 +36,45 @@ namespace AZ
explicit ScriptTimePoint(AZStd::chrono::system_clock::time_point timePoint)
: m_timePoint(timePoint) {}
AZStd::string ToString() const
{
return AZStd::string::format("Time %llu", m_timePoint.time_since_epoch().count());
}
//! Formats the time point in a string formatted as: "Time <seconds since epoch>".
AZStd::string ToString() const;
const AZStd::chrono::system_clock::time_point& Get() { return m_timePoint; }
//! Returns the time point.
const AZStd::chrono::system_clock::time_point& Get() const;
// Returns the time point in seconds
double GetSeconds() const
{
typedef AZStd::chrono::duration<double> double_seconds;
return AZStd::chrono::duration_cast<double_seconds>(m_timePoint.time_since_epoch()).count();
}
//! Returns the time point in seconds
double GetSeconds() const;
// Returns the time point in milliseconds
double GetMilliseconds() const
{
typedef AZStd::chrono::duration<double, AZStd::milli> double_ms;
return AZStd::chrono::duration_cast<double_ms>(m_timePoint.time_since_epoch()).count();
}
//! Returns the time point in milliseconds
double GetMilliseconds() const;
static void Reflect(ReflectContext* reflection);
protected:
AZStd::chrono::system_clock::time_point m_timePoint;
};
inline AZStd::string ScriptTimePoint::ToString() const
{
return AZStd::string::format("Time %llu", m_timePoint.time_since_epoch().count());
}
inline const AZStd::chrono::system_clock::time_point& ScriptTimePoint::Get() const
{
return m_timePoint;
}
inline double ScriptTimePoint::GetSeconds() const
{
typedef AZStd::chrono::duration<double> double_seconds;
return AZStd::chrono::duration_cast<double_seconds>(m_timePoint.time_since_epoch()).count();
}
inline double ScriptTimePoint::GetMilliseconds() const
{
typedef AZStd::chrono::duration<double, AZStd::milli> double_ms;
return AZStd::chrono::duration_cast<double_ms>(m_timePoint.time_since_epoch()).count();
}
}
@@ -11,13 +11,17 @@
*/
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/JSON/stringbuffer.h>
#include <AzCore/Serialization/Json/JsonMerger.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/StackedString.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/std/string/osstring.h>
namespace AZ
{
using ReporterString = AZStd::fixed_string<1024>;
JsonSerializationResult::ResultCode JsonMerger::ApplyPatch(rapidjson::Value& target,
rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& patch,
JsonApplyPatchSettings& settings)
@@ -105,8 +109,7 @@ namespace AZ
}
else
{
AZ::OSString message = AZ::OSString::format(R"(Unknown operation "%.*s".)",
aznumeric_cast<int>(operationName.length()), operationName.data());
auto message = ReporterString::format(R"(Unknown operation "%.*s".)", AZ_STRING_ARG(operationName));
return settings.m_reporting(message.c_str(), ResultCode(Tasks::Merge, Outcomes::Unknown), element);
}
@@ -131,6 +134,14 @@ namespace AZ
JsonSerializationResult::ResultCode JsonMerger::ApplyMergePatch(rapidjson::Value& target,
rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& patch,
JsonApplyPatchSettings& settings)
{
StackedString element(StackedString::Format::JsonPointer);
return ApplyMergePatchInternal(target, allocator, patch, settings, element);
}
JsonSerializationResult::ResultCode JsonMerger::ApplyMergePatchInternal(rapidjson::Value& target,
rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& patch,
JsonApplyPatchSettings& settings, StackedString& element)
{
using namespace JsonSerializationResult;
@@ -150,14 +161,18 @@ namespace AZ
{
if (targetField != target.MemberEnd())
{
result.Combine(ApplyMergePatch(targetField->value, allocator, field.value, settings));
ScopedStackedString fieldNameScope{ element,
AZStd::string_view(field.name.GetString(), field.name.GetStringLength()) };
result.Combine(ApplyMergePatchInternal(targetField->value, allocator, field.value, settings, element));
}
else
{
rapidjson::Value name;
name.CopyFrom(field.name, allocator, true);
rapidjson::Value value;
result.Combine(ApplyMergePatch(value, allocator, field.value, settings));
ScopedStackedString fieldNameScope{ element,
AZStd::string_view(field.name.GetString(), field.name.GetStringLength()) };
result.Combine(ApplyMergePatchInternal(value, allocator, field.value, settings, element));
target.AddMember(AZStd::move(name), AZStd::move(value), allocator);
}
}
@@ -165,7 +180,14 @@ namespace AZ
{
if (targetField != target.MemberEnd())
{
ScopedStackedString fieldNameScope{ element,
AZStd::string_view(field.name.GetString(), field.name.GetStringLength()) };
AZStd::string_view jsonPath = element.Get();
target.RemoveMember(targetField);
result.Combine(settings.m_reporting(ReporterString::format(
R"(Successfully removed member from "%.*s" using JSON Merge Patch)", AZ_STRING_ARG(jsonPath)),
ResultCode(Tasks::Merge, Outcomes::Success), element));
}
}
else
@@ -173,6 +195,12 @@ namespace AZ
if (targetField != target.MemberEnd())
{
targetField->value.CopyFrom(field.value, allocator, true);
ScopedStackedString fieldNameScope{ element, AZStd::string_view(field.name.GetString(), field.name.GetStringLength()) };
AZStd::string_view jsonPath = element.Get();
result.Combine(settings.m_reporting(ReporterString::format(
R"(Successfully updated JSON field "%.*s" using JSON Merge Patch)", AZ_STRING_ARG(jsonPath)),
ResultCode(Tasks::Merge, Outcomes::Success), element));
}
else
{
@@ -181,6 +209,12 @@ namespace AZ
name.CopyFrom(field.name, allocator, true);
value.CopyFrom(field.value, allocator, true);
target.AddMember(AZStd::move(name), AZStd::move(value), allocator);
ScopedStackedString fieldNameScope{ element, AZStd::string_view(field.name.GetString(), field.name.GetStringLength()) };
AZStd::string_view jsonPath = element.Get();
result.Combine(settings.m_reporting(ReporterString::format(
R"(Successfully added JSON field "%.*s" using JSON Merge Patch)", AZ_STRING_ARG(jsonPath)),
ResultCode(Tasks::Merge, Outcomes::Success), element));
}
}
}
@@ -190,7 +224,7 @@ namespace AZ
target.CopyFrom(patch, allocator, true);
}
result.Combine(settings.m_reporting("Successfully applied patch to target using JSON Merge Patch.",
ResultCode(Tasks::Merge, Outcomes::Success), StackedString(StackedString::Format::JsonPointer)));
ResultCode(Tasks::Merge, Outcomes::Success), element));
return result;
}
@@ -268,9 +302,11 @@ namespace AZ
const rapidjson::Pointer::Token* const tokens = path.GetTokens();
if (path.GetTokenCount() == 0)
{
rapidjson::StringBuffer pointerPathString;
path.Stringify(pointerPathString);
target = AZStd::move(newValue);
return settings.m_reporting(R"(Successfully applied "add" operation.)",
ResultCode(Tasks::Merge, Outcomes::Success), element);
ResultCode(Tasks::Merge, Outcomes::Success), pointerPathString.GetString());
}
rapidjson::Pointer parent = rapidjson::Pointer(tokens, path.GetTokenCount() - 1);
@@ -342,8 +378,10 @@ namespace AZ
ResultCode(Tasks::Merge, Outcomes::TypeMismatch), element);
}
rapidjson::StringBuffer pointerPathString;
path.Stringify(pointerPathString);
return settings.m_reporting(R"(Successfully applied "add" operation.)",
ResultCode(Tasks::Merge, Outcomes::Success), element);
ResultCode(Tasks::Merge, Outcomes::Success), pointerPathString.GetString());
}
JsonSerializationResult::ResultCode JsonMerger::ApplyPatch_Remove(rapidjson::Value& target, const rapidjson::Pointer& path,
@@ -393,8 +431,10 @@ namespace AZ
ResultCode(Tasks::Merge, Outcomes::TypeMismatch), element);
}
rapidjson::StringBuffer pointerPathString;
path.Stringify(pointerPathString);
return settings.m_reporting(R"(Successfully applied "remove" operation.)",
ResultCode(Tasks::Merge, Outcomes::Success), element);
ResultCode(Tasks::Merge, Outcomes::Success), pointerPathString.GetString());
}
JsonSerializationResult::ResultCode JsonMerger::ApplyPatch_Replace(rapidjson::Value& target,
@@ -420,8 +460,10 @@ namespace AZ
memberValue->CopyFrom(value->value, allocator);
rapidjson::StringBuffer pointerPathString;
path.Stringify(pointerPathString);
return settings.m_reporting(R"(Successfully applied "replace" operation.)",
ResultCode(Tasks::Merge, Outcomes::Success), element);
ResultCode(Tasks::Merge, Outcomes::Success), pointerPathString.GetString());
}
JsonSerializationResult::ResultCode JsonMerger::ApplyPatch_Move(rapidjson::Value& target,
@@ -42,6 +42,11 @@ namespace AZ
rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& patch,
JsonApplyPatchSettings& settings);
//! Implementation of the JSON Merge Patch algorithm: https://tools.ietf.org/html/rfc7386
static JsonSerializationResult::ResultCode ApplyMergePatchInternal(rapidjson::Value& target,
rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& patch,
JsonApplyPatchSettings& settings, StackedString& element);
//! Function to create JSON Merge Patches: https://tools.ietf.org/html/rfc7386
static JsonSerializationResult::ResultCode CreateMergePatch(rapidjson::Value& patch,
rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& source,
@@ -88,4 +88,28 @@ namespace AZ
{
return index < m_names.size() ? m_names[index] : AZStd::string_view();
}
SettingsRegistryInterface::CommandLineArgumentSettings::CommandLineArgumentSettings()
{
m_delimiterFunc = [](AZStd::string_view line) -> JsonPathValue
{
constexpr AZStd::string_view CommandLineArgumentDelimiters{ "=:" };
JsonPathValue pathValue;
pathValue.m_value = line;
// Splits the line on the first delimiter and stores that in the pathValue.m_path variable
// The StringFunc::TokenizeNext function updates the pathValue.m_value parameter in place
// to contain all the text after the first delimiter
// So if pathValue.m_value="foo = Hello Ice Cream=World:17", the call to TokenizeNext would
// split the value as follows
// pathValue.m_path = "foo"
// pathValue.m_value = "Hello Ice Cream=World:17"
if (auto path = AZ::StringFunc::TokenizeNext(pathValue.m_value, CommandLineArgumentDelimiters); path.has_value())
{
pathValue.m_path = AZ::StringFunc::StripEnds(*path);
}
pathValue.m_value = AZ::StringFunc::StripEnds(pathValue.m_value);
return pathValue;
};
}
} // namespace AZ
@@ -26,6 +26,7 @@
namespace AZ
{
struct JsonApplyPatchSettings;
//! The Settings Registry is the central storage for global settings. Having application-wide settings
//! stored in a central location allows different tools such as command lines, consoles, configuration
//! files, etc. to work in a universal way.
@@ -260,21 +261,20 @@ namespace AZ
virtual bool Remove(AZStd::string_view path) = 0;
//! Structure which contains configuration settings for how to parse a single command line argument
//! It supports supplying a functor for determining if a character is a delimiter
//! It supports supplying a functor for splitting a line into JSON path and JSON value
struct CommandLineArgumentSettings
{
inline static constexpr AZStd::string_view CommandLineArgumentDelimiters{ "=:"};
CommandLineArgumentSettings()
struct JsonPathValue
{
m_delimiterFunc = [](const char delimiter) -> bool
{
return CommandLineArgumentDelimiters.find_first_of(delimiter) != AZStd::string_view::npos;
};
}
//! Callback function which is invoked to determine whether a delimiter has been found
//! return value of true indicates that a delimiter has been found
using DelimiterFunc = AZStd::function<bool(const char delimiter)>;
AZStd::string_view m_path;
AZStd::string_view m_value;
};
CommandLineArgumentSettings();
//! Callback function which is invoked to determine how to split a command line argument
//! into a JSON path and a JSON value
using DelimiterFunc = AZStd::function<JsonPathValue(AZStd::string_view line)>;
DelimiterFunc m_delimiterFunc;
};
//! Merges a single command line argument into the settings registry. Command line arguments
@@ -322,6 +322,14 @@ namespace AZ
//! @return True if the registry folder was successfully merged, otherwise false.
virtual bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations,
AZStd::string_view platform = {}, AZStd::string_view rootKey = "", AZStd::vector<char>* scratchBuffer = nullptr) = 0;
//! Stores the settings structure which is used when merging settings to the Settings Registry
//! using JSON Merge Patch or JSON Merge Patch.
//! The settings contain an issue reporting callback which can be used to track patching process.
//! Potential application of the reporting callback could be to update a UI whenever a key receives an updated value
//! @param applyPatchSettings The ApplyPatchSettings which are using during JSON Merging
virtual void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) = 0;
virtual void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) = 0;
};
inline SettingsRegistryInterface::Visitor::~Visitor() = default;
@@ -11,6 +11,7 @@
*/
#include <cctype>
#include <cerrno>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/JSON/error/en.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
@@ -419,9 +420,6 @@ namespace AZ
bool SettingsRegistryImpl::MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey,
const CommandLineArgumentSettings& commandLineSettings)
{
const char* front = argument.begin();
const char* back = argument.end();
if (!commandLineSettings.m_delimiterFunc)
{
AZ_Error("SettingsRegistry", false,
@@ -429,87 +427,40 @@ namespace AZ
aznumeric_cast<int>(argument.size()), argument.data());
return false;
}
const char* split = AZStd::find_if(front, back, commandLineSettings.m_delimiterFunc);
if (split == front || // There is no key
split == (back-1) || // There is no value
split == back) // Split character not found.
auto [key, value] = commandLineSettings.m_delimiterFunc(argument);
if (key.empty())
{
// They key where to set the JSON value cannot be empty
// The value of the JSON can be though
// This is so that a key can be set to empty string using "/KeyPath="
return false;
}
const char* keyStart = front;
while (std::isspace(*keyStart)) // This is safe because it will eventually stop on =
// Prepend the rootKey as an anchor to the argument key
SettingsRegistryInterface::FixedValueString keyPath{ rootKey.ends_with('/')
? rootKey.substr(0, rootKey.size() - 1)
: rootKey };
// Append the JSON reference token prefix of '/' to the keyPath
if (!key.starts_with('/'))
{
keyStart++;
keyPath.push_back('/');
}
if (keyStart == split) // Key is just white spaces
if ((key.size() + keyPath.size()) > keyPath.max_size())
{
// The key portion is longer than the FixedValueString max size that can be stored
// This limitation is arbitrary, if an AZStd::string is used or if the C++17 std::to_chars
// function is used, there wouldn't need to be a limitation
return false;
}
const char* keyEnd = split;
while (std::isspace(*--keyEnd));
keyEnd++;
keyPath += key;
key = keyPath;
char buffer[MaxJsonPathLength];
AZStd::string_view key;
bool keyHasDivider = *keyStart == '/';
if (!rootKey.empty())
if (value.empty())
{
bool rootKeyHasDivider = (rootKey[rootKey.length() - 1]) == '/';
size_t count;
if (!rootKeyHasDivider && !keyHasDivider)
{
count = azsnprintf(buffer, AZ_ARRAY_SIZE(buffer), "%.*s/%.*s",
aznumeric_cast<int>(rootKey.length()), rootKey.data(),
aznumeric_cast<int>(keyEnd - keyStart), keyStart);
}
else if (rootKeyHasDivider && keyHasDivider)
{
count = azsnprintf(buffer, AZ_ARRAY_SIZE(buffer), "%.*s%.*s",
aznumeric_cast<int>(rootKey.length()) - 1, rootKey.data(),
aznumeric_cast<int>(keyEnd - keyStart), keyStart);
}
else
{
count = azsnprintf(buffer, AZ_ARRAY_SIZE(buffer), "%.*s%.*s",
aznumeric_cast<int>(rootKey.length()), rootKey.data(),
aznumeric_cast<int>(keyEnd - keyStart), keyStart);
}
if (count >= AZ_ARRAY_SIZE(buffer) - 1)
{
return false;
}
key = AZStd::string_view(buffer, count);
}
else if (!keyHasDivider)
{
size_t count = azsnprintf(buffer, AZ_ARRAY_SIZE(buffer), "/%.*s",
aznumeric_cast<int>(keyEnd - keyStart), keyStart);
if (count >= AZ_ARRAY_SIZE(buffer) - 1)
{
return false;
}
key = AZStd::string_view(buffer, count);
}
else
{
key = AZStd::string_view(keyStart, keyEnd);
return Set(key, value);
}
const char* valueStart = split + 1;
while (std::isspace(*valueStart) && valueStart < back)
{
valueStart++;
}
if (valueStart == back)
{
return false; // The value is empty
}
const char* valueEnd = back;
while (std::isspace(*(--valueEnd)));
valueEnd++;
AZStd::string_view value(valueStart, valueEnd);
if (value == "true")
{
return Set(key, true);
@@ -519,23 +470,35 @@ namespace AZ
return Set(key, false);
}
if (value.length() - 1 >= MaxCommandLineArgumentLength)
SettingsRegistryInterface::FixedValueString valueString;
if (value.size() > valueString.max_size())
{
// The value portion is longer than the FixedValueString max size that can be stored
// This limitation is arbitrary, if an AZStd::string is used or if the C++17 std::to_chars
// function is used, there wouldn't need to be a limitation
return false;
}
char argumentString[MaxCommandLineArgumentLength];
snprintf(argumentString, AZ_ARRAY_SIZE(argument), "%.*s", aznumeric_cast<int>(value.length()), value.data());
char* argumentStringEnd = argumentString + value.length();
valueString = value;
const char* valueStringEnd = valueString.c_str() + valueString.size();
errno = 0;
char* convertEnd = nullptr;
s64 intValue = strtoll(argumentString, &convertEnd, 0);
if (convertEnd == argumentStringEnd)
s64 intValue = strtoll(valueString.c_str(), &convertEnd, 0);
if (errno != ERANGE && convertEnd == valueStringEnd)
{
return Set(key, intValue);
}
errno = 0;
convertEnd = nullptr;
double floatingPointValue = strtod(argumentString, &convertEnd);
if (convertEnd == argumentStringEnd)
u64 uintValue = strtoull(valueString.c_str(), &convertEnd, 0);
if (errno != ERANGE && convertEnd == valueStringEnd)
{
return Set(key, uintValue);
}
errno = 0;
convertEnd = nullptr;
double floatingPointValue = strtod(valueString.c_str(), &convertEnd);
if (errno != ERANGE && convertEnd == valueStringEnd)
{
return Set(key, floatingPointValue);
}
@@ -611,7 +574,7 @@ namespace AZ
}
else
{
if (MaxFilePathLength < path.length() + 1)
if (AZ::IO::MaxPathLength < path.length() + 1)
{
AZ_Error("Settings Registry", false,
R"(Path "%.*s" is too long. Either make sure that the provided path is terminated or use a shorter path.)",
@@ -623,10 +586,8 @@ namespace AZ
.AddMember(StringRef("Path"), AZStd::move(pathValue), m_settings.GetAllocator());
return false;
}
char filePath[MaxFilePathLength];
azstrncpy(filePath, AZ_ARRAY_SIZE(filePath), path.data(), path.length());
filePath[path.length()] = 0;
result = MergeSettingsFileInternal(filePath, format, rootKey, *scratchBuffer);
AZ::IO::FixedMaxPathString filePath(path);
result = MergeSettingsFileInternal(filePath.c_str(), format, rootKey, *scratchBuffer);
}
scratchBuffer->clear();
@@ -660,7 +621,7 @@ namespace AZ
additionalSpaceRequired += AZ_ARRAY_SIZE(PlatformFolder) + platform.length() + 2; // +2 for the two slashes.
}
if (path.length() + additionalSpaceRequired > MaxFilePathLength)
if (path.length() + additionalSpaceRequired > AZ::IO::MaxPathLength)
{
AZ_Error("Settings Registry", false, "Folder path for the Setting Registry is too long: %.*s",
static_cast<int>(path.size()), path.data());
@@ -673,7 +634,7 @@ namespace AZ
RegistryFileList fileList;
scratchBuffer->clear();
AZStd::fixed_string<MaxFilePathLength> folderPath{ path };
AZ::IO::FixedMaxPathString folderPath{ path };
constexpr AZStd::string_view pathSeparators{ AZ_CORRECT_AND_WRONG_DATABASE_SEPARATOR };
if (pathSeparators.find_first_of(folderPath.back()) == AZStd::string_view::npos)
{
@@ -926,7 +887,7 @@ namespace AZ
// Sort by the name first so the registry file gets applied with all its specializations.
if (lhs.m_tags[0] != rhs.m_tags[0])
{
return strcmp(lhs.m_relativePath, rhs.m_relativePath) < 0;
return lhs.m_relativePath < rhs.m_relativePath;
}
// Then sort by size first so the files with the fewest specializations get applied first.
@@ -956,14 +917,14 @@ namespace AZ
}
collisionFound = true;
AZ_Error("Settings Registry", false, R"(Two registry files point to the same specialization: "%s" and "%s")",
lhs.m_relativePath, rhs.m_relativePath);
AZ_Error("Settings Registry", false, R"(Two registry files in "%.*s" point to the same specialization: "%s" and "%s")",
AZ_STRING_ARG(folderPath), lhs.m_relativePath.c_str(), rhs.m_relativePath.c_str());
historyPointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Too many files in registry folder."), m_settings.GetAllocator())
.AddMember(StringRef("Path"),
Value(folderPath.data(), aznumeric_caster(folderPath.length()), m_settings.GetAllocator()), m_settings.GetAllocator())
.AddMember(StringRef("File1"), Value(lhs.m_relativePath, m_settings.GetAllocator()), m_settings.GetAllocator())
.AddMember(StringRef("File2"), Value(rhs.m_relativePath, m_settings.GetAllocator()), m_settings.GetAllocator());
.AddMember(StringRef("File1"), Value(lhs.m_relativePath.c_str(), m_settings.GetAllocator()), m_settings.GetAllocator())
.AddMember(StringRef("File2"), Value(rhs.m_relativePath.c_str(), m_settings.GetAllocator()), m_settings.GetAllocator());
return false;
}
@@ -1036,9 +997,9 @@ namespace AZ
// thats the name tag.
AZStd::sort(AZStd::next(output.m_tags.begin()), output.m_tags.end());
if (filePathSize < AZ_ARRAY_SIZE(output.m_relativePath))
if (filePathSize < output.m_relativePath.max_size())
{
azstrcpy(output.m_relativePath, AZ_ARRAY_SIZE(output.m_relativePath), filename);
output.m_relativePath = filename;
return true;
}
else
@@ -1145,7 +1106,7 @@ namespace AZ
JsonSerializationResult::ResultCode mergeResult(JsonSerializationResult::Tasks::Merge);
if (rootKey.empty())
{
mergeResult = JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach);
mergeResult = JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
}
else
{
@@ -1153,7 +1114,7 @@ namespace AZ
if (root.IsValid())
{
Value& rootValue = root.Create(m_settings, m_settings.GetAllocator());
mergeResult = JsonSerialization::ApplyPatch(rootValue, m_settings.GetAllocator(), jsonPatch, mergeApproach);
mergeResult = JsonSerialization::ApplyPatch(rootValue, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
}
else
{
@@ -1180,4 +1141,13 @@ namespace AZ
return true;
}
void SettingsRegistryImpl::SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings)
{
m_applyPatchSettings = applyPatchSettings;
}
void SettingsRegistryImpl::GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings)
{
applyPatchSettings = m_applyPatchSettings;
}
} // namespace AZ
@@ -14,6 +14,7 @@
#include <AzCore/JSON/document.h>
#include <AzCore/JSON/pointer.h>
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Settings/SettingsRegistry.h>
@@ -35,9 +36,6 @@ namespace AZ
AZ_CLASS_ALLOCATOR(SettingsRegistryImpl, AZ::OSAllocator, 0);
AZ_RTTI(AZ::SettingsRegistryImpl, "{E9C34190-F888-48CA-83C9-9F24B4E21D72}", AZ::SettingsRegistryInterface);
static constexpr size_t MaxFilePathLength = AZ_MAX_PATH_LEN;
static constexpr size_t MaxJsonPathLength = 1024;
static constexpr size_t MaxCommandLineArgumentLength = 1024;
static constexpr size_t MaxRegistryFolderEntries = 128;
SettingsRegistryImpl();
@@ -80,11 +78,14 @@ namespace AZ
bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations,
AZStd::string_view platform, AZStd::string_view rootKey = "", AZStd::vector<char>* scratchBuffer = nullptr) override;
void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) override;
void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) override;
private:
using TagList = AZStd::fixed_vector<size_t, Specializations::MaxCount + 1>;
struct RegistryFile
{
char m_relativePath[MaxFilePathLength]{ 0 };
AZ::IO::FixedMaxPathString m_relativePath;
TagList m_tags;
bool m_isPatch{ false };
bool m_isPlatformFile{ false };
@@ -109,5 +110,6 @@ namespace AZ
rapidjson::Document m_settings;
JsonSerializerSettings m_serializationSettings;
JsonDeserializerSettings m_deserializationSettings;
JsonApplyPatchSettings m_applyPatchSettings;
};
} // namespace AZ
@@ -369,46 +369,6 @@ namespace AZ::SettingsRegistryMergeUtils
return sectionName;
}
// Encodes a key, value delimited line such that the entire "key" can be stored as a single
// JSON Pointer key by escaping the tilde(~) and forward slash(/)
template<size_t BufferSize>
static AZStd::fixed_string<BufferSize> EncodeLineForJsonPointer(AZStd::string_view token,
const AZ::SettingsRegistryInterface::CommandLineArgumentSettings::DelimiterFunc& delimiterFunc)
{
if (!delimiterFunc)
{
// Since the delimiter function is not valid, return the token unchanged
return AZStd::fixed_string<BufferSize>{ token };
}
// Iterate over the line and escape the '~' and '/' values
AZStd::fixed_string<BufferSize> encodedToken;
size_t chIndex = 0;
for (; chIndex < token.size(); ++chIndex)
{
const char ch = token[chIndex];
if (delimiterFunc(ch))
{
// If the delimiter is found, this indicates that the end of the key has been found
break;
}
switch (ch)
{
case '~':
encodedToken += "~0";
break;
case '/':
encodedToken += "~1";
break;
default:
encodedToken += ch;
}
}
// Copy over the rest of the post delimited line to the encoded token
encodedToken.append(token.data() + chIndex, token.data() + token.size());
return encodedToken;
}
void QuerySpecializationsFromRegistry(SettingsRegistryInterface& registry, SettingsRegistryInterface::Specializations& specializations)
{
// Append any specializations stored in the registry
@@ -528,14 +488,7 @@ namespace AZ::SettingsRegistryMergeUtils
}
}
// Check if the "key" portion of the line has '~' or '/' as the SettingsRegistry uses JSON Pointer
// to set the "value" portion. Those characters need to be escaped with ~0 and ~1 respectively
// to allow them to be embedded in a single json key
// Iterate over the line and escape the '~' and '/' values
AZStd::fixed_string<ConfigBufferMaxSize> escapedLine = EncodeLineForJsonPointer<ConfigBufferMaxSize>(line,
configParserSettings.m_commandLineSettings.m_delimiterFunc);
registry.MergeCommandLineArgument(escapedLine, currentJsonPointerPath, configParserSettings.m_commandLineSettings);
registry.MergeCommandLineArgument(line, currentJsonPointerPath, configParserSettings.m_commandLineSettings);
// Skip past the newline character if found
frontIter = lineEndIter + (foundNewLine ? 1 : 0);
@@ -628,6 +581,34 @@ namespace AZ::SettingsRegistryMergeUtils
? devWriteStorage.value()
: projectUserPath.Native());
// Set the project in-memory build path if the ProjectBuildPath key has been supplied
if (AZ::IO::FixedMaxPath projectBuildPath; registry.Get(projectBuildPath.Native(), ProjectBuildPath))
{
registry.Remove(FilePathKey_ProjectBuildPath);
registry.Remove(FilePathKey_ProjectConfigurationBinPath);
AZ::IO::FixedMaxPath buildConfigurationPath = normalizedProjectPath / projectBuildPath;
if (IO::SystemFile::Exists(buildConfigurationPath.c_str()))
{
registry.Set(FilePathKey_ProjectBuildPath, buildConfigurationPath.LexicallyNormal().Native());
}
// Add the specific build configuration paths to the Settings Registry
// First try <project-build-path>/bin/$<CONFIG> and if that path doesn't exist
// try <project-build-path>/bin/$<PLATFORM>/$<CONFIG>
buildConfigurationPath /= "bin";
if (IO::SystemFile::Exists((buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
{
registry.Set(FilePathKey_ProjectConfigurationBinPath,
(buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native());
}
else if (IO::SystemFile::Exists((buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
{
registry.Set(FilePathKey_ProjectConfigurationBinPath,
(buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native());
}
}
// Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name.
auto projectNameKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey)
@@ -718,6 +699,14 @@ namespace AZ::SettingsRegistryMergeUtils
mergePath /= SettingsRegistryInterface::RegistryFolder;
registry.MergeSettingsFolder(mergePath.Native(), specializations, platform, "", scratchBuffer);
}
AZ::IO::FixedMaxPath projectBinPath;
if (registry.Get(projectBinPath.Native(), FilePathKey_ProjectConfigurationBinPath))
{
// Append the project build path path to the project root
projectBinPath /= SettingsRegistryInterface::RegistryFolder;
registry.MergeSettingsFolder(projectBinPath.Native(), specializations, platform, "", scratchBuffer);
}
}
void MergeSettingsToRegistry_EngineRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
@@ -963,7 +952,8 @@ namespace AZ::SettingsRegistryMergeUtils
"project-path", AZStd::string::format("%s/project_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)},
OptionKeyToRegsetKey{
"project-cache-path",
AZStd::string::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)}};
AZStd::string::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)},
OptionKeyToRegsetKey{"project-build-path", ProjectBuildPath} };
AZStd::fixed_vector<AZStd::string, commandOptions.size()> overrideArgs;
@@ -52,6 +52,14 @@ namespace AZ::SettingsRegistryMergeUtils
//! project settings can be stored
inline static constexpr char FilePathKey_ProjectUserPath[] = "/Amazon/AzCore/Runtime/FilePaths/SourceProjectUserPath";
//! User facing key which represents the root of a project cmake build tree. i.e the ${CMAKE_BINARY_DIR}
//! A relative path is taking relative to the *project* root, NOT *engine* root.
inline constexpr AZStd::string_view ProjectBuildPath = "/Amazon/Project/Settings/Build/project_build_path";
//! In-Memory only key which stores an absolute path to the project build directory
inline constexpr AZStd::string_view FilePathKey_ProjectBuildPath = "/Amazon/AzCore/Runtime/FilePaths/ProjectBuildPath";
//! In-Memory only key which stores the configuration directory containing the built binaries
inline constexpr AZStd::string_view FilePathKey_ProjectConfigurationBinPath = "/Amazon/AzCore/Runtime/FilePaths/ProjectConfigurationBinPath";
//! Development write storage path may be considered temporary or cache storage on some platforms
inline static constexpr char FilePathKey_DevWriteStorage[] = "/Amazon/AzCore/Runtime/FilePaths/DevWriteStorage";
@@ -128,7 +136,7 @@ namespace AZ::SettingsRegistryMergeUtils
//! Callback function that is after a has been filtered through the CommentPrefixFunc
//! to determine if the text matches a section header
//! returns a view of the section name if the line contains a section
//! Otherwise an empty view is returend
//! Otherwise an empty view is returned
using SectionHeaderFunc = AZStd::function<AZStd::string_view(AZStd::string_view line)>;
//! Root JSON pointer path to place all key=values pairs of configuration data within
@@ -54,6 +54,9 @@ namespace AZ
MOCK_METHOD5(
MergeSettingsFolder,
bool(AZStd::string_view, const Specializations&, AZStd::string_view, AZStd::string_view, AZStd::vector<char>*));
MOCK_METHOD1(SetApplyPatchSettings, void(const JsonApplyPatchSettings&));
MOCK_METHOD1(GetApplyPatchSettings, void(JsonApplyPatchSettings&));
};
} // namespace AZ
@@ -17,8 +17,9 @@ namespace AZ
{
namespace Platform
{
void GetModulePath(AZ::OSString& path)
AZ::IO::FixedMaxPath GetModulePath()
{
return {};
}
void* OpenModule(const AZ::OSString& fileName, bool&)
@@ -26,10 +27,9 @@ namespace AZ
// Android 19 does not have RTLD_NOLOAD but it should be OK since only the Editor expects to reopen modules
return dlopen(fileName.c_str(), RTLD_NOW);
}
void ConstructModuleFullFileName(const AZ::OSString& path, const AZ::OSString& fileName, AZ::OSString& fullPath)
void ConstructModuleFullFileName(AZ::IO::FixedMaxPath&)
{
fullPath = path + fileName;
}
}
}
@@ -10,7 +10,6 @@
*
*/
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include <AzCore/std/string/osstring.h>
#include <AzCore/Utils/Utils.h>
#include <dlfcn.h>
@@ -19,15 +18,9 @@ namespace AZ
{
namespace Platform
{
void GetModulePath(AZ::OSString& path)
AZ::IO::FixedMaxPath GetModulePath()
{
char exePath[AZ_MAX_PATH_LEN];
if (AZ::Utils::GetExecutableDirectory(exePath, AZ_ARRAY_SIZE(exePath)) ==
AZ::Utils::ExecutablePathResult::Success)
{
path = exePath;
path.push_back('/');
}
return AZ::Utils::GetExecutableDirectory();
}
void* OpenModule(const AZ::OSString& fileName, bool& alreadyOpen)
@@ -40,10 +33,9 @@ namespace AZ
}
return handle;
}
void ConstructModuleFullFileName(const AZ::OSString& path, const AZ::OSString& fileName, AZ::OSString& fullPath)
void ConstructModuleFullFileName(AZ::IO::FixedMaxPath&)
{
fullPath = path + fileName;
}
}
}
@@ -11,9 +11,11 @@
*/
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <dlfcn.h>
#include <libgen.h>
@@ -21,9 +23,9 @@ namespace AZ
{
namespace Platform
{
void GetModulePath(AZ::OSString& path);
AZ::IO::FixedMaxPath GetModulePath();
void* OpenModule(const AZ::OSString& fileName, bool& alreadyOpen);
void ConstructModuleFullFileName(const AZ::OSString& path, const AZ::OSString& fileName, AZ::OSString& fullPath);
void ConstructModuleFullFileName(AZ::IO::FixedMaxPath& fullPath);
}
class DynamicModuleHandleUnixLike
@@ -36,40 +38,55 @@ namespace AZ
: DynamicModuleHandle(fullFileName)
, m_handle(nullptr)
{
AZ::OSString path;
AZ::OSString fileName;
AZ::OSString fullPath = "";
AZ::OSString::size_type finalSlash = m_fileName.find_last_of("/");
if (finalSlash != AZ::OSString::npos)
AZ::IO::FixedMaxPath fullFilePath(AZStd::string_view{m_fileName});
if (fullFilePath.HasFilename())
{
// Path up to and including final slash
path = m_fileName.substr(0, finalSlash + 1);
// Everything after the final slash
// If m_fileName ends in /, the end result is path/lib.dylib, which just fails to load.
fileName = m_fileName.substr(finalSlash + 1);
}
else
{
// If no slash found, assume empty path, only file name
path = "";
Platform::GetModulePath(path);
fileName = m_fileName;
AZ::IO::FixedMaxPathString fileNamePath{fullFilePath.Filename().Native()};
if (!fileNamePath.starts_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX))
{
fileNamePath = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX + fileNamePath;
}
if (!fileNamePath.ends_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION))
{
fileNamePath += AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
}
fullFilePath.ReplaceFilename(AZStd::string_view(fileNamePath));
}
if (fileName.substr(0, 3) != AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX)
Platform::ConstructModuleFullFileName(fullFilePath);
// Check if the module exist at the given path within the current working directory
// If it doesn't attempt to append the path to the executable path
if (!AZ::IO::SystemFile::Exists(fullFilePath.c_str()))
{
fileName = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX + fileName;
auto candidatePath = Platform::GetModulePath() / fullFilePath;
if (AZ::IO::SystemFile::Exists(candidatePath.c_str()))
{
fullFilePath = candidatePath;
}
}
size_t extensionLen = strlen(AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION);
if (fileName.substr(fileName.length() - extensionLen, extensionLen) != AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION)
// If the path still doesn't exist at this point, check the SettingsRegistryMergeUtils
// FilePathKey_ProjectBuildPath key to see if a project-build-path argument has been supplied
if (!AZ::IO::SystemFile::Exists(fullFilePath.c_str()))
{
fileName = fileName + AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if(AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
projectModulePath /= fullFilePath;
if (AZ::IO::SystemFile::Exists(projectModulePath.c_str()))
{
fullFilePath = projectModulePath;
}
}
}
}
Platform::ConstructModuleFullFileName(path, fileName, fullPath);
m_fileName = fullPath;
m_fileName = AZStd::string_view{fullFilePath.Native()};
}
~DynamicModuleHandleUnixLike() override
@@ -81,9 +98,9 @@ namespace AZ
{
AZ::Debug::Trace::Printf("Module", "Attempting to load module:%s\n", m_fileName.c_str());
bool alreadyOpen = false;
m_handle = Platform::OpenModule(m_fileName, alreadyOpen);
if(m_handle)
{
if (alreadyOpen)
@@ -15,6 +15,7 @@
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/PlatformIncl.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
namespace AZ
@@ -31,13 +32,13 @@ namespace AZ
{
// Ensure filename ends in ".dll"
// Otherwise filenames like "gem.1.0.0" fail to load (.0 is assumed to be the extension).
if (m_fileName.substr(m_fileName.length() - 4) != AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION)
if (!m_fileName.ends_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION))
{
m_fileName = m_fileName + AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
m_fileName += AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
}
AZ::IO::PathView modulePathView{ m_fileName };
// If the module path doesn't have a directory within it, prepend it to the path
// If the module path doesn't have a directory within it, prepend the executable directory to the path
// and check if the new path exist
if (modulePathView.HasFilename() && !modulePathView.HasParentPath())
{
@@ -54,6 +55,27 @@ namespace AZ
}
}
}
// If the module file path does not exist, attempt to search for the module within
// the project's build directory
if (!AZ::IO::SystemFile::Exists(m_fileName.c_str()))
{
// The Settings Registry may not exist in early startup if modules are loaded
// before the ComponentApplication is crated(such as in the Editor main.cpp)
// Therefore an existence check is needed
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if(AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
projectModulePath /= AZStd::string_view(m_fileName);
if (AZ::IO::SystemFile::Exists(projectModulePath.c_str()))
{
m_fileName.assign(projectModulePath.c_str(), projectModulePath.Native().size());
}
}
}
}
}
~DynamicModuleHandleWindows() override
@@ -10,7 +10,6 @@
*
*/
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include <AzCore/std/string/osstring.h>
#include <AzCore/Utils/Utils.h>
#include <dlfcn.h>
@@ -19,15 +18,9 @@ namespace AZ
{
namespace Platform
{
void GetModulePath(AZ::OSString& path)
AZ::IO::FixedMaxPath GetModulePath()
{
char exePath[AZ_MAX_PATH_LEN];
if (AZ::Utils::GetExecutableDirectory(exePath, AZ_ARRAY_SIZE(exePath)) ==
AZ::Utils::ExecutablePathResult::Success)
{
path = exePath;
path.push_back('/');
}
return AZ::Utils::GetExecutableDirectory();
}
void* OpenModule(const AZ::OSString& fileName, bool& alreadyOpen)
@@ -40,10 +33,9 @@ namespace AZ
}
return handle;
}
void ConstructModuleFullFileName(const AZ::OSString& path, const AZ::OSString& fileName, AZ::OSString& fullPath)
void ConstructModuleFullFileName(AZ::IO::FixedMaxPath&)
{
fullPath = path + fileName;
}
}
}
@@ -10,7 +10,6 @@
*
*/
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include <AzCore/std/string/osstring.h>
#include <AzCore/Utils/Utils.h>
#include <dlfcn.h>
@@ -19,15 +18,9 @@ namespace AZ
{
namespace Platform
{
void GetModulePath(AZ::OSString& path)
AZ::IO::FixedMaxPath GetModulePath()
{
char exePath[AZ_MAX_PATH_LEN];
if (AZ::Utils::GetExecutableDirectory(exePath, AZ_ARRAY_SIZE(exePath)) ==
AZ::Utils::ExecutablePathResult::Success)
{
AZ::OSString frameworks = "/Frameworks/";
path = exePath + frameworks;
}
return AZ::IO::FixedMaxPath(AZ::Utils::GetExecutableDirectory()) / "Frameworks";
}
void* OpenModule(const AZ::OSString& fileName, bool& alreadyOpen)
@@ -40,10 +33,15 @@ namespace AZ
}
return handle;
}
void ConstructModuleFullFileName(const AZ::OSString& path, const AZ::OSString& fileName, AZ::OSString& fullPath)
void ConstructModuleFullFileName(AZ::IO::FixedMaxPath& fullPath)
{
fullPath = path + fileName + ".framework/" + fileName;
// Append .framework to the name of full path
// Afterwards use the AZ::IO::Path Append function append the filename as a child
// of the framework directory
AZ::IO::FixedMaxPathString fileName = fullPath.Filename().Native();
fullPath.ReplaceFilename(fileName + ".framework");
fullPath /= fileName;
}
}
}
@@ -351,6 +351,7 @@ namespace UnitTest
public AZ::Data::AssetCatalog
{
static inline const AZ::Uuid TestAssetId{"{E970B177-5F45-44EB-A2C4-9F29D9A0B2A2}"};
static inline const AZ::Uuid MissingAssetId{"{11111111-1111-1111-1111-111111111111}"};
static inline constexpr AZStd::string_view TestAssetPath = "test";
void SetUp() override
@@ -431,24 +432,40 @@ namespace UnitTest
// AssetCatalogRequestBus implementation
// Minimalist mocks to provide our desired asset path or asset id
AZStd::string GetAssetPathById([[maybe_unused]] const AZ::Data::AssetId& id) override
AZStd::string GetAssetPathById(const AZ::Data::AssetId& id) override
{
return TestAssetPath;
if (id == TestAssetId)
{
return TestAssetPath;
}
return "";
}
AZ::Data::AssetId GetAssetIdByPath(
[[maybe_unused]] const char* path, [[maybe_unused]] const AZ::Data::AssetType& typeToRegister,
const char* path, [[maybe_unused]] const AZ::Data::AssetType& typeToRegister,
[[maybe_unused]] bool autoRegisterIfNotFound) override
{
return TestAssetId;
if (path == TestAssetPath)
{
return TestAssetId;
}
return AZ::Data::AssetId();
}
// Return the mocked-out information for our test asset
AZ::Data::AssetInfo GetAssetInfoById([[maybe_unused]] const AZ::Data::AssetId& id) override
AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& id) override
{
AZ::Data::AssetInfo assetInfo;
assetInfo.m_assetId = TestAssetId;
assetInfo.m_assetType = AZ::AzTypeInfo<EmptyAsset>::Uuid();
assetInfo.m_relativePath = TestAssetPath;
if (id == TestAssetId)
{
assetInfo.m_assetId = TestAssetId;
assetInfo.m_assetType = AZ::AzTypeInfo<EmptyAsset>::Uuid();
assetInfo.m_relativePath = TestAssetPath;
}
return assetInfo;
}
@@ -456,15 +473,20 @@ namespace UnitTest
// Set the mocked-out asset load to have a 0-byte length so that the load skips I/O and immediately returns success
AZ::Data::AssetStreamInfo GetStreamInfoForLoad(
[[maybe_unused]] const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override
const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override
{
EXPECT_TRUE(type == AZ::AzTypeInfo<EmptyAsset>::Uuid());
AZ::Data::AssetStreamInfo info;
info.m_dataOffset = 0;
info.m_streamName = TestAssetPath;
info.m_dataLen = 0;
info.m_streamFlags = AZ::IO::OpenMode::ModeRead;
if (id == TestAssetId)
{
info.m_streamName = TestAssetPath;
}
return info;
}
@@ -489,4 +511,27 @@ namespace UnitTest
EXPECT_TRUE(testAsset.IsReady());
}
// This test verifies that even if the asset loading returns immediately with an error, all of the loading code works
// successfully. The test itself loads a missing asset twice - the first time is a non-immediate error, where the error
// isn't reported until the DispatchEvents() call. The second time is an immediate error, because now the asset is already
// registered in an Error state. If the test fails, it will likely get caught in the shutdown of the test class, if any
// assets still exist at the point that the asset handler is unregistered. If they're present, then handling of the immediate
// error didn't work, as it left around extra references to the asset that haven't been cleaned up.
TEST_F(AssetManagerStreamerImmediateCompletionTests, ImmediateAssetError_WorksSuccessfully)
{
AZ::Data::AssetLoadParameters loadParams;
// Attempt to load a missing asset the first time. It will get an error, but not until the DispatchEvents() call happens.
auto testAsset1 = AssetManager::Instance().GetAsset<EmptyAsset>(MissingAssetId, AZ::Data::AssetLoadBehavior::Default, loadParams);
AZ::Data::AssetManager::Instance().DispatchEvents();
EXPECT_TRUE(testAsset1.IsError());
// While the reference to the missing asset still exists, try to get it again. This will cause a more immediate error in
// the AssetContainer code, which should still get handled correctly. In the failure condition, it will instead leave the
// AssetContainer in a state where it never sends the final OnAssetContainerReady/Canceled message.
auto testAsset2 = AssetManager::Instance().GetAsset<EmptyAsset>(MissingAssetId, AZ::Data::AssetLoadBehavior::Default, loadParams);
AZ::Data::AssetManager::Instance().DispatchEvents();
EXPECT_TRUE(testAsset2.IsError());
}
} // namespace UnitTest
@@ -13,24 +13,25 @@
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Console/Console.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <AzCore/Utils/Utils.h>
namespace AZ
{
using namespace UnitTest;
AZ_CVAR(bool, testBool, false, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(char, testChar, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(int8_t, testInt8, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(int16_t, testInt16, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(int32_t, testInt32, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(int64_t, testInt64, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(uint8_t, testUInt8, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(bool, testBool, false, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(char, testChar, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(int8_t, testInt8, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(int16_t, testInt16, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(int32_t, testInt32, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(int64_t, testInt64, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(uint8_t, testUInt8, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(uint16_t, testUInt16, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(uint32_t, testUInt32, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(uint64_t, testUInt64, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, testFloat, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(double, testDouble, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(float, testFloat, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(double, testDouble, 0, nullptr, ConsoleFunctorFlags::Null, "");
AZ_CVAR(AZ::CVarFixedString, testString, "default", nullptr, ConsoleFunctorFlags::Null, "");
@@ -189,7 +190,7 @@ namespace AZ
TEST_F(ConsoleTests, CVar_GetSetTest_Vector2)
{
testVec2 = AZ::Vector2{ 0.0f, 0.0f};
testVec2 = AZ::Vector2{ 0.0f, 0.0f };
TestCVarHelper(testVec2, "testVec2", "testVec2 1 1", "testVec2 asdf", AZ::Vector2(100, 100), AZ::Vector2(0, 0), AZ::Vector2(1, 1));
}
@@ -350,3 +351,245 @@ namespace AZ
}
}
}
namespace ConsoleSettingsRegistryTests
{
//! ConfigFile MergeUtils Test
struct ConfigFileParams
{
AZStd::string_view m_testConfigFileName;
AZStd::string_view m_testConfigContents;
};
class ConsoleSettingsRegistryFixture
: public UnitTest::ScopedAllocatorSetupFixture
, public ::testing::WithParamInterface<ConfigFileParams>
{
public:
void SetUp() override
{
m_registry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
// Store off the old global settings registry to restore after each test
m_oldSettingsRegistry = AZ::SettingsRegistry::Get();
if (m_oldSettingsRegistry != nullptr)
{
AZ::SettingsRegistry::Unregister(m_oldSettingsRegistry);
}
AZ::SettingsRegistry::Register(m_registry.get());
// Create a TestFile in the Test Directory
m_testFolder = AZ::IO::FixedMaxPath(AZ::Utils::GetExecutableDirectory()) / "ConsoleTestFolder";
auto configFileParams = GetParam();
CreateTestFile(m_testFolder / configFileParams.m_testConfigFileName, configFileParams.m_testConfigContents);
}
void TearDown() override
{
// Remove the Test Directory
DeleteFolderRecursive(m_testFolder);
// Restore the old global settings registry
AZ::SettingsRegistry::Unregister(m_registry.get());
if (m_oldSettingsRegistry != nullptr)
{
AZ::SettingsRegistry::Register(m_oldSettingsRegistry);
m_oldSettingsRegistry = {};
}
m_registry.reset();
}
void TestClassFunc(const AZ::ConsoleCommandContainer& someStrings)
{
m_stringArgCount = someStrings.size();
}
AZ_CONSOLEFUNC(ConsoleSettingsRegistryFixture, TestClassFunc, AZ::ConsoleFunctorFlags::Null, "");
static void DeleteFolderRecursive(const AZ::IO::PathView& path)
{
auto callback = [&path](AZStd::string_view filename, bool isFile) -> bool
{
if (isFile)
{
auto filePath = AZ::IO::FixedMaxPath(path) / filename;
AZ::IO::SystemFile::Delete(filePath.c_str());
}
else
{
if (filename != "." && filename != "..")
{
auto folderPath = AZ::IO::FixedMaxPath(path) / filename;
DeleteFolderRecursive(folderPath);
}
}
return true;
};
auto searchPath = AZ::IO::FixedMaxPath(path) / "*";
AZ::IO::SystemFile::FindFiles(searchPath.c_str(), callback);
AZ::IO::SystemFile::DeleteDir(AZ::IO::FixedMaxPathString(path.Native()).c_str());
}
static bool CreateTestFile(const AZ::IO::FixedMaxPath& testPath, AZStd::string_view content)
{
AZ::IO::SystemFile file;
if (!file.Open(testPath.c_str(), AZ::IO::SystemFile::OpenMode::SF_OPEN_CREATE
| AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY))
{
AZ_Assert(false, "Unable to open test file for writing: %s", testPath.c_str());
return false;
}
if (file.Write(content.data(), content.size()) != content.size())
{
AZ_Assert(false, "Unable to write content to test file: %s", testPath.c_str());
return false;
}
return true;
}
protected:
size_t m_stringArgCount{};
AZStd::unique_ptr<AZ::SettingsRegistryInterface> m_registry;
AZ::IO::FixedMaxPath m_testFolder;
private:
AZ::SettingsRegistryInterface* m_oldSettingsRegistry{};
};
static bool s_consoleFreeFunctionInvoked = false;
static void TestSettingsRegistryFreeFunc(const AZ::ConsoleCommandContainer& someStrings)
{
EXPECT_TRUE(someStrings.empty());
s_consoleFreeFunctionInvoked = true;
}
AZ_CONSOLEFREEFUNC(TestSettingsRegistryFreeFunc, AZ::ConsoleFunctorFlags::Null, "");
TEST_P(ConsoleSettingsRegistryFixture, Console_AbleToLoadSettingsFile_Successfully)
{
AZ::Console testConsole(*m_registry);
testConsole.LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead());
AZ::Interface<AZ::IConsole>::Register(&testConsole);
AZ_CVAR_SCOPED(int32_t, testInit, 0, nullptr, AZ::ConsoleFunctorFlags::Null, "");
s_consoleFreeFunctionInvoked = false;
testInit = {};
AZ::testChar = {};
AZ::testBool = {};
AZ::testInt8 = {};
AZ::testInt16 = {};
AZ::testInt32 = {};
AZ::testInt64 = {};
AZ::testUInt8 = {};
AZ::testUInt16 = {};
AZ::testUInt32 = {};
AZ::testUInt64 = {};
AZ::testFloat= {};
AZ::testDouble = {};
AZ::testString = {};
auto configFileParams = GetParam();
auto testFilePath = m_testFolder / configFileParams.m_testConfigFileName;
EXPECT_TRUE(AZ::IO::SystemFile::Exists(testFilePath.c_str()));
testConsole.ExecuteConfigFile(testFilePath.Native());
EXPECT_TRUE(s_consoleFreeFunctionInvoked);
EXPECT_EQ(3, testInit);
EXPECT_TRUE(static_cast<bool>(AZ::testBool));
EXPECT_EQ('Q', AZ::testChar);
EXPECT_EQ(24, AZ::testInt8);
EXPECT_EQ(-32, AZ::testInt16);
EXPECT_EQ(41, AZ::testInt32);
EXPECT_EQ(-51, AZ::testInt64);
EXPECT_EQ(3, AZ::testUInt8);
EXPECT_EQ(5, AZ::testUInt16);
EXPECT_EQ(6, AZ::testUInt32);
EXPECT_EQ(0xFFFF'FFFF'FFFF'FFFF, AZ::testUInt64);
EXPECT_FLOAT_EQ(1.0f, AZ::testFloat);
EXPECT_DOUBLE_EQ(2, AZ::testDouble);
EXPECT_STREQ("Stable", static_cast<AZ::CVarFixedString>(AZ::testString).c_str());
EXPECT_EQ(3, m_stringArgCount);
AZ::Interface<AZ::IConsole>::Unregister(&testConsole);
}
static constexpr AZStd::string_view UserINIStyleContent =
R"(
testInit = 3
testBool true
testChar Q
testInt8 24
testInt16 -32
testInt32 41
testInt64 -51
testUInt8 3
testUInt16 5
testUInt32 6
testUInt64 18446744073709551615
testFloat 1.0
testDouble 2
testString Stable
ConsoleSettingsRegistryFixture.testClassFunc Foo Bar Baz
TestSettingsRegistryFreeFunc
)";
static constexpr AZStd::string_view UserJsonMergePatchContent =
R"(
{
"Amazon": {
"AzCore": {
"Runtime": {
"ConsoleCommands": {
"testInit": 3,
"testBool": true,
"testChar": "Q",
"testInt8": 24,
"testInt16": -32,
"testInt32": 41,
"testInt64": -51,
"testUInt8": 3,
"testUInt16": 5,
"testUInt32": 6,
"testUInt64": 18446744073709551615,
"testFloat": 1.0,
"testDouble": 2,
"testString": "Stable",
"ConsoleSettingsRegistryFixture.testClassFunc": "Foo Bar Baz",
"TestSettingsRegistryFreeFunc": ""
}
}
}
}
}
)";
static constexpr AZStd::string_view UserJsonPatchContent =
R"(
[
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testInit", "value": 3 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testBool", "value": true },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testChar", "value": "Q" },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testInt8", "value": 24 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testInt16", "value": -32 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testInt32", "value": 41 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testInt64", "value": -51 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testUInt8", "value": 3 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testUInt16", "value": 5 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testUInt32", "value": 6 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testUInt64", "value": 18446744073709551615 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testFloat", "value": 1.0 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testDouble", "value": 2 },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testString", "value": "Stable" },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/ConsoleSettingsRegistryFixture.testClassFunc", "value": "Foo Bar Baz" },
{ "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/TestSettingsRegistryFreeFunc", "value": "" }
]
)";
INSTANTIATE_TEST_CASE_P(
ExecuteCommandFromSettingsFile,
ConsoleSettingsRegistryFixture,
::testing::Values(
ConfigFileParams{"user.cfg", UserINIStyleContent},
ConfigFileParams{"user.setreg", UserJsonMergePatchContent},
ConfigFileParams{"user.setregpatch", UserJsonPatchContent}
)
);
}
@@ -55,7 +55,7 @@ namespace SettingsRegistryConsoleUtilsTests
{
constexpr const char* settingsKey = "/TestKey";
constexpr const char* expectedValue = "TestValue";
AZ::Console testConsole;
AZ::Console testConsole(*m_registry);
AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle handle{
AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_registry, testConsole) };
EXPECT_TRUE(testConsole.PerformCommand(AZ::SettingsRegistryConsoleUtils::SettingsRegistrySet, { settingsKey, expectedValue }));
@@ -69,7 +69,7 @@ namespace SettingsRegistryConsoleUtilsTests
{
constexpr const char* settingsKey = "/TestKey";
constexpr const char* expectedValue = "TestValue";
AZ::Console testConsole;
AZ::Console testConsole(*m_registry);
// Scopes the console functor handle so that it destructs and unregisters the console functors
{
@@ -89,7 +89,7 @@ namespace SettingsRegistryConsoleUtilsTests
constexpr const char* settingsKey2 = "/TestKey2";
constexpr const char* expectedValue = R"(TestValue)";
constexpr const char* expectedValue2 = R"(Hello World)";
AZ::Console testConsole;
AZ::Console testConsole(*m_registry);
AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle handle{
AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_registry, testConsole) };
@@ -109,7 +109,7 @@ namespace SettingsRegistryConsoleUtilsTests
constexpr const char* settingsKey2 = "/TestKey2";
constexpr const char* expectedValue = R"(TestValue)";
constexpr const char* expectedValue2 = R"(Hello World)";
AZ::Console testConsole;
AZ::Console testConsole(*m_registry);
// Add settings to settings registry
EXPECT_TRUE(m_registry->Set(settingsKey, expectedValue));
@@ -137,7 +137,7 @@ namespace SettingsRegistryConsoleUtilsTests
constexpr const char* settingsKey2 = "/TestKey2";
constexpr const char* expectedValue = R"(TestValue)";
constexpr const char* expectedValue2 = R"(Hello World)";
AZ::Console testConsole;
AZ::Console testConsole(*m_registry);
AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle handle{
AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_registry, testConsole) };
@@ -195,7 +195,7 @@ namespace SettingsRegistryConsoleUtilsTests
constexpr const char* SettingsKey2 = "TestKey2";
constexpr const char* ExpectedValue = R"(TestValue)";
constexpr const char* ExpectedValue2 = R"(Hello World)";
AZ::Console testConsole;
AZ::Console testConsole(*m_registry);
AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle handle{
AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_registry, testConsole) };
@@ -1228,27 +1228,33 @@ namespace SettingsRegistryTests
TEST_F(SettingsRegistryTest, MergeCommandLineArgument_KeyIsTooLong_ReturnsFalse)
{
AZStd::string argument = AZStd::string::format("Te%*cst=Value", aznumeric_cast<int>(AZ::SettingsRegistryImpl::MaxJsonPathLength), ' ');
constexpr int LongKeySize = 1024;
AZStd::string argument = AZStd::string::format("Te%*cst=Value", LongKeySize, ' ');
EXPECT_FALSE(m_registry->MergeCommandLineArgument(argument, {}, {}));
}
TEST_F(SettingsRegistryTest, MergeCommandLineArgument_KeyIsTooLongWithDivider_ReturnsFalse)
{
AZStd::string argument = AZStd::string::format("/Te%*cst=Value", aznumeric_cast<int>(AZ::SettingsRegistryImpl::MaxJsonPathLength), ' ');
constexpr int LongKeySize = 1024;
AZStd::string argument = AZStd::string::format("/Te%*cst=Value", LongKeySize, ' ');
EXPECT_FALSE(m_registry->MergeCommandLineArgument(argument, "/Path", {}));
}
TEST_F(SettingsRegistryTest, MergeCommandLineArgument_ValueIsTooLong_ReturnsFalse)
{
AZStd::string argument = AZStd::string::format("Test=Val%*cue", aznumeric_cast<int>(AZ::SettingsRegistryImpl::MaxCommandLineArgumentLength), ' ');
constexpr int LongValueSize = 1024;
AZStd::string argument = AZStd::string::format("Test=Val%*cue", LongValueSize, ' ');
EXPECT_FALSE(m_registry->MergeCommandLineArgument(argument, {}, {}));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::NoType, m_registry->GetType("/Test"));
}
TEST_F(SettingsRegistryTest, MergeCommandLineArgument_MissingValue_ReturnsFalse)
TEST_F(SettingsRegistryTest, MergeCommandLineArgument_MissingValue_ReturnsEmptyString)
{
EXPECT_FALSE(m_registry->MergeCommandLineArgument("Test=", {}, {}));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::NoType, m_registry->GetType("/Test"));
EXPECT_TRUE(m_registry->MergeCommandLineArgument("Test=", {}, {}));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType("/Test"));
AZ::SettingsRegistryInterface::FixedValueString value;
EXPECT_TRUE(m_registry->Get(value, "/Test"));
EXPECT_TRUE(value.empty());
}
TEST_F(SettingsRegistryTest, MergeCommandLineArgument_MissingKey_ReturnsFalse)
@@ -1271,9 +1277,13 @@ namespace SettingsRegistryTests
EXPECT_FALSE(m_registry->MergeCommandLineArgument(" =Value", {}, {}));
}
TEST_F(SettingsRegistryTest, MergeCommandLineArgument_ValueIsSpaces_ReturnsFalse)
TEST_F(SettingsRegistryTest, MergeCommandLineArgument_ValueIsSpaces_ReturnsEmptyString)
{
EXPECT_FALSE(m_registry->MergeCommandLineArgument("Key= ", {}, {}));
EXPECT_TRUE(m_registry->MergeCommandLineArgument("Key= ", {}, {}));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType("/Key"));
AZ::SettingsRegistryInterface::FixedValueString value;
EXPECT_TRUE(m_registry->Get(value, "/Key"));
EXPECT_TRUE(value.empty());
}
TEST_F(SettingsRegistryTest, MergeCommandLineArgument_KeyAndValueAreSpaces_ReturnsFalse)
@@ -1367,9 +1377,8 @@ namespace SettingsRegistryTests
TEST_F(SettingsRegistryTest, MergeSettingsFile_PathAsSubStringThatsTooLong_ReturnsFalse)
{
char path[AZ::SettingsRegistryImpl::MaxFilePathLength + 1];
memset(path, '1', sizeof(path));
AZStd::string_view subPath(path, AZ::SettingsRegistryImpl::MaxFilePathLength);
constexpr AZStd::fixed_string<AZ::IO::MaxPathLength + 1> path(AZ::IO::MaxPathLength + 1, '1');
const AZStd::string_view subPath(path);
AZ_TEST_START_TRACE_SUPPRESSION;
bool result = m_registry->MergeSettingsFile(subPath, AZ::SettingsRegistryInterface::Format::JsonMergePatch, {}, nullptr);
@@ -1719,8 +1728,7 @@ namespace SettingsRegistryTests
TEST_F(SettingsRegistryTest, MergeSettingsFolder_PathTooLong_ReportsErrorAndReturnsFalse)
{
char path[AZ::SettingsRegistryImpl::MaxFilePathLength + 1]{};
memset(path, 'a', AZ_ARRAY_SIZE(path));
constexpr AZStd::fixed_string<AZ::IO::MaxPathLength + 1> path(AZ::IO::MaxPathLength + 1, 'a');
AZ_TEST_START_TRACE_SUPPRESSION;
bool result = m_registry->MergeSettingsFolder(path, { "editor", "test" }, {}, nullptr);
@@ -1741,7 +1749,7 @@ namespace SettingsRegistryTests
m_testFolder->push_back(AZ_CORRECT_DATABASE_SEPARATOR);
*m_testFolder += AZ::SettingsRegistryInterface::RegistryFolder;
bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {}, nullptr);
AZ_TEST_STOP_TRACE_SUPPRESSION(2);
EXPECT_GT(::UnitTest::TestRunner::Instance().StopAssertTests(), 0);
EXPECT_FALSE(result);
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Object, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/0")); // Folder and specialization settings.
@@ -1751,11 +1759,5 @@ namespace SettingsRegistryTests
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/1/Path"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/1/File1"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/1/File2"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Object, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/2"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/2/Error"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/2/Path"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/2/File1"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/2/File2"));
}
} // namespace SettingsRegistryTests
@@ -236,8 +236,6 @@ namespace AzFramework
// Archive classes relies on the FileIOBase DirectInstance to close
// files properly
m_directFileIO.reset();
// The AZ::Console skips destruction and always leaks to allow it to be used in static memory
}
void Application::Start(const Descriptor& descriptor, const StartupParameters& startupParameters)
@@ -24,7 +24,7 @@ namespace AZ::IO
ePakPriorityPakOnly = 2
};
// variables that control behavior of Archive/StreamEngine subsystems
// variables that control behavior of the Archive subsystem
struct ArchiveVars
{
#if defined(_RELEASE)
@@ -881,7 +881,7 @@ namespace AzFramework
serializeContext->ClassDeprecate("NetBindable", "{80206665-D429-4703-B42E-94434F82F381}");
serializeContext->Class<TransformComponent, AZ::Component>()
->Version(4, &TransformComponentVersionConverter)
->Version(5, &TransformComponentVersionConverter)
->Field("Parent", &TransformComponent::m_parentId)
->Field("Transform", &TransformComponent::m_worldTM)
->Field("LocalTransform", &TransformComponent::m_localTM)
@@ -40,17 +40,18 @@ namespace AzFramework
//! Standard parameters for drawing text on screen
struct TextDrawParameters
{
ViewportId m_drawViewportId = InvalidViewportId; //! Viewport to draw into
AZ::Vector3 m_position; //! world space position for 3d draws, screen space x,y,depth for 2d.
AZ::Color m_color = AZ::Colors::White; //! Color to draw the text
AZ::Vector2 m_scale = AZ::Vector2(1.0f); //! font scale
TextHorizontalAlignment m_hAlign = TextHorizontalAlignment::Left; //! Horizontal text alignment
TextVerticalAlignment m_vAlign = TextVerticalAlignment::Top; //! Vertical text alignment
bool m_monospace = false; //! disable character proportional spacing
bool m_depthTest = false; //! Test character against the depth buffer
bool m_virtual800x600ScreenSize = true; //! Text placement and size are scaled relative to a virtual 800x600 resolution
bool m_scaleWithWindow = false; //! Font gets bigger as the window gets bigger
bool m_multiline = true; //! text respects ascii newline characters
ViewportId m_drawViewportId = InvalidViewportId; //!< Viewport to draw into
AZ::Vector3 m_position; //!< world space position for 3d draws, screen space x,y,depth for 2d.
AZ::Color m_color = AZ::Colors::White; //!< Color to draw the text
AZ::Vector2 m_scale = AZ::Vector2(1.0f); //!< font scale
float m_lineSpacing; //!< Spacing between new lines, as a percentage of m_scale.
TextHorizontalAlignment m_hAlign = TextHorizontalAlignment::Left; //!< Horizontal text alignment
TextVerticalAlignment m_vAlign = TextVerticalAlignment::Top; //!< Vertical text alignment
bool m_monospace = false; //!< disable character proportional spacing
bool m_depthTest = false; //!< Test character against the depth buffer
bool m_virtual800x600ScreenSize = true; //!< Text placement and size are scaled relative to a virtual 800x600 resolution
bool m_scaleWithWindow = false; //!< Font gets bigger as the window gets bigger
bool m_multiline = true; //!< text respects ascii newline characters
};
class FontDrawInterface
@@ -63,10 +64,13 @@ namespace AzFramework
virtual void DrawScreenAlignedText2d(
const TextDrawParameters& params,
const AZStd::string_view& string) = 0;
AZStd::string_view text) = 0;
virtual void DrawScreenAlignedText3d(
const TextDrawParameters& params,
const AZStd::string_view& string) = 0;
AZStd::string_view text) = 0;
virtual AZ::Vector2 GetTextSize(
const TextDrawParameters& params,
AZStd::string_view text) = 0;
};
class FontQueryInterface
@@ -979,7 +979,7 @@ namespace AzFramework
};
serializeContext->Class<ScriptComponent, AZ::Component>()
->Version(3, converter)
->Version(4, converter)
->Field("ContextID", &ScriptComponent::m_contextId)
->Field("Properties", &ScriptComponent::m_properties)
->Field("Script", &ScriptComponent::m_script)
@@ -0,0 +1,54 @@
/*
* 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.
*
*/
// Description : Ebus for querying thermal information of the device.
#pragma once
#include <AzCore/EBus/EBus.h>
// The different types of temperature sensor that could be available on the device.
enum class ThermalSensorType : int
{
CPU = 0,
GPU,
Battery,
Count
};
// Handles requests for thermal information
class ThermalInfoHandler : public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
virtual ~ThermalInfoHandler() = default;
/**
* Returns the current temperature of a specific sensor in Celcius degrees.
* If the type of sensor is not available on the device it returns 0.
*
* \param sensor The sensor type.
*/
virtual float GetSensorTemp(ThermalSensorType sensor) = 0;
/**
* Returns the temperature that is considered as overheating for a specific sensor.
* The value returned is in Celcius degrees.
*
* \param sensor The sensor type.
*/
virtual float GetSensorOverheatingTemp(ThermalSensorType sensor) = 0;
};
using ThermalInfoRequestsBus = AZ::EBus<ThermalInfoHandler>;
@@ -296,6 +296,7 @@ set(FILES
Spawnable/SpawnableSystemComponent.cpp
Terrain/TerrainDataRequestBus.h
Terrain/TerrainDataRequestBus.cpp
Thermal/ThermalInfo.h
Platform/PlatformDefaults.h
Windowing/WindowBus.h
Windowing/NativeWindow.cpp
@@ -13,6 +13,7 @@
#include <AzFramework/API/ApplicationAPI_Platform.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
#include <AzFramework/Thermal/ThermalInfo_Android.h>
#include <AzCore/Android/AndroidEnv.h>
#include <AzCore/Android/JNI/Object.h>
@@ -95,6 +96,7 @@ namespace AzFramework
private:
AndroidEventDispatcher* m_eventDispatcher;
ApplicationLifecycleEvents::Event m_lastEvent;
AZStd::unique_ptr<ThermalInfoHandler> m_thermalInfoHandler;
AZStd::atomic<bool> m_requestResponseReceived;
AZStd::unique_ptr<AZ::Android::JNI::Object> m_lumberyardActivity;
@@ -125,6 +127,10 @@ namespace AzFramework
AndroidLifecycleEvents::Bus::Handler::BusConnect();
AndroidAppRequests::Bus::Handler::BusConnect();
PermissionRequestResultNotification::Bus::Handler::BusConnect();
#if !defined(AZ_RELEASE_BUILD)
m_thermalInfoHandler = AZStd::make_unique<ThermalInfoAndroidHandler>();
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,124 @@
/*
* 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.
*
*/
#if !defined(AZ_RELEASE_BUILD)
#include "ThermalInfo_Android.h"
#include <AzCore/std/string/string.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <cstdio>
#include <sys/types.h>
#include <dirent.h>
ThermalInfoAndroidHandler::ThermalInfoAndroidHandler()
{
static_assert(AZ_ARRAY_SIZE(m_temperatureFiles) == static_cast<int>(ThermalSensorType::Count), "Thermal count does not match temperature array size");
ThermalInfoRequestsBus::Handler::BusConnect();
memset(m_temperatureFiles, 0, sizeof(m_temperatureFiles));
const int sensorCount = static_cast<int>(ThermalSensorType::Count);
const char* sensorTypes[sensorCount] = { "cpu", "gpu", "battery" };
const int maxStringLen = 128;
char tempString[maxStringLen];
const char* thermalPath = "/sys/class/thermal";
// List the elements from the thermal folder to get the thermal_zones available on the device
DIR* directory = opendir(thermalPath);
if (directory)
{
struct dirent* item;
const char* thermalPrefix = "thermal_zone";
// List all items of the directory and find the one that start with thermal_zone (thermal_zone0, thermal_zone1, etc)
while ((item = readdir(directory)) != nullptr)
{
if (strncmp(item->d_name, thermalPrefix, strlen(thermalPrefix)) == 0)
{
// Try to deduce the type of sensor. For this we read the "type" file of the thermal zone.
// This "type" is a string set by the manufacturer, so it can be anything.
AZStd::string path = AZStd::string::format("%s/%s/type", thermalPath, item->d_name);
FILE* sensorTypeFile = fopen(path.c_str(), "r");
if (sensorTypeFile)
{
if (fscanf(sensorTypeFile, "%s", tempString))
{
for (int i = 0; i < sensorCount; ++i)
{
if (m_temperatureFiles[i])
{
continue;
}
size_t foundPos = AzFramework::StringFunc::Find(tempString, sensorTypes[i]);
if (foundPos != AZStd::string::npos)
{
path = AZStd::string::format("%s/%s/temp", thermalPath, item->d_name);
m_temperatureFiles[i] = fopen(path.c_str(), "r");
break;
}
}
}
fclose(sensorTypeFile);
}
}
}
closedir(directory);
}
int cpuSensorIndex = static_cast<int>(ThermalSensorType::CPU);
if (!m_temperatureFiles[cpuSensorIndex])
{
// If we didn't find the CPU sensor just assume it's the first one.
AZStd::string path = AZStd::string::format("%s/thermal_zone0/temp", thermalPath);
m_temperatureFiles[cpuSensorIndex] = fopen(path.c_str(), "r");
}
}
ThermalInfoAndroidHandler::~ThermalInfoAndroidHandler()
{
ThermalInfoRequestsBus::Handler::BusDisconnect();
for (int i = 0; i < static_cast<int>(ThermalSensorType::Count); ++i)
{
if (m_temperatureFiles[i])
{
fclose(m_temperatureFiles[i]);
}
}
}
float ThermalInfoAndroidHandler::GetSensorTemp(ThermalSensorType sensor)
{
FILE* tempFile = m_temperatureFiles[static_cast<int>(sensor)];
if (!tempFile)
{
return 0.f;
}
fseek(tempFile, 0, SEEK_SET);
float temperature = 0.f;
fscanf(tempFile, "%f", &temperature);
temperature /= 1000.0f;
return temperature;
}
float ThermalInfoAndroidHandler::GetSensorOverheatingTemp(ThermalSensorType sensor)
{
const int overheatingTemperatures[static_cast<int>(ThermalSensorType::Count)] =
{
70, // CPU
70, // GPU
40 // Battery
};
return overheatingTemperatures[static_cast<int>(sensor)];
}
#endif // !defined(AZ_RELEASE_BUILD)
@@ -0,0 +1,30 @@
/*
* 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.
*
*/
#pragma once
#if !defined(AZ_RELEASE_BUILD)
#include <AzFramework/Thermal/ThermalInfo.h>
class ThermalInfoAndroidHandler : public ThermalInfoRequestsBus::Handler
{
public:
ThermalInfoAndroidHandler();
~ThermalInfoAndroidHandler() override;
float GetSensorTemp(ThermalSensorType sensor) override;
float GetSensorOverheatingTemp(ThermalSensorType sensor) override;
private:
FILE* m_temperatureFiles[static_cast<int>(ThermalSensorType::Count)];
};
#endif // !defined(AZ_RELEASE_BUILD)
@@ -36,4 +36,6 @@ set(FILES
AzFramework/Process/ProcessCommon.h
AzFramework/Process/ProcessWatcher_Android.cpp
AzFramework/Process/ProcessCommunicator_Android.cpp
AzFramework/Thermal/ThermalInfo_Android.cpp
AzFramework/Thermal/ThermalInfo_Android.h
)
@@ -57,13 +57,16 @@ namespace AzGameFramework
AZStd::vector<char> scratchBuffer;
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, true);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
#endif
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
// Used the lowercase the platform name since the bootstrap.game.<config>.<platform>.setreg is being loaded
// from the asset cache root where all the files are in lowercased from regardless of the filesystem case-sensitivity
static constexpr char filename[] = "bootstrap.game." AZ_BUILD_CONFIGURATION_TYPE "." AZ_TRAIT_OS_PLATFORM_CODENAME_LOWER ".setreg";
@@ -77,6 +80,7 @@ namespace AzGameFramework
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, true);
#endif
@@ -22,7 +22,9 @@ namespace AzNetworking
AZ::HashValue32 HashSerializer::GetHash() const
{
// Just truncate the upper bits
return static_cast<AZ::HashValue32>(m_hash);
const AZ::HashValue32 lower = static_cast<AZ::HashValue32>(m_hash);
const AZ::HashValue32 upper = static_cast<AZ::HashValue32>(m_hash >> 32);
return lower ^ upper;
}
SerializerMode HashSerializer::GetSerializerMode() const
@@ -56,6 +56,6 @@ namespace AzNetworking
private:
AZ::HashValue64 m_hash;
AZ::HashValue64 m_hash = AZ::HashValue64{ 0 };
};
}
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="20px" viewBox="0 0 22 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icon / Toolbar / Play Console / Simulate Physics</title>
<defs>
<filter id="filter-1">
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 1.000000 0"></feColorMatrix>
</filter>
<path d="M15.6428742,11.9827626 C14.6770188,10.7687802 14.4956657,9.03975584 15.318317,7.61488202 C16.3923878,5.75453685 18.7712019,5.11713552 20.6315471,6.1912063 C22.4918923,7.26527709 23.1292936,9.64409122 22.0552228,11.5044364 C21.013572,13.3086286 18.7447543,13.9625898 16.9118187,13.0207075 C16.3649862,12.6909949 16.1306352,12.5503038 15.6428742,11.9827626 Z M21.8449657,10.7460039 C22.0595758,10.1342007 22.2373043,8.55906064 21.8449657,9.05833735 C21.4526271,9.55761407 21.2918858,9.96701497 20.8130083,10.4818331 C19.9042528,11.4587922 18.2692551,11.5405181 17.2685207,11.5405181 C16.2677863,11.5405181 17.2685207,12.7159692 18.7447536,12.7159692 C20.2209864,12.7159692 21.4894947,11.7593684 21.8449657,10.7460039 Z M16.197388,14.0090117 C16.3975463,13.8229455 16.701528,13.7946404 16.9039947,13.9824218 C17.1064613,14.1702033 17.0967404,14.4628636 16.9305845,14.6890285 C16.3625338,15.4622373 15.7176919,17.2794303 15.043117,20.0013354 L13.9731652,20.0013354 C13.74245,13.5862059 13.0475403,9.88991503 12.1237705,9.88991503 C11.4442663,9.88991503 10.8362307,11.6219 10.2533824,15.1128617 C10.1857467,15.5179652 9.53567637,19.2951617 9.47653253,20.0013354 L8.32801304,20.0013354 C8.32801304,11.4928776 6.37313749,5.95119082 2.36188186,4.12720757 C2.11050727,4.01290346 1.65264126,3.52516756 2.0367453,2.87964604 C2.42084934,2.23412452 3.17248781,2.63112648 3.42386239,2.7454306 C7.09988702,4.41697884 8.58435154,8.916133 9.2170288,15.2523916 C9.23745765,15.1271094 9.25478369,15.0215631 9.26703535,14.9481819 C9.97359325,10.7162629 10.1786779,8.42129315 12.1237705,8.42129315 C14.0688632,8.42129315 14.4491297,12.5275507 14.8251327,17.6881742 C15.0978485,16.4034552 15.5446067,14.6158341 16.197388,14.0090117 Z M2.0241711,20.6412764 L22.0595758,20.6412764 L20.6246231,21.7592943 L3.70859025,21.7592943 L2.0241711,20.6412764 Z" id="path-2"></path>
</defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Play-Console-v2" transform="translate(-169.000000, -8.000000)">
<g id="Play-Console" transform="translate(100.000000, 0.000000)">
<g id="Icon-/-Toolbar-/-Play-Console-/-Simulate-Physics" transform="translate(68.000000, 6.000000)" filter="url(#filter-1)">
<g>
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<use id="Shape" fill="#FFFFFF" fill-rule="nonzero" xlink:href="#path-2"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -372,6 +372,7 @@
<file>img/UI20/toolbar/Select.svg</file>
<file>img/UI20/toolbar/select_object.svg</file>
<file>img/UI20/toolbar/Select_terrain.svg</file>
<file>img/UI20/toolbar/Simulate_Physics.svg</file>
<file>img/UI20/toolbar/Simulate_Physics_on_selected_objects.svg</file>
<file>img/UI20/toolbar/Terrain.svg</file>
<file>img/UI20/toolbar/Terrain_Texture.svg</file>
+2 -3
View File
@@ -17,7 +17,7 @@
#include <array>
AZ_PUSH_DISABLE_WARNING(4389 4800, "-Wunknown-warning-option"); // 'int' : forcing value to bool 'true' or 'false' (performance warning).
#undef strdup // platform.h in CryCommon changes this define which is required by googletest
#undef strdup // This define is required by googletest
#include <gtest/gtest.h>
#include <gmock/gmock.h>
AZ_POP_DISABLE_WARNING;
@@ -477,8 +477,7 @@ int main(int argc, char** argv)
} \
} while (0); // safe multi-line macro - creates a single statement
// Avoid accidentally being managed by CryMemory, or problems with new/delete when
// AZ allocators are not ready or properly un/initialized.
// Avoid problems with new/delete when AZ allocators are not ready or properly un/initialized.
#define AZ_TEST_CLASS_ALLOCATOR(Class_) \
void* operator new (size_t size) \
{ \
@@ -199,6 +199,43 @@ namespace AzToolsFramework
return true;
}
void GetTemplateSourcePaths(const PrefabDomValue& prefabDom, AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths)
{
PrefabDomValueConstReference findSourceResult = PrefabDomUtils::FindPrefabDomValue(prefabDom, PrefabDomUtils::SourceName);
if (!findSourceResult.has_value() || !(findSourceResult->get().IsString()) ||
findSourceResult->get().GetStringLength() == 0)
{
AZ_Assert(
false,
"PrefabDomUtils::GetDependentTemplatePath - Source value of prefab in the provided DOM is not a valid string.");
return;
}
templateSourcePaths.emplace(findSourceResult->get().GetString());
PrefabDomValueConstReference instancesReference = GetInstancesValue(prefabDom);
if (instancesReference.has_value())
{
const PrefabDomValue& instances = instancesReference->get();
for (PrefabDomValue::ConstMemberIterator instanceIterator = instances.MemberBegin();
instanceIterator != instances.MemberEnd(); ++instanceIterator)
{
GetTemplateSourcePaths(instanceIterator->value, templateSourcePaths);
}
}
}
PrefabDomValueConstReference GetInstancesValue(const PrefabDomValue& prefabDom)
{
PrefabDomValueConstReference findInstancesResult = FindPrefabDomValue(prefabDom, PrefabDomUtils::InstancesName);
if (!findInstancesResult.has_value() || !(findInstancesResult->get().IsObject()))
{
return AZStd::nullopt;
}
return findInstancesResult->get();
}
void PrintPrefabDomValue(
[[maybe_unused]] const AZStd::string_view printMessage,
[[maybe_unused]] const PrefabDomValue& prefabDomValue)
@@ -100,6 +100,20 @@ namespace AzToolsFramework
.Append(instanceName);
};
/**
* Gets a set of all the template source paths in the given dom.
* @param prefabDom The DOM to get the template source paths from.
* @param[out] templateSourcePaths The set of template source paths to populate.
*/
void GetTemplateSourcePaths(const PrefabDomValue& prefabDom, AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths);
/**
* Gets the instances DOM value from the given prefab DOM.
*
* @return the instances DOM value or AZStd::nullopt if it instances can't be found.
*/
PrefabDomValueConstReference GetInstancesValue(const PrefabDomValue& prefabDom);
/**
* Prints the contents of the given prefab DOM value to the debug output console in a readable format.
* @param printMessage The message that will be printed before printing the PrefabDomValue
@@ -210,25 +210,30 @@ namespace AzToolsFramework
auto relativePath = m_prefabLoaderInterface->GetRelativePathToProject(filePath);
Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
// If the template isn't currently loaded, there's no way for it to be in the hierarchy so we just skip the check.
if (templateId != Prefab::InvalidTemplateId && IsPrefabInInstanceAncestorHierarchy(templateId, instanceToParentUnder->get()))
if (templateId == InvalidTemplateId)
{
return AZ::Failure(
AZStd::string::format(
"Instantiate Prefab operation aborted - Cyclical dependency detected\n(%s depends on %s).",
relativePath.Native().c_str(),
instanceToParentUnder->get().GetTemplateSourcePath().Native().c_str()
)
);
// Load the template from the file
templateId = m_prefabLoaderInterface->LoadTemplateFromFile(filePath);
AZ_Assert(templateId != InvalidTemplateId, "Template with source path %s couldn't be loaded correctly.", filePath);
}
const PrefabDom& templateDom = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
AZStd::unordered_set<AZ::IO::Path> templatePaths;
PrefabDomUtils::GetTemplateSourcePaths(templateDom, templatePaths);
if (IsCyclicalDependencyFound(instanceToParentUnder->get(), templatePaths))
{
return AZ::Failure(AZStd::string::format(
"Instantiate Prefab operation aborted - Cyclical dependency detected\n(%s depends on %s).",
relativePath.Native().c_str(), instanceToParentUnder->get().GetTemplateSourcePath().Native().c_str()));
}
{
// Initialize Undo Batch object
ScopedUndoBatch undoBatch("Instantiate Prefab");
PrefabDom instanceToParentUnderDomBeforeCreate;
m_instanceToTemplateInterface->GenerateDomForInstance(
instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get());
m_instanceToTemplateInterface->GenerateDomForInstance(instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get());
// Instantiate the Prefab
auto instanceToCreate = prefabEditorEntityOwnershipInterface->InstantiatePrefab(relativePath, instanceToParentUnder);
@@ -242,8 +247,7 @@ namespace AzToolsFramework
PrefabUndoHelpers::UpdatePrefabInstance(
instanceToParentUnder->get(), "Update prefab instance", instanceToParentUnderDomBeforeCreate, undoBatch.GetUndoBatch());
CreateLink({}, instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(),
undoBatch.GetUndoBatch(), parent);
CreateLink({}, instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), parent);
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
// Apply position
@@ -296,17 +300,17 @@ namespace AzToolsFramework
return AZ::Success();
}
bool PrefabPublicHandler::IsPrefabInInstanceAncestorHierarchy(TemplateId prefabTemplateId, InstanceOptionalConstReference instance)
bool PrefabPublicHandler::IsCyclicalDependencyFound(
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths)
{
InstanceOptionalConstReference currentInstance = instance;
while (currentInstance.has_value())
{
if (currentInstance->get().GetTemplateId() == prefabTemplateId)
if (templateSourcePaths.contains(currentInstance->get().GetTemplateSourcePath()))
{
return true;
}
currentInstance = currentInstance->get().GetParentInstance();
}
@@ -636,11 +640,18 @@ namespace AzToolsFramework
if (!EntitiesBelongToSameInstance(entityIds))
{
return AZ::Failure(AZStd::string("DeleteEntitiesAndAllDescendantsInInstance - Deletion Error. Cannot delete multiple "
"entities belonging to different instances with one operation."));
return AZ::Failure(AZStd::string("Cannot delete multiple entities belonging to different instances with one operation."));
}
InstanceOptionalReference instance = GetOwnerInstanceByEntityId(entityIds[0]);
AZ::EntityId firstEntityIdToDelete = entityIds[0];
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDelete);
// If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you
// cannot delete an instance from itself.
if (commonOwningInstance->get().GetContainerEntityId() == firstEntityIdToDelete)
{
commonOwningInstance = commonOwningInstance->get().GetParentInstance();
}
// Retrieve entityList from entityIds
EntityList inputEntityList = EntityIdListToEntityList(entityIds);
@@ -680,14 +691,14 @@ namespace AzToolsFramework
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities");
Prefab::PrefabDom instanceDomBefore;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, instance->get());
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
if (deleteDescendants)
{
AZStd::vector<AZ::Entity*> entities;
AZStd::vector<AZStd::unique_ptr<Instance>> instances;
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, instance->get(), entities, instances);
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
if (!success)
{
@@ -701,6 +712,7 @@ namespace AzToolsFramework
for (auto& nestedInstance : instances)
{
RemoveLink(nestedInstance, commonOwningInstance->get().GetTemplateId(), currentUndoBatch);
nestedInstance.reset();
}
}
@@ -712,22 +724,22 @@ namespace AzToolsFramework
// If this is the container entity, it actually represents the instance so get its owner
if (owningInstance->get().GetContainerEntityId() == entityId)
{
auto instancePtr = instance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
instancePtr.reset();
auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), currentUndoBatch);
}
else
{
instance->get().DetachEntity(entityId);
commonOwningInstance->get().DetachEntity(entityId);
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entityId);
}
}
}
Prefab::PrefabDom instanceDomAfter;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, instance->get());
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, commonOwningInstance->get());
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance deletion");
command->Capture(instanceDomBefore, instanceDomAfter, instance->get().GetTemplateId());
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
command->SetParent(selCommand);
}
@@ -995,5 +1007,5 @@ namespace AzToolsFramework
return true;
}
}
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -12,8 +12,8 @@
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
@@ -107,13 +107,15 @@ namespace AzToolsFramework
const AZStd::vector<AZ::EntityId>& entityIds, EntityList& inputEntityList, EntityList& topLevelEntities,
AZ::EntityId& commonRootEntityId, InstanceOptionalReference& commonRootEntityOwningInstance);
/* Detects whether an instance of prefabTemplateId is present in the hierarchy of ancestors of instance.
/* Checks whether the template source path of any of the ancestors in the instance hierarchy matches with one of the
* paths provided in a set.
*
* \param prefabTemplateId The template id to test for
* \param instance The instance whose ancestor hierarchy prefabTemplateId will be tested against.
* \return true if an instance of the template of id prefabTemplateId could be found in the ancestor hierarchy of instance, false otherwise.
* \param instance The instance whose ancestor hierarchy the provided set of template source paths will be tested against.
* \param templateSourcePaths The template source paths provided to be checked against the instance ancestor hierarchy.
* \return true if any of the template source paths could be found in the ancestor hierarchy of instance, false otherwise.
*/
bool IsPrefabInInstanceAncestorHierarchy(TemplateId prefabTemplateId, InstanceOptionalConstReference instance);
bool IsCyclicalDependencyFound(
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths);
static Instance* GetParentInstance(Instance* instance);
static Instance* GetAncestorOfInstanceThatIsChildOfRoot(const Instance* ancestor, Instance* descendant);
@@ -129,5 +131,5 @@ namespace AzToolsFramework
uint64_t m_newEntityCounter = 1;
};
}
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -254,7 +254,7 @@ namespace AzToolsFramework
m_localTransformDirty = true;
m_worldTransformDirty = true;
if (GetEntity())
if (const AZ::Entity* entity = GetEntity())
{
SetDirty();
@@ -273,6 +273,22 @@ namespace AzToolsFramework
{
boundsUnion->OnTransformUpdated(GetEntity());
}
// Fire a property changed notification for this component
if (const AZ::Component* component = entity->FindComponent<Components::TransformComponent>())
{
PropertyEditorEntityChangeNotificationBus::Event(
GetEntityId(), &PropertyEditorEntityChangeNotifications::OnEntityComponentPropertyChanged, component->GetId());
}
// Refresh the property editor if we're selected
bool selected = false;
ToolsApplicationRequestBus::BroadcastResult(
selected, &AzToolsFramework::ToolsApplicationRequests::IsSelected, GetEntityId());
if (selected)
{
ToolsApplicationEvents::Bus::Broadcast(
&ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_Values);
}
}
}
@@ -403,9 +403,12 @@ namespace AzToolsFramework
AzToolsFramework::EntityIdList selectedEntityIds;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
selectedEntityIds, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
&AzToolsFramework::ToolsApplicationRequests::DeleteEntitiesAndAllDescendants, selectedEntityIds);
PrefabOperationResult deleteSelectedResult =
s_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(selectedEntityIds);
if (!deleteSelectedResult.IsSuccess())
{
WarnUserOfError("Delete selected entities error", deleteSelectedResult.GetError());
}
}
void PrefabIntegrationManager::GenerateSuggestedFilenameFromEntities(const EntityIdList& entityIds, AZStd::string& outName)