Merge branch 'development' of https://github.com/o3de/o3de into sc-editor-asset-redux
This commit is contained in:
@@ -192,11 +192,86 @@ namespace AZ
|
||||
};
|
||||
|
||||
|
||||
//! SettingsRegistry notifier handler which is responsible for loading
|
||||
//! the project.json file at the new project path
|
||||
//! if an update to '<BootstrapSettingsRootKey>/project_path' key occurs.
|
||||
struct ProjectPathChangedEventHandler
|
||||
{
|
||||
ProjectPathChangedEventHandler(AZ::SettingsRegistryInterface& registry)
|
||||
: m_registry{ registry }
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
// Update the project settings when the project path is set
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
|
||||
AZ::IO::FixedMaxPath newProjectPath;
|
||||
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectPathKey, path)
|
||||
&& m_registry.Get(newProjectPath.Native(), projectPathKey) && newProjectPath != m_oldProjectPath)
|
||||
{
|
||||
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
|
||||
m_oldProjectPath = newProjectPath;
|
||||
|
||||
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
|
||||
AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath };
|
||||
projectMetadataFile /= "project.json";
|
||||
m_registry.MergeSettingsFile(projectMetadataFile.Native(),
|
||||
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
|
||||
|
||||
// Update all the runtime file paths based on the new "project_path" value.
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
AZ::IO::FixedMaxPath m_oldProjectPath;
|
||||
AZ::SettingsRegistryInterface& m_registry;
|
||||
};
|
||||
|
||||
//! SettingsRegistry notifier handler which adds the project name as a specialization tag
|
||||
//! to the registry
|
||||
//! if an update to '<ProjectSettingsRootKey>/project_name' key occurs.
|
||||
struct ProjectNameChangedEventHandler
|
||||
{
|
||||
ProjectNameChangedEventHandler(AZ::SettingsRegistryInterface& registry)
|
||||
: m_registry{ registry }
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
// Update the project specialization when the project name is set
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name";
|
||||
|
||||
FixedValueString newProjectName;
|
||||
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectNameKey, path)
|
||||
&& m_registry.Get(newProjectName, projectNameKey) && newProjectName != m_oldProjectName)
|
||||
{
|
||||
// Add the project_name as a specialization for loading the build system dependency .setreg files
|
||||
auto newProjectNameSpecialization = FixedValueString::format("%s/%.*s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
|
||||
aznumeric_cast<int>(newProjectName.size()), newProjectName.data());
|
||||
auto oldProjectNameSpecialization = FixedValueString::format("%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
|
||||
m_oldProjectName.c_str());
|
||||
m_registry.Remove(oldProjectNameSpecialization);
|
||||
m_oldProjectName = newProjectName;
|
||||
m_registry.Set(newProjectNameSpecialization, true);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
AZ::SettingsRegistryInterface::FixedValueString m_oldProjectName;
|
||||
AZ::SettingsRegistryInterface& m_registry;
|
||||
};
|
||||
|
||||
//! SettingsRegistry notifier handler which updates relevant registry settings based
|
||||
//! on an update to '/Amazon/AzCore/Bootstrap/project_path' key.
|
||||
struct UpdateProjectSettingsEventHandler
|
||||
struct UpdateCommandLineEventHandler
|
||||
{
|
||||
UpdateProjectSettingsEventHandler(AZ::SettingsRegistryInterface& registry, AZ::CommandLine& commandLine)
|
||||
UpdateCommandLineEventHandler(AZ::SettingsRegistryInterface& registry, AZ::CommandLine& commandLine)
|
||||
: m_registry{ registry }
|
||||
, m_commandLine{ commandLine }
|
||||
{
|
||||
@@ -204,70 +279,14 @@ namespace AZ
|
||||
|
||||
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
// #1 Update the project settings when the project path is set
|
||||
const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
AZ::IO::FixedMaxPath newProjectPath;
|
||||
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectPathKey, path)
|
||||
&& m_registry.Get(newProjectPath.Native(), projectPathKey) && newProjectPath != m_oldProjectPath)
|
||||
{
|
||||
UpdateProjectSettingsFromProjectPath(AZ::IO::PathView(newProjectPath));
|
||||
}
|
||||
|
||||
// #2 Update the project specialization when the project name is set
|
||||
const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name";
|
||||
FixedValueString newProjectName;
|
||||
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectNameKey, path)
|
||||
&& m_registry.Get(newProjectName, projectNameKey) && newProjectName != m_oldProjectName)
|
||||
{
|
||||
UpdateProjectSpecializationFromProjectName(newProjectName);
|
||||
}
|
||||
|
||||
// #3 Update the ComponentApplication CommandLine instance when the command line settings are merged into the Settings Registry
|
||||
// Update the ComponentApplication CommandLine instance when the command line settings are merged into the Settings Registry
|
||||
if (path == AZ::SettingsRegistryMergeUtils::CommandLineValueChangedKey)
|
||||
{
|
||||
UpdateCommandLine();
|
||||
AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(m_registry, m_commandLine);
|
||||
}
|
||||
}
|
||||
|
||||
//! Add the project name as a specialization underneath the /Amazon/AzCore/Settings/Specializations path
|
||||
//! and remove the current project name specialization if one exists.
|
||||
void UpdateProjectSpecializationFromProjectName(AZStd::string_view newProjectName)
|
||||
{
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
// Add the project_name as a specialization for loading the build system dependency .setreg files
|
||||
auto newProjectNameSpecialization = FixedValueString::format("%s/%.*s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
|
||||
aznumeric_cast<int>(newProjectName.size()), newProjectName.data());
|
||||
auto oldProjectNameSpecialization = FixedValueString::format("%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
|
||||
m_oldProjectName.c_str());
|
||||
m_registry.Remove(oldProjectNameSpecialization);
|
||||
m_oldProjectName = newProjectName;
|
||||
m_registry.Set(newProjectNameSpecialization, true);
|
||||
}
|
||||
|
||||
void UpdateProjectSettingsFromProjectPath(AZ::IO::PathView newProjectPath)
|
||||
{
|
||||
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
|
||||
m_oldProjectPath = newProjectPath;
|
||||
|
||||
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
|
||||
AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath };
|
||||
projectMetadataFile /= "project.json";
|
||||
m_registry.MergeSettingsFile(projectMetadataFile.Native(),
|
||||
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
|
||||
|
||||
// Update all the runtime file paths based on the new "project_path" value.
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
|
||||
}
|
||||
|
||||
void UpdateCommandLine()
|
||||
{
|
||||
AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(m_registry, m_commandLine);
|
||||
}
|
||||
|
||||
private:
|
||||
AZ::IO::FixedMaxPath m_oldProjectPath;
|
||||
AZ::SettingsRegistryInterface::FixedValueString m_oldProjectName;
|
||||
AZ::SettingsRegistryInterface& m_registry;
|
||||
AZ::CommandLine& m_commandLine;
|
||||
};
|
||||
@@ -462,7 +481,12 @@ namespace AZ
|
||||
// 1. The 'project_path' key changes
|
||||
// 2. The project specialization when the 'project-name' key changes
|
||||
// 3. The ComponentApplication command line when the command line is stored to the registry
|
||||
m_projectChangedHandler = m_settingsRegistry->RegisterNotifier(UpdateProjectSettingsEventHandler{ *m_settingsRegistry, m_commandLine });
|
||||
m_projectPathChangedHandler = m_settingsRegistry->RegisterNotifier(ProjectPathChangedEventHandler{
|
||||
*m_settingsRegistry });
|
||||
m_projectNameChangedHandler = m_settingsRegistry->RegisterNotifier(ProjectNameChangedEventHandler{
|
||||
*m_settingsRegistry });
|
||||
m_commandLineUpdatedHandler = m_settingsRegistry->RegisterNotifier(UpdateCommandLineEventHandler{
|
||||
*m_settingsRegistry, m_commandLine });
|
||||
|
||||
// Merge Command Line arguments
|
||||
constexpr bool executeRegDumpCommands = false;
|
||||
@@ -515,11 +539,12 @@ namespace AZ
|
||||
Destroy();
|
||||
}
|
||||
|
||||
// The m_projectChangedHandler stores an AZStd::function internally
|
||||
// which allocates using the AZ SystemAllocator
|
||||
// m_projectChangedHandler is being default value initialized
|
||||
// to clear out the AZStd::function
|
||||
m_projectChangedHandler = {};
|
||||
// The SettingsRegistry Notify handlers stores an AZStd::function internally
|
||||
// which may allocates using the AZ SystemAllocator(if the functor > 16 bytes)
|
||||
// The handlers are being default value initialized to clear out the AZStd::function
|
||||
m_commandLineUpdatedHandler = {};
|
||||
m_projectNameChangedHandler = {};
|
||||
m_projectPathChangedHandler = {};
|
||||
|
||||
// Delete the AZ::IConsole if it was created by this application instance
|
||||
if (m_ownsConsole)
|
||||
|
||||
@@ -390,7 +390,9 @@ namespace AZ
|
||||
AZ::IO::FixedMaxPath m_engineRoot;
|
||||
AZ::IO::FixedMaxPath m_appRoot;
|
||||
|
||||
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectChangedHandler;
|
||||
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectPathChangedHandler;
|
||||
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectNameChangedHandler;
|
||||
AZ::SettingsRegistryInterface::NotifyEventHandler m_commandLineUpdatedHandler;
|
||||
|
||||
// ConsoleFunctorHandle is responsible for unregistering the Settings Registry Console
|
||||
// from the m_console member when it goes out of scope
|
||||
|
||||
@@ -639,9 +639,10 @@ namespace AZ
|
||||
{
|
||||
// Make sure the there is a JSON object at the ConsoleRuntimeCommandKey or ConsoleAutoexecKey
|
||||
// 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": {} } } })"
|
||||
R"(,"O3DE": { "Autoexec": { "ConsoleCommands": {} } } })",
|
||||
SettingsRegistryInterface::Format::JsonMergePatch);
|
||||
settingsRegistry.MergeSettings(R"({})", SettingsRegistryInterface::Format::JsonMergePatch,
|
||||
IConsole::ConsoleRuntimeCommandKey);
|
||||
settingsRegistry.MergeSettings(R"({})", SettingsRegistryInterface::Format::JsonMergePatch,
|
||||
IConsole::ConsoleAutoexecCommandKey);
|
||||
m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this });
|
||||
|
||||
JsonApplyPatchSettings applyPatchSettings;
|
||||
|
||||
@@ -40,6 +40,12 @@ namespace AZ
|
||||
static unsigned int Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount = 0, void* nativeThread = 0);
|
||||
};
|
||||
|
||||
class StackConverter
|
||||
{
|
||||
public:
|
||||
static unsigned int FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext);
|
||||
};
|
||||
|
||||
class SymbolStorage
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
struct StackFrame;
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
#if defined(AZ_ENABLE_DEBUG_TOOLS)
|
||||
@@ -551,17 +553,19 @@ namespace AZ
|
||||
{
|
||||
StackFrame frames[25];
|
||||
|
||||
// Without StackFrame explicit alignment frames array is aligned to 4 bytes
|
||||
// which causes the stack tracing to fail.
|
||||
//size_t bla = AZStd::alignment_of<StackFrame>::value;
|
||||
//printf("Alignment value %d address 0x%08x : 0x%08x\n",bla,frames);
|
||||
SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)];
|
||||
unsigned int numFrames = 0;
|
||||
|
||||
if (!nativeContext)
|
||||
{
|
||||
suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrinCallstack).
|
||||
suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrintCallstack).
|
||||
numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount);
|
||||
}
|
||||
unsigned int numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount, nativeContext);
|
||||
else
|
||||
{
|
||||
numFrames = StackConverter::FromNative(frames, AZ_ARRAY_SIZE(frames), nativeContext);
|
||||
}
|
||||
|
||||
if (numFrames)
|
||||
{
|
||||
SymbolStorage::DecodeFrames(frames, numFrames, lines);
|
||||
|
||||
@@ -192,26 +192,34 @@ namespace AZ
|
||||
//! @param path An offset at which traversal should start.
|
||||
//! @return Whether or not entries could be visited.
|
||||
virtual bool Visit(const VisitorCallback& callback, AZStd::string_view path) const = 0;
|
||||
|
||||
//! Register a callback that will be called whenever an entry gets a new/updated value.
|
||||
//!
|
||||
//! @callback The function to call when an entry gets a new/updated value.
|
||||
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(const NotifyCallback& callback) = 0;
|
||||
//! Register a callback that will be called whenever an entry gets a new/updated value.
|
||||
//! @callback The function to call when an entry gets a new/updated value.
|
||||
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(NotifyCallback&& callback) = 0;
|
||||
//! @return NotifyEventHandler instance which must persist to receive event signal
|
||||
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(NotifyCallback callback) = 0;
|
||||
//! Register a notify event handler with the NotifyEvent.
|
||||
//! The handler will be called whenever an entry gets a new/updated value.
|
||||
//! @param handler The handler to register with the NotifyEvent.
|
||||
virtual void RegisterNotifier(NotifyEventHandler& handler) = 0;
|
||||
|
||||
//! Register a function that will be called before a file is merged.
|
||||
//! @callback The function to call before a file is merged.
|
||||
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) = 0;
|
||||
//! Register a function that will be called before a file is merged.
|
||||
//! @callback The function to call before a file is merged.
|
||||
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) = 0;
|
||||
//! @param callback The function to call before a file is merged.
|
||||
//! @return PreMergeEventHandler instance which must persist to receive event signal
|
||||
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback callback) = 0;
|
||||
//! Register a pre-merge handler with the PreMergeEvent.
|
||||
//! The handler will be called before a file is merged.
|
||||
//! @param handler The hanlder to register with the PreMergeEvent.
|
||||
virtual void RegisterPreMergeEvent(PreMergeEventHandler& handler) = 0;
|
||||
|
||||
//! Register a function that will be called after a file is merged.
|
||||
//! @callback The function to call after a file is merged.
|
||||
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) = 0;
|
||||
//! Register a function that will be called after a file is merged.
|
||||
//! @callback The function to call after a file is merged.
|
||||
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) = 0;
|
||||
//! @param callback The function to call after a file is merged.
|
||||
//! @return PostMergeEventHandler instance which must persist to receive event signal
|
||||
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback callback) = 0;
|
||||
//! Register a post-merge hahndler with the PostMergeEvent.
|
||||
//! The handler will be called after a file is merged.
|
||||
//! @param handler The handler to register with the PostmergeEVent.
|
||||
virtual void RegisterPostMergeEvent(PostMergeEventHandler& hanlder) = 0;
|
||||
|
||||
//! Gets the boolean value at the provided path.
|
||||
//! @param result The target to write the result to.
|
||||
@@ -326,23 +334,25 @@ namespace AZ
|
||||
//! - all digits and dot -> floating point number
|
||||
//! - Everything else is considered a string.
|
||||
//! @param argument The command line argument.
|
||||
//! @param structure which contains functors which determine what characters are delimiters
|
||||
//! @param anchorKey The key where the merged command line argument will be anchored under
|
||||
//! @param commandLineSettings structure which contains functors which determine what characters are delimiters
|
||||
//! @return True if the command line argument could be parsed, otherwise false.
|
||||
virtual bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey = "",
|
||||
virtual bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view anchorKey = "",
|
||||
const CommandLineArgumentSettings& commandLineSettings = {}) = 0;
|
||||
//! Merges the json data provided into the settings registry.
|
||||
//! @param data The json data stored in a string.
|
||||
//! @param format The format of the provided data.
|
||||
//! @param anchorKey The key where the merged json content will be anchored under.
|
||||
//! @return True if the data was successfully merged, otherwise false.
|
||||
virtual bool MergeSettings(AZStd::string_view data, Format format) = 0;
|
||||
virtual bool MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey = "") = 0;
|
||||
//! Loads a settings file and merges it into the registry.
|
||||
//! @param path The path to the registry file.
|
||||
//! @param format The format of the text data in the file at the provided path.
|
||||
//! @param rootKey The key where the root of the settings file will be stored under.
|
||||
//! @param anchorKey The key where the content of the settings file will be anchored.
|
||||
//! @param scratchBuffer An optional buffer that's used to load the file into. Use this when loading multiple patches to
|
||||
//! reduce the number of intermediate memory allocations.
|
||||
//! @return True if the registry file was successfully merged, otherwise false.
|
||||
virtual bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view rootKey = "",
|
||||
virtual bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view anchorKey = "",
|
||||
AZStd::vector<char>* scratchBuffer = nullptr) = 0;
|
||||
//! Loads all settings files in a folder and merges them into the registry.
|
||||
//! With the specializations "a" and "b" and platform "c" the files would be loaded in the order:
|
||||
@@ -357,11 +367,12 @@ namespace AZ
|
||||
//! @param platform An optional name of a platform. Platform overloads are located at <path>/Platform/<platform>/
|
||||
//! Files in a platform are applied in the same order as for the main folder but always after the same file
|
||||
//! in the main folder.
|
||||
//! @param anchorKey The registry path location where the settings will be anchored
|
||||
//! @param scratchBuffer An optional buffer that's used to load the file into. Use this when loading multiple patches to
|
||||
//! reduce the number of intermediate memory allocations.
|
||||
//! @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;
|
||||
AZStd::string_view platform = {}, AZStd::string_view anchorKey = "", 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.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <AzCore/IO/FileReader.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/JSON/error/en.h>
|
||||
#include <AzCore/NativeUI//NativeUIRequests.h>
|
||||
#include <AzCore/NativeUI/NativeUIRequests.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/StackedString.h>
|
||||
#include <AzCore/Settings/SettingsRegistryImpl.h>
|
||||
@@ -21,6 +21,34 @@
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzCore/std/parallel/scoped_lock.h>
|
||||
|
||||
namespace AZ::SettingsRegistryImplInternal
|
||||
{
|
||||
AZ::SettingsRegistryInterface::Type RapidjsonToSettingsRegistryType(const rapidjson::Value& value)
|
||||
{
|
||||
using Type = AZ::SettingsRegistryInterface::Type;
|
||||
switch (value.GetType())
|
||||
{
|
||||
case rapidjson::Type::kNullType:
|
||||
return Type::Null;
|
||||
case rapidjson::Type::kFalseType:
|
||||
return Type::Boolean;
|
||||
case rapidjson::Type::kTrueType:
|
||||
return Type::Boolean;
|
||||
case rapidjson::Type::kObjectType:
|
||||
return Type::Object;
|
||||
case rapidjson::Type::kArrayType:
|
||||
return Type::Array;
|
||||
case rapidjson::Type::kStringType:
|
||||
return Type::String;
|
||||
case rapidjson::Type::kNumberType:
|
||||
return value.IsDouble() ? Type::FloatingPoint :
|
||||
Type::Integer;
|
||||
}
|
||||
|
||||
return Type::NoType;
|
||||
}
|
||||
}
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
template<typename T>
|
||||
@@ -28,7 +56,7 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
// rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
@@ -70,7 +98,7 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
// rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
@@ -161,7 +189,7 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
// rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
@@ -212,17 +240,7 @@ namespace AZ
|
||||
return Visit(visitor, path);
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterNotifier(const NotifyCallback& callback) -> NotifyEventHandler
|
||||
{
|
||||
NotifyEventHandler notifyHandler{ callback };
|
||||
{
|
||||
AZStd::scoped_lock lock(m_notifierMutex);
|
||||
notifyHandler.Connect(m_notifiers);
|
||||
}
|
||||
return notifyHandler;
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterNotifier(NotifyCallback&& callback) -> NotifyEventHandler
|
||||
auto SettingsRegistryImpl::RegisterNotifier(NotifyCallback callback) -> NotifyEventHandler
|
||||
{
|
||||
NotifyEventHandler notifyHandler{ AZStd::move(callback) };
|
||||
{
|
||||
@@ -232,23 +250,19 @@ namespace AZ
|
||||
return notifyHandler;
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterNotifier(NotifyEventHandler& notifyHandler) -> void
|
||||
{
|
||||
AZStd::scoped_lock lock(m_notifierMutex);
|
||||
notifyHandler.Connect(m_notifiers);
|
||||
}
|
||||
|
||||
void SettingsRegistryImpl::ClearNotifiers()
|
||||
{
|
||||
AZStd::scoped_lock lock(m_notifierMutex);
|
||||
m_notifiers.DisconnectAllHandlers();
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterPreMergeEvent(const PreMergeEventCallback& callback) -> PreMergeEventHandler
|
||||
{
|
||||
PreMergeEventHandler preMergeHandler{ callback };
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
preMergeHandler.Connect(m_preMergeEvent);
|
||||
}
|
||||
return preMergeHandler;
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventCallback&& callback) -> PreMergeEventHandler
|
||||
auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventCallback callback) -> PreMergeEventHandler
|
||||
{
|
||||
PreMergeEventHandler preMergeHandler{ AZStd::move(callback) };
|
||||
{
|
||||
@@ -258,17 +272,13 @@ namespace AZ
|
||||
return preMergeHandler;
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterPostMergeEvent(const PostMergeEventCallback& callback) -> PostMergeEventHandler
|
||||
auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventHandler& preMergeHandler) -> void
|
||||
{
|
||||
PostMergeEventHandler postMergeHandler{ callback };
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
postMergeHandler.Connect(m_postMergeEvent);
|
||||
}
|
||||
return postMergeHandler;
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
preMergeHandler.Connect(m_preMergeEvent);
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventCallback&& callback) -> PostMergeEventHandler
|
||||
auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventCallback callback) -> PostMergeEventHandler
|
||||
{
|
||||
PostMergeEventHandler postMergeHandler{ AZStd::move(callback) };
|
||||
{
|
||||
@@ -278,6 +288,12 @@ namespace AZ
|
||||
return postMergeHandler;
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventHandler& postMergeHandler) -> void
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
postMergeHandler.Connect(m_postMergeEvent);
|
||||
}
|
||||
|
||||
void SettingsRegistryImpl::ClearMergeEvents()
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
@@ -297,7 +313,36 @@ namespace AZ
|
||||
localNotifierEvent = AZStd::move(m_notifiers);
|
||||
}
|
||||
|
||||
localNotifierEvent.Signal(jsonPath, type);
|
||||
// Signal the NotifyEvent for each queued argument
|
||||
decltype(m_signalNotifierQueue) localNotifierQueue;
|
||||
{
|
||||
AZStd::scoped_lock signalLock(m_signalMutex);
|
||||
m_signalNotifierQueue.push_back({ FixedValueString{jsonPath}, type });
|
||||
// If the signal count was 0, then a dispatch is in progress
|
||||
if (m_signalCount++ == 0)
|
||||
{
|
||||
AZStd::swap(localNotifierQueue, m_signalNotifierQueue);
|
||||
}
|
||||
}
|
||||
|
||||
while (!localNotifierQueue.empty())
|
||||
{
|
||||
for (SignalNotifierArgs notifierArgs : localNotifierQueue)
|
||||
{
|
||||
localNotifierEvent.Signal(notifierArgs.m_jsonPath, notifierArgs.m_type);
|
||||
}
|
||||
// Clear the local notifier queue and check if more notifiers have been added
|
||||
localNotifierQueue = {};
|
||||
{
|
||||
AZStd::scoped_lock signalLock(m_signalMutex);
|
||||
AZStd::swap(localNotifierQueue, m_signalNotifierQueue);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
AZStd::scoped_lock signalLock(m_signalMutex);
|
||||
--m_signalCount;
|
||||
}
|
||||
|
||||
{
|
||||
// Swap the local handlers with the current m_notifiers which
|
||||
@@ -314,39 +359,19 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
//rapidjson::Pointer assets that the supplied string
|
||||
//rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
}
|
||||
|
||||
|
||||
rapidjson::Pointer pointer(path.data(), path.length());
|
||||
if (pointer.IsValid())
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
const rapidjson::Value* value = pointer.Get(m_settings);
|
||||
if (value)
|
||||
if (const rapidjson::Value* value = pointer.Get(m_settings); value != nullptr)
|
||||
{
|
||||
switch (value->GetType())
|
||||
{
|
||||
case rapidjson::Type::kNullType:
|
||||
return Type::Null;
|
||||
case rapidjson::Type::kFalseType:
|
||||
return Type::Boolean;
|
||||
case rapidjson::Type::kTrueType:
|
||||
return Type::Boolean;
|
||||
case rapidjson::Type::kObjectType:
|
||||
return Type::Object;
|
||||
case rapidjson::Type::kArrayType:
|
||||
return Type::Array;
|
||||
case rapidjson::Type::kStringType:
|
||||
return Type::String;
|
||||
case rapidjson::Type::kNumberType:
|
||||
return
|
||||
value->IsDouble() ? Type::FloatingPoint :
|
||||
Type::Integer;
|
||||
}
|
||||
return SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(*value);
|
||||
}
|
||||
}
|
||||
return Type::NoType;
|
||||
@@ -392,7 +417,7 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
// rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
@@ -471,13 +496,12 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
//rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
}
|
||||
|
||||
|
||||
rapidjson::Pointer pointer(path.data(), path.length());
|
||||
if (pointer.IsValid())
|
||||
{
|
||||
@@ -486,10 +510,14 @@ namespace AZ
|
||||
value, nullptr, valueTypeID, m_serializationSettings);
|
||||
if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Halted)
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
rapidjson::Value& setting = pointer.Create(m_settings, m_settings.GetAllocator());
|
||||
setting = AZStd::move(store);
|
||||
SignalNotifier(path, Type::Object);
|
||||
auto anchorType = Type::NoType;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
rapidjson::Value& setting = pointer.Create(m_settings, m_settings.GetAllocator());
|
||||
setting = AZStd::move(store);
|
||||
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(setting);
|
||||
}
|
||||
SignalNotifier(path, anchorType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -500,7 +528,7 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
// rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
@@ -605,7 +633,7 @@ namespace AZ
|
||||
return Set(key, value);
|
||||
}
|
||||
|
||||
bool SettingsRegistryImpl::MergeSettings(AZStd::string_view data, Format format)
|
||||
bool SettingsRegistryImpl::MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey)
|
||||
{
|
||||
rapidjson::Document jsonPatch;
|
||||
constexpr int flags = rapidjson::kParseStopWhenDoneFlag | rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag;
|
||||
@@ -631,17 +659,43 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
|
||||
JsonSerializationResult::ResultCode mergeResult =
|
||||
JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach);
|
||||
if (mergeResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
|
||||
rapidjson::Pointer anchorPath;
|
||||
if (!anchorKey.empty())
|
||||
{
|
||||
AZ_Error("Settings Registry", false, "Failed to fully merge data into registry.");
|
||||
return false;
|
||||
anchorPath = rapidjson::Pointer(anchorKey.data(), anchorKey.size());
|
||||
if (!anchorPath.IsValid())
|
||||
{
|
||||
rapidjson::Pointer pointer(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/-");
|
||||
AZ_Error("Settings Registry", false, R"(Anchor path "%.*s" is invalid.)", AZ_STRING_ARG(anchorKey));
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
|
||||
.AddMember(rapidjson::StringRef("Error"), rapidjson::StringRef("Invalid anchor key."), m_settings.GetAllocator())
|
||||
.AddMember(rapidjson::StringRef("Path"),
|
||||
rapidjson::Value(anchorKey.data(), aznumeric_caster(anchorKey.size()), m_settings.GetAllocator()),
|
||||
m_settings.GetAllocator());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SignalNotifier("", Type::Object);
|
||||
auto anchorType = AZ::SettingsRegistryInterface::Type::NoType;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
rapidjson::Value& anchorRoot = anchorPath.IsValid() ? anchorPath.Create(m_settings, m_settings.GetAllocator())
|
||||
: m_settings;
|
||||
|
||||
JsonSerializationResult::ResultCode mergeResult =
|
||||
JsonSerialization::ApplyPatch(anchorRoot, m_settings.GetAllocator(), jsonPatch, mergeApproach);
|
||||
if (mergeResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
|
||||
{
|
||||
AZ_Error("Settings Registry", false, "Failed to fully merge data into registry.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// The settings have been successfully merged, query the type at the anchor key
|
||||
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(anchorRoot);
|
||||
}
|
||||
|
||||
SignalNotifier(anchorKey, anchorType);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1225,10 +1279,12 @@ namespace AZ
|
||||
ScopedMergeEvent scopedMergeEvent(m_preMergeEvent, m_postMergeEvent, path, rootKey);
|
||||
|
||||
JsonSerializationResult::ResultCode mergeResult(JsonSerializationResult::Tasks::Merge);
|
||||
auto anchorType = Type::NoType;
|
||||
if (rootKey.empty())
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
mergeResult = JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
|
||||
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(m_settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1238,6 +1294,7 @@ namespace AZ
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
Value& rootValue = root.Create(m_settings, m_settings.GetAllocator());
|
||||
mergeResult = JsonSerialization::ApplyPatch(rootValue, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
|
||||
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(rootValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1265,7 +1322,7 @@ namespace AZ
|
||||
pointer.Create(m_settings, m_settings.GetAllocator()).SetString(path, m_settings.GetAllocator());
|
||||
}
|
||||
|
||||
SignalNotifier("", Type::Object);
|
||||
SignalNotifier(rootKey, anchorType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -48,14 +48,14 @@ namespace AZ
|
||||
Type GetType(AZStd::string_view path) const override;
|
||||
bool Visit(Visitor& visitor, AZStd::string_view path) const override;
|
||||
bool Visit(const VisitorCallback& callback, AZStd::string_view path) const override;
|
||||
[[nodiscard]] NotifyEventHandler RegisterNotifier(const NotifyCallback& callback) override;
|
||||
[[nodiscard]] NotifyEventHandler RegisterNotifier(NotifyCallback&& callback) override;
|
||||
[[nodiscard]] NotifyEventHandler RegisterNotifier(NotifyCallback callback) override;
|
||||
void RegisterNotifier(NotifyEventHandler& hanlder) override;
|
||||
void ClearNotifiers();
|
||||
|
||||
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) override;
|
||||
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) override;
|
||||
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) override;
|
||||
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) override;
|
||||
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback callback) override;
|
||||
void RegisterPreMergeEvent(PreMergeEventHandler& handler) override;
|
||||
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback callback) override;
|
||||
void RegisterPostMergeEvent(PostMergeEventHandler& handler) override;
|
||||
void ClearMergeEvents();
|
||||
|
||||
bool Get(bool& result, AZStd::string_view path) const override;
|
||||
@@ -76,13 +76,13 @@ namespace AZ
|
||||
|
||||
bool Remove(AZStd::string_view path) override;
|
||||
|
||||
bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey,
|
||||
bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view anchorKey,
|
||||
const CommandLineArgumentSettings& commandLineSettings) override;
|
||||
bool MergeSettings(AZStd::string_view data, Format format) override;
|
||||
bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view rootKey,
|
||||
bool MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey = "") override;
|
||||
bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view anchorKey = "",
|
||||
AZStd::vector<char>* scratchBuffer = nullptr) override;
|
||||
bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations,
|
||||
AZStd::string_view platform, AZStd::string_view rootKey = "", AZStd::vector<char>* scratchBuffer = nullptr) override;
|
||||
AZStd::string_view platform, AZStd::string_view anchorKey = "", AZStd::vector<char>* scratchBuffer = nullptr) override;
|
||||
|
||||
void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) override;
|
||||
void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) override;
|
||||
@@ -121,6 +121,19 @@ namespace AZ
|
||||
PreMergeEvent m_preMergeEvent;
|
||||
PostMergeEvent m_postMergeEvent;
|
||||
|
||||
//! NOTE: During SignalNotifier, the registered notify event handlers are moved to a local NotifyEvent
|
||||
//! Therefore setting a value within the registry during signaling will queue future SignalNotifer calls
|
||||
//! These calls will then be invoked after the current signaling has completex
|
||||
//! This is done to avoid deadlock if another thread attempts to access register a notifier or signal one
|
||||
mutable AZStd::mutex m_signalMutex;
|
||||
struct SignalNotifierArgs
|
||||
{
|
||||
FixedValueString m_jsonPath;
|
||||
Type m_type;
|
||||
};
|
||||
AZStd::deque<SignalNotifierArgs> m_signalNotifierQueue;
|
||||
AZStd::atomic_int m_signalCount{};
|
||||
|
||||
rapidjson::Document m_settings;
|
||||
JsonSerializerSettings m_serializationSettings;
|
||||
JsonDeserializerSettings m_deserializationSettings;
|
||||
|
||||
@@ -23,12 +23,12 @@ namespace AZ
|
||||
MOCK_CONST_METHOD1(GetType, Type(AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Visit, bool(Visitor&, AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Visit, bool(const VisitorCallback&, AZStd::string_view));
|
||||
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(const NotifyCallback&));
|
||||
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(NotifyCallback&&));
|
||||
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(const PreMergeEventCallback&));
|
||||
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(PreMergeEventCallback&&));
|
||||
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(const PostMergeEventCallback&));
|
||||
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(PostMergeEventCallback&&));
|
||||
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(NotifyCallback));
|
||||
MOCK_METHOD1(RegisterNotifier, void(NotifyEventHandler&));
|
||||
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(PreMergeEventCallback));
|
||||
MOCK_METHOD1(RegisterPreMergeEvent, void(PreMergeEventHandler&));
|
||||
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(PostMergeEventCallback));
|
||||
MOCK_METHOD1(RegisterPostMergeEvent, void(PostMergeEventHandler&));
|
||||
|
||||
MOCK_CONST_METHOD2(Get, bool(bool&, AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Get, bool(s64&, AZStd::string_view));
|
||||
@@ -49,7 +49,7 @@ namespace AZ
|
||||
MOCK_METHOD1(Remove, bool(AZStd::string_view));
|
||||
|
||||
MOCK_METHOD3(MergeCommandLineArgument, bool(AZStd::string_view, AZStd::string_view, const CommandLineArgumentSettings&));
|
||||
MOCK_METHOD2(MergeSettings, bool(AZStd::string_view, Format));
|
||||
MOCK_METHOD3(MergeSettings, bool(AZStd::string_view, Format, AZStd::string_view));
|
||||
MOCK_METHOD4(MergeSettingsFile, bool(AZStd::string_view, Format, AZStd::string_view, AZStd::vector<char>*));
|
||||
MOCK_METHOD5(
|
||||
MergeSettingsFolder,
|
||||
|
||||
+5
@@ -17,6 +17,11 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int StackConverter::FromNative(StackFrame*, unsigned int, void*)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SymbolStorage::LoadModuleData(const void*, unsigned int)
|
||||
{}
|
||||
|
||||
|
||||
@@ -78,6 +78,12 @@ StackRecorder::Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned
|
||||
return count;
|
||||
}
|
||||
|
||||
unsigned int StackConverter::FromNative([[maybe_unused]] StackFrame* frames, [[maybe_unused]] unsigned int maxNumOfFrames, [[maybe_unused]] void* nativeContext)
|
||||
{
|
||||
AZ_Assert(false, "StackConverter::FromNative() is not supported for UnixLike platform yet");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
SymbolStorage::DecodeFrames(const StackFrame* frames, unsigned int numFrames, StackLine* textLines)
|
||||
{
|
||||
|
||||
@@ -1048,9 +1048,9 @@ cleanup:
|
||||
unsigned int
|
||||
StackRecorder::Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount, void* nativeThread)
|
||||
{
|
||||
#if defined(AZ_ENABLE_DEBUG_TOOLS)
|
||||
unsigned int numFrames = 0;
|
||||
|
||||
#if defined(AZ_ENABLE_DEBUG_TOOLS)
|
||||
if (nativeThread == NULL)
|
||||
{
|
||||
++suppressCount; // Skip current call
|
||||
@@ -1079,9 +1079,8 @@ cleanup:
|
||||
|
||||
STACKFRAME64 sf;
|
||||
memset(&sf, 0, sizeof(STACKFRAME64));
|
||||
DWORD imageType;
|
||||
DWORD imageType = IMAGE_FILE_MACHINE_AMD64;
|
||||
|
||||
imageType = IMAGE_FILE_MACHINE_AMD64;
|
||||
sf.AddrPC.Offset = context.Rip;
|
||||
sf.AddrPC.Mode = AddrModeFlat;
|
||||
sf.AddrFrame.Offset = context.Rsp;
|
||||
@@ -1090,8 +1089,7 @@ cleanup:
|
||||
sf.AddrStack.Mode = AddrModeFlat;
|
||||
|
||||
EnterCriticalSection(&g_csDbgHelpDll);
|
||||
s32 frame = -(s32)suppressCount;
|
||||
for (; frame < (s32)maxNumOfFrames; ++frame)
|
||||
for (s32 frame = -static_cast<s32>(suppressCount); frame < static_cast<s32>(maxNumOfFrames); ++frame)
|
||||
{
|
||||
if (!g_StackWalk64(imageType, g_currentProcess, hThread, &sf, &context, 0, g_SymFunctionTableAccess64, g_SymGetModuleBase64, 0))
|
||||
{
|
||||
@@ -1111,15 +1109,68 @@ cleanup:
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&g_csDbgHelpDll);
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
#else
|
||||
(void)frames;
|
||||
(void)maxNumOfFrames;
|
||||
(void)suppressCount;
|
||||
(void)nativeThread;
|
||||
return 0;
|
||||
AZ_UNUSED(frames);
|
||||
AZ_UNUSED(maxNumOfFrames);
|
||||
AZ_UNUSED(suppressCount);
|
||||
AZ_UNUSED(nativeThread);
|
||||
#endif // AZ_ENABLE_DEBUG_TOOLS
|
||||
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
unsigned int StackConverter::FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext)
|
||||
{
|
||||
unsigned int numFrames = 0;
|
||||
|
||||
#if defined(AZ_ENABLE_DEBUG_TOOLS)
|
||||
if (!g_dbgHelpLoaded)
|
||||
{
|
||||
LoadDbgHelp();
|
||||
}
|
||||
|
||||
HANDLE hThread;
|
||||
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS);
|
||||
|
||||
PCONTEXT nativeContextType = reinterpret_cast<PCONTEXT>(nativeContext);
|
||||
STACKFRAME64 sf;
|
||||
memset(&sf, 0, sizeof(STACKFRAME64));
|
||||
|
||||
DWORD imageType = IMAGE_FILE_MACHINE_AMD64;
|
||||
|
||||
sf.AddrPC.Offset = nativeContextType->Rip;
|
||||
sf.AddrPC.Mode = AddrModeFlat;
|
||||
sf.AddrFrame.Offset = nativeContextType->Rsp;
|
||||
sf.AddrFrame.Mode = AddrModeFlat;
|
||||
sf.AddrStack.Offset = nativeContextType->Rsp;
|
||||
sf.AddrStack.Mode = AddrModeFlat;
|
||||
|
||||
EnterCriticalSection(&g_csDbgHelpDll);
|
||||
for (unsigned int frame = 0; frame < maxNumOfFrames; ++frame)
|
||||
{
|
||||
if (!g_StackWalk64(imageType, g_currentProcess, hThread, &sf, nativeContext, 0, g_SymFunctionTableAccess64, g_SymGetModuleBase64, 0))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (sf.AddrPC.Offset == sf.AddrReturn.Offset)
|
||||
{
|
||||
// "StackWalk64-Endless-Callstack!"
|
||||
break;
|
||||
}
|
||||
|
||||
frames[numFrames++].m_programCounter = sf.AddrPC.Offset;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&g_csDbgHelpDll);
|
||||
#else
|
||||
AZ_UNUSED(frames);
|
||||
AZ_UNUSED(maxNumOfFrames);
|
||||
AZ_UNUSED(nativeContext);
|
||||
#endif
|
||||
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1413,7 +1413,7 @@ namespace UnitTest
|
||||
>;
|
||||
TYPED_TEST_CASE(HashedSetDifferentAllocatorFixture, SetTemplateConfigs);
|
||||
|
||||
#if GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
TYPED_TEST(HashedSetDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
|
||||
{
|
||||
using ContainerType = typename TypeParam::ContainerType;
|
||||
@@ -1435,7 +1435,7 @@ namespace UnitTest
|
||||
}
|
||||
}, ".*");
|
||||
}
|
||||
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
|
||||
template<typename ContainerType>
|
||||
class HashedMapContainers
|
||||
@@ -1811,7 +1811,7 @@ namespace UnitTest
|
||||
>;
|
||||
TYPED_TEST_CASE(HashedMapDifferentAllocatorFixture, MapTemplateConfigs);
|
||||
|
||||
#if GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
TYPED_TEST(HashedMapDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
|
||||
{
|
||||
using ContainerType = typename TypeParam::ContainerType;
|
||||
@@ -1833,7 +1833,7 @@ namespace UnitTest
|
||||
}
|
||||
} , ".*");
|
||||
}
|
||||
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
|
||||
namespace HashedContainerTransparentTestInternal
|
||||
{
|
||||
|
||||
@@ -1095,7 +1095,7 @@ namespace UnitTest
|
||||
>;
|
||||
TYPED_TEST_CASE(TreeSetDifferentAllocatorFixture, SetTemplateConfigs);
|
||||
|
||||
#if GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
TYPED_TEST(TreeSetDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
|
||||
{
|
||||
using ContainerType = typename TypeParam::ContainerType;
|
||||
@@ -1117,7 +1117,7 @@ namespace UnitTest
|
||||
}
|
||||
}, ".*");
|
||||
}
|
||||
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
|
||||
TYPED_TEST(TreeSetDifferentAllocatorFixture, SwapMovesElementsWhenAllocatorsDiffer)
|
||||
{
|
||||
@@ -1516,7 +1516,7 @@ namespace UnitTest
|
||||
>;
|
||||
TYPED_TEST_CASE(TreeMapDifferentAllocatorFixture, MapTemplateConfigs);
|
||||
|
||||
#if GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
TYPED_TEST(TreeMapDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
|
||||
{
|
||||
using ContainerType = typename TypeParam::ContainerType;
|
||||
@@ -1538,7 +1538,7 @@ namespace UnitTest
|
||||
}
|
||||
}, ".*");
|
||||
}
|
||||
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
|
||||
TYPED_TEST(TreeMapDifferentAllocatorFixture, SwapMovesElementsWhenAllocatorsDiffer)
|
||||
{
|
||||
|
||||
@@ -1595,7 +1595,7 @@ namespace UnitTest
|
||||
}
|
||||
};
|
||||
|
||||
#if GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
TEST_F(ThreadEventsDeathTest, UsingClientBus_AvoidsDeadlock)
|
||||
{
|
||||
EXPECT_EXIT(
|
||||
@@ -1608,5 +1608,5 @@ namespace UnitTest
|
||||
, ::testing::ExitedWithCode(0),".*");
|
||||
|
||||
}
|
||||
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class UnhandledExceptions
|
||||
: public ScopedAllocatorSetupFixture
|
||||
{
|
||||
|
||||
public:
|
||||
void causeAccessViolation()
|
||||
{
|
||||
int* someVariable = reinterpret_cast<int*>(0);
|
||||
*someVariable = 0;
|
||||
}
|
||||
};
|
||||
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
TEST_F(UnhandledExceptions, Handle)
|
||||
{
|
||||
EXPECT_DEATH(causeAccessViolation(), "");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -144,14 +144,13 @@ namespace UnitTest
|
||||
}
|
||||
};
|
||||
|
||||
#if GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
// SPEC-2669: Disabled since it is causing hangs on Linux
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
TEST_F(AllocatorsTestFixtureLeakDetectionDeathTest_SKIPCODECOVERAGE, AllocatorLeak)
|
||||
{
|
||||
// testing that the TraceBusHook will fail on cause the test to die
|
||||
EXPECT_DEATH(TestAllocatorLeak(), "");
|
||||
}
|
||||
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Testing ScopedAllocatorSetupFixture. Testing that detects leaks
|
||||
|
||||
@@ -327,7 +327,7 @@ namespace JsonSerializationTests
|
||||
SerializerWithOneType::Unreflect(m_jsonRegistrationContext.get());
|
||||
}
|
||||
|
||||
#if GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
using JsonSerializationDeathTests = JsonRegistrationContextTests;
|
||||
TEST_F(JsonSerializationDeathTests, DoubleUnregisterSerializer_Asserts)
|
||||
{
|
||||
@@ -338,5 +338,6 @@ namespace JsonSerializationTests
|
||||
}, ".*"
|
||||
);
|
||||
}
|
||||
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
|
||||
} //namespace JsonSerializationTests
|
||||
|
||||
@@ -1299,6 +1299,35 @@ namespace SettingsRegistryTests
|
||||
EXPECT_FALSE(m_registry->MergeCommandLineArgument(" ", {}, {}));
|
||||
}
|
||||
|
||||
//
|
||||
// MergeSettings
|
||||
//
|
||||
TEST_F(SettingsRegistryTest, MergeSettings_MergeJsonWithAnchorKey_StoresSettingsUnderneathKey)
|
||||
{
|
||||
constexpr AZStd::string_view anchorKey = "/Anchor/Root/0";
|
||||
constexpr auto mergeFormat = AZ::SettingsRegistryInterface::Format::JsonMergePatch;
|
||||
EXPECT_TRUE(m_registry->MergeSettings(R"({ "Test": "1" })", mergeFormat, anchorKey));
|
||||
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Array, m_registry->GetType("/Anchor/Root"));
|
||||
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Object, m_registry->GetType("/Anchor/Root/0"));
|
||||
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType("/Anchor/Root/0/Test"));
|
||||
}
|
||||
|
||||
TEST_F(SettingsRegistryTest, MergeSettings_NotifierSignals_AtAnchorKeyAndStoresMergeType)
|
||||
{
|
||||
AZStd::string_view anchorKey = "/Anchor/Root";
|
||||
bool callbackInvoked{};
|
||||
auto callback = [anchorKey, &callbackInvoked](AZStd::string_view path, AZ::SettingsRegistryInterface::Type type)
|
||||
{
|
||||
EXPECT_EQ(anchorKey, path);
|
||||
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Array, type);
|
||||
callbackInvoked = true;
|
||||
};
|
||||
auto testNotifier1 = m_registry->RegisterNotifier(callback);
|
||||
constexpr auto mergeFormat = AZ::SettingsRegistryInterface::Format::JsonMergePatch;
|
||||
EXPECT_TRUE(m_registry->MergeSettings(R"([ "Test" ])", mergeFormat, anchorKey));
|
||||
EXPECT_TRUE(callbackInvoked);
|
||||
}
|
||||
|
||||
//
|
||||
// MergeSettingsFile
|
||||
//
|
||||
@@ -1331,7 +1360,7 @@ namespace SettingsRegistryTests
|
||||
|
||||
auto callback = [this](AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
EXPECT_TRUE(path.empty());
|
||||
EXPECT_EQ("/Path", path);
|
||||
AZ::s64 value = -1;
|
||||
bool result = m_registry->Get(value, "/Path/Test");
|
||||
EXPECT_TRUE(result);
|
||||
|
||||
@@ -72,6 +72,7 @@ set(FILES
|
||||
Debug/AssetTracking.cpp
|
||||
Debug/LocalFileEventLoggerTests.cpp
|
||||
Debug/Trace.cpp
|
||||
Debug/UnhandledExceptions.cpp
|
||||
Name/NameJsonSerializerTests.cpp
|
||||
Name/NameTests.cpp
|
||||
RTTI/TypeSafeIntegralTests.cpp
|
||||
|
||||
@@ -1924,13 +1924,11 @@ namespace AZ::IO
|
||||
ArchiveLocationPriority Archive::GetPakPriority() const
|
||||
{
|
||||
int pakPriority = aznumeric_cast<int>(ArchiveVars{}.nPriority);
|
||||
#if defined(AZ_ENABLE_TRACING)
|
||||
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console != nullptr)
|
||||
{
|
||||
AZ::GetValueResult getCvarResult = console->GetCvarValue("sys_PakPriority", pakPriority);
|
||||
[[maybe_unused]] AZ::GetValueResult getCvarResult = console->GetCvarValue("sys_PakPriority", pakPriority);
|
||||
AZ_Error("Archive", getCvarResult == AZ::GetValueResult::Success, "Lookup of 'sys_PakPriority console variable failed with error %s", AZ::GetEnumString(getCvarResult));
|
||||
}
|
||||
#endif
|
||||
return static_cast<ArchiveLocationPriority>(pakPriority);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,22 +30,42 @@ namespace AzFramework
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// OnSessionHealthCheck is fired in health check process
|
||||
// @return The result of all OnSessionHealthCheck
|
||||
// Use this notification to perform any custom health check
|
||||
// @return True if OnSessionHealthCheck succeeds, false otherwise
|
||||
virtual bool OnSessionHealthCheck() = 0;
|
||||
|
||||
// OnCreateSessionBegin is fired at the beginning of session creation
|
||||
// OnCreateSessionBegin is fired at the beginning of session creation process
|
||||
// Use this notification to perform any necessary configuration or initialization before
|
||||
// creating session
|
||||
// @param sessionConfig The properties to describe a session
|
||||
// @return The result of all OnCreateSessionBegin notifications
|
||||
// @return True if OnCreateSessionBegin succeeds, false otherwise
|
||||
virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0;
|
||||
|
||||
// OnDestroySessionBegin is fired at the beginning of session termination
|
||||
// @return The result of all OnDestroySessionBegin notifications
|
||||
// OnCreateSessionEnd is fired at the end of session creation process
|
||||
// Use this notification to perform any follow-up operation after session is created and active
|
||||
virtual void OnCreateSessionEnd() = 0;
|
||||
|
||||
// OnDestroySessionBegin is fired at the beginning of session termination process
|
||||
// Use this notification to perform any cleanup operation before destroying session,
|
||||
// like gracefully disconnect players, cleanup data, etc.
|
||||
// @return True if OnDestroySessionBegin succeeds, false otherwise
|
||||
virtual bool OnDestroySessionBegin() = 0;
|
||||
|
||||
// OnUpdateSessionBegin is fired at the beginning of session update
|
||||
// OnDestroySessionEnd is fired at the end of session termination process
|
||||
// Use this notification to perform any follow-up operation after session is destroyed,
|
||||
// like shutdown application process, etc.
|
||||
virtual void OnDestroySessionEnd() = 0;
|
||||
|
||||
// OnUpdateSessionBegin is fired at the beginning of session update process
|
||||
// Use this notification to perform any configuration or initialization to handle
|
||||
// the session settings changing
|
||||
// @param sessionConfig The properties to describe a session
|
||||
// @param updateReason The reason for session update
|
||||
virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0;
|
||||
|
||||
// OnUpdateSessionBegin is fired at the end of session update process
|
||||
// Use this notification to perform any follow-up operations after session is updated
|
||||
virtual void OnUpdateSessionEnd() = 0;
|
||||
};
|
||||
using SessionNotificationBus = AZ::EBus<SessionNotifications>;
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -23,10 +23,12 @@ namespace AzFramework
|
||||
virtual ~XcbEventHandler() = default;
|
||||
|
||||
virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0;
|
||||
|
||||
// ATTN This is used as a workaround for RAW Input events when using the Editor.
|
||||
virtual void PollSpecialEvents(){};
|
||||
};
|
||||
|
||||
class XcbEventHandlerBusTraits
|
||||
: public AZ::EBusTraits
|
||||
class XcbEventHandlerBusTraits : public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/std/typetraits/integral_constant.h>
|
||||
#include <AzFramework/API/ApplicationAPI_Linux.h>
|
||||
#include <AzFramework/XcbConnectionManager.h>
|
||||
#include <AzFramework/XcbInputDeviceMouse.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
xcb_window_t GetSystemCursorFocusWindow()
|
||||
{
|
||||
void* systemCursorFocusWindow = nullptr;
|
||||
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
|
||||
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
|
||||
|
||||
if (!systemCursorFocusWindow)
|
||||
{
|
||||
return XCB_NONE;
|
||||
}
|
||||
|
||||
// TODO Clang compile error because cast .... loses information. On GNU/Linux HWND is void* and on 64-bit
|
||||
// machines its obviously 64 bit but we receive the window id from m_renderOverlay.winId() which is xcb_window_t 32-bit.
|
||||
|
||||
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
|
||||
}
|
||||
|
||||
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
|
||||
xcb_screen_t* XcbInputDeviceMouse::s_xcbScreen = nullptr;
|
||||
bool XcbInputDeviceMouse::m_xfixesInitialized = false;
|
||||
bool XcbInputDeviceMouse::m_xInputInitialized = false;
|
||||
|
||||
XcbInputDeviceMouse::XcbInputDeviceMouse(InputDeviceMouse& inputDevice)
|
||||
: InputDeviceMouse::Implementation(inputDevice)
|
||||
, m_systemCursorState(SystemCursorState::Unknown)
|
||||
, m_systemCursorPositionNormalized(0.5f, 0.5f)
|
||||
, m_prevConstraintWindow(XCB_NONE)
|
||||
, m_focusWindow(XCB_NONE)
|
||||
, m_cursorShown(true)
|
||||
{
|
||||
XcbEventHandlerBus::Handler::BusConnect();
|
||||
|
||||
SetSystemCursorState(SystemCursorState::Unknown);
|
||||
}
|
||||
|
||||
XcbInputDeviceMouse::~XcbInputDeviceMouse()
|
||||
{
|
||||
XcbEventHandlerBus::Handler::BusDisconnect();
|
||||
|
||||
SetSystemCursorState(SystemCursorState::Unknown);
|
||||
}
|
||||
|
||||
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
|
||||
{
|
||||
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
if (!interface)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB interface not available");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
|
||||
if (!s_xcbConnection)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB connection not available");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const xcb_setup_t* xcbSetup = xcb_get_setup(s_xcbConnection);
|
||||
s_xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
|
||||
if (!s_xcbScreen)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB screen not available");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Initialize XFixes extension which we use to create pointer barriers.
|
||||
if (!InitializeXFixes())
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB XFixes initialization failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Initialize XInput extension which is used to get RAW Input events.
|
||||
if (!InitializeXInput())
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB XInput initialization failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return aznew XcbInputDeviceMouse(inputDevice);
|
||||
}
|
||||
|
||||
bool XcbInputDeviceMouse::IsConnected() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::CreateBarriers(xcb_window_t window, bool create)
|
||||
{
|
||||
// Don't create any barriers if we are debugging. This will cause artifacts but better then
|
||||
// a confined cursor during debugging.
|
||||
if (AZ::Debug::Trace::IsDebuggerPresent())
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "Debugger running. Barriers will not be created.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (create)
|
||||
{
|
||||
// Destroy barriers if they are active already.
|
||||
if (!m_activeBarriers.empty())
|
||||
{
|
||||
for (const auto& barrier : m_activeBarriers)
|
||||
{
|
||||
xcb_xfixes_delete_pointer_barrier_checked(s_xcbConnection, barrier.id);
|
||||
}
|
||||
|
||||
m_activeBarriers.clear();
|
||||
}
|
||||
|
||||
// Get window information.
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
|
||||
if (!xcbGeometryReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const xcb_translate_coordinates_cookie_t translate_coord =
|
||||
xcb_translate_coordinates(s_xcbConnection, window, s_xcbScreen->root, 0, 0);
|
||||
|
||||
const XcbStdFreePtr<xcb_translate_coordinates_reply_t> xkbTranslateCoordReply{ xcb_translate_coordinates_reply(
|
||||
s_xcbConnection, translate_coord, NULL) };
|
||||
|
||||
if (!xkbTranslateCoordReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t x0 = xkbTranslateCoordReply->dst_x < 0 ? 0 : xkbTranslateCoordReply->dst_x;
|
||||
const int16_t y0 = xkbTranslateCoordReply->dst_y < 0 ? 0 : xkbTranslateCoordReply->dst_y;
|
||||
const int16_t x1 = xkbTranslateCoordReply->dst_x + xcbGeometryReply->width;
|
||||
const int16_t y1 = xkbTranslateCoordReply->dst_y + xcbGeometryReply->height;
|
||||
|
||||
// ATTN For whatever reason, when making an exact rectangle the pointer will escape the top right corner in some cases. Adding
|
||||
// an offset to the lines so that they cross each other prevents that.
|
||||
const int16_t offset = 30;
|
||||
|
||||
// Create the left barrier info.
|
||||
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_POSITIVE_X, x0, Clamp(y0 - offset),
|
||||
x0, Clamp(y1 + offset) });
|
||||
|
||||
// Create the right barrier info.
|
||||
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_NEGATIVE_X, x1, Clamp(y0 - offset),
|
||||
x1, Clamp(y1 + offset) });
|
||||
|
||||
// Create the top barrier info.
|
||||
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_POSITIVE_Y, Clamp(x0 - offset), y0,
|
||||
Clamp(x1 + offset), y0 });
|
||||
|
||||
// Create the bottom barrier info.
|
||||
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_NEGATIVE_Y, Clamp(x0 - offset), y1,
|
||||
Clamp(x1 + offset), y1 });
|
||||
|
||||
// Create the xfixes barriers.
|
||||
for (const auto& barrier : m_activeBarriers)
|
||||
{
|
||||
xcb_void_cookie_t cookie = xcb_xfixes_create_pointer_barrier_checked(
|
||||
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, NULL);
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
|
||||
|
||||
AZ_Warning(
|
||||
"XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
|
||||
barrier.x1, barrier.y1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& barrier : m_activeBarriers)
|
||||
{
|
||||
xcb_xfixes_delete_pointer_barrier_checked(s_xcbConnection, barrier.id);
|
||||
}
|
||||
|
||||
m_activeBarriers.clear();
|
||||
}
|
||||
|
||||
xcb_flush(s_xcbConnection);
|
||||
}
|
||||
|
||||
bool XcbInputDeviceMouse::InitializeXFixes()
|
||||
{
|
||||
m_xfixesInitialized = false;
|
||||
|
||||
// We don't have to free query_extension_reply according to xcb documentation.
|
||||
const xcb_query_extension_reply_t* query_extension_reply = xcb_get_extension_data(s_xcbConnection, &xcb_xfixes_id);
|
||||
if (!query_extension_reply || !query_extension_reply->present)
|
||||
{
|
||||
return m_xfixesInitialized;
|
||||
}
|
||||
|
||||
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
|
||||
|
||||
xcb_generic_error_t* error = NULL;
|
||||
const XcbStdFreePtr<xcb_xfixes_query_version_reply_t> xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
|
||||
s_xcbConnection, query_cookie, &error) };
|
||||
|
||||
if (!xkbQueryRequestReply || error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "Retrieving XFixes version failed : Error code %d", error->error_code);
|
||||
free(error);
|
||||
}
|
||||
return m_xfixesInitialized;
|
||||
}
|
||||
else if (xkbQueryRequestReply->major_version < 5)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XFixes version fails the minimum version check (%d<5)", xkbQueryRequestReply->major_version);
|
||||
return m_xfixesInitialized;
|
||||
}
|
||||
|
||||
m_xfixesInitialized = true;
|
||||
|
||||
return m_xfixesInitialized;
|
||||
}
|
||||
|
||||
bool XcbInputDeviceMouse::InitializeXInput()
|
||||
{
|
||||
m_xInputInitialized = false;
|
||||
|
||||
// We don't have to free query_extension_reply according to xcb documentation.
|
||||
const xcb_query_extension_reply_t* query_extension_reply = xcb_get_extension_data(s_xcbConnection, &xcb_input_id);
|
||||
if (!query_extension_reply || !query_extension_reply->present)
|
||||
{
|
||||
return m_xInputInitialized;
|
||||
}
|
||||
|
||||
const xcb_input_xi_query_version_cookie_t query_version_cookie = xcb_input_xi_query_version(s_xcbConnection, 2, 2);
|
||||
|
||||
xcb_generic_error_t* error = NULL;
|
||||
const XcbStdFreePtr<xcb_input_xi_query_version_reply_t> xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
|
||||
s_xcbConnection, query_version_cookie, &error) };
|
||||
|
||||
if (!xkbQueryRequestReply || error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "Retrieving XInput version failed : Error code %d", error->error_code);
|
||||
free(error);
|
||||
}
|
||||
return m_xInputInitialized;
|
||||
}
|
||||
else if (xkbQueryRequestReply->major_version < 2)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XInput version fails the minimum version check (%d<5)", xkbQueryRequestReply->major_version);
|
||||
return m_xInputInitialized;
|
||||
}
|
||||
|
||||
m_xInputInitialized = true;
|
||||
|
||||
return m_xInputInitialized;
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetEnableXInput(bool enable)
|
||||
{
|
||||
struct
|
||||
{
|
||||
xcb_input_event_mask_t head;
|
||||
int mask;
|
||||
} mask;
|
||||
|
||||
mask.head.deviceid = XCB_INPUT_DEVICE_ALL;
|
||||
mask.head.mask_len = 1;
|
||||
|
||||
if (enable)
|
||||
{
|
||||
mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS |
|
||||
XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE | XCB_INPUT_XI_EVENT_MASK_MOTION | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
|
||||
XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
|
||||
}
|
||||
else
|
||||
{
|
||||
mask.mask = XCB_NONE;
|
||||
}
|
||||
|
||||
xcb_input_xi_select_events(s_xcbConnection, s_xcbScreen->root, 1, &mask.head);
|
||||
|
||||
xcb_flush(s_xcbConnection);
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetSystemCursorState(SystemCursorState systemCursorState)
|
||||
{
|
||||
if (systemCursorState != m_systemCursorState)
|
||||
{
|
||||
m_systemCursorState = systemCursorState;
|
||||
|
||||
m_focusWindow = GetSystemCursorFocusWindow();
|
||||
|
||||
HandleCursorState(m_focusWindow, systemCursorState);
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState)
|
||||
{
|
||||
bool confined = false, cursorShown = true;
|
||||
switch (systemCursorState)
|
||||
{
|
||||
case SystemCursorState::ConstrainedAndHidden:
|
||||
{
|
||||
//!< Constrained to the application's main window and hidden
|
||||
confined = true;
|
||||
cursorShown = false;
|
||||
}
|
||||
break;
|
||||
case SystemCursorState::ConstrainedAndVisible:
|
||||
{
|
||||
//!< Constrained to the application's main window and visible
|
||||
confined = true;
|
||||
}
|
||||
break;
|
||||
case SystemCursorState::UnconstrainedAndHidden:
|
||||
{
|
||||
//!< Free to move outside the main window but hidden while inside
|
||||
cursorShown = false;
|
||||
}
|
||||
break;
|
||||
case SystemCursorState::UnconstrainedAndVisible:
|
||||
{
|
||||
//!< Free to move outside the application's main window and visible
|
||||
}
|
||||
case SystemCursorState::Unknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// ATTN GetSystemCursorFocusWindow when getting out of the play in editor will return XCB_NONE
|
||||
// We need however the window id to reset the cursor.
|
||||
if (XCB_NONE == window && (confined || cursorShown))
|
||||
{
|
||||
// Reuse the previous window to reset states.
|
||||
window = m_prevConstraintWindow;
|
||||
m_prevConstraintWindow = XCB_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remember the window we used to modify cursor and barrier states.
|
||||
m_prevConstraintWindow = window;
|
||||
}
|
||||
|
||||
SetEnableXInput(!cursorShown);
|
||||
|
||||
CreateBarriers(window, confined);
|
||||
ShowCursor(window, cursorShown);
|
||||
}
|
||||
|
||||
SystemCursorState XcbInputDeviceMouse::GetSystemCursorState() const
|
||||
{
|
||||
return m_systemCursorState;
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized)
|
||||
{
|
||||
// TODO Basically not done at all. Added only the basic functions needed.
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
|
||||
if (!xkbGeometryReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xkbGeometryReply->width);
|
||||
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xkbGeometryReply->height);
|
||||
|
||||
xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y);
|
||||
|
||||
xcb_flush(s_xcbConnection);
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
|
||||
{
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow();
|
||||
if (XCB_NONE == window)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetSystemCursorPositionNormalizedInternal(window, positionNormalized);
|
||||
}
|
||||
|
||||
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalizedInternal(xcb_window_t window) const
|
||||
{
|
||||
AZ::Vector2 position = AZ::Vector2::CreateZero();
|
||||
|
||||
const xcb_query_pointer_cookie_t pointer = xcb_query_pointer(s_xcbConnection, window);
|
||||
|
||||
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, NULL) };
|
||||
|
||||
if (!xkbQueryPointerReply)
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
|
||||
if (!xkbGeometryReply)
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
AZ_Assert(xkbGeometryReply->width != 0, "xkbGeometry response width must be non-zero. (%d)", xkbGeometryReply->width);
|
||||
const float normalizedCursorPostionX = static_cast<float>(xkbQueryPointerReply->win_x) / xkbGeometryReply->width;
|
||||
|
||||
AZ_Assert(xkbGeometryReply->height != 0, "xkbGeometry response height must be non-zero. (%d)", xkbGeometryReply->height);
|
||||
const float normalizedCursorPostionY = static_cast<float>(xkbQueryPointerReply->win_y) / xkbGeometryReply->height;
|
||||
|
||||
position = AZ::Vector2(normalizedCursorPostionX, normalizedCursorPostionY);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
|
||||
{
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow();
|
||||
if (XCB_NONE == window)
|
||||
{
|
||||
return AZ::Vector2::CreateZero();
|
||||
}
|
||||
|
||||
return GetSystemCursorPositionNormalizedInternal(window);
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::TickInputDevice()
|
||||
{
|
||||
ProcessRawEventQueues();
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::ShowCursor(xcb_window_t window, bool show)
|
||||
{
|
||||
xcb_void_cookie_t cookie;
|
||||
if (show)
|
||||
{
|
||||
cookie = xcb_xfixes_show_cursor_checked(s_xcbConnection, window);
|
||||
}
|
||||
else
|
||||
{
|
||||
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
|
||||
}
|
||||
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
|
||||
|
||||
if (xkbError)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ATTN In the following part we will when cursor gets hidden store the position of the cursor in screen space
|
||||
// not window space. We use that to re-position when showing the cursor again. Is this the correct
|
||||
// behavior?
|
||||
|
||||
const bool cursorWasHidden = !m_cursorShown;
|
||||
m_cursorShown = show;
|
||||
if (!m_cursorShown)
|
||||
{
|
||||
m_cursorHiddenPosition = GetSystemCursorPositionNormalizedInternal(s_xcbScreen->root);
|
||||
|
||||
SetSystemCursorPositionNormalized(AZ::Vector2(0.5f, 0.5f));
|
||||
}
|
||||
else if (cursorWasHidden)
|
||||
{
|
||||
SetSystemCursorPositionNormalizedInternal(s_xcbScreen->root, m_cursorHiddenPosition);
|
||||
}
|
||||
|
||||
xcb_flush(s_xcbConnection);
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandleButtonPressEvents(uint32_t detail, bool pressed)
|
||||
{
|
||||
bool isWheel;
|
||||
float wheelDirection;
|
||||
const auto* button = InputChannelFromMouseEvent(detail, isWheel, wheelDirection);
|
||||
if (button)
|
||||
{
|
||||
QueueRawButtonEvent(*button, pressed);
|
||||
}
|
||||
if (isWheel)
|
||||
{
|
||||
float axisValue = MAX_XI_WHEEL_SENSITIVITY * wheelDirection;
|
||||
QueueRawMovementEvent(InputDeviceMouse::Movement::Z, axisValue);
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandlePointerMotionEvents(const xcb_generic_event_t* event)
|
||||
{
|
||||
const xcb_input_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_motion_event_t*>(event);
|
||||
|
||||
m_systemCursorPosition[0] = mouseMotionEvent->event_x;
|
||||
m_systemCursorPosition[1] = mouseMotionEvent->event_y;
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandleRawInputEvents(const xcb_ge_generic_event_t* event)
|
||||
{
|
||||
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
|
||||
switch (genericEvent->event_type)
|
||||
{
|
||||
case XCB_INPUT_RAW_BUTTON_PRESS:
|
||||
{
|
||||
const xcb_input_raw_button_press_event_t* mouseButtonEvent =
|
||||
reinterpret_cast<const xcb_input_raw_button_press_event_t*>(event);
|
||||
HandleButtonPressEvents(mouseButtonEvent->detail, true);
|
||||
}
|
||||
break;
|
||||
case XCB_INPUT_RAW_BUTTON_RELEASE:
|
||||
{
|
||||
const xcb_input_raw_button_release_event_t* mouseButtonEvent =
|
||||
reinterpret_cast<const xcb_input_raw_button_release_event_t*>(event);
|
||||
HandleButtonPressEvents(mouseButtonEvent->detail, false);
|
||||
}
|
||||
break;
|
||||
case XCB_INPUT_RAW_MOTION:
|
||||
{
|
||||
const xcb_input_raw_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_raw_motion_event_t*>(event);
|
||||
|
||||
int axisLen = xcb_input_raw_button_press_axisvalues_length(mouseMotionEvent);
|
||||
const xcb_input_fp3232_t* axisvalues = xcb_input_raw_button_press_axisvalues_raw(mouseMotionEvent);
|
||||
for (int i = 0; i < axisLen; ++i)
|
||||
{
|
||||
const float axisValue = fp3232ToFloat(axisvalues[i]);
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
QueueRawMovementEvent(InputDeviceMouse::Movement::X, axisValue);
|
||||
break;
|
||||
case 1:
|
||||
QueueRawMovementEvent(InputDeviceMouse::Movement::Y, axisValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::PollSpecialEvents()
|
||||
{
|
||||
while (xcb_generic_event_t* genericEvent = xcb_poll_for_queued_event(s_xcbConnection))
|
||||
{
|
||||
// TODO Is the following correct? If we are showing the cursor, don't poll RAW Input events.
|
||||
switch (genericEvent->response_type & ~0x80)
|
||||
{
|
||||
case XCB_GE_GENERIC:
|
||||
{
|
||||
const xcb_ge_generic_event_t* geGenericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(genericEvent);
|
||||
|
||||
// Only handle raw inputs if we have focus.
|
||||
// Handle Raw Input events first.
|
||||
if ((geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
|
||||
(geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) ||
|
||||
(geGenericEvent->event_type == XCB_INPUT_RAW_MOTION))
|
||||
{
|
||||
HandleRawInputEvents(geGenericEvent);
|
||||
|
||||
free(genericEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandleXcbEvent(xcb_generic_event_t* event)
|
||||
{
|
||||
switch (event->response_type & ~0x80)
|
||||
{
|
||||
// QT5 is using by default XInput which means we do need to check for XCB_GE_GENERIC event to parse all mouse related events.
|
||||
case XCB_GE_GENERIC:
|
||||
{
|
||||
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
|
||||
|
||||
// Handling RAW Inputs here works in GameMode but not in Editor mode because QT is
|
||||
// not handling RAW input events and passing to.
|
||||
if (!m_cursorShown)
|
||||
{
|
||||
// Handle Raw Input events first.
|
||||
if ((genericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
|
||||
(genericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || (genericEvent->event_type == XCB_INPUT_RAW_MOTION))
|
||||
{
|
||||
HandleRawInputEvents(genericEvent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (genericEvent->event_type)
|
||||
{
|
||||
case XCB_INPUT_BUTTON_PRESS:
|
||||
{
|
||||
const xcb_input_button_press_event_t* mouseButtonEvent =
|
||||
reinterpret_cast<const xcb_input_button_press_event_t*>(genericEvent);
|
||||
HandleButtonPressEvents(mouseButtonEvent->detail, true);
|
||||
}
|
||||
break;
|
||||
case XCB_INPUT_BUTTON_RELEASE:
|
||||
{
|
||||
const xcb_input_button_release_event_t* mouseButtonEvent =
|
||||
reinterpret_cast<const xcb_input_button_release_event_t*>(genericEvent);
|
||||
HandleButtonPressEvents(mouseButtonEvent->detail, false);
|
||||
}
|
||||
break;
|
||||
case XCB_INPUT_MOTION:
|
||||
{
|
||||
HandlePointerMotionEvents(event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case XCB_FOCUS_IN:
|
||||
{
|
||||
const xcb_focus_in_event_t* focusInEvent = reinterpret_cast<const xcb_focus_in_event_t*>(event);
|
||||
if (m_focusWindow != focusInEvent->event)
|
||||
{
|
||||
m_focusWindow = focusInEvent->event;
|
||||
HandleCursorState(m_focusWindow, m_systemCursorState);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case XCB_FOCUS_OUT:
|
||||
{
|
||||
const xcb_focus_out_event_t* focusOutEvent = reinterpret_cast<const xcb_focus_out_event_t*>(event);
|
||||
HandleCursorState(focusOutEvent->event, SystemCursorState::UnconstrainedAndVisible);
|
||||
|
||||
ProcessRawEventQueues();
|
||||
ResetInputChannelStates();
|
||||
|
||||
m_focusWindow = XCB_NONE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
#include <AzFramework/XcbConnectionManager.h>
|
||||
#include <AzFramework/XcbEventHandler.h>
|
||||
#include <AzFramework/XcbInterface.h>
|
||||
|
||||
#include <xcb/xfixes.h>
|
||||
#include <xcb/xinput.h>
|
||||
|
||||
// The maximum number of raw input axis this mouse device supports.
|
||||
constexpr uint32_t MAX_XI_RAW_AXIS = 2;
|
||||
|
||||
// The sensitivity of the wheel.
|
||||
constexpr float MAX_XI_WHEEL_SENSITIVITY = 140.0f;
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class XcbInputDeviceMouse
|
||||
: public InputDeviceMouse::Implementation
|
||||
, public XcbEventHandlerBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(XcbInputDeviceMouse, AZ::SystemAllocator, 0);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Constructor
|
||||
//! \param[in] inputDevice Reference to the input device being implemented
|
||||
XcbInputDeviceMouse(InputDeviceMouse& inputDevice);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Destructor
|
||||
virtual ~XcbInputDeviceMouse();
|
||||
|
||||
static XcbInputDeviceMouse::Implementation* Create(InputDeviceMouse& inputDevice);
|
||||
|
||||
protected:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::IsConnected
|
||||
bool IsConnected() const override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorState
|
||||
void SetSystemCursorState(SystemCursorState systemCursorState) override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorState
|
||||
SystemCursorState GetSystemCursorState() const override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorPositionNormalized
|
||||
void SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized) override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorPositionNormalized
|
||||
AZ::Vector2 GetSystemCursorPositionNormalized() const override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
|
||||
void TickInputDevice() override;
|
||||
|
||||
//! This method is called by the Editor to accommodate some events with the Editor. Never called in Game mode.
|
||||
void PollSpecialEvents() override;
|
||||
|
||||
//! Handle X11 events.
|
||||
void HandleXcbEvent(xcb_generic_event_t* event) override;
|
||||
|
||||
//! Initialize XFixes extension. Used for barriers.
|
||||
static bool InitializeXFixes();
|
||||
|
||||
//! Initialize XInput extension. Used for raw input during confinement and showing/hiding the cursor.
|
||||
static bool InitializeXInput();
|
||||
|
||||
//! Enables/Disables XInput Raw Input events.
|
||||
void SetEnableXInput(bool enable);
|
||||
|
||||
//! Create barriers.
|
||||
void CreateBarriers(xcb_window_t window, bool create);
|
||||
|
||||
//! Helper function.
|
||||
void SystemCursorStateToLogic(SystemCursorState systemCursorState, bool& confined, bool& cursorShown);
|
||||
|
||||
//! Shows/Hides the cursor.
|
||||
void ShowCursor(xcb_window_t window, bool show);
|
||||
|
||||
//! Get the normalized cursor position. The coordinates returned are relative to the specified window.
|
||||
AZ::Vector2 GetSystemCursorPositionNormalizedInternal(xcb_window_t window) const;
|
||||
|
||||
//! Set the normalized cursor position. The normalized position will be relative to the specified window.
|
||||
void SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized);
|
||||
|
||||
//! Handle button press/release events.
|
||||
void HandleButtonPressEvents(uint32_t detail, bool pressed);
|
||||
|
||||
//! Handle motion notify events.
|
||||
void HandlePointerMotionEvents(const xcb_generic_event_t* event);
|
||||
|
||||
//! Will set cursor states and confinement modes.
|
||||
void HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState);
|
||||
|
||||
//! Will handle all raw input events.
|
||||
void HandleRawInputEvents(const xcb_ge_generic_event_t* event);
|
||||
|
||||
//! Convert XInput fp1616 to float.
|
||||
inline float fp1616ToFloat(xcb_input_fp1616_t value) const
|
||||
{
|
||||
return static_cast<float>((value >> 16) + (value & 0xffff) / 0xffff);
|
||||
}
|
||||
|
||||
//! Convert XInput fp3232 to float.
|
||||
inline float fp3232ToFloat(xcb_input_fp3232_t value) const
|
||||
{
|
||||
return static_cast<float>(value.integral) + static_cast<float>(value.frac / (float)(1ull << 32));
|
||||
}
|
||||
|
||||
const InputChannelId* InputChannelFromMouseEvent(xcb_button_t button, bool& isWheel, float& direction) const
|
||||
{
|
||||
isWheel = false;
|
||||
direction = 1.0f;
|
||||
switch (button)
|
||||
{
|
||||
case XCB_BUTTON_INDEX_1:
|
||||
return &InputDeviceMouse::Button::Left;
|
||||
case XCB_BUTTON_INDEX_2:
|
||||
return &InputDeviceMouse::Button::Right;
|
||||
case XCB_BUTTON_INDEX_3:
|
||||
return &InputDeviceMouse::Button::Middle;
|
||||
case XCB_BUTTON_INDEX_4:
|
||||
isWheel = true;
|
||||
direction = 1.0f;
|
||||
break;
|
||||
case XCB_BUTTON_INDEX_5:
|
||||
isWheel = true;
|
||||
direction = -1.0f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Barriers work only with positive values. We clamp here to zero.
|
||||
inline int16_t Clamp(int16_t value) const
|
||||
{
|
||||
return value < 0 ? 0 : value;
|
||||
}
|
||||
|
||||
private:
|
||||
//! The current system cursor state
|
||||
SystemCursorState m_systemCursorState;
|
||||
|
||||
//! The cursor position before it got hidden.
|
||||
AZ::Vector2 m_cursorHiddenPosition;
|
||||
|
||||
AZ::Vector2 m_systemCursorPositionNormalized;
|
||||
uint32_t m_systemCursorPosition[MAX_XI_RAW_AXIS];
|
||||
|
||||
static xcb_connection_t* s_xcbConnection;
|
||||
static xcb_screen_t* s_xcbScreen;
|
||||
|
||||
//! Will be true if the xfixes extension could be initialized.
|
||||
static bool m_xfixesInitialized;
|
||||
|
||||
//! Will be true if the xinput2 extension could be initialized.
|
||||
static bool m_xInputInitialized;
|
||||
|
||||
//! The window that had focus
|
||||
xcb_window_t m_prevConstraintWindow;
|
||||
|
||||
//! The current window that has focus
|
||||
xcb_window_t m_focusWindow;
|
||||
|
||||
//! Will be true if the cursor is shown else false.
|
||||
bool m_cursorShown;
|
||||
|
||||
struct XFixesBarrierProperty
|
||||
{
|
||||
xcb_xfixes_barrier_t id;
|
||||
uint32_t direction;
|
||||
int16_t x0, y0, x1, y1;
|
||||
};
|
||||
|
||||
//! Array that holds barrier information used to confine the cursor.
|
||||
std::vector<XFixesBarrierProperty> m_activeBarriers;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
@@ -8,25 +8,31 @@
|
||||
|
||||
#include <AzFramework/Application/Application.h>
|
||||
#include <AzFramework/Windowing/NativeWindow.h>
|
||||
#include <AzFramework/XcbNativeWindow.h>
|
||||
#include <AzFramework/XcbConnectionManager.h>
|
||||
#include <AzFramework/XcbInterface.h>
|
||||
#include <AzFramework/XcbNativeWindow.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
[[maybe_unused]] const char XcbErrorWindow[] = "XcbNativeWindow";
|
||||
static constexpr uint8_t s_XcbFormatDataSize = 32; // Format indicator for xcb for client messages
|
||||
static constexpr uint16_t s_DefaultXcbWindowBorderWidth = 4; // The default border with in pixels if a border was specified
|
||||
static constexpr uint8_t s_XcbResponseTypeMask = 0x7f; // Mask to extract the specific event type from an xcb event
|
||||
static constexpr uint8_t s_XcbFormatDataSize = 32; // Format indicator for xcb for client messages
|
||||
static constexpr uint16_t s_DefaultXcbWindowBorderWidth = 4; // The default border with in pixels if a border was specified
|
||||
static constexpr uint8_t s_XcbResponseTypeMask = 0x7f; // Mask to extract the specific event type from an xcb event
|
||||
|
||||
#define _NET_WM_STATE_REMOVE 0l
|
||||
#define _NET_WM_STATE_ADD 1l
|
||||
#define _NET_WM_STATE_TOGGLE 2l
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
XcbNativeWindow::XcbNativeWindow()
|
||||
XcbNativeWindow::XcbNativeWindow()
|
||||
: NativeWindow::Implementation()
|
||||
, m_xcbConnection(nullptr)
|
||||
, m_xcbRootScreen(nullptr)
|
||||
, m_xcbWindow(XCB_NONE)
|
||||
{
|
||||
if (auto xcbConnectionManager = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
xcbConnectionManager != nullptr)
|
||||
if (auto xcbConnectionManager = AzFramework::XcbConnectionManagerInterface::Get(); xcbConnectionManager != nullptr)
|
||||
{
|
||||
m_xcbConnection = xcbConnectionManager->GetXcbConnection();
|
||||
}
|
||||
@@ -34,89 +40,184 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
XcbNativeWindow::~XcbNativeWindow() = default;
|
||||
XcbNativeWindow::~XcbNativeWindow()
|
||||
{
|
||||
if (XCB_NONE != m_xcbWindow)
|
||||
{
|
||||
xcb_destroy_window(m_xcbConnection, m_xcbWindow);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void XcbNativeWindow::InitWindow(const AZStd::string& title,
|
||||
const WindowGeometry& geometry,
|
||||
const WindowStyleMasks& styleMasks)
|
||||
void XcbNativeWindow::InitWindow(const AZStd::string& title, const WindowGeometry& geometry, const WindowStyleMasks& styleMasks)
|
||||
{
|
||||
// Get the parent window
|
||||
// Get the parent window
|
||||
const xcb_setup_t* xcbSetup = xcb_get_setup(m_xcbConnection);
|
||||
xcb_screen_t* xcbRootScreen = xcb_setup_roots_iterator(xcbSetup).data;
|
||||
xcb_window_t xcbParentWindow = xcbRootScreen->root;
|
||||
m_xcbRootScreen = xcb_setup_roots_iterator(xcbSetup).data;
|
||||
xcb_window_t xcbParentWindow = m_xcbRootScreen->root;
|
||||
|
||||
// Create an XCB window from the connection
|
||||
m_xcbWindow = xcb_generate_id(m_xcbConnection);
|
||||
|
||||
uint16_t borderWidth = 0;
|
||||
const uint32_t mask = styleMasks.m_platformAgnosticStyleMask;
|
||||
if ((mask & WindowStyleMasks::WINDOW_STYLE_BORDERED) ||
|
||||
(mask & WindowStyleMasks::WINDOW_STYLE_RESIZEABLE))
|
||||
if ((mask & WindowStyleMasks::WINDOW_STYLE_BORDERED) || (mask & WindowStyleMasks::WINDOW_STYLE_RESIZEABLE))
|
||||
{
|
||||
borderWidth = s_DefaultXcbWindowBorderWidth;
|
||||
}
|
||||
|
||||
uint32_t eventMask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
|
||||
|
||||
const uint32_t interestedEvents =
|
||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY
|
||||
| XCB_EVENT_MASK_BUTTON_PRESS
|
||||
| XCB_EVENT_MASK_BUTTON_RELEASE
|
||||
| XCB_EVENT_MASK_KEY_PRESS
|
||||
| XCB_EVENT_MASK_KEY_RELEASE
|
||||
| XCB_EVENT_MASK_POINTER_MOTION
|
||||
;
|
||||
uint32_t valueList[] = { xcbRootScreen->black_pixel,
|
||||
interestedEvents };
|
||||
|
||||
const uint32_t interestedEvents = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
|
||||
XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE;
|
||||
uint32_t valueList[] = { m_xcbRootScreen->black_pixel, interestedEvents };
|
||||
|
||||
xcb_void_cookie_t xcbCheckResult;
|
||||
|
||||
xcbCheckResult = xcb_create_window_checked(m_xcbConnection,
|
||||
XCB_COPY_FROM_PARENT,
|
||||
m_xcbWindow,
|
||||
xcbParentWindow,
|
||||
aznumeric_cast<int16_t>(geometry.m_posX),
|
||||
aznumeric_cast<int16_t>(geometry.m_posY),
|
||||
aznumeric_cast<int16_t>(geometry.m_width),
|
||||
aznumeric_cast<int16_t>(geometry.m_height),
|
||||
borderWidth,
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
xcbRootScreen->root_visual,
|
||||
eventMask,
|
||||
valueList);
|
||||
xcbCheckResult = xcb_create_window_checked(
|
||||
m_xcbConnection, XCB_COPY_FROM_PARENT, m_xcbWindow, xcbParentWindow, aznumeric_cast<int16_t>(geometry.m_posX),
|
||||
aznumeric_cast<int16_t>(geometry.m_posY), aznumeric_cast<int16_t>(geometry.m_width), aznumeric_cast<int16_t>(geometry.m_height),
|
||||
borderWidth, XCB_WINDOW_CLASS_INPUT_OUTPUT, m_xcbRootScreen->root_visual, eventMask, valueList);
|
||||
|
||||
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to create xcb window.");
|
||||
|
||||
SetWindowTitle(title);
|
||||
|
||||
// Setup the window close event
|
||||
const static char* wmProtocolString = "WM_PROTOCOLS";
|
||||
|
||||
xcb_intern_atom_cookie_t cookieProtocol = xcb_intern_atom(m_xcbConnection, 1, strlen(wmProtocolString), wmProtocolString);
|
||||
xcb_intern_atom_reply_t* replyProtocol = xcb_intern_atom_reply(m_xcbConnection, cookieProtocol, nullptr);
|
||||
AZ_Error(XcbErrorWindow, replyProtocol != nullptr, "Unable to query xcb '%s' atom", wmProtocolString);
|
||||
m_xcbAtomProtocols = replyProtocol->atom;
|
||||
|
||||
const static char* wmDeleteWindowString = "WM_DELETE_WINDOW";
|
||||
xcb_intern_atom_cookie_t cookieDeleteWindow = xcb_intern_atom(m_xcbConnection, 0, strlen(wmDeleteWindowString), wmDeleteWindowString);
|
||||
xcb_intern_atom_reply_t* replyDeleteWindow = xcb_intern_atom_reply(m_xcbConnection, cookieDeleteWindow, nullptr);
|
||||
AZ_Error(XcbErrorWindow, replyDeleteWindow != nullptr, "Unable to query xcb '%s' atom", wmDeleteWindowString);
|
||||
m_xcbAtomDeleteWindow = replyDeleteWindow->atom;
|
||||
|
||||
xcbCheckResult = xcb_change_property_checked(m_xcbConnection,
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
m_xcbWindow,
|
||||
m_xcbAtomProtocols,
|
||||
XCB_ATOM_ATOM,
|
||||
s_XcbFormatDataSize,
|
||||
1,
|
||||
&m_xcbAtomDeleteWindow);
|
||||
|
||||
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to change the xcb atom property for WM_CLOSE event");
|
||||
|
||||
m_posX = geometry.m_posX;
|
||||
m_posY = geometry.m_posY;
|
||||
m_width = geometry.m_width;
|
||||
m_height = geometry.m_height;
|
||||
|
||||
InitializeAtoms();
|
||||
|
||||
xcb_client_message_event_t event;
|
||||
event.response_type = XCB_CLIENT_MESSAGE;
|
||||
event.type = _NET_REQUEST_FRAME_EXTENTS;
|
||||
event.window = m_xcbWindow;
|
||||
event.format = 32;
|
||||
event.sequence = 0;
|
||||
event.data.data32[0] = 0l;
|
||||
event.data.data32[1] = 0l;
|
||||
event.data.data32[2] = 0l;
|
||||
event.data.data32[3] = 0l;
|
||||
event.data.data32[4] = 0l;
|
||||
xcbCheckResult = xcb_send_event(
|
||||
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
||||
(const char*)&event);
|
||||
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_REQUEST_FRAME_EXTENTS");
|
||||
|
||||
// The WM will be able to kill the application if it gets unresponsive.
|
||||
int32_t pid = getpid();
|
||||
xcb_change_property(m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
|
||||
|
||||
xcb_flush(m_xcbConnection);
|
||||
}
|
||||
|
||||
xcb_atom_t XcbNativeWindow::GetAtom(const char* atomName)
|
||||
{
|
||||
xcb_intern_atom_cookie_t intern_atom_cookie = xcb_intern_atom(m_xcbConnection, 0, strlen(atomName), atomName);
|
||||
XcbStdFreePtr<xcb_intern_atom_reply_t> xkbinternAtom{ xcb_intern_atom_reply(m_xcbConnection, intern_atom_cookie, NULL) };
|
||||
|
||||
if (!xkbinternAtom)
|
||||
{
|
||||
AZ_Error(XcbErrorWindow, xkbinternAtom != nullptr, "Unable to query xcb '%s' atom", atomName);
|
||||
return XCB_NONE;
|
||||
}
|
||||
|
||||
return xkbinternAtom->atom;
|
||||
}
|
||||
|
||||
int XcbNativeWindow::SetAtom(xcb_window_t window, xcb_atom_t atom, xcb_atom_t type, size_t len, void* data)
|
||||
{
|
||||
xcb_void_cookie_t cookie = xcb_change_property_checked(m_xcbConnection, XCB_PROP_MODE_REPLACE, window, atom, type, 32, len, data);
|
||||
XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(m_xcbConnection, cookie) };
|
||||
|
||||
if (!xkbError)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return xkbError->error_code;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void XcbNativeWindow::InitializeAtoms()
|
||||
{
|
||||
AZStd::vector<xcb_atom_t> Atoms;
|
||||
|
||||
_NET_ACTIVE_WINDOW = GetAtom("_NET_ACTIVE_WINDOW");
|
||||
_NET_WM_BYPASS_COMPOSITOR = GetAtom("_NET_WM_BYPASS_COMPOSITOR");
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Handle all WM Protocols atoms.
|
||||
//
|
||||
|
||||
WM_PROTOCOLS = GetAtom("WM_PROTOCOLS");
|
||||
|
||||
// This atom is used to close a window. Emitted when user clicks the close button.
|
||||
WM_DELETE_WINDOW = GetAtom("WM_DELETE_WINDOW");
|
||||
|
||||
Atoms.push_back(WM_DELETE_WINDOW);
|
||||
|
||||
xcb_change_property(
|
||||
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, WM_PROTOCOLS, XCB_ATOM_ATOM, 32, Atoms.size(), Atoms.data());
|
||||
|
||||
xcb_flush(m_xcbConnection);
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Handle all WM State atoms.
|
||||
//
|
||||
|
||||
_NET_WM_STATE = GetAtom("_NET_WM_STATE");
|
||||
_NET_WM_STATE_FULLSCREEN = GetAtom("_NET_WM_STATE_FULLSCREEN");
|
||||
_NET_WM_STATE_MAXIMIZED_VERT = GetAtom("_NET_WM_STATE_MAXIMIZED_VERT");
|
||||
_NET_WM_STATE_MAXIMIZED_HORZ = GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ");
|
||||
_NET_MOVERESIZE_WINDOW = GetAtom("_NET_MOVERESIZE_WINDOW");
|
||||
_NET_REQUEST_FRAME_EXTENTS = GetAtom("_NET_REQUEST_FRAME_EXTENTS");
|
||||
_NET_FRAME_EXTENTS = GetAtom("_NET_FRAME_EXTENTS");
|
||||
_NET_WM_PID = GetAtom("_NET_WM_PID");
|
||||
}
|
||||
|
||||
void XcbNativeWindow::GetWMStates()
|
||||
{
|
||||
xcb_get_property_cookie_t cookie = xcb_get_property(m_xcbConnection, 0, m_xcbWindow, _NET_WM_STATE, XCB_ATOM_ATOM, 0, 1024);
|
||||
|
||||
xcb_generic_error_t* error = nullptr;
|
||||
XcbStdFreePtr<xcb_get_property_reply_t> xkbGetPropertyReply{ xcb_get_property_reply(m_xcbConnection, cookie, &error) };
|
||||
|
||||
if (!xkbGetPropertyReply || error || !((xkbGetPropertyReply->format == 32) && (xkbGetPropertyReply->type == XCB_ATOM_ATOM)))
|
||||
{
|
||||
AZ_Warning("ApplicationLinux", false, "Acquiring _NET_WM_STATE information from the WM failed.");
|
||||
|
||||
if (error)
|
||||
{
|
||||
AZ_TracePrintf("Error", "Error code %d", error->error_code);
|
||||
free(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
m_fullscreenState = false;
|
||||
m_horizontalyMaximized = false;
|
||||
m_verticallyMaximized = false;
|
||||
|
||||
const xcb_atom_t* states = static_cast<const xcb_atom_t*>(xcb_get_property_value(xkbGetPropertyReply.get()));
|
||||
for (int i = 0; i < xkbGetPropertyReply->length; i++)
|
||||
{
|
||||
if (states[i] == _NET_WM_STATE_FULLSCREEN)
|
||||
{
|
||||
m_fullscreenState = true;
|
||||
}
|
||||
else if (states[i] == _NET_WM_STATE_MAXIMIZED_HORZ)
|
||||
{
|
||||
m_horizontalyMaximized = true;
|
||||
}
|
||||
else if (states[i] == _NET_WM_STATE_MAXIMIZED_VERT)
|
||||
{
|
||||
m_verticallyMaximized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -146,7 +247,7 @@ namespace AzFramework
|
||||
xcb_flush(m_xcbConnection);
|
||||
}
|
||||
XcbEventHandlerBus::Handler::BusDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
NativeWindowHandle XcbNativeWindow::GetWindowHandle() const
|
||||
@@ -158,14 +259,9 @@ namespace AzFramework
|
||||
void XcbNativeWindow::SetWindowTitle(const AZStd::string& title)
|
||||
{
|
||||
xcb_void_cookie_t xcbCheckResult;
|
||||
xcbCheckResult = xcb_change_property(m_xcbConnection,
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
m_xcbWindow,
|
||||
XCB_ATOM_WM_NAME,
|
||||
XCB_ATOM_STRING,
|
||||
8,
|
||||
static_cast<uint32_t>(title.size()),
|
||||
title.c_str());
|
||||
xcbCheckResult = xcb_change_property(
|
||||
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, static_cast<uint32_t>(title.size()),
|
||||
title.c_str());
|
||||
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set window title.");
|
||||
}
|
||||
|
||||
@@ -175,7 +271,7 @@ namespace AzFramework
|
||||
const uint32_t values[] = { clientAreaSize.m_width, clientAreaSize.m_height };
|
||||
|
||||
xcb_configure_window(m_xcbConnection, m_xcbWindow, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
|
||||
|
||||
|
||||
m_width = clientAreaSize.m_width;
|
||||
m_height = clientAreaSize.m_height;
|
||||
}
|
||||
@@ -185,16 +281,77 @@ namespace AzFramework
|
||||
{
|
||||
// [GFX TODO][GHI - 2678]
|
||||
// Using 60 for now until proper support is added
|
||||
|
||||
return 60;
|
||||
}
|
||||
|
||||
bool XcbNativeWindow::GetFullScreenState() const
|
||||
{
|
||||
return m_fullscreenState;
|
||||
}
|
||||
|
||||
void XcbNativeWindow::SetFullScreenState(bool fullScreenState)
|
||||
{
|
||||
// TODO This is a pretty basic full-screen implementation using WM's _NET_WM_STATE_FULLSCREEN state.
|
||||
// Do we have to provide also the old way?
|
||||
|
||||
GetWMStates();
|
||||
|
||||
xcb_client_message_event_t event;
|
||||
event.response_type = XCB_CLIENT_MESSAGE;
|
||||
event.type = _NET_WM_STATE;
|
||||
event.window = m_xcbWindow;
|
||||
event.format = 32;
|
||||
event.sequence = 0;
|
||||
event.data.data32[0] = fullScreenState ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
|
||||
event.data.data32[1] = _NET_WM_STATE_FULLSCREEN;
|
||||
event.data.data32[2] = 0;
|
||||
event.data.data32[3] = 1;
|
||||
event.data.data32[4] = 0;
|
||||
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
|
||||
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
||||
(const char*)&event);
|
||||
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_WM_STATE_FULLSCREEN");
|
||||
|
||||
// Also try to disable/enable the compositor if possible. Might help in some cases.
|
||||
const long _NET_WM_BYPASS_COMPOSITOR_HINT_ON = m_fullscreenState ? 1 : 0;
|
||||
SetAtom(m_xcbWindow, _NET_WM_BYPASS_COMPOSITOR, XCB_ATOM_CARDINAL, 32, (char*)&_NET_WM_BYPASS_COMPOSITOR_HINT_ON);
|
||||
|
||||
if (!fullScreenState)
|
||||
{
|
||||
if (m_horizontalyMaximized || m_verticallyMaximized)
|
||||
{
|
||||
printf("Remove maximized state.\n");
|
||||
xcb_client_message_event_t event;
|
||||
event.response_type = XCB_CLIENT_MESSAGE;
|
||||
event.type = _NET_WM_STATE;
|
||||
event.window = m_xcbWindow;
|
||||
event.format = 32;
|
||||
event.sequence = 0;
|
||||
event.data.data32[0] = _NET_WM_STATE_MAXIMIZED_VERT;
|
||||
event.data.data32[1] = _NET_WM_STATE_MAXIMIZED_HORZ;
|
||||
event.data.data32[2] = 0;
|
||||
event.data.data32[3] = 0;
|
||||
event.data.data32[4] = 0;
|
||||
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
|
||||
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
||||
(const char*)&event);
|
||||
AZ_Assert(
|
||||
ValidateXcbResult(xcbCheckResult), "Failed to remove _NET_WM_STATE_MAXIMIZED_VERT | _NET_WM_STATE_MAXIMIZED_HORZ");
|
||||
}
|
||||
}
|
||||
|
||||
xcb_flush(m_xcbConnection);
|
||||
m_fullscreenState = fullScreenState;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool XcbNativeWindow::ValidateXcbResult(xcb_void_cookie_t cookie)
|
||||
{
|
||||
bool result = true;
|
||||
if (xcb_generic_error_t* error = xcb_request_check(m_xcbConnection, cookie))
|
||||
{
|
||||
AZ_TracePrintf("Error","Error code %d", error->error_code);
|
||||
AZ_TracePrintf("Error", "Error code %d", error->error_code);
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
@@ -205,20 +362,20 @@ namespace AzFramework
|
||||
{
|
||||
switch (event->response_type & s_XcbResponseTypeMask)
|
||||
{
|
||||
case XCB_CONFIGURE_NOTIFY:
|
||||
case XCB_CONFIGURE_NOTIFY:
|
||||
{
|
||||
xcb_configure_notify_event_t* cne = reinterpret_cast<xcb_configure_notify_event_t*>(event);
|
||||
WindowSizeChanged(aznumeric_cast<uint32_t>(cne->width),
|
||||
aznumeric_cast<uint32_t>(cne->height));
|
||||
|
||||
if ((cne->width != m_width) || (cne->height != m_height))
|
||||
{
|
||||
WindowSizeChanged(aznumeric_cast<uint32_t>(cne->width), aznumeric_cast<uint32_t>(cne->height));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case XCB_CLIENT_MESSAGE:
|
||||
case XCB_CLIENT_MESSAGE:
|
||||
{
|
||||
xcb_client_message_event_t* cme = reinterpret_cast<xcb_client_message_event_t*>(event);
|
||||
if ((cme->type == m_xcbAtomProtocols) &&
|
||||
(cme->format == s_XcbFormatDataSize) &&
|
||||
(cme->data.data32[0] == m_xcbAtomDeleteWindow))
|
||||
|
||||
if ((cme->type == WM_PROTOCOLS) && (cme->format == s_XcbFormatDataSize) && (cme->data.data32[0] == WM_DELETE_WINDOW))
|
||||
{
|
||||
Deactivate();
|
||||
|
||||
@@ -239,7 +396,8 @@ namespace AzFramework
|
||||
|
||||
if (m_activated)
|
||||
{
|
||||
WindowNotificationBus::Event(reinterpret_cast<NativeWindowHandle>(m_xcbWindow), &WindowNotificationBus::Events::OnWindowResized, width, height);
|
||||
WindowNotificationBus::Event(
|
||||
reinterpret_cast<NativeWindowHandle>(m_xcbWindow), &WindowNotificationBus::Events::OnWindowResized, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,15 +27,16 @@ namespace AzFramework
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// NativeWindow::Implementation
|
||||
void InitWindow(const AZStd::string& title,
|
||||
const WindowGeometry& geometry,
|
||||
const WindowStyleMasks& styleMasks) override;
|
||||
void InitWindow(const AZStd::string& title, const WindowGeometry& geometry, const WindowStyleMasks& styleMasks) override;
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
NativeWindowHandle GetWindowHandle() const override;
|
||||
void SetWindowTitle(const AZStd::string& title) override;
|
||||
void ResizeClientArea(WindowSize clientAreaSize) override;
|
||||
uint32_t GetDisplayRefreshRate() const override;
|
||||
uint32_t GetDisplayRefreshRate() const override;
|
||||
|
||||
bool GetFullScreenState() const override;
|
||||
void SetFullScreenState(bool fullScreenState) override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// XcbEventHandlerBus::Handler
|
||||
@@ -44,10 +45,46 @@ namespace AzFramework
|
||||
private:
|
||||
bool ValidateXcbResult(xcb_void_cookie_t cookie);
|
||||
void WindowSizeChanged(const uint32_t width, const uint32_t height);
|
||||
int SetAtom(xcb_window_t window, xcb_atom_t atom, xcb_atom_t type, size_t len, void* data);
|
||||
|
||||
xcb_connection_t* m_xcbConnection = nullptr;
|
||||
xcb_window_t m_xcbWindow = 0;
|
||||
xcb_atom_t m_xcbAtomProtocols;
|
||||
xcb_atom_t m_xcbAtomDeleteWindow;
|
||||
// Initialize one atom.
|
||||
xcb_atom_t GetAtom(const char* atomName);
|
||||
|
||||
// Initialize all used atoms.
|
||||
void InitializeAtoms();
|
||||
void GetWMStates();
|
||||
|
||||
xcb_connection_t* m_xcbConnection = nullptr;
|
||||
xcb_screen_t* m_xcbRootScreen = nullptr;
|
||||
xcb_window_t m_xcbWindow = 0;
|
||||
int32_t m_posX;
|
||||
int32_t m_posY;
|
||||
bool m_fullscreenState = false;
|
||||
bool m_horizontalyMaximized = false;
|
||||
bool m_verticallyMaximized = false;
|
||||
|
||||
// Use exact atom names for easy readability and usage.
|
||||
xcb_atom_t WM_PROTOCOLS;
|
||||
xcb_atom_t WM_DELETE_WINDOW;
|
||||
// This atom is used to activate a window.
|
||||
xcb_atom_t _NET_ACTIVE_WINDOW;
|
||||
// This atom is use to bypass a compositor. Used during fullscreen mode.
|
||||
xcb_atom_t _NET_WM_BYPASS_COMPOSITOR;
|
||||
// This atom is used to change the state of a window using the WM.
|
||||
xcb_atom_t _NET_WM_STATE;
|
||||
// This atom is used to enable/disable fullscreen mode of a window.
|
||||
xcb_atom_t _NET_WM_STATE_FULLSCREEN;
|
||||
// This atom is used to extend the window to max vertically.
|
||||
xcb_atom_t _NET_WM_STATE_MAXIMIZED_VERT;
|
||||
// This atom is used to extend the window to max horizontally.
|
||||
xcb_atom_t _NET_WM_STATE_MAXIMIZED_HORZ;
|
||||
// This atom is used to position and resize a window.
|
||||
xcb_atom_t _NET_MOVERESIZE_WINDOW;
|
||||
// This atom is used to request the extent of the window.
|
||||
xcb_atom_t _NET_REQUEST_FRAME_EXTENTS;
|
||||
// This atom is used to identify the reply event for _NET_REQUEST_FRAME_EXTENTS
|
||||
xcb_atom_t _NET_FRAME_EXTENTS;
|
||||
// This atom is used to allow WM to kill app if not responsive anymore
|
||||
xcb_atom_t _NET_WM_PID;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -12,6 +12,8 @@ set(FILES
|
||||
AzFramework/XcbConnectionManager.h
|
||||
AzFramework/XcbInputDeviceKeyboard.cpp
|
||||
AzFramework/XcbInputDeviceKeyboard.h
|
||||
AzFramework/XcbInputDeviceMouse.cpp
|
||||
AzFramework/XcbInputDeviceMouse.h
|
||||
AzFramework/XcbInterface.h
|
||||
AzFramework/XcbNativeWindow.cpp
|
||||
AzFramework/XcbNativeWindow.h
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
#include <AzFramework/XcbInputDeviceMouse.h>
|
||||
#endif
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
InputDeviceMouse::Implementation* InputDeviceMouse::Implementation::Create(InputDeviceMouse& inputDevice)
|
||||
{
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
return XcbInputDeviceMouse::Create(inputDevice);
|
||||
#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND
|
||||
#error "Linux Window Manager Wayland not supported."
|
||||
return nullptr;
|
||||
#else
|
||||
#error "Linux Window Manager not recognized."
|
||||
return nullptr;
|
||||
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
}
|
||||
} // namespace AzFramework
|
||||
@@ -22,8 +22,10 @@ if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb")
|
||||
PRIVATE
|
||||
3rdParty::X11::xcb
|
||||
3rdParty::X11::xcb_xkb
|
||||
3rdParty::X11::xcb_xfixes
|
||||
3rdParty::X11::xkbcommon
|
||||
3rdParty::X11::xkbcommon_X11
|
||||
xcb-xinput
|
||||
)
|
||||
|
||||
elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "wayland")
|
||||
|
||||
@@ -22,8 +22,8 @@ set(FILES
|
||||
AzFramework/Windowing/NativeWindow_Linux.cpp
|
||||
../Common/Unimplemented/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Unimplemented.cpp
|
||||
AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Linux.cpp
|
||||
AzFramework/Input/Devices/Mouse/InputDeviceMouse_Linux.cpp
|
||||
../Common/Unimplemented/AzFramework/Input/Devices/Motion/InputDeviceMotion_Unimplemented.cpp
|
||||
../Common/Unimplemented/AzFramework/Input/Devices/Mouse/InputDeviceMouse_Unimplemented.cpp
|
||||
../Common/Unimplemented/AzFramework/Input/Devices/Touch/InputDeviceTouch_Unimplemented.cpp
|
||||
AzFramework/Input/User/LocalUserId_Platform.h
|
||||
../Common/Default/AzFramework/Input/User/LocalUserId_Default.h
|
||||
|
||||
@@ -92,10 +92,14 @@ namespace AZ
|
||||
|
||||
void ApplyGlobalParameters(int* argc, char** argv)
|
||||
{
|
||||
// this is a hook that can be used to apply any other global non-google parameters
|
||||
// that we use.
|
||||
// this is a hook that can be used to apply any other global parameters that we use.
|
||||
AZ_UNUSED(argc);
|
||||
AZ_UNUSED(argv);
|
||||
|
||||
// Disable gtest catching unhandled exceptions, instead, AzTestRunner will do it through:
|
||||
// AZ::Debug::Trace::HandleExceptions(true). This gives us a stack trace when the exception
|
||||
// is thrown (googletest does not).
|
||||
testing::FLAGS_gtest_catch_exceptions = false;
|
||||
}
|
||||
|
||||
//! Print out parameters that are not used by the framework
|
||||
|
||||
+2
-1
@@ -12,7 +12,7 @@
|
||||
#include <Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <Prefab/PrefabSystemScriptingHandler.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <Prefab/EditorPrefabComponent.h>
|
||||
#include <ToolsComponents/TransformComponent.h>
|
||||
|
||||
namespace AzToolsFramework::Prefab
|
||||
@@ -72,6 +72,7 @@ namespace AzToolsFramework::Prefab
|
||||
entities, commonRoot, &topLevelEntities);
|
||||
|
||||
auto containerEntity = AZStd::make_unique<AZ::Entity>();
|
||||
containerEntity->CreateComponent<Prefab::EditorPrefabComponent>();
|
||||
|
||||
for (AZ::Entity* entity : topLevelEntities)
|
||||
{
|
||||
|
||||
+1
-8
@@ -766,15 +766,8 @@ namespace AzToolsFramework
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
outPrefabAssetPath = product->GetRelativePath();
|
||||
|
||||
auto asset = AZ::Data::AssetManager::Instance().GetAsset(
|
||||
product->GetAssetId(),
|
||||
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(),
|
||||
AZ::Data::AssetLoadBehavior::Default);
|
||||
|
||||
return asset.BlockUntilLoadComplete() != AZ::Data::AssetData::AssetStatus::Error;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::WarnUserOfError(AZStd::string_view title, AZStd::string_view message)
|
||||
|
||||
+32
-4
@@ -115,6 +115,33 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
CursorEntityIdQuery::CursorEntityIdQuery(AZ::EntityId entityId, AZ::EntityId rootEntityId)
|
||||
: m_entityId(entityId)
|
||||
, m_containerAncestorEntityId(rootEntityId)
|
||||
{
|
||||
}
|
||||
|
||||
AZ::EntityId CursorEntityIdQuery::EntityIdUnderCursor() const
|
||||
{
|
||||
return m_entityId;
|
||||
}
|
||||
|
||||
AZ::EntityId CursorEntityIdQuery::ContainerAncestorEntityId() const
|
||||
{
|
||||
return m_containerAncestorEntityId;
|
||||
}
|
||||
|
||||
bool CursorEntityIdQuery::HasContainerAncestorEntityId() const
|
||||
{
|
||||
if (m_entityId.IsValid())
|
||||
{
|
||||
return m_entityId != m_containerAncestorEntityId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache)
|
||||
: m_entityDataCache(entityDataCache)
|
||||
{
|
||||
@@ -131,7 +158,7 @@ namespace AzToolsFramework
|
||||
m_invalidClicks = AZStd::make_unique<InvalidClicks>(AZStd::move(invalidClicks));
|
||||
}
|
||||
|
||||
AZ::EntityId EditorHelpers::HandleMouseInteraction(
|
||||
CursorEntityIdQuery EditorHelpers::FindEntityIdUnderCursor(
|
||||
const AzFramework::CameraState& cameraState, const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
||||
@@ -202,17 +229,18 @@ namespace AzToolsFramework
|
||||
m_invalidClicks->AddInvalidClick(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
|
||||
}
|
||||
|
||||
return AZ::EntityId();
|
||||
return CursorEntityIdQuery(AZ::EntityId(), AZ::EntityId());
|
||||
}
|
||||
|
||||
// container entity support - if the entity that is being selected is part of a closed container,
|
||||
// change the selection to the container instead.
|
||||
if (ContainerEntityInterface* containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get())
|
||||
{
|
||||
return containerEntityInterface->FindHighestSelectableEntity(entityIdUnderCursor);
|
||||
const auto highestSelectableEntity = containerEntityInterface->FindHighestSelectableEntity(entityIdUnderCursor);
|
||||
return CursorEntityIdQuery(entityIdUnderCursor, highestSelectableEntity);
|
||||
}
|
||||
|
||||
return entityIdUnderCursor;
|
||||
return CursorEntityIdQuery(entityIdUnderCursor, AZ::EntityId());
|
||||
}
|
||||
|
||||
void EditorHelpers::Display2d(
|
||||
|
||||
@@ -32,6 +32,28 @@ namespace AzToolsFramework
|
||||
struct MouseInteractionEvent;
|
||||
}
|
||||
|
||||
//!< Represents the result of a query to find the id of the entity under the cursor (if any).
|
||||
class CursorEntityIdQuery
|
||||
{
|
||||
public:
|
||||
CursorEntityIdQuery(AZ::EntityId entityId, AZ::EntityId rootEntityId);
|
||||
|
||||
//! Returns the entity id under the cursor (if any).
|
||||
//! @note In the case of no entity id under the cursor, an invalid entity id is returned.
|
||||
AZ::EntityId EntityIdUnderCursor() const;
|
||||
|
||||
//! Returns the topmost container entity id in the hierarchy if the entity id under the cursor is inside a container entity, otherwise returns the entity id.
|
||||
//! @note In the case of no entity id under the cursor, an invalid entity id is returned.
|
||||
AZ::EntityId ContainerAncestorEntityId() const;
|
||||
|
||||
//! Returns true if the query has a container ancestor entity id, otherwise false.
|
||||
bool HasContainerAncestorEntityId() const;
|
||||
|
||||
private:
|
||||
AZ::EntityId m_entityId; //<! The entity id under the cursor.
|
||||
AZ::EntityId m_containerAncestorEntityId; //<! For entities in container entities, the topmost container entity id in the hierarchy, otherwise the entity id under the cursor.
|
||||
};
|
||||
|
||||
//! EditorHelpers are the visualizations that appear for entities
|
||||
//! when 'Display Helpers' is toggled on inside the editor.
|
||||
//! These include but are not limited to entity icons and shape visualizations.
|
||||
@@ -47,9 +69,9 @@ namespace AzToolsFramework
|
||||
EditorHelpers& operator=(const EditorHelpers&) = delete;
|
||||
~EditorHelpers() = default;
|
||||
|
||||
//! Handle any mouse interaction with the EditorHelpers.
|
||||
//! Finds the id of the entity under the cursor (if any). For entities in container entities, also finds the topmost container entity id in the hierarchy.
|
||||
//! Used to check if a particular entity was selected.
|
||||
AZ::EntityId HandleMouseInteraction(
|
||||
CursorEntityIdQuery FindEntityIdUnderCursor(
|
||||
const AzFramework::CameraState& cameraState, const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
|
||||
|
||||
//! Do the drawing responsible for the EditorHelpers.
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ namespace AzToolsFramework
|
||||
|
||||
const AzFramework::CameraState cameraState = GetCameraState(viewportId);
|
||||
|
||||
m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction);
|
||||
m_cachedEntityIdUnderCursor = m_editorHelpers->FindEntityIdUnderCursor(cameraState, mouseInteraction).ContainerAncestorEntityId();
|
||||
|
||||
// when left clicking, if we successfully clicked an entity, assign that
|
||||
// to the entity field selected in the entity inspector (RPE)
|
||||
|
||||
+18
-3
@@ -27,6 +27,7 @@
|
||||
#include <AzToolsFramework/Manipulators/ScaleManipulators.h>
|
||||
#include <AzToolsFramework/Manipulators/TranslationManipulators.h>
|
||||
#include <AzToolsFramework/Maths/TransformUtils.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorLockComponentBus.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
|
||||
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
||||
@@ -1799,7 +1800,8 @@ namespace AzToolsFramework
|
||||
const AzFramework::ViewportId viewportId = mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId;
|
||||
const AzFramework::CameraState cameraState = GetCameraState(viewportId);
|
||||
|
||||
m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction);
|
||||
const auto cursorEntityIdQuery = m_editorHelpers->FindEntityIdUnderCursor(cameraState, mouseInteraction);
|
||||
m_cachedEntityIdUnderCursor = cursorEntityIdQuery.ContainerAncestorEntityId();
|
||||
|
||||
const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
|
||||
m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
|
||||
@@ -1825,8 +1827,6 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
const AZ::EntityId entityIdUnderCursor = m_cachedEntityIdUnderCursor;
|
||||
|
||||
EditorContextMenuUpdate(m_contextMenu, mouseInteraction);
|
||||
|
||||
m_boxSelect.HandleMouseInteraction(mouseInteraction);
|
||||
@@ -1842,6 +1842,21 @@ namespace AzToolsFramework
|
||||
return true;
|
||||
}
|
||||
|
||||
const AZ::EntityId entityIdUnderCursor = m_cachedEntityIdUnderCursor;
|
||||
|
||||
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick &&
|
||||
mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
|
||||
{
|
||||
if (cursorEntityIdQuery.HasContainerAncestorEntityId())
|
||||
{
|
||||
if (auto prefabFocusInterface = AZ::Interface<Prefab::PrefabFocusInterface>::Get())
|
||||
{
|
||||
prefabFocusInterface->FocusOnPrefabInstanceOwningEntityId(cursorEntityIdQuery.ContainerAncestorEntityId());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool stickySelect = false;
|
||||
ViewportInteraction::ViewportSettingsRequestBus::EventResult(
|
||||
stickySelect, viewportId, &ViewportInteraction::ViewportSettingsRequestBus::Events::StickySelectEnabled);
|
||||
|
||||
Reference in New Issue
Block a user