Merge branch 'development' of https://github.com/o3de/o3de into sc-editor-asset-redux
Signed-off-by: chcurran <82187351+carlitosan@users.noreply.github.com>
This commit is contained in:
@@ -70,6 +70,9 @@ namespace AZ::Data
|
||||
|
||||
const char* GetFilename() const override { return m_filePath.c_str(); }
|
||||
|
||||
AZStd::chrono::milliseconds GetStreamingDeadline() const { return m_curDeadline; }
|
||||
AZ::IO::IStreamerTypes::Priority GetStreamingPriority() const { return m_curPriority; }
|
||||
|
||||
// AssetDataStream specific APIs
|
||||
|
||||
//! Whether or not all data has been loaded.
|
||||
|
||||
@@ -214,7 +214,6 @@ namespace AZ
|
||||
// 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.
|
||||
// Update all the runtime file paths based on the new "project_path" value.
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
|
||||
}
|
||||
|
||||
@@ -457,8 +457,6 @@ void JobManagerWorkStealing::ProcessJobsInternal(ThreadInfo* info, Job* suspende
|
||||
else
|
||||
{
|
||||
//attempt to steal a job from another thread's queue
|
||||
AZ_PROFILE_SCOPE(AzCore, "JobManagerWorkStealing::ProcessJobsInternal:WorkStealing");
|
||||
|
||||
unsigned int numStealAttempts = 0;
|
||||
const unsigned int maxStealAttempts = (unsigned int)m_workerThreads.size() * 3; //try every thread a few times before giving up
|
||||
while (!job)
|
||||
|
||||
@@ -3408,7 +3408,14 @@ LUA_API const Node* lua_getDummyNode()
|
||||
const BehaviorParameter* arg = method->GetArgument(iArg);
|
||||
BehaviorClass* argClass = nullptr;
|
||||
LuaLoadFromStack fromStack = FromLuaStack(context, arg, argClass);
|
||||
AZ_Assert(fromStack, "Argument %s for Method %s doesn't have support to be converted to Lua!", arg->m_name, method->m_name.c_str());
|
||||
AZ_Assert(fromStack,
|
||||
"The argument type: %s for method: %s is not serialized and/or reflected for scripting.\n"
|
||||
"Make sure %s is added to the SerializeContext and reflected to the BehaviorContext\n"
|
||||
"For example, verify these two exist and are being called in a Reflect function:\n"
|
||||
"serializeContext->Class<%s>();\n"
|
||||
"behaviorContext->Class<%s>();\n"
|
||||
"%s will not be available for scripting unless these requirements are met."
|
||||
, arg->m_name, method->m_name.c_str(), arg->m_name, arg->m_name, arg->m_name, method->m_name.c_str());
|
||||
|
||||
m_fromLua.push_back(AZStd::make_pair(fromStack, argClass));
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
namespace AZ::Internal
|
||||
{
|
||||
static constexpr const char* ProductCacheDirectoryName = "Cache";
|
||||
|
||||
AZ::SettingsRegistryInterface::FixedValueString GetEngineMonikerForProject(
|
||||
SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectJsonPath)
|
||||
{
|
||||
@@ -228,19 +230,20 @@ namespace AZ::Internal
|
||||
|
||||
namespace AZ::SettingsRegistryMergeUtils
|
||||
{
|
||||
constexpr AZStd::string_view InternalScanUpEngineRootKey{ "/O3DE/Settings/Internal/engine_root_scan_up_path" };
|
||||
constexpr AZStd::string_view InternalScanUpProjectRootKey{ "/O3DE/Settings/Internal/project_root_scan_up_path" };
|
||||
|
||||
AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry)
|
||||
{
|
||||
static constexpr AZStd::string_view InternalScanUpEngineRootKey{ "/O3DE/Runtime/Internal/engine_root_scan_up_path" };
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
using Type = SettingsRegistryInterface::Type;
|
||||
|
||||
AZ::IO::FixedMaxPath engineRoot;
|
||||
// This is the 'external' engine root key, as in passed from command-line or .setreg files.
|
||||
auto engineRootKey = SettingsRegistryInterface::FixedValueString::format("%s/engine_path", BootstrapSettingsRootKey);
|
||||
constexpr auto engineRootKey = FixedValueString(BootstrapSettingsRootKey) + "/engine_path";
|
||||
|
||||
// Step 1 Run the scan upwards logic once to find the location of the engine.json if it exist
|
||||
// Once this step is run the {InternalScanUpEngineRootKey} is set in the Settings Registry
|
||||
// to have this scan logic only run once InternalScanUpEngineRootKey the supplied registry
|
||||
if (settingsRegistry.GetType(InternalScanUpEngineRootKey) == SettingsRegistryInterface::Type::NoType)
|
||||
if (settingsRegistry.GetType(InternalScanUpEngineRootKey) == Type::NoType)
|
||||
{
|
||||
// We can scan up from exe directory to find engine.json, use that for engine root if it exists.
|
||||
engineRoot = Internal::ScanUpRootLocator("engine.json");
|
||||
@@ -283,14 +286,18 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
|
||||
AZ::IO::FixedMaxPath FindProjectRoot(SettingsRegistryInterface& settingsRegistry)
|
||||
{
|
||||
AZ::IO::FixedMaxPath projectRoot;
|
||||
const auto projectRootKey = SettingsRegistryInterface::FixedValueString::format("%s/project_path", BootstrapSettingsRootKey);
|
||||
static constexpr AZStd::string_view InternalScanUpProjectRootKey{ "/O3DE/Runtime/Internal/project_root_scan_up_path" };
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
using Type = SettingsRegistryInterface::Type;
|
||||
|
||||
// Step 1 Run the scan upwards logic once to find the location of the project.json if it exist
|
||||
AZ::IO::FixedMaxPath projectRoot;
|
||||
constexpr auto projectRootKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path";
|
||||
|
||||
// Step 1 Run the scan upwards logic once to find the location of the closest ancestor project.json
|
||||
// Once this step is run the {InternalScanUpProjectRootKey} is set in the Settings Registry
|
||||
// to have this scan logic only run once for the supplied registry
|
||||
// SettingsRegistryInterface::GetType is used to check if a key is set
|
||||
if (settingsRegistry.GetType(InternalScanUpProjectRootKey) == SettingsRegistryInterface::Type::NoType)
|
||||
if (settingsRegistry.GetType(InternalScanUpProjectRootKey) == Type::NoType)
|
||||
{
|
||||
projectRoot = Internal::ScanUpRootLocator("project.json");
|
||||
// Set the {InternalScanUpProjectRootKey} to make sure this code path isn't called again for this settings registry
|
||||
@@ -305,19 +312,129 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
}
|
||||
|
||||
// Step 2 Check the project-path key
|
||||
// This is the project path root key, as in passed from command-line or .setreg files.
|
||||
if (settingsRegistry.Get(projectRoot.Native(), projectRootKey))
|
||||
// This is the project path root key, as passed from command-line or *.setreg files.
|
||||
settingsRegistry.Get(projectRoot.Native(), projectRootKey);
|
||||
return projectRoot;
|
||||
}
|
||||
|
||||
//! The algorithm that is used to find the project cache is as follows
|
||||
//! 1. The "{BootstrapSettingsRootKey}/project_cache_path" is checked for the path
|
||||
//! 2. Otherwise append the ProductCacheDirectoryName constant to the <project-path>
|
||||
static AZ::IO::FixedMaxPath FindProjectCachePath(SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath)
|
||||
{
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
|
||||
constexpr auto projectCachePathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_cache_path";
|
||||
|
||||
// Step 1 Check the project-cache-path key
|
||||
if (AZ::IO::FixedMaxPath projectCachePath; settingsRegistry.Get(projectCachePath.Native(), projectCachePathKey))
|
||||
{
|
||||
return projectRoot;
|
||||
return projectCachePath;
|
||||
}
|
||||
|
||||
// Step 3 Check for a "Cache" directory by scanning upwards from the executable directory
|
||||
if (auto candidateRoot = Internal::ScanUpRootLocator("Cache");
|
||||
!candidateRoot.empty() && AZ::IO::SystemFile::IsDirectory(candidateRoot.c_str()))
|
||||
// Step 2 Append the "Cache" directory to the project-path
|
||||
return projectPath / Internal::ProductCacheDirectoryName;
|
||||
}
|
||||
|
||||
//! Set the user directory with the provided path or using <project-path>/user as default
|
||||
static AZ::IO::FixedMaxPath FindProjectUserPath(SettingsRegistryInterface& settingsRegistry,
|
||||
const AZ::IO::FixedMaxPath& projectPath)
|
||||
{
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
|
||||
// User: root - same as the @user@ alias, this is the starting path for transient data and log files.
|
||||
constexpr auto projectUserPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_user_path";
|
||||
|
||||
// Step 1 Check the project-user-path key
|
||||
if (AZ::IO::FixedMaxPath projectUserPath; settingsRegistry.Get(projectUserPath.Native(), projectUserPathKey))
|
||||
{
|
||||
projectRoot = AZStd::move(candidateRoot);
|
||||
return projectUserPath;
|
||||
}
|
||||
|
||||
// Step 2 Append the "User" directory to the project-path
|
||||
return projectPath / "user";
|
||||
}
|
||||
|
||||
//! Set the log directory using the settings registry path or using <project-user-path>/log as default
|
||||
static AZ::IO::FixedMaxPath FindProjectLogPath(SettingsRegistryInterface& settingsRegistry,
|
||||
const AZ::IO::FixedMaxPath& projectUserPath)
|
||||
{
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
|
||||
// User: root - same as the @log@ alias, this is the starting path for transient data and log files.
|
||||
constexpr auto projectLogPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_log_path";
|
||||
|
||||
// Step 1 Check the project-user-path key
|
||||
if (AZ::IO::FixedMaxPath projectLogPath; settingsRegistry.Get(projectLogPath.Native(), projectLogPathKey))
|
||||
{
|
||||
return projectLogPath;
|
||||
}
|
||||
|
||||
// Step 2 Append the "Log" directory to the project-user-path
|
||||
return projectUserPath / "log";
|
||||
}
|
||||
|
||||
// check for a default write storage path, fall back to the <project-user-path> if not
|
||||
static AZ::IO::FixedMaxPath FindDevWriteStoragePath(const AZ::IO::FixedMaxPath& projectUserPath)
|
||||
{
|
||||
AZStd::optional<AZ::IO::FixedMaxPathString> devWriteStorage = Utils::GetDevWriteStoragePath();
|
||||
return devWriteStorage.has_value() ? *devWriteStorage : projectUserPath;
|
||||
}
|
||||
|
||||
// check for the project build path, which is a relative path from the project root
|
||||
// that specifies where the build directory is located
|
||||
static void SetProjectBuildPath(SettingsRegistryInterface& settingsRegistry,
|
||||
const AZ::IO::FixedMaxPath& projectPath)
|
||||
{
|
||||
if (AZ::IO::FixedMaxPath projectBuildPath; settingsRegistry.Get(projectBuildPath.Native(), ProjectBuildPath))
|
||||
{
|
||||
settingsRegistry.Remove(FilePathKey_ProjectBuildPath);
|
||||
settingsRegistry.Remove(FilePathKey_ProjectConfigurationBinPath);
|
||||
AZ::IO::FixedMaxPath buildConfigurationPath = (projectPath / projectBuildPath).LexicallyNormal();
|
||||
if (IO::SystemFile::Exists(buildConfigurationPath.c_str()))
|
||||
{
|
||||
settingsRegistry.Set(FilePathKey_ProjectBuildPath, buildConfigurationPath.Native());
|
||||
}
|
||||
|
||||
// Add the specific build configuration paths to the Settings Registry
|
||||
// First try <project-build-path>/bin/$<CONFIG> and if that path doesn't exist
|
||||
// try <project-build-path>/bin/$<PLATFORM>/$<CONFIG>
|
||||
buildConfigurationPath /= "bin";
|
||||
if (IO::SystemFile::Exists((buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
|
||||
{
|
||||
settingsRegistry.Set(FilePathKey_ProjectConfigurationBinPath,
|
||||
(buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).Native());
|
||||
}
|
||||
else if (IO::SystemFile::Exists((buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
|
||||
{
|
||||
settingsRegistry.Set(FilePathKey_ProjectConfigurationBinPath,
|
||||
(buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).Native());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the project name within the Settings Registry by looking up the "project_name"
|
||||
// within the project.json file
|
||||
static void SetProjectName(SettingsRegistryInterface& settingsRegistry,
|
||||
const AZ::IO::FixedMaxPath& projectPath)
|
||||
{
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
// Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name.
|
||||
constexpr auto projectNameKey = FixedValueString(ProjectSettingsRootKey) + "/project_name";
|
||||
|
||||
// Read the project name from the project.json file if it exists
|
||||
if (AZ::IO::FixedMaxPath projectJsonPath = projectPath / "project.json";
|
||||
AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
|
||||
{
|
||||
settingsRegistry.MergeSettingsFile(projectJsonPath.Native(),
|
||||
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
|
||||
}
|
||||
// If a project name isn't set the default will be set to the final path segment of the project path
|
||||
if (FixedValueString projectName; !settingsRegistry.Get(projectName, projectNameKey))
|
||||
{
|
||||
projectName = projectPath.Filename().Native();
|
||||
settingsRegistry.Set(projectNameKey, projectName);
|
||||
}
|
||||
return projectRoot;
|
||||
}
|
||||
|
||||
AZStd::string_view ConfigParserSettings::DefaultCommentPrefixFilter(AZStd::string_view line)
|
||||
@@ -397,7 +514,7 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
bool MergeSettingsToRegistry_ConfigFile(SettingsRegistryInterface& registry, AZStd::string_view filePath,
|
||||
const ConfigParserSettings& configParserSettings)
|
||||
{
|
||||
auto configPath = FindEngineRoot(registry) / filePath;
|
||||
auto configPath = FindProjectRoot(registry) / filePath;
|
||||
IO::FileReader configFile;
|
||||
bool configFileOpened{};
|
||||
switch (configParserSettings.m_fileReaderClass)
|
||||
@@ -542,19 +659,77 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
void MergeSettingsToRegistry_AddRuntimeFilePaths(SettingsRegistryInterface& registry)
|
||||
{
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
// Binary folder
|
||||
AZ::IO::FixedMaxPath path = AZ::Utils::GetExecutableDirectory();
|
||||
registry.Set(FilePathKey_BinaryFolder, path.LexicallyNormal().Native());
|
||||
|
||||
// Engine root folder - corresponds to the @engroot@ and @engroot@ aliases
|
||||
AZ::IO::FixedMaxPath engineRoot = FindEngineRoot(registry);
|
||||
registry.Set(FilePathKey_EngineRootFolder, engineRoot.LexicallyNormal().Native());
|
||||
// Binary folder - corresponds to the @exefolder@ alias
|
||||
AZ::IO::FixedMaxPath exePath = AZ::Utils::GetExecutableDirectory();
|
||||
registry.Set(FilePathKey_BinaryFolder, exePath.LexicallyNormal().Native());
|
||||
|
||||
auto projectPathKey = FixedValueString::format("%s/project_path", BootstrapSettingsRootKey);
|
||||
SettingsRegistryInterface::FixedValueString projectPathValue;
|
||||
if (registry.Get(projectPathValue, projectPathKey))
|
||||
// Project path - corresponds to the @projectroot@ alias
|
||||
// NOTE: We make the project-path in the BootstrapSettingsRootKey absolute first
|
||||
|
||||
AZ::IO::FixedMaxPath projectPath = FindProjectRoot(registry);
|
||||
if (constexpr auto projectPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path";
|
||||
!projectPath.empty())
|
||||
{
|
||||
// Cache folder
|
||||
if (projectPath.IsRelative())
|
||||
{
|
||||
if (auto projectAbsPath = AZ::Utils::ConvertToAbsolutePath(projectPath.Native());
|
||||
projectAbsPath.has_value())
|
||||
{
|
||||
projectPath = AZStd::move(*projectAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
projectPath = projectPath.LexicallyNormal();
|
||||
AZ_Warning("SettingsRegistryMergeUtils", AZ::IO::SystemFile::Exists(projectPath.c_str()),
|
||||
R"(Project path "%s" does not exist. Is the "%.*s" registry setting set to a valid absolute path?)"
|
||||
, projectPath.c_str(), AZ_STRING_ARG(projectPathKey));
|
||||
|
||||
registry.Set(FilePathKey_ProjectPath, projectPath.Native());
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_TracePrintf("SettingsRegistryMergeUtils",
|
||||
R"(Project path isn't set in the Settings Registry at "%.*s".)"
|
||||
" Project-related filepaths will be set relative to the executable directory\n",
|
||||
AZ_STRING_ARG(projectPathKey));
|
||||
registry.Set(FilePathKey_ProjectPath, exePath.Native());
|
||||
}
|
||||
|
||||
// Engine root folder - corresponds to the @engroot@ alias
|
||||
AZ::IO::FixedMaxPath engineRoot = FindEngineRoot(registry);
|
||||
if (!engineRoot.empty())
|
||||
{
|
||||
if (engineRoot.IsRelative())
|
||||
{
|
||||
if (auto engineRootAbsPath = AZ::Utils::ConvertToAbsolutePath(engineRoot.Native());
|
||||
engineRootAbsPath.has_value())
|
||||
{
|
||||
engineRoot = AZStd::move(*engineRootAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
engineRoot = engineRoot.LexicallyNormal();
|
||||
registry.Set(FilePathKey_EngineRootFolder, engineRoot.Native());
|
||||
}
|
||||
|
||||
// Cache folder
|
||||
AZ::IO::FixedMaxPath projectCachePath = FindProjectCachePath(registry, projectPath).LexicallyNormal();
|
||||
if (!projectCachePath.empty())
|
||||
{
|
||||
if (projectCachePath.IsRelative())
|
||||
{
|
||||
if (auto projectCacheAbsPath = AZ::Utils::ConvertToAbsolutePath(projectCachePath.Native());
|
||||
projectCacheAbsPath.has_value())
|
||||
{
|
||||
projectCachePath = AZStd::move(*projectCacheAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
projectCachePath = projectCachePath.LexicallyNormal();
|
||||
registry.Set(FilePathKey_CacheProjectRootFolder, projectCachePath.Native());
|
||||
|
||||
// Cache/<asset-platform> folder
|
||||
// Get the name of the asset platform assigned by the bootstrap. First check for platform version such as "windows_assets"
|
||||
// and if that's missing just get "assets".
|
||||
FixedValueString assetPlatform;
|
||||
@@ -570,124 +745,67 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
assetPlatform = AZ::OSPlatformToDefaultAssetPlatform(AZ_TRAIT_OS_PLATFORM_CODENAME);
|
||||
}
|
||||
|
||||
// Project path - corresponds to the @projectroot@ alias
|
||||
// NOTE: Here we append to engineRoot, but if projectPathValue is absolute then engineRoot is discarded.
|
||||
path = engineRoot / projectPathValue;
|
||||
|
||||
AZ_Warning("SettingsRegistryMergeUtils", AZ::IO::SystemFile::Exists(path.c_str()),
|
||||
R"(Project path "%s" does not exist. Is the "%.*s" registry setting set to valid absolute path?)"
|
||||
, path.c_str(), aznumeric_cast<int>(projectPathKey.size()), projectPathKey.data());
|
||||
|
||||
AZ::IO::FixedMaxPath normalizedProjectPath = path.LexicallyNormal();
|
||||
registry.Set(FilePathKey_ProjectPath, normalizedProjectPath.Native());
|
||||
|
||||
// Set the user directory with the provided path or using project/user as default
|
||||
auto projectUserPathKey = FixedValueString::format("%s/project_user_path", BootstrapSettingsRootKey);
|
||||
AZ::IO::FixedMaxPath projectUserPath;
|
||||
if (!registry.Get(projectUserPath.Native(), projectUserPathKey))
|
||||
{
|
||||
projectUserPath = (normalizedProjectPath / "user").LexicallyNormal();
|
||||
}
|
||||
registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native());
|
||||
|
||||
// Set the log directory with the provided path or using project/user/log as default
|
||||
auto projectLogPathKey = FixedValueString::format("%s/project_log_path", BootstrapSettingsRootKey);
|
||||
AZ::IO::FixedMaxPath projectLogPath;
|
||||
if (!registry.Get(projectLogPath.Native(), projectLogPathKey))
|
||||
{
|
||||
projectLogPath = (projectUserPath / "log").LexicallyNormal();
|
||||
}
|
||||
registry.Set(FilePathKey_ProjectLogPath, projectLogPath.Native());
|
||||
|
||||
// check for a default write storage path, fall back to the project's user/ directory if not
|
||||
AZStd::optional<AZ::IO::FixedMaxPathString> devWriteStorage = Utils::GetDevWriteStoragePath();
|
||||
registry.Set(FilePathKey_DevWriteStorage, devWriteStorage.has_value()
|
||||
? devWriteStorage.value()
|
||||
: projectUserPath.Native());
|
||||
|
||||
// Set the project in-memory build path if the ProjectBuildPath key has been supplied
|
||||
if (AZ::IO::FixedMaxPath projectBuildPath; registry.Get(projectBuildPath.Native(), ProjectBuildPath))
|
||||
{
|
||||
registry.Remove(FilePathKey_ProjectBuildPath);
|
||||
registry.Remove(FilePathKey_ProjectConfigurationBinPath);
|
||||
AZ::IO::FixedMaxPath buildConfigurationPath = normalizedProjectPath / projectBuildPath;
|
||||
if (IO::SystemFile::Exists(buildConfigurationPath.c_str()))
|
||||
{
|
||||
registry.Set(FilePathKey_ProjectBuildPath, buildConfigurationPath.LexicallyNormal().Native());
|
||||
}
|
||||
|
||||
// Add the specific build configuration paths to the Settings Registry
|
||||
// First try <project-build-path>/bin/$<CONFIG> and if that path doesn't exist
|
||||
// try <project-build-path>/bin/$<PLATFORM>/$<CONFIG>
|
||||
buildConfigurationPath /= "bin";
|
||||
if (IO::SystemFile::Exists((buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
|
||||
{
|
||||
registry.Set(FilePathKey_ProjectConfigurationBinPath,
|
||||
(buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native());
|
||||
}
|
||||
else if (IO::SystemFile::Exists((buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
|
||||
{
|
||||
registry.Set(FilePathKey_ProjectConfigurationBinPath,
|
||||
(buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name.
|
||||
constexpr auto projectNameKey =
|
||||
FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey)
|
||||
+ "/project_name";
|
||||
|
||||
// Read the project name from the project.json file if it exists
|
||||
if (AZ::IO::FixedMaxPath projectJsonPath = normalizedProjectPath / "project.json";
|
||||
AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
|
||||
{
|
||||
registry.MergeSettingsFile(projectJsonPath.Native(),
|
||||
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
|
||||
}
|
||||
if (FixedValueString projectName; !registry.Get(projectName, projectNameKey))
|
||||
{
|
||||
projectName = path.Filename().Native();
|
||||
registry.Set(projectNameKey, projectName);
|
||||
}
|
||||
|
||||
// Cache folders - sets up various paths in registry for the cache.
|
||||
// Make sure the asset platform is set before setting these cache paths.
|
||||
// Make sure the asset platform is set before setting cache path for the asset platform.
|
||||
if (!assetPlatform.empty())
|
||||
{
|
||||
// Cache: project root - no corresponding fileIO alias, but this is where the asset database lives.
|
||||
// A registry override is accepted using the "project_cache_path" key.
|
||||
auto projectCacheRootOverrideKey = FixedValueString::format("%s/project_cache_path", BootstrapSettingsRootKey);
|
||||
// Clear path to make sure that the `project_cache_path` value isn't concatenated to the project path
|
||||
path.clear();
|
||||
if (registry.Get(path.Native(), projectCacheRootOverrideKey))
|
||||
{
|
||||
registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
|
||||
path /= assetPlatform;
|
||||
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cache: root - same as the @products@ alias, this is the starting path for cache files.
|
||||
path = normalizedProjectPath / "Cache";
|
||||
registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
|
||||
path /= assetPlatform;
|
||||
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
|
||||
}
|
||||
registry.Set(FilePathKey_CacheRootFolder, (projectCachePath / assetPlatform).Native());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
// User folder
|
||||
AZ::IO::FixedMaxPath projectUserPath = FindProjectUserPath(registry, projectPath);
|
||||
if (!projectUserPath.empty())
|
||||
{
|
||||
// Set the default ProjectUserPath to the <engine-root>/user directory
|
||||
registry.Set(FilePathKey_ProjectUserPath, (engineRoot / "user").LexicallyNormal().Native());
|
||||
AZ_TracePrintf("SettingsRegistryMergeUtils",
|
||||
R"(Project path isn't set in the Settings Registry at "%.*s". Project-related filepaths will not be set)" "\n",
|
||||
aznumeric_cast<int>(projectPathKey.size()), projectPathKey.data());
|
||||
if (projectUserPath.IsRelative())
|
||||
{
|
||||
if (auto projectUserAbsPath = AZ::Utils::ConvertToAbsolutePath(projectUserPath.Native());
|
||||
projectUserAbsPath.has_value())
|
||||
{
|
||||
projectUserPath = AZStd::move(*projectUserAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
projectUserPath = projectUserPath.LexicallyNormal();
|
||||
registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native());
|
||||
}
|
||||
|
||||
// Log folder
|
||||
if (AZ::IO::FixedMaxPath projectLogPath = FindProjectLogPath(registry, projectUserPath); !projectLogPath.empty())
|
||||
{
|
||||
if (projectLogPath.IsRelative())
|
||||
{
|
||||
if (auto projectLogAbsPath = AZ::Utils::ConvertToAbsolutePath(projectLogPath.Native()))
|
||||
{
|
||||
projectLogPath = AZStd::move(*projectLogAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
projectLogPath = projectLogPath.LexicallyNormal();
|
||||
registry.Set(FilePathKey_ProjectLogPath, projectLogPath.Native());
|
||||
}
|
||||
|
||||
// Developer Write Storage folder
|
||||
if (AZ::IO::FixedMaxPath devWriteStoragePath = FindDevWriteStoragePath(projectUserPath); !devWriteStoragePath.empty())
|
||||
{
|
||||
if (devWriteStoragePath.IsRelative())
|
||||
{
|
||||
if (auto devWriteStorageAbsPath = AZ::Utils::ConvertToAbsolutePath(devWriteStoragePath.Native()))
|
||||
{
|
||||
devWriteStoragePath = AZStd::move(*devWriteStorageAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
devWriteStoragePath = devWriteStoragePath.LexicallyNormal();
|
||||
registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.Native());
|
||||
}
|
||||
|
||||
// Set the project in-memory build path if the ProjectBuildPath key has been supplied
|
||||
SetProjectBuildPath(registry, projectPath);
|
||||
// Set the project name using the "project_name" key
|
||||
SetProjectName(registry, projectPath);
|
||||
|
||||
#if !AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
|
||||
// Setup the cache, user, and log paths to platform specific locations when running on non-host platforms
|
||||
path = engineRoot;
|
||||
if (AZStd::optional<AZ::IO::FixedMaxPathString> nonHostCacheRoot = Utils::GetDefaultAppRootPath();
|
||||
nonHostCacheRoot)
|
||||
{
|
||||
@@ -696,25 +814,25 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
}
|
||||
else
|
||||
{
|
||||
registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_CacheProjectRootFolder, projectPath.Native());
|
||||
registry.Set(FilePathKey_CacheRootFolder, projectPath.Native());
|
||||
}
|
||||
if (AZStd::optional<AZ::IO::FixedMaxPathString> devWriteStorage = Utils::GetDevWriteStoragePath();
|
||||
devWriteStorage)
|
||||
{
|
||||
const AZ::IO::FixedMaxPath devWriteStoragePath(*devWriteStorage);
|
||||
registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_ProjectUserPath, (devWriteStoragePath / "user").LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_ProjectLogPath, (devWriteStoragePath / "user/log").LexicallyNormal().Native());
|
||||
const auto devWriteStoragePath = AZ::IO::PathView(*devWriteStorage).LexicallyNormal();
|
||||
registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.Native());
|
||||
registry.Set(FilePathKey_ProjectUserPath, (devWriteStoragePath / "user").Native());
|
||||
registry.Set(FilePathKey_ProjectLogPath, (devWriteStoragePath / "user" / "log").Native());
|
||||
}
|
||||
else
|
||||
{
|
||||
registry.Set(FilePathKey_DevWriteStorage, path.LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_ProjectUserPath, (path / "user").LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_ProjectLogPath, (path / "user/log").LexicallyNormal().Native());
|
||||
}
|
||||
#endif // AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
|
||||
registry.Set(FilePathKey_DevWriteStorage, projectPath.Native());
|
||||
registry.Set(FilePathKey_ProjectUserPath, (projectPath / "user").Native());
|
||||
registry.Set(FilePathKey_ProjectLogPath, (projectPath / "user" / "log").Native());
|
||||
}
|
||||
#endif // AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
|
||||
}
|
||||
|
||||
void MergeSettingsToRegistry_TargetBuildDependencyRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
|
||||
const SettingsRegistryInterface::Specializations& specializations, AZStd::vector<char>* scratchBuffer)
|
||||
|
||||
@@ -87,9 +87,9 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry);
|
||||
|
||||
//! The algorithm that is used to find the project root is as follows
|
||||
//! 1. The first time this function is it performs a upward scan for a project.json file from
|
||||
//! the executable directory and if found stores that path to an internal key.
|
||||
//! In the same step it injects the path into the front of list of command line parameters
|
||||
//! 1. The first time this function runs it performs an upward scan for a "project.json" file from
|
||||
//! the executable directory and stores that path into an internal key.
|
||||
//! In the same step it injects the path into the back of the command line parameters
|
||||
//! using the --regset="{BootstrapSettingsRootKey}/project_path=<path>" value
|
||||
//! 2. Next the "{BootstrapSettingsRootKey}/project_path" is checked to see if it has a project path set
|
||||
//!
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace AZ::Utils
|
||||
{
|
||||
// Fix the size value of the fixed string by calculating the c-string length using char traits
|
||||
absolutePath.resize_no_construct(AZStd::char_traits<char>::length(absolutePath.data()));
|
||||
return srcPath;
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
return AZStd::nullopt;
|
||||
|
||||
@@ -635,6 +635,7 @@ namespace AZ
|
||||
size_t longestMatch = 0;
|
||||
size_t bufStringLength = inBuffer.size();
|
||||
AZStd::string_view longestAlias;
|
||||
AZStd::string_view longestResolvedAlias;
|
||||
|
||||
for (const auto& [alias, resolvedAlias] : m_aliases)
|
||||
{
|
||||
@@ -653,6 +654,7 @@ namespace AZ
|
||||
{
|
||||
longestMatch = resolvedAlias.size();
|
||||
longestAlias = alias;
|
||||
longestResolvedAlias = resolvedAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -661,7 +663,10 @@ namespace AZ
|
||||
// rearrange the buffer to have
|
||||
// [alias][old path]
|
||||
size_t aliasSize = longestAlias.size();
|
||||
size_t charsToAbsorb = longestMatch;
|
||||
// If the resolved alias ends in a path separator, do not consume it.
|
||||
const bool resolvedAliasEndsInPathSeparator = (longestResolvedAlias.ends_with(AZ::IO::PosixPathSeparator) ||
|
||||
longestResolvedAlias.ends_with(AZ::IO::WindowsPathSeparator));
|
||||
const size_t charsToAbsorb = resolvedAliasEndsInPathSeparator ? longestMatch - 1 : longestMatch;
|
||||
size_t remainingData = bufStringLength - charsToAbsorb;
|
||||
size_t finalStringSize = aliasSize + remainingData;
|
||||
if (finalStringSize >= outBufferLength)
|
||||
|
||||
@@ -27,28 +27,4 @@ namespace AzFramework
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const char* InputChannelId::GetName() const
|
||||
{
|
||||
return m_name.c_str();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const AZ::Crc32& InputChannelId::GetNameCrc32() const
|
||||
{
|
||||
return m_crc32;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputChannelId::operator==(const InputChannelId& other) const
|
||||
{
|
||||
return (m_crc32 == other.m_crc32);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputChannelId::operator!=(const InputChannelId& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -39,53 +39,58 @@ namespace AzFramework
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Constructor
|
||||
//! \param[in] name Name of the input channel (will be ignored if exceeds MAX_NAME_LENGTH)
|
||||
//! \param[in] name Name of the input channel (will be truncated if exceeds MAX_NAME_LENGTH)
|
||||
explicit constexpr InputChannelId(AZStd::string_view name = "")
|
||||
: m_name(name)
|
||||
, m_crc32(name)
|
||||
: m_name(name.substr(0, MAX_NAME_LENGTH))
|
||||
, m_crc32(name.substr(0, MAX_NAME_LENGTH))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr InputChannelId(const InputChannelId& other) = default;
|
||||
constexpr InputChannelId(InputChannelId&& other) = default;
|
||||
constexpr InputChannelId& operator=(const InputChannelId& other)
|
||||
{
|
||||
m_name = other.m_name;
|
||||
m_crc32 = other.m_crc32;
|
||||
return *this;
|
||||
}
|
||||
constexpr InputChannelId& operator=(InputChannelId&& other)
|
||||
{
|
||||
m_name = AZStd::move(other.m_name);
|
||||
m_crc32 = AZStd::move(other.m_crc32);
|
||||
other.m_crc32 = 0;
|
||||
return *this;
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Default copying and moving
|
||||
AZ_DEFAULT_COPY_MOVE(InputChannelId);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Default destructor
|
||||
~InputChannelId() = default;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Access to the input channel's name
|
||||
//! \return Name of the input channel
|
||||
const char* GetName() const;
|
||||
constexpr const char* GetName() const
|
||||
{
|
||||
return m_name.c_str();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Access to the crc32 of the input channel's name
|
||||
//! \return crc32 of the input channel name
|
||||
const AZ::Crc32& GetNameCrc32() const;
|
||||
constexpr const AZ::Crc32& GetNameCrc32() const
|
||||
{
|
||||
return m_crc32;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///@{
|
||||
//! Equality comparison operator
|
||||
//! \param[in] other Another instance of the class to compare for equality
|
||||
bool operator==(const InputChannelId& other) const;
|
||||
bool operator!=(const InputChannelId& other) const;
|
||||
///@}
|
||||
constexpr bool operator==(const InputChannelId& other) const
|
||||
{
|
||||
return m_crc32 == other.m_crc32;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Inequality comparison operator
|
||||
//! \param[in] other Another instance of the class to compare for inequality
|
||||
constexpr bool operator!=(const InputChannelId& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Variables
|
||||
AZStd::fixed_string<MAX_NAME_LENGTH> m_name; //!< Name of the input channel
|
||||
AZ::Crc32 m_crc32; //!< Crc32 of the input channel
|
||||
AZ::Crc32 m_crc32; //!< Crc32 of the input channel name
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
|
||||
@@ -14,14 +14,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const char* InputDeviceGamepad::Name("gamepad");
|
||||
const InputDeviceId InputDeviceGamepad::IdForIndex0(Name, 0);
|
||||
const InputDeviceId InputDeviceGamepad::IdForIndex1(Name, 1);
|
||||
const InputDeviceId InputDeviceGamepad::IdForIndex2(Name, 2);
|
||||
const InputDeviceId InputDeviceGamepad::IdForIndex3(Name, 3);
|
||||
const InputDeviceId InputDeviceGamepad::IdForIndexN(AZ::u32 n) { return InputDeviceId(Name, n); }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceGamepad::IsGamepadDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
@@ -32,16 +32,16 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The name used to identify any game-pad input device
|
||||
static const char* Name;
|
||||
static constexpr inline const char* Name{"gamepad"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify a game-pad input device with a specific index
|
||||
///@{
|
||||
static const InputDeviceId IdForIndex0;
|
||||
static const InputDeviceId IdForIndex1;
|
||||
static const InputDeviceId IdForIndex2;
|
||||
static const InputDeviceId IdForIndex3;
|
||||
static const InputDeviceId IdForIndexN(AZ::u32 n);
|
||||
static constexpr inline InputDeviceId IdForIndex0{Name, 0};
|
||||
static constexpr inline InputDeviceId IdForIndex1{Name, 1};
|
||||
static constexpr inline InputDeviceId IdForIndex2{Name, 2};
|
||||
static constexpr inline InputDeviceId IdForIndex3{Name, 3};
|
||||
static constexpr inline InputDeviceId IdForIndexN(AZ::u32 n) { return InputDeviceId(Name, n); }
|
||||
///@}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -29,71 +29,4 @@ namespace AzFramework
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
InputDeviceId::InputDeviceId(const char* name, AZ::u32 index)
|
||||
: m_crc32(name)
|
||||
, m_index(index)
|
||||
{
|
||||
memset(m_name, 0, AZ_ARRAY_SIZE(m_name));
|
||||
azstrncpy(m_name, NAME_BUFFER_SIZE, name, MAX_NAME_LENGTH);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
InputDeviceId::InputDeviceId(const InputDeviceId& other)
|
||||
: m_crc32(other.m_crc32)
|
||||
, m_index(other.m_index)
|
||||
{
|
||||
memset(m_name, 0, AZ_ARRAY_SIZE(m_name));
|
||||
azstrcpy(m_name, NAME_BUFFER_SIZE, other.m_name);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
InputDeviceId& InputDeviceId::operator=(const InputDeviceId& other)
|
||||
{
|
||||
azstrcpy(m_name, NAME_BUFFER_SIZE, other.m_name);
|
||||
m_crc32 = other.m_crc32;
|
||||
m_index = other.m_index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const char* InputDeviceId::GetName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const AZ::Crc32& InputDeviceId::GetNameCrc32() const
|
||||
{
|
||||
return m_crc32;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
AZ::u32 InputDeviceId::GetIndex() const
|
||||
{
|
||||
return m_index;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceId::operator==(const InputDeviceId& other) const
|
||||
{
|
||||
return (m_crc32 == other.m_crc32) && (m_index == other.m_index);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceId::operator!=(const InputDeviceId& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceId::operator<(const InputDeviceId& other) const
|
||||
{
|
||||
if (m_index == other.m_index)
|
||||
{
|
||||
return m_crc32 < other.m_crc32;
|
||||
}
|
||||
return m_index < other.m_index;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <AzCore/Math/Crc.h>
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/std/hash.h>
|
||||
#include <AzCore/std/string/fixed_string.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
@@ -22,8 +23,7 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constants
|
||||
static const int NAME_BUFFER_SIZE = 64;
|
||||
static const int MAX_NAME_LENGTH = NAME_BUFFER_SIZE - 1;
|
||||
static constexpr int MAX_NAME_LENGTH = 64;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Allocator
|
||||
@@ -41,17 +41,16 @@ namespace AzFramework
|
||||
//! Constructor
|
||||
//! \param[in] name Name of the input device (will be truncated if exceeds MAX_NAME_LENGTH)
|
||||
//! \param[in] index Index of the input device (optional)
|
||||
explicit InputDeviceId(const char* name, AZ::u32 index = 0);
|
||||
explicit constexpr InputDeviceId(AZStd::string_view name, AZ::u32 index = 0)
|
||||
: m_name(name.substr(0, MAX_NAME_LENGTH))
|
||||
, m_crc32(name.substr(0, MAX_NAME_LENGTH))
|
||||
, m_index(index)
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Copy constructor
|
||||
//! \param[in] other Another instance of the class to copy from
|
||||
InputDeviceId(const InputDeviceId& other);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Copy assignment operator
|
||||
//! \param[in] other Another instance of the class to copy from
|
||||
InputDeviceId& operator=(const InputDeviceId& other);
|
||||
// Default copying and moving
|
||||
AZ_DEFAULT_COPY_MOVE(InputDeviceId);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Default destructor
|
||||
@@ -60,12 +59,18 @@ namespace AzFramework
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Access to the input device's name
|
||||
//! \return Name of the input device
|
||||
const char* GetName() const;
|
||||
constexpr const char* GetName() const
|
||||
{
|
||||
return m_name.c_str();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Access to the crc32 of the input device's name
|
||||
//! \return crc32 of the input device name
|
||||
const AZ::Crc32& GetNameCrc32() const;
|
||||
constexpr const AZ::Crc32& GetNameCrc32() const
|
||||
{
|
||||
return m_crc32;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Access to the input device's index. Used for differentiating between multiple instances
|
||||
@@ -75,27 +80,45 @@ namespace AzFramework
|
||||
//! at startup using indicies 0->3. As gamepads connect/disconnect at runtime we assign the
|
||||
//! appropriate (system dependent) local user id (see InputDevice::GetAssignedLocalUserId).
|
||||
//! \return Index of the input device
|
||||
AZ::u32 GetIndex() const;
|
||||
constexpr AZ::u32 GetIndex() const
|
||||
{
|
||||
return m_index;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///@{
|
||||
//! Equality comparison operator
|
||||
//! \param[in] other Another instance of the class to compare for equality
|
||||
bool operator==(const InputDeviceId& other) const;
|
||||
bool operator!=(const InputDeviceId& other) const;
|
||||
///@}
|
||||
constexpr bool operator==(const InputDeviceId& other) const
|
||||
{
|
||||
return (m_crc32 == other.m_crc32) && (m_index == other.m_index);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Inequality comparison operator
|
||||
//! \param[in] other Another instance of the class to compare for inequality
|
||||
constexpr bool operator!=(const InputDeviceId& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Less than comparison operator
|
||||
//! \param[in] other Another instance of the class to compare
|
||||
bool operator<(const InputDeviceId& other) const;
|
||||
constexpr bool operator<(const InputDeviceId& other) const
|
||||
{
|
||||
if (m_index == other.m_index)
|
||||
{
|
||||
return m_crc32 < other.m_crc32;
|
||||
}
|
||||
return m_index < other.m_index;
|
||||
}
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Variables
|
||||
char m_name[NAME_BUFFER_SIZE]; //!< Name of the input device
|
||||
AZ::Crc32 m_crc32; //!< Crc32 of the input device
|
||||
AZ::u32 m_index; //!< Index of the input device
|
||||
AZStd::fixed_string<MAX_NAME_LENGTH> m_name; //!< Name of the input device
|
||||
AZ::Crc32 m_crc32; //!< Crc32 of the input device name
|
||||
AZ::u32 m_index; //!< Index of the input device
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const InputDeviceId InputDeviceKeyboard::Id("keyboard");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceKeyboard::IsKeyboardDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify the primary physical keyboard input device
|
||||
static const InputDeviceId Id;
|
||||
static constexpr inline InputDeviceId Id{"keyboard"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Check whether an input device id identifies a physical keyboard (regardless of index)
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const InputDeviceId InputDeviceMotion::Id("motion");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceMotion::IsMotionDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify the primary motion input device
|
||||
static const InputDeviceId Id;
|
||||
static constexpr inline InputDeviceId Id{"motion"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Check whether an input device id identifies a motion device (regardless of index)
|
||||
|
||||
@@ -15,18 +15,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const AZ::u32 InputDeviceMouse::MovementSampleRateDefault = 60;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const AZ::u32 InputDeviceMouse::MovementSampleRateQueueAll = std::numeric_limits<AZ::u32>::max();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const AZ::u32 InputDeviceMouse::MovementSampleRateAccumulateAll = 0;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const InputDeviceId InputDeviceMouse::Id("mouse");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceMouse::IsMouseDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
@@ -31,23 +31,23 @@ namespace AzFramework
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Default sample rate for raw mouse movement events that aims to strike a balance between
|
||||
//! responsiveness and performance.
|
||||
static const AZ::u32 MovementSampleRateDefault;
|
||||
static constexpr inline AZ::u32 MovementSampleRateDefault{60};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Sample rate for raw mouse movement that will cause all events received in the same frame
|
||||
//! to be queued and dispatched as individual events. This results in maximum responsiveness
|
||||
//! but may potentially impact performance depending how many events happen over each frame.
|
||||
static const AZ::u32 MovementSampleRateQueueAll;
|
||||
static constexpr inline AZ::u32 MovementSampleRateQueueAll{std::numeric_limits<AZ::u32>::max()};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Sample rate for raw mouse movement that will cause all events received in the same frame
|
||||
//! to be accumulated and dispatched as a single event. Optimal for performance, but results
|
||||
//! in sluggish/unresponsive mouse movement, especially when running at low frame rates.
|
||||
static const AZ::u32 MovementSampleRateAccumulateAll;
|
||||
static constexpr inline AZ::u32 MovementSampleRateAccumulateAll{0};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify the primary mouse input device
|
||||
static const InputDeviceId Id;
|
||||
static constexpr inline InputDeviceId Id{"mouse"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Check whether an input device id identifies a mouse (regardless of index)
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const InputDeviceId InputDeviceTouch::Id("touch");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceTouch::IsTouchDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify the primary touch input device
|
||||
static const InputDeviceId Id;
|
||||
static constexpr inline InputDeviceId Id{"touch"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Check whether an input device id identifies a touch device (regardless of index)
|
||||
|
||||
-3
@@ -14,9 +14,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const InputDeviceId InputDeviceVirtualKeyboard::Id("virtual_keyboard");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceVirtualKeyboard::IsVirtualKeyboardDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify the primary virtual keyboard input device
|
||||
static const InputDeviceId Id;
|
||||
static constexpr inline InputDeviceId Id{"virtual_keyboard"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Check whether an input device id identifies a virtual keyboard (regardless of index)
|
||||
|
||||
@@ -30,12 +30,22 @@ namespace AzFramework
|
||||
|
||||
//! Called when the root spawnable has been assigned a new value. This may be called several times without a call to release
|
||||
//! in between.
|
||||
//! @note: The callback is not queued but immediately called from a random thread. This is done because this callback is typically
|
||||
//! used before entities are spawned and if it's queued then the entities spawn before this callback is called.
|
||||
//! @param rootSpawnable The new root spawnable that was assigned.
|
||||
//! @param generation The generation of the root spawnable. This will increment every time a new spawnable is assigned.
|
||||
virtual void OnRootSpawnableAssigned([[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable,
|
||||
[[maybe_unused]] uint32_t generation) {}
|
||||
//! Called when the root spawnable has completed spawning of entities. This may be called several times without a call to release
|
||||
//! in between.
|
||||
//! @note: This callback is queued and will be called with a delay and from the main thread.
|
||||
//! @param rootSpawnable The new root spawnable that was used to spawn entities from.
|
||||
//! @param generation The generation of the root spawnable. This will increment every time a new spawnable is assigned.
|
||||
virtual void OnRootSpawnableReady(
|
||||
[[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation) {}
|
||||
//! Called when the root spawnable has Released. This will only be called if there's no root spawnable assigned to take the
|
||||
//! place of the original root spawnable.
|
||||
//! Note: This callback is queued and will be called with a delay and from the main thread.
|
||||
//! @param generation The generation of the root spawnable that was released.
|
||||
virtual void OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) {}
|
||||
};
|
||||
|
||||
@@ -6,12 +6,473 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Asset/AssetSerializer.h>
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/numeric.h>
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzCore/std/typetraits/typetraits.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
//
|
||||
// EntityAlias
|
||||
//
|
||||
|
||||
bool Spawnable::EntityAlias::HasLowerIndex(const EntityAlias& other) const
|
||||
{
|
||||
return m_sourceIndex == other.m_sourceIndex ?
|
||||
m_aliasType < other.m_aliasType :
|
||||
m_sourceIndex < other.m_sourceIndex;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// EntityAliasVisitorBase
|
||||
//
|
||||
|
||||
bool Spawnable::EntityAliasVisitorBase::IsValid(const EntityAliasList* aliases) const
|
||||
{
|
||||
return aliases != nullptr;
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasVisitorBase::HasAliases(const EntityAliasList* aliases) const
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
return !aliases->empty();
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasVisitorBase::AreAllSpawnablesReady(const EntityAliasList* aliases) const
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
for (const EntityAlias& alias : *aliases)
|
||||
{
|
||||
if (!alias.m_queueLoad ||
|
||||
alias.m_aliasType == Spawnable::EntityAliasType::Original ||
|
||||
alias.m_aliasType == Spawnable::EntityAliasType::Disable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!alias.m_spawnable.IsReady() && !alias.m_spawnable.IsError())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitorBase::begin(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
return aliases->cbegin();
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitorBase::end(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
return aliases->cend();
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitorBase::cbegin(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
return aliases->cbegin();
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitorBase::cend(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
return aliases->cend();
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitorBase::ListTargetSpawnables(
|
||||
const EntityAliasList* aliases, const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
AZStd::unordered_set<AZ::Data::AssetId> spawnableIds;
|
||||
for (const Spawnable::EntityAlias& alias : *aliases)
|
||||
{
|
||||
// If the spawnable id is not valid it means that the alias is referencing the spawnable it's stored on.
|
||||
if (alias.m_spawnable.GetId().IsValid())
|
||||
{
|
||||
auto it = spawnableIds.find(alias.m_spawnable.GetId());
|
||||
if (it == spawnableIds.end())
|
||||
{
|
||||
callback(alias.m_spawnable);
|
||||
spawnableIds.emplace(alias.m_spawnable.GetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitorBase::ListTargetSpawnables(
|
||||
const EntityAliasList* aliases, AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
AZStd::unordered_set<AZ::Data::AssetId> spawnableIds;
|
||||
for (const Spawnable::EntityAlias& alias : *aliases)
|
||||
{
|
||||
// If the spawnable id is not valid it means that the alias is referencing the spawnable it's stored on.
|
||||
if (alias.m_tag == tag && alias.m_spawnable.GetId().IsValid())
|
||||
{
|
||||
auto it = spawnableIds.find(alias.m_spawnable.GetId());
|
||||
if (it == spawnableIds.end())
|
||||
{
|
||||
callback(alias.m_spawnable);
|
||||
spawnableIds.emplace(alias.m_spawnable.GetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// EntityAliasVisitor
|
||||
//
|
||||
|
||||
Spawnable::EntityAliasVisitor::EntityAliasVisitor(Spawnable& owner, EntityAliasList* entityAliasList)
|
||||
: m_owner(owner)
|
||||
, m_entityAliasList(entityAliasList)
|
||||
{
|
||||
}
|
||||
|
||||
Spawnable::EntityAliasVisitor::~EntityAliasVisitor()
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
Optimize();
|
||||
|
||||
AZ_Assert(
|
||||
m_owner.m_shareState == ShareState::ReadWrite, "Attempting to unlock a spawnable that's not in the locked state (%i).",
|
||||
m_owner.m_shareState.load());
|
||||
m_owner.m_shareState = ShareState::NotShared;
|
||||
}
|
||||
}
|
||||
|
||||
Spawnable::EntityAliasVisitor::EntityAliasVisitor(EntityAliasVisitor&& rhs)
|
||||
: m_owner(rhs.m_owner)
|
||||
, m_entityAliasList(rhs.m_entityAliasList)
|
||||
{
|
||||
m_dirty = rhs.m_dirty;
|
||||
|
||||
rhs.m_entityAliasList = nullptr;
|
||||
rhs.m_dirty = false;
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitor::operator=(EntityAliasVisitor&& rhs) -> EntityAliasVisitor&
|
||||
{
|
||||
if (this != &rhs)
|
||||
{
|
||||
this->~EntityAliasVisitor();
|
||||
new(this) EntityAliasVisitor(AZStd::move(rhs));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasVisitor::IsValid() const
|
||||
{
|
||||
return EntityAliasVisitorBase::IsValid(m_entityAliasList);
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasVisitor::HasAliases() const
|
||||
{
|
||||
return EntityAliasVisitorBase::HasAliases(m_entityAliasList);
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasVisitor::AreAllSpawnablesReady() const
|
||||
{
|
||||
return EntityAliasVisitorBase::AreAllSpawnablesReady(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitor::begin() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::begin(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitor::end() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::end(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitor::cbegin() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::cbegin(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitor::cend() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::cend(m_entityAliasList);
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, callback);
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, tag, callback);
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::AddAlias(
|
||||
AZ::Data::Asset<Spawnable> targetSpawnable,
|
||||
AZ::Crc32 tag,
|
||||
uint32_t sourceIndex,
|
||||
uint32_t targetIndex,
|
||||
Spawnable::EntityAliasType aliasType,
|
||||
bool queueLoad)
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
AZ_Assert(sourceIndex < m_owner.GetEntities().size(), "Invalid source index (%i) for entity alias", sourceIndex);
|
||||
if (targetSpawnable.IsReady())
|
||||
{
|
||||
AZ_Assert(
|
||||
targetIndex < targetSpawnable->GetEntities().size(), "Invalid target index (%i) for entity alias '%s'", targetIndex,
|
||||
targetSpawnable.GetHint().c_str());
|
||||
}
|
||||
|
||||
m_entityAliasList->push_back(Spawnable::EntityAlias{ targetSpawnable, tag, sourceIndex, targetIndex, aliasType, queueLoad });
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::ListSpawnablesRequiringLoad(const ListSpawnablesRequiringLoadCallback& callback)
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
for (Spawnable::EntityAlias& alias : *m_entityAliasList)
|
||||
{
|
||||
if (alias.m_queueLoad &&
|
||||
alias.m_aliasType != Spawnable::EntityAliasType::Original &&
|
||||
alias.m_aliasType != Spawnable::EntityAliasType::Disable &&
|
||||
!alias.m_spawnable.IsLoading() &&
|
||||
!alias.m_spawnable.IsReady() &&
|
||||
!alias.m_spawnable.IsError())
|
||||
{
|
||||
callback(alias.m_spawnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::UpdateAliases(const UpdateCallback& callback)
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
for (Spawnable::EntityAlias& alias : *m_entityAliasList)
|
||||
{
|
||||
AZ::Data::Asset<Spawnable> targetSpawnable(alias.m_spawnable);
|
||||
callback(alias.m_aliasType, alias.m_queueLoad, targetSpawnable, alias.m_tag, alias.m_sourceIndex, alias.m_targetIndex);
|
||||
}
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::UpdateAliases(AZ::Crc32 tag, const UpdateCallback& callback)
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
for (Spawnable::EntityAlias& alias : *m_entityAliasList)
|
||||
{
|
||||
if (alias.m_tag == tag)
|
||||
{
|
||||
AZ::Data::Asset<Spawnable> targetSpawnable(alias.m_spawnable);
|
||||
callback(alias.m_aliasType, alias.m_queueLoad, targetSpawnable, alias.m_tag, alias.m_sourceIndex, alias.m_targetIndex);
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::UpdateAliasType(uint32_t index, Spawnable::EntityAliasType newType)
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
AZ_Assert(
|
||||
index < m_entityAliasList->size(), "Unable to update entity alias at index %i as there are only %zu aliases in spawnable.",
|
||||
index, m_entityAliasList->size());
|
||||
(*m_entityAliasList)[index].m_aliasType = newType;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::Optimize()
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
if (m_dirty)
|
||||
{
|
||||
AZStd::stable_sort(
|
||||
m_entityAliasList->begin(), m_entityAliasList->end(),
|
||||
[](const Spawnable::EntityAlias& lhs, const Spawnable::EntityAlias& rhs)
|
||||
{
|
||||
// Sort by source index from smallest to largest so during spawning the entities can be iterated linearly over.
|
||||
// If the source index is the same then sort by alias type so the next steps can optimize away superfluous steps.
|
||||
return lhs.HasLowerIndex(rhs);
|
||||
});
|
||||
|
||||
// Remove aliases that are not going to have any practical effect and insert aliases where needed to simplify the spawning.
|
||||
// This is done at runtime rather than at build time because the above ebus allows other systems to make adjustments to the
|
||||
// aliases, for instance Networking can decide to disable certain aliases when running on a client. This in turn also requires
|
||||
// the aliases to be in their recorded order during building as the ebus handlers may depend on that order to determine what
|
||||
// entities need to be updated.
|
||||
uint32_t previousIndex = AZStd::numeric_limits<uint32_t>::max();
|
||||
Spawnable::EntityAliasType previousType =
|
||||
static_cast<Spawnable::EntityAliasType>(AZStd::numeric_limits<AZStd::underlying_type_t<Spawnable::EntityAliasType>>::max());
|
||||
Spawnable::EntityAlias* it = m_entityAliasList->begin();
|
||||
Spawnable::EntityAlias* end = m_entityAliasList->end();
|
||||
while (it < end)
|
||||
{
|
||||
// If there's a switch to a new source index and the previous index only had an original it can
|
||||
// be removed.
|
||||
if (previousType == Spawnable::EntityAliasType::Original && previousIndex != it->m_sourceIndex)
|
||||
{
|
||||
it = m_entityAliasList->erase(it - 1);
|
||||
end = m_entityAliasList->end();
|
||||
if (it == end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (it->m_aliasType)
|
||||
{
|
||||
case Spawnable::EntityAliasType::Original:
|
||||
[[fallthrough]];
|
||||
case Spawnable::EntityAliasType::Disable:
|
||||
[[fallthrough]];
|
||||
case Spawnable::EntityAliasType::Replace:
|
||||
// If the previous entry was a disabled, original or replace alias then remove it as it will be overwritten by the
|
||||
// current entry.
|
||||
if (previousIndex == it->m_sourceIndex &&
|
||||
(previousType == Spawnable::EntityAliasType::Original ||
|
||||
previousType == Spawnable::EntityAliasType::Disable ||
|
||||
previousType == Spawnable::EntityAliasType::Replace))
|
||||
{
|
||||
previousIndex = it->m_sourceIndex;
|
||||
previousType = it->m_aliasType;
|
||||
// Erase instead of a swap-and-pop in order to preserver the order.
|
||||
it = m_entityAliasList->erase(it - 1) + 1;
|
||||
end = m_entityAliasList->end();
|
||||
}
|
||||
else
|
||||
{
|
||||
previousIndex = it->m_sourceIndex;
|
||||
previousType = it->m_aliasType;
|
||||
++it;
|
||||
}
|
||||
break;
|
||||
case Spawnable::EntityAliasType::Additional:
|
||||
[[fallthrough]];
|
||||
case Spawnable::EntityAliasType::Merge:
|
||||
// If this is the first entry for this index then insert an original in front of it so the spawnable entity manager
|
||||
// doesn't have to check for the case there's a merge and/or addition without an entity to extend.
|
||||
if (previousIndex != it->m_sourceIndex)
|
||||
{
|
||||
Spawnable::EntityAlias insert;
|
||||
// No load, as the asset is already loaded.
|
||||
insert.m_spawnable = AZ::Data::Asset<Spawnable>({}, azrtti_typeid<Spawnable>());
|
||||
insert.m_sourceIndex = it->m_sourceIndex;
|
||||
insert.m_targetIndex = it->m_sourceIndex; // Source index as the original entry for this slot is added.
|
||||
insert.m_aliasType = Spawnable::EntityAliasType::Original;
|
||||
|
||||
previousIndex = it->m_sourceIndex;
|
||||
previousType = it->m_aliasType;
|
||||
|
||||
// Insert to maintain the order.
|
||||
it = m_entityAliasList->insert(it, AZStd::move(insert));
|
||||
it += 2;
|
||||
end = m_entityAliasList->end();
|
||||
}
|
||||
else
|
||||
{
|
||||
previousType = it->m_aliasType;
|
||||
++it;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "Invalid Spawnable entity alias type found during asset loading: %i", it->m_aliasType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the last entry is an "Original" in which case it can be removed.
|
||||
if (!m_entityAliasList->empty() && m_entityAliasList->back().m_aliasType == Spawnable::EntityAliasType::Original)
|
||||
{
|
||||
m_entityAliasList->pop_back();
|
||||
}
|
||||
|
||||
// Reclaim memory because after this point the aliases will not change anymore.
|
||||
m_entityAliasList->shrink_to_fit();
|
||||
m_dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// EntityAliasConstVisitor
|
||||
//
|
||||
|
||||
Spawnable::EntityAliasConstVisitor::EntityAliasConstVisitor(const Spawnable& owner, const EntityAliasList* entityAliasList)
|
||||
: m_owner(owner)
|
||||
, m_entityAliasList(entityAliasList)
|
||||
{
|
||||
}
|
||||
|
||||
Spawnable::EntityAliasConstVisitor::~EntityAliasConstVisitor()
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
AZ_Assert(
|
||||
m_owner.m_shareState <= ShareState::Read, "Attempting to unlock a read shared spawnable that was not in a read shared mode (%i).",
|
||||
m_owner.m_shareState.load());
|
||||
m_owner.m_shareState++;
|
||||
}
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasConstVisitor::IsValid() const
|
||||
{
|
||||
return EntityAliasVisitorBase::IsValid(m_entityAliasList);
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasConstVisitor::HasAliases() const
|
||||
{
|
||||
return EntityAliasVisitorBase::HasAliases(m_entityAliasList);
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasConstVisitor::AreAllSpawnablesReady() const
|
||||
{
|
||||
return EntityAliasVisitorBase::AreAllSpawnablesReady(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasConstVisitor::begin() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::begin(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasConstVisitor::end() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::end(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasConstVisitor::cbegin() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::cbegin(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasConstVisitor::cend() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::cend(m_entityAliasList);
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasConstVisitor::ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, callback);
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasConstVisitor::ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, tag, callback);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Spawnable
|
||||
//
|
||||
|
||||
Spawnable::Spawnable(const AZ::Data::AssetId& id, AssetStatus status)
|
||||
: AZ::Data::AssetData(id, status)
|
||||
{
|
||||
@@ -27,6 +488,33 @@ namespace AzFramework
|
||||
return m_entities;
|
||||
}
|
||||
|
||||
auto Spawnable::TryGetAliasesConst() const -> EntityAliasConstVisitor
|
||||
{
|
||||
int32_t expected = ShareState::NotShared;
|
||||
do
|
||||
{
|
||||
// Try to set the lock to a negative number to indicate a shared read.
|
||||
if (m_shareState.compare_exchange_strong(expected, expected - 1))
|
||||
{
|
||||
return EntityAliasConstVisitor(*this, &m_entityAliases);
|
||||
}
|
||||
// as long as the value is negative or not shared then keep trying to get a shared read lock.
|
||||
} while (expected <= 0);
|
||||
return EntityAliasConstVisitor(*this, nullptr);
|
||||
}
|
||||
|
||||
auto Spawnable::TryGetAliases() const -> EntityAliasConstVisitor
|
||||
{
|
||||
return TryGetAliasesConst();
|
||||
}
|
||||
|
||||
auto Spawnable::TryGetAliases() -> EntityAliasVisitor
|
||||
{
|
||||
int32_t expected = ShareState::NotShared;
|
||||
return m_shareState.compare_exchange_strong(expected, ShareState::ReadWrite) ? EntityAliasVisitor(*this, &m_entityAliases)
|
||||
: EntityAliasVisitor(*this, nullptr);
|
||||
}
|
||||
|
||||
bool Spawnable::IsEmpty() const
|
||||
{
|
||||
return m_entities.empty();
|
||||
@@ -44,11 +532,29 @@ namespace AzFramework
|
||||
|
||||
void Spawnable::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
EntityAlias::Reflect(context);
|
||||
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
serializeContext->Class<Spawnable, AZ::Data::AssetData>()->Version(1)
|
||||
serializeContext->Class<Spawnable, AZ::Data::AssetData>()->Version(2)
|
||||
->Field("Meta data", &Spawnable::m_metaData)
|
||||
->Field("Entity aliases", &Spawnable::m_entityAliases)
|
||||
->Field("Entities", &Spawnable::m_entities);
|
||||
}
|
||||
}
|
||||
|
||||
void Spawnable::EntityAlias::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
serializeContext->Class<Spawnable::EntityAlias>()
|
||||
->Version(1)
|
||||
->Field("Spawnable", &EntityAlias::m_spawnable)
|
||||
->Field("Tag", &EntityAlias::m_tag)
|
||||
->Field("Source Index", &EntityAlias::m_sourceIndex)
|
||||
->Field("Target Index", &EntityAlias::m_targetIndex)
|
||||
->Field("Alias Type", &EntityAlias::m_aliasType)
|
||||
->Field("Queue Load", &EntityAlias::m_queueLoad);
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/std/parallel/atomic.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzFramework/Spawnable/SpawnableMetaData.h>
|
||||
@@ -29,7 +30,148 @@ namespace AzFramework
|
||||
AZ_CLASS_ALLOCATOR(Spawnable, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(AzFramework::Spawnable, "{855E3021-D305-4845-B284-20C3F7FDF16B}", AZ::Data::AssetData);
|
||||
|
||||
// The order is important for sorting in the SpawnableAssetHandler.
|
||||
enum class EntityAliasType : uint8_t
|
||||
{
|
||||
Original, //!< The original entity is spawned.
|
||||
Disable, //!< No entity will be spawned.
|
||||
Replace, //!< The entity alias is spawned instead of the original.
|
||||
Additional, //!< The original entity is spawned as well as the alias. The alias will get a new entity id.
|
||||
Merge //!< The original entity is spawned and the components of the alias are added. The caller is responsible for
|
||||
//!< maintaining a valid component list.
|
||||
};
|
||||
|
||||
enum ShareState : int32_t
|
||||
{
|
||||
Read = -1,
|
||||
NotShared = 0,
|
||||
ReadWrite = 1
|
||||
};
|
||||
|
||||
//! An entity alias redirects the spawning of an entity to another entity, possibly in another spawnable.
|
||||
struct EntityAlias
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(EntityAlias, AZ::SystemAllocator, 0);
|
||||
AZ_TYPE_INFO(AzFramework::Spawnable::EntityAlias, "{C8D0C5BC-1F0B-4572-98C1-73B2CA8C9356}");
|
||||
|
||||
bool HasLowerIndex(const EntityAlias& other) const;
|
||||
|
||||
AZ::Data::Asset<Spawnable> m_spawnable; //!< The spawnable containing the target entity to spawn.
|
||||
uint32_t m_tag{ 0 }; //!< A unique tag to identify this alias with.
|
||||
uint32_t m_sourceIndex{ 0 }; //!< The index of the entity in the original spawnable that will be replaced.
|
||||
uint32_t m_targetIndex{ 0 }; //!< The index of the entity in the target spawnable that will be used to replace the original.
|
||||
EntityAliasType m_aliasType{ EntityAliasType::Original }; //!< The kind of replacement.
|
||||
bool m_queueLoad{ false }; //!< Whether or not to automatically queue the spawnable for loading.
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
};
|
||||
|
||||
using EntityList = AZStd::vector<AZStd::unique_ptr<AZ::Entity>>;
|
||||
using EntityAliasList = AZStd::vector<EntityAlias>;
|
||||
|
||||
private:
|
||||
class EntityAliasVisitorBase
|
||||
{
|
||||
protected:
|
||||
bool IsValid(const EntityAliasList* aliases) const;
|
||||
|
||||
bool HasAliases(const EntityAliasList* aliases) const;
|
||||
bool AreAllSpawnablesReady(const EntityAliasList* aliases) const;
|
||||
|
||||
EntityAliasList::const_iterator begin(const EntityAliasList* aliases) const;
|
||||
EntityAliasList::const_iterator end(const EntityAliasList* aliases) const;
|
||||
EntityAliasList::const_iterator cbegin(const EntityAliasList* aliases) const;
|
||||
EntityAliasList::const_iterator cend(const EntityAliasList* aliases) const;
|
||||
|
||||
using ListTargetSpawanblesCallback = AZStd::function<void(const AZ::Data::Asset<Spawnable>& targetSpawnable)>;
|
||||
void ListTargetSpawnables(const EntityAliasList* aliases, const ListTargetSpawanblesCallback& callback) const;
|
||||
void ListTargetSpawnables(const EntityAliasList* aliases, AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const;
|
||||
};
|
||||
|
||||
public:
|
||||
class EntityAliasVisitor final : public EntityAliasVisitorBase
|
||||
{
|
||||
public:
|
||||
EntityAliasVisitor(Spawnable& owner, EntityAliasList* m_entityAliasList);
|
||||
~EntityAliasVisitor();
|
||||
|
||||
EntityAliasVisitor(EntityAliasVisitor&& rhs);
|
||||
EntityAliasVisitor& operator=(EntityAliasVisitor&& rhs);
|
||||
|
||||
EntityAliasVisitor(const EntityAliasVisitor& rhs) = delete;
|
||||
EntityAliasVisitor& operator=(const EntityAliasVisitor& rhs) = delete;
|
||||
|
||||
//! Checks if the visitor was able to retrieve data. This needs to be checked before calling any other functions.
|
||||
bool IsValid() const;
|
||||
|
||||
bool HasAliases() const;
|
||||
bool AreAllSpawnablesReady() const;
|
||||
|
||||
// Modification of aliases is limited to specific changes that can only be done through the available modification functions.
|
||||
// For this reason access through iterators is limited to unmodifiable constant iterators.
|
||||
|
||||
EntityAliasList::const_iterator begin() const;
|
||||
EntityAliasList::const_iterator end() const;
|
||||
EntityAliasList::const_iterator cbegin() const;
|
||||
EntityAliasList::const_iterator cend() const;
|
||||
|
||||
void ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const;
|
||||
void ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const;
|
||||
|
||||
void AddAlias(
|
||||
AZ::Data::Asset<Spawnable> targetSpawnable,
|
||||
AZ::Crc32 tag,
|
||||
uint32_t sourceIndex,
|
||||
uint32_t targetIndex,
|
||||
Spawnable::EntityAliasType aliasType,
|
||||
bool queueLoad);
|
||||
|
||||
using ListSpawnablesRequiringLoadCallback = AZStd::function<void(AZ::Data::Asset<Spawnable>& spawnablePendingLoad)>;
|
||||
void ListSpawnablesRequiringLoad(const ListSpawnablesRequiringLoadCallback& callback);
|
||||
|
||||
using UpdateCallback = AZStd::function<void(
|
||||
Spawnable::EntityAliasType& aliasType,
|
||||
bool& queueLoad,
|
||||
const AZ::Data::Asset<Spawnable>& aliasedSpawnable,
|
||||
const AZ::Crc32 tag,
|
||||
const uint32_t sourceIndex,
|
||||
const uint32_t targetIndex)>;
|
||||
void UpdateAliases(const UpdateCallback& callback);
|
||||
void UpdateAliases(AZ::Crc32 tag, const UpdateCallback& callback);
|
||||
void UpdateAliasType(uint32_t index, Spawnable::EntityAliasType newType);
|
||||
|
||||
void Optimize();
|
||||
|
||||
private:
|
||||
Spawnable& m_owner;
|
||||
EntityAliasList* m_entityAliasList{ nullptr };
|
||||
bool m_dirty{ false };
|
||||
};
|
||||
|
||||
class EntityAliasConstVisitor final : public EntityAliasVisitorBase
|
||||
{
|
||||
public:
|
||||
EntityAliasConstVisitor(const Spawnable& owner, const EntityAliasList* entityAliasList);
|
||||
~EntityAliasConstVisitor();
|
||||
|
||||
//! Checks if the visitor was able to retrieve data. This needs to be checked before calling any other functions.
|
||||
bool IsValid() const;
|
||||
|
||||
bool HasAliases() const;
|
||||
bool AreAllSpawnablesReady() const;
|
||||
|
||||
EntityAliasList::const_iterator begin() const;
|
||||
EntityAliasList::const_iterator end() const;
|
||||
EntityAliasList::const_iterator cbegin() const;
|
||||
EntityAliasList::const_iterator cend() const;
|
||||
|
||||
void ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const;
|
||||
void ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const;
|
||||
|
||||
private:
|
||||
const Spawnable& m_owner;
|
||||
const EntityAliasList* m_entityAliasList;
|
||||
};
|
||||
|
||||
inline static constexpr const char* FileExtension = "spawnable";
|
||||
inline static constexpr const char* DotFileExtension = ".spawnable";
|
||||
@@ -45,6 +187,9 @@ namespace AzFramework
|
||||
|
||||
const EntityList& GetEntities() const;
|
||||
EntityList& GetEntities();
|
||||
EntityAliasConstVisitor TryGetAliasesConst() const;
|
||||
EntityAliasConstVisitor TryGetAliases() const;
|
||||
EntityAliasVisitor TryGetAliases();
|
||||
bool IsEmpty() const;
|
||||
|
||||
SpawnableMetaData& GetMetaData();
|
||||
@@ -55,11 +200,12 @@ namespace AzFramework
|
||||
private:
|
||||
SpawnableMetaData m_metaData;
|
||||
|
||||
// Aliases that optionally replace the ones stored in this spawnable.
|
||||
EntityAliasList m_entityAliases;
|
||||
// Container for keeping all entities of the prefab the Spawnable was created from.
|
||||
// Includes both direct and nested entities of the prefab.
|
||||
EntityList m_entities;
|
||||
|
||||
mutable AZStd::atomic<int32_t> m_shareState{ ShareState::NotShared };
|
||||
};
|
||||
|
||||
using SpawnableList = AZStd::vector<Spawnable>;
|
||||
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/std/parallel/mutex.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class SpawnableAssetEvents : public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
|
||||
//! Callback to allow the entity aliases in a spawnable to adjusted based on runtime requirements.
|
||||
//! This will be called by the Asset Manager as part of the creation of the spawnable asset from loaded file data. Any work done
|
||||
//! in this callback will be counted towards the maximum amount of time allocated to asset handlers to construct their assets,
|
||||
//! it's recommended to keep work done in this callback to a minimum and prefer delaying any complex processing.
|
||||
//!
|
||||
//! ALERT: Do not start blocking asset requests in this callback.
|
||||
//! Since this is part of the Asset Manager's asset streaming, doing a blocking load in this callback will cause the job
|
||||
//! processing the spawnable asset to locked out of doing any asset streaming work. If there are more spawnables doing
|
||||
//! this than there are job threads available the engine will enter a deadlock situation as no more assets can complete
|
||||
//! loading and no job threads become free as they're all waiting for assets to complete. It is however safe to queue
|
||||
//! an asset for loading.
|
||||
virtual void OnResolveAliases(
|
||||
Spawnable::EntityAliasVisitor& aliases, const SpawnableMetaData& metadata, const Spawnable::EntityList& entities) = 0;
|
||||
};
|
||||
|
||||
using SpawnableAssetEventsBus = AZ::EBus<SpawnableAssetEvents>;
|
||||
} // namespace AzFramework
|
||||
@@ -9,8 +9,10 @@
|
||||
#include <AzCore/Casting/lossy_cast.h>
|
||||
#include <AzCore/Serialization/Utils.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzFramework/Spawnable/SpawnableAssetHandler.h>
|
||||
#include <AzFramework/Spawnable/SpawnableAssetBus.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
@@ -52,6 +54,7 @@ namespace AzFramework
|
||||
AZ::ObjectStream::FilterDescriptor filter(assetLoadFilterCB);
|
||||
if (AZ::Utils::LoadObjectFromStreamInPlace(*stream, *spawnable, nullptr /*SerializeContext*/, filter))
|
||||
{
|
||||
ResolveEntityAliases(spawnable, asset, stream->GetStreamingDeadline(), stream->GetStreamingPriority(), assetLoadFilterCB);
|
||||
return AZ::Data::AssetHandler::LoadResult::LoadComplete;
|
||||
}
|
||||
else
|
||||
@@ -91,4 +94,41 @@ namespace AzFramework
|
||||
AZ::Uuid subIdHash = AZ::Uuid::CreateData(id.data(), id.size());
|
||||
return azlossy_caster(subIdHash.GetHash());
|
||||
}
|
||||
|
||||
void SpawnableAssetHandler::ResolveEntityAliases(
|
||||
Spawnable* spawnable,
|
||||
[[maybe_unused]] const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::chrono::milliseconds streamingDeadline,
|
||||
AZ::IO::IStreamerTypes::Priority streamingPriority,
|
||||
const AZ::Data::AssetFilterCB& assetLoadFilterCB)
|
||||
{
|
||||
Spawnable::EntityAliasVisitor aliases = spawnable->TryGetAliases();
|
||||
AZ_Assert(aliases.IsValid(), "Newly created Spawnable '%s' was already locked.", asset.GetHint().c_str());
|
||||
if (aliases.HasAliases())
|
||||
{
|
||||
AZ_Assert(
|
||||
AZStd::is_sorted(
|
||||
aliases.begin(), aliases.end(),
|
||||
[](const Spawnable::EntityAlias& lhs, const Spawnable::EntityAlias& rhs)
|
||||
{
|
||||
return lhs.HasLowerIndex(rhs);
|
||||
}),
|
||||
"Spawnable '%s' has an unsorted entity alias list.", asset.GetHint().c_str());
|
||||
|
||||
SpawnableAssetEventsBus::Broadcast(
|
||||
&SpawnableAssetEvents::OnResolveAliases, aliases, spawnable->GetMetaData(), spawnable->GetEntities());
|
||||
|
||||
// The aliases will only be optimized if OnResolveAliases has made any changes.
|
||||
aliases.Optimize();
|
||||
aliases.ListSpawnablesRequiringLoad(
|
||||
[&assetLoadFilterCB, streamingDeadline, streamingPriority](AZ::Data::Asset<Spawnable>& assetPendingLoad)
|
||||
{
|
||||
AZ::Data::AssetLoadParameters loadInfo;
|
||||
loadInfo.m_assetLoadFilterCB = assetLoadFilterCB;
|
||||
loadInfo.m_deadline = streamingDeadline;
|
||||
loadInfo.m_priority = streamingPriority;
|
||||
assetPendingLoad.QueueLoad(loadInfo);
|
||||
});
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -50,5 +50,13 @@ namespace AzFramework
|
||||
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
|
||||
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
|
||||
|
||||
private:
|
||||
void ResolveEntityAliases(
|
||||
class Spawnable* spawnable,
|
||||
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::chrono::milliseconds streamingDeadline,
|
||||
AZ::IO::IStreamerTypes::Priority streamingPriority,
|
||||
const AZ::Data::AssetFilterCB& assetLoadFilterCB);
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace AzFramework
|
||||
return m_threadData != nullptr;
|
||||
}
|
||||
|
||||
uint64_t SpawnableEntitiesContainer::GetCurrentGeneration() const
|
||||
uint32_t SpawnableEntitiesContainer::GetCurrentGeneration() const
|
||||
{
|
||||
return m_currentGeneration;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace AzFramework
|
||||
SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket);
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector<size_t> entityIndices)
|
||||
void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector<uint32_t> entityIndices)
|
||||
{
|
||||
AZ_Assert(m_threadData, "Calling SpawnEntities on a Spawnable container that's not set.");
|
||||
SpawnableEntitiesInterface::Get()->SpawnEntities(
|
||||
@@ -78,15 +78,21 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::Alert(AlertCallback callback)
|
||||
void SpawnableEntitiesContainer::Alert(AlertCallback callback, CheckIfSpawnableIsLoaded spawnableCheck)
|
||||
{
|
||||
AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set.");
|
||||
SpawnableEntitiesInterface::Get()->Barrier(
|
||||
m_threadData->m_spawnedEntitiesTicket,
|
||||
[generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id)
|
||||
{
|
||||
callback(generation);
|
||||
});
|
||||
auto callbackWrapper = [generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id)
|
||||
{
|
||||
callback(generation);
|
||||
};
|
||||
if (spawnableCheck == CheckIfSpawnableIsLoaded::No)
|
||||
{
|
||||
SpawnableEntitiesInterface::Get()->Barrier(m_threadData->m_spawnedEntitiesTicket, AZStd::move(callbackWrapper));
|
||||
}
|
||||
else
|
||||
{
|
||||
SpawnableEntitiesInterface::Get()->LoadBarrier(m_threadData->m_spawnedEntitiesTicket, AZStd::move(callbackWrapper));
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::Connect(AZ::Data::Asset<Spawnable> spawnable)
|
||||
|
||||
@@ -36,6 +36,12 @@ namespace AzFramework
|
||||
public:
|
||||
using AlertCallback = AZStd::function<void(uint32_t generation)>;
|
||||
|
||||
enum class CheckIfSpawnableIsLoaded : bool
|
||||
{
|
||||
Yes,
|
||||
No
|
||||
};
|
||||
|
||||
//! Constructs a new spawnables entity container that has not been connected.
|
||||
SpawnableEntitiesContainer() = default;
|
||||
//! Constructs a new spawnables entity container that connects to the provided spawnable.
|
||||
@@ -48,13 +54,13 @@ namespace AzFramework
|
||||
//! Returns a number that identifies the current generation of the container with. The completion callback can still receive
|
||||
//! calls from older generations as processing completes on those. The returned value can be used to help calls tell
|
||||
//! older versions apart from newer ones.
|
||||
[[nodiscard]] uint64_t GetCurrentGeneration() const;
|
||||
[[nodiscard]] uint32_t GetCurrentGeneration() const;
|
||||
|
||||
//! Puts in a request to spawn entities using all entities in the provided spawnable as a template.
|
||||
void SpawnAllEntities();
|
||||
//! Puts in a request to spawn entities using the entities found in the spawnable at the provided indices as a template.
|
||||
//! @param entityIndices A list of indices to the entities in the spawnable.
|
||||
void SpawnEntities(AZStd::vector<size_t> entityIndices);
|
||||
void SpawnEntities(AZStd::vector<uint32_t> entityIndices);
|
||||
//! Puts in a request to despawn all previous spawned entities.
|
||||
void DespawnAllEntities();
|
||||
|
||||
@@ -73,7 +79,11 @@ namespace AzFramework
|
||||
//! other than the calling thread including the main thread. Note that because the alert is queued it can still be called
|
||||
//! after the container has been deleted or can be called for a previously assigned spawnable. In the latter case check
|
||||
//! if the current generation matches the generation provided with the callback.
|
||||
void Alert(AlertCallback callback);
|
||||
//! @param callback The function called when the alert triggers. This can be called from a different thread than the one that
|
||||
//! the one that made the call to Alert.
|
||||
//! @param checkSpawnableIsLoaded If true the alert will also block until the spawnable has been loaded. If false then it will
|
||||
//! be called after all previous calls have completed, but the spawnable may not be loaded at that point.
|
||||
void Alert(AlertCallback callback, CheckIfSpawnableIsLoaded spawnableCheck = CheckIfSpawnableIsLoaded::No);
|
||||
|
||||
private:
|
||||
void Connect(AZ::Data::Asset<Spawnable> spawnable);
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace AzFramework
|
||||
// SpawnableIndexEntityPair
|
||||
//
|
||||
|
||||
SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator)
|
||||
SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, uint32_t* indexIterator)
|
||||
: m_entity(entityIterator)
|
||||
, m_index(indexIterator)
|
||||
{
|
||||
@@ -168,7 +168,7 @@ namespace AzFramework
|
||||
return *m_entity;
|
||||
}
|
||||
|
||||
size_t SpawnableIndexEntityPair::GetIndex() const
|
||||
uint32_t SpawnableIndexEntityPair::GetIndex() const
|
||||
{
|
||||
return *m_index;
|
||||
}
|
||||
@@ -177,7 +177,7 @@ namespace AzFramework
|
||||
// SpawnableIndexEntityIterator
|
||||
//
|
||||
|
||||
SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator)
|
||||
SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, uint32_t* indexIterator)
|
||||
: m_value(entityIterator, indexIterator)
|
||||
{
|
||||
}
|
||||
@@ -248,7 +248,7 @@ namespace AzFramework
|
||||
//
|
||||
|
||||
SpawnableConstIndexEntityContainerView::SpawnableConstIndexEntityContainerView(
|
||||
AZ::Entity** beginEntity, size_t* beginIndices, size_t length)
|
||||
AZ::Entity** beginEntity, uint32_t* beginIndices, size_t length)
|
||||
: m_begin(beginEntity, beginIndices)
|
||||
, m_end(beginEntity + length, beginIndices + length)
|
||||
{
|
||||
|
||||
@@ -85,19 +85,19 @@ namespace AzFramework
|
||||
|
||||
AZ::Entity* GetEntity();
|
||||
const AZ::Entity* GetEntity() const;
|
||||
size_t GetIndex() const;
|
||||
uint32_t GetIndex() const;
|
||||
|
||||
private:
|
||||
SpawnableIndexEntityPair() = default;
|
||||
SpawnableIndexEntityPair(const SpawnableIndexEntityPair&) = default;
|
||||
SpawnableIndexEntityPair(SpawnableIndexEntityPair&&) = default;
|
||||
SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator);
|
||||
SpawnableIndexEntityPair(AZ::Entity** entityIterator, uint32_t* indexIterator);
|
||||
|
||||
SpawnableIndexEntityPair& operator=(const SpawnableIndexEntityPair&) = default;
|
||||
SpawnableIndexEntityPair& operator=(SpawnableIndexEntityPair&&) = default;
|
||||
|
||||
AZ::Entity** m_entity { nullptr };
|
||||
size_t* m_index { nullptr };
|
||||
uint32_t* m_index { nullptr };
|
||||
};
|
||||
|
||||
class SpawnableIndexEntityIterator
|
||||
@@ -110,7 +110,7 @@ namespace AzFramework
|
||||
using pointer = SpawnableIndexEntityPair*;
|
||||
using reference = SpawnableIndexEntityPair&;
|
||||
|
||||
SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator);
|
||||
SpawnableIndexEntityIterator(AZ::Entity** entityIterator, uint32_t* indexIterator);
|
||||
|
||||
SpawnableIndexEntityIterator& operator++();
|
||||
SpawnableIndexEntityIterator operator++(int);
|
||||
@@ -132,7 +132,7 @@ namespace AzFramework
|
||||
class SpawnableConstIndexEntityContainerView
|
||||
{
|
||||
public:
|
||||
SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, size_t* beginIndices, size_t length);
|
||||
SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, uint32_t* beginIndices, size_t length);
|
||||
|
||||
const SpawnableIndexEntityIterator& begin();
|
||||
const SpawnableIndexEntityIterator& end();
|
||||
@@ -144,6 +144,16 @@ namespace AzFramework
|
||||
SpawnableIndexEntityIterator m_end;
|
||||
};
|
||||
|
||||
//! Information used when updating the type of an entity alias.
|
||||
struct EntityAliasTypeChange
|
||||
{
|
||||
//! The index of the alias in the spawnable. Note that due to optimizations done on the entity aliases the index of an alias
|
||||
//! can change over time.
|
||||
uint32_t m_aliasIndex;
|
||||
//! The type to replace type stored in the spawnable at the index provided by m_aliasIndex.
|
||||
Spawnable::EntityAliasType m_newAliasType;
|
||||
};
|
||||
|
||||
//! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that is used as a template. A ticket can
|
||||
//! be reused for multiple calls on the same spawnable and is safe to be used by multiple threads at the same time. Entities created
|
||||
//! from the spawnable may be tracked by the ticket and so using the same ticket is needed to despawn the exact entities created
|
||||
@@ -178,6 +188,7 @@ namespace AzFramework
|
||||
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
|
||||
using RetrieveEntitySpawnTicketCallback = AZStd::function<void(EntitySpawnTicket*)>;
|
||||
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using UpdateEntityAliasTypesCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
|
||||
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using ListIndicesEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstIndexEntityContainerView)>;
|
||||
using ClaimEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableEntityContainerView)>;
|
||||
@@ -247,6 +258,15 @@ namespace AzFramework
|
||||
SpawnablePriority m_priority { SpawnablePriority_Default };
|
||||
};
|
||||
|
||||
struct UpdateEntityAliasTypesOptionalArgs final
|
||||
{
|
||||
//! Callback that's called when entity aliases are updated. This can be triggered from a different thread than the one that
|
||||
//! made the function call to update.
|
||||
UpdateEntityAliasTypesCallback m_completionCallback;
|
||||
//! The priority at which this call will be executed.
|
||||
SpawnablePriority m_priority{ SpawnablePriority_Default };
|
||||
};
|
||||
|
||||
struct ListEntitiesOptionalArgs final
|
||||
{
|
||||
//! The priority at which this call will be executed.
|
||||
@@ -265,6 +285,14 @@ namespace AzFramework
|
||||
SpawnablePriority m_priority{ SpawnablePriority_Default };
|
||||
};
|
||||
|
||||
struct LoadBarrierOptionalArgs final
|
||||
{
|
||||
//! The priority at which this call will be executed.
|
||||
SpawnablePriority m_priority{ SpawnablePriority_Default };
|
||||
//! Also checks if the spawnables referenced in the entity aliases that are marked to be loaded are loaded.
|
||||
bool m_checkAliasSpawnables{ true };
|
||||
};
|
||||
|
||||
//! Interface definition to (de)spawn entities from a spawnable into the game world.
|
||||
//!
|
||||
//! While the callbacks of the individual calls are being processed they will block processing any other request. Callbacks can be
|
||||
@@ -298,7 +326,7 @@ namespace AzFramework
|
||||
//! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from.
|
||||
//! @param optionalArgs Optional additional arguments, see SpawnEntitiesOptionalArgs.
|
||||
virtual void SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0;
|
||||
EntitySpawnTicket& ticket, AZStd::vector<uint32_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0;
|
||||
//! Removes all entities in the provided list from the environment.
|
||||
//! @param ticket The ticket previously used to spawn entities with.
|
||||
//! @param optionalArgs Optional additional arguments, see DespawnAllEntitiesOptionalArgs.
|
||||
@@ -320,6 +348,16 @@ namespace AzFramework
|
||||
virtual void ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) = 0;
|
||||
|
||||
//! Allows updating the entity alias on a spawnable. This allows the spawning behavior for all entities spawned from the used
|
||||
//! spawnable to be changed and is not restricted to this ticket alone.
|
||||
//! @param ticket Holds the information for the spawnable.
|
||||
//! @param updateAliases An array of index and alias type values used to update the entity alias list.
|
||||
//! @param optionalArgs Optional additional arguments, see UpdateEntityAliasTypesOptionalArgs.
|
||||
virtual void UpdateEntityAliasTypes(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs = {}) = 0;
|
||||
|
||||
//! List all entities that are spawned using this ticket.
|
||||
//! @param ticket Only the entities associated with this ticket will be listed.
|
||||
//! @param listCallback Required callback that will be called to list the entities on.
|
||||
@@ -351,31 +389,37 @@ namespace AzFramework
|
||||
//! @param completionCallback Required callback that will be called as soon as the barrier has been reached.
|
||||
//! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs.
|
||||
virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) = 0;
|
||||
//! Blocks until the spawnable is loaded and all operations made on the provided ticket before the barrier call have completed.
|
||||
//! @param ticket The ticket to monitor.
|
||||
//! @param completionCallback Required callback that will be called as soon as the barrier has been reached.
|
||||
//! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs.
|
||||
virtual void LoadBarrier(
|
||||
EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs = {}) = 0;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] virtual AZStd::pair<EntitySpawnTicket::Id, void*> CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 0;
|
||||
virtual void DestroyTicket(void* ticket) = 0;
|
||||
|
||||
template<typename T>
|
||||
static T& GetTicketPayload(EntitySpawnTicket& ticket)
|
||||
[[nodiscard]] static T& GetTicketPayload(EntitySpawnTicket& ticket)
|
||||
{
|
||||
return *reinterpret_cast<T*>(ticket.m_payload);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static const T& GetTicketPayload(const EntitySpawnTicket& ticket)
|
||||
[[nodiscard]] static const T& GetTicketPayload(const EntitySpawnTicket& ticket)
|
||||
{
|
||||
return *reinterpret_cast<const T*>(ticket.m_payload);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static T* GetTicketPayload(EntitySpawnTicket* ticket)
|
||||
[[nodiscard]] static T* GetTicketPayload(EntitySpawnTicket* ticket)
|
||||
{
|
||||
return reinterpret_cast<T*>(ticket->m_payload);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static const T* GetTicketPayload(const EntitySpawnTicket* ticket)
|
||||
[[nodiscard]] static const T* GetTicketPayload(const EntitySpawnTicket* ticket)
|
||||
{
|
||||
return reinterpret_cast<const T*>(ticket->m_payload);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs)
|
||||
EntitySpawnTicket& ticket, AZStd::vector<uint32_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized.");
|
||||
|
||||
@@ -128,6 +128,20 @@ namespace AzFramework
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::UpdateEntityAliasTypes(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized.");
|
||||
|
||||
UpdateEntityAliasTypesCommand queueEntry;
|
||||
queueEntry.m_entityAliases = AZStd::move(updatedAliases);
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback);
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ListEntities(
|
||||
EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs)
|
||||
{
|
||||
@@ -175,6 +189,19 @@ namespace AzFramework
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::LoadBarrier(
|
||||
EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs)
|
||||
{
|
||||
AZ_Assert(completionCallback, "Load barrier on spawnable entities called without a valid callback to use.");
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to LoadBarrier hasn't been initialized.");
|
||||
|
||||
LoadBarrierCommand queueEntry;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
queueEntry.m_checkAliasSpawnables = optionalArgs.m_checkAliasSpawnables;
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus
|
||||
{
|
||||
CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft;
|
||||
@@ -203,13 +230,13 @@ namespace AzFramework
|
||||
for (size_t i = 0; i < delayedSize; ++i)
|
||||
{
|
||||
Requests& request = queue.m_delayed.front();
|
||||
bool result = AZStd::visit(
|
||||
[this](auto&& args) -> bool
|
||||
CommandResult result = AZStd::visit(
|
||||
[this](auto&& args) -> CommandResult
|
||||
{
|
||||
return ProcessRequest(args);
|
||||
},
|
||||
request);
|
||||
if (!result)
|
||||
if (result == CommandResult::Requeue)
|
||||
{
|
||||
queue.m_delayed.emplace_back(AZStd::move(request));
|
||||
}
|
||||
@@ -230,13 +257,13 @@ namespace AzFramework
|
||||
while (!pendingRequestQueue.empty())
|
||||
{
|
||||
Requests& request = pendingRequestQueue.front();
|
||||
bool result = AZStd::visit(
|
||||
[this](auto&& args) -> bool
|
||||
CommandResult result = AZStd::visit(
|
||||
[this](auto&& args) -> CommandResult
|
||||
{
|
||||
return ProcessRequest(args);
|
||||
},
|
||||
request);
|
||||
if (!result)
|
||||
if (result == CommandResult::Requeue)
|
||||
{
|
||||
queue.m_delayed.emplace_back(AZStd::move(request));
|
||||
}
|
||||
@@ -273,14 +300,73 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate,
|
||||
EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext)
|
||||
AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityPrototype,
|
||||
EntityIdMap& prototypeToCloneMap, AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
// If the same ID gets remapped more than once, preserve the original remapping instead of overwriting it.
|
||||
constexpr bool allowDuplicateIds = false;
|
||||
|
||||
return AZ::IdUtils::Remapper<AZ::EntityId, allowDuplicateIds>::CloneObjectAndGenerateNewIdsAndFixRefs(
|
||||
&entityTemplate, templateToCloneMap, &serializeContext);
|
||||
&entityPrototype, prototypeToCloneMap, &serializeContext);
|
||||
}
|
||||
|
||||
AZ::Entity* SpawnableEntitiesManager::CloneSingleAliasedEntity(
|
||||
const AZ::Entity& entityPrototype,
|
||||
const Spawnable::EntityAlias& alias,
|
||||
EntityIdMap& prototypeToCloneMap,
|
||||
AZ::Entity* previouslySpawnedEntity,
|
||||
AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
AZ::Entity* clone = nullptr;
|
||||
switch (alias.m_aliasType)
|
||||
{
|
||||
case Spawnable::EntityAliasType::Original:
|
||||
// Behave as the original version.
|
||||
clone = CloneSingleEntity(entityPrototype, prototypeToCloneMap, serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
return clone;
|
||||
case Spawnable::EntityAliasType::Disable:
|
||||
// Do nothing.
|
||||
return nullptr;
|
||||
case Spawnable::EntityAliasType::Replace:
|
||||
clone = CloneSingleEntity(*(alias.m_spawnable->GetEntities()[alias.m_targetIndex]), prototypeToCloneMap, serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
return clone;
|
||||
case Spawnable::EntityAliasType::Additional:
|
||||
// The asset handler will have sorted and inserted a Spawnable::EntityAliasType::Original, so the just
|
||||
// spawn the additional entity.
|
||||
clone = CloneSingleEntity(*(alias.m_spawnable->GetEntities()[alias.m_targetIndex]), prototypeToCloneMap, serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
return clone;
|
||||
case Spawnable::EntityAliasType::Merge:
|
||||
AZ_Assert(previouslySpawnedEntity != nullptr, "Merging components but there's no entity to add to yet.");
|
||||
AppendComponents(
|
||||
*previouslySpawnedEntity, alias.m_spawnable->GetEntities()[alias.m_targetIndex]->GetComponents(), prototypeToCloneMap,
|
||||
serializeContext);
|
||||
return nullptr;
|
||||
default:
|
||||
AZ_Assert(false, "Unsupported spawnable entity alias type: %i", alias.m_aliasType);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::AppendComponents(
|
||||
AZ::Entity& target,
|
||||
const AZ::Entity::ComponentArrayType& componentPrototypes,
|
||||
EntityIdMap& prototypeToCloneMap,
|
||||
AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
// Only components are added and entities are looked up so no duplicate entity ids should be encountered.
|
||||
constexpr bool allowDuplicateIds = false;
|
||||
|
||||
for (const AZ::Component* component : componentPrototypes)
|
||||
{
|
||||
AZ::Component* clone = AZ::IdUtils::Remapper<AZ::EntityId, allowDuplicateIds>::CloneObjectAndGenerateNewIdsAndFixRefs(
|
||||
component, prototypeToCloneMap, &serializeContext);
|
||||
AZ_Assert(clone, "Unable to clone component for entity '%s' (%zu).", target.GetName().c_str(), target.GetId());
|
||||
[[maybe_unused]] bool result = target.AddComponent(clone);
|
||||
AZ_Assert(result, "Unable to add cloned component to entity '%s' (%zu).", target.GetName().c_str(), target.GetId());
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::InitializeEntityIdMappings(
|
||||
@@ -316,161 +402,264 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<size_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
|
||||
// Keep track how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
// These are 'template' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
size_t entitiesToSpawnSize = entitiesToSpawn.size();
|
||||
|
||||
// Reserve buffers
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
// Pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
|
||||
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
|
||||
// We clear out and regenerate the set of IDs on every SpawnAllEntities call, because presumably every entity reference
|
||||
// in every entity we're about to instantiate is intended to point to an entity in our newly-instantiated batch, regardless
|
||||
// of spawn order. If we didn't clear out the map, it would be possible for some entities here to have references to
|
||||
// previously-spawned entities from a previous SpawnEntities or SpawnAllEntities call.
|
||||
InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst();
|
||||
aliases.IsValid() && aliases.AreAllSpawnablesReady())
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<uint32_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
|
||||
AZ::Entity* clone = CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
// Keep track how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
// These are 'prototype' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
uint32_t entitiesToSpawnSize = aznumeric_caster(entitiesToSpawn.size());
|
||||
|
||||
// loadAll is true if every entity has been spawned only once
|
||||
ticket.m_loadAll = (spawnedEntities.size() == entitiesToSpawnSize);
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
// Reserve buffers
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
|
||||
{
|
||||
(*it)->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
|
||||
// Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context.
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request)
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<size_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
AZ_Assert(
|
||||
spawnedEntities.size() == spawnedEntityIndices.size(),
|
||||
"The indices for the spawned entities has gone out of sync with the entities.");
|
||||
|
||||
// Keep track of how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
// These are 'template' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
size_t entitiesToSpawnSize = request.m_entityIndices.size();
|
||||
|
||||
if (ticket.m_entityIdReferenceMap.empty() || !request.m_referencePreviouslySpawnedEntities)
|
||||
{
|
||||
// This map keeps track of ids from template (spawnable) to clone (instance) allowing patch ups of fields referring
|
||||
// to entityIds outside of a given entity.
|
||||
// We pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
|
||||
// Pre-generate the full set of entity-id-to-new-entity-id mappings, so that during the clone operation below,
|
||||
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
|
||||
// By default, we only initialize this map once because it needs to persist across multiple SpawnEntities calls, so
|
||||
// that reference fixups work even when the entity being referenced is spawned in a different SpawnEntities
|
||||
// (or SpawnAllEntities) call.
|
||||
// However, the caller can also choose to reset the map by passing in "m_referencePreviouslySpawnedEntities = false".
|
||||
// We clear out and regenerate the set of IDs on every SpawnAllEntities call, because presumably every entity reference
|
||||
// in every entity we're about to instantiate is intended to point to an entity in our newly-instantiated batch, regardless
|
||||
// of spawn order. If we didn't clear out the map, it would be possible for some entities here to have references to
|
||||
// previously-spawned entities from a previous SpawnEntities or SpawnAllEntities call.
|
||||
InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
}
|
||||
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
for (size_t index : request.m_entityIndices)
|
||||
{
|
||||
if (index < entitiesToSpawn.size())
|
||||
auto aliasIt = aliases.begin();
|
||||
auto aliasEnd = aliases.end();
|
||||
if (aliasIt == aliasEnd)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
for (uint32_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
AZ::Entity* clone =
|
||||
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
|
||||
spawnedEntities.push_back(clone);
|
||||
spawnedEntityIndices.push_back(index);
|
||||
spawnedEntities.emplace_back(
|
||||
CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext));
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
ticket.m_loadAll = false;
|
||||
else
|
||||
{
|
||||
for (uint32_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
if (aliasIt == aliasEnd || aliasIt->m_sourceIndex != i)
|
||||
{
|
||||
spawnedEntities.emplace_back(
|
||||
CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext));
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The list of entities has already been sorted and optimized (See SpawnableEntitiesAliasList:Optimize) so can
|
||||
// be safely executed in order without risking an invalid state.
|
||||
AZ::Entity* previousEntity = nullptr;
|
||||
do
|
||||
{
|
||||
AZ::Entity* clone = CloneSingleAliasedEntity(
|
||||
*entitiesToSpawn[i], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity,
|
||||
*request.m_serializeContext);
|
||||
previousEntity = clone;
|
||||
if (clone)
|
||||
{
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
++aliasIt;
|
||||
} while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
|
||||
{
|
||||
(*it)->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
// There were no initial entities then the ticket now holds exactly all entities. If there were already entities then
|
||||
// a new set are not added so it no longer holds exactly the number of entities.
|
||||
ticket.m_loadAll = spawnedEntitiesInitialCount == 0;
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
auto newEntitiesBegin = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount;
|
||||
auto newEntitiesEnd = ticket.m_spawnedEntities.end();
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(newEntitiesBegin, newEntitiesEnd));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = newEntitiesBegin; it != newEntitiesEnd; ++it)
|
||||
{
|
||||
AZ::Entity* clone = (*it);
|
||||
// The entity component framework doesn't handle entities without TransformComponent safely.
|
||||
if (!clone->GetComponents().empty())
|
||||
{
|
||||
clone->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
}
|
||||
|
||||
// Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context.
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(newEntitiesBegin, newEntitiesEnd));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
}
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst();
|
||||
aliases.IsValid() && aliases.AreAllSpawnablesReady())
|
||||
{
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<uint32_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
AZ_Assert(
|
||||
spawnedEntities.size() == spawnedEntityIndices.size(),
|
||||
"The indices for the spawned entities has gone out of sync with the entities.");
|
||||
|
||||
// Keep track of how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
// These are 'prototype' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
size_t entitiesToSpawnSize = request.m_entityIndices.size();
|
||||
|
||||
if (ticket.m_entityIdReferenceMap.empty() || !request.m_referencePreviouslySpawnedEntities)
|
||||
{
|
||||
// This map keeps track of ids from prototype (spawnable) to clone (instance) allowing patch ups of fields referring
|
||||
// to entityIds outside of a given entity.
|
||||
// We pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
|
||||
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
|
||||
// By default, we only initialize this map once because it needs to persist across multiple SpawnEntities calls, so
|
||||
// that reference fixups work even when the entity being referenced is spawned in a different SpawnEntities
|
||||
// (or SpawnAllEntities) call.
|
||||
// However, the caller can also choose to reset the map by passing in "m_referencePreviouslySpawnedEntities = false".
|
||||
InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
}
|
||||
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
auto aliasBegin = aliases.begin();
|
||||
auto aliasEnd = aliases.end();
|
||||
if (aliasBegin == aliasEnd)
|
||||
{
|
||||
for (uint32_t index : request.m_entityIndices)
|
||||
{
|
||||
if (index < entitiesToSpawn.size())
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
spawnedEntities.push_back(
|
||||
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext));
|
||||
spawnedEntityIndices.push_back(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32_t index : request.m_entityIndices)
|
||||
{
|
||||
if (index < entitiesToSpawn.size())
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
auto aliasIt = AZStd::lower_bound(
|
||||
aliasBegin, aliasEnd, index,
|
||||
[](const Spawnable::EntityAlias& lhs, uint32_t rhs)
|
||||
{
|
||||
return lhs.m_sourceIndex < rhs;
|
||||
});
|
||||
|
||||
if (aliasIt == aliasEnd || aliasIt->m_sourceIndex != index)
|
||||
{
|
||||
spawnedEntities.emplace_back(
|
||||
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext));
|
||||
spawnedEntityIndices.push_back(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The list of entities has already been sorted and optimized (See SpawnableEntitiesAliasList:Optimize) so
|
||||
// can be safely executed in order without risking an invalid state.
|
||||
AZ::Entity* previousEntity = nullptr;
|
||||
do
|
||||
{
|
||||
AZ::Entity* clone = CloneSingleAliasedEntity(
|
||||
*entitiesToSpawn[index], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity,
|
||||
*request.m_serializeContext);
|
||||
previousEntity = clone;
|
||||
if (clone)
|
||||
{
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(index);
|
||||
}
|
||||
|
||||
++aliasIt;
|
||||
} while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ticket.m_loadAll = false;
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(
|
||||
request.m_ticketId,
|
||||
SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
|
||||
{
|
||||
AZ::Entity* clone = (*it);
|
||||
// The entity component framework doesn't handle entities without TransformComponent safely.
|
||||
if (!clone->GetComponents().empty())
|
||||
{
|
||||
clone->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(
|
||||
request.m_ticketId,
|
||||
SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
}
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -495,15 +684,15 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -529,15 +718,15 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
AZ_Assert(ticket.m_spawnable.GetId() == request.m_spawnable.GetId(),
|
||||
@@ -564,7 +753,7 @@ namespace AzFramework
|
||||
// Pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
|
||||
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
|
||||
// This map is intentionally cleared out and regenerated here to ensure that we're starting fresh with mappings that
|
||||
// match the new set of template entities getting spawned.
|
||||
// match the new set of prototype entities getting spawned.
|
||||
InitializeEntityIdMappings(entities, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
if (ticket.m_loadAll)
|
||||
@@ -574,7 +763,7 @@ namespace AzFramework
|
||||
ticket.m_spawnedEntityIndices.clear();
|
||||
size_t entitiesToSpawnSize = entities.size();
|
||||
|
||||
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
for (uint32_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(entities[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
@@ -590,7 +779,7 @@ namespace AzFramework
|
||||
{
|
||||
size_t entitiesSize = entities.size();
|
||||
|
||||
for (size_t index : ticket.m_spawnedEntityIndices)
|
||||
for (uint32_t index : ticket.m_spawnedEntityIndices)
|
||||
{
|
||||
// It's possible for the new spawnable to have a different number of entities, so guard against this.
|
||||
// It's also possible that the entities have moved within the spawnable to a new index. This can't be
|
||||
@@ -616,15 +805,40 @@ namespace AzFramework
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(UpdateEntityAliasTypesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (Spawnable::EntityAliasVisitor aliases = ticket.m_spawnable->TryGetAliases(); aliases.IsValid())
|
||||
{
|
||||
for (EntityAliasTypeChange& replacement : request.m_entityAliases)
|
||||
{
|
||||
aliases.UpdateAliasType(replacement.m_aliasIndex, replacement.m_newAliasType);
|
||||
}
|
||||
aliases.Optimize();
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId);
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
}
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -632,15 +846,15 @@ namespace AzFramework
|
||||
request.m_listCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -651,15 +865,15 @@ namespace AzFramework
|
||||
request.m_listCallback(request.m_ticketId, SpawnableConstIndexEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntityIndices.begin(), ticket.m_spawnedEntities.size()));
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -671,15 +885,15 @@ namespace AzFramework
|
||||
ticket.m_spawnedEntityIndices.clear();
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -690,15 +904,39 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(LoadBarrierCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (request.m_checkAliasSpawnables)
|
||||
{
|
||||
if (Spawnable::EntityAliasConstVisitor visitor = ticket.m_spawnable->TryGetAliasesConst();
|
||||
!visitor.IsValid() || !visitor.AreAllSpawnablesReady())
|
||||
{
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
request.m_completionCallback(request.m_ticketId);
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request) -> CommandResult
|
||||
{
|
||||
if (request.m_requestId == request.m_ticket->m_currentRequestId)
|
||||
{
|
||||
@@ -706,19 +944,24 @@ namespace AzFramework
|
||||
{
|
||||
if (entity != nullptr)
|
||||
{
|
||||
// Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager.
|
||||
// Setting it to 0 is needed to avoid the infinite loop between GameEntityContext and SpawnableEntitiesManager.
|
||||
entity->SetSpawnTicketId(0);
|
||||
GameEntityContextRequestBus::Broadcast(
|
||||
&GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Entities without components wouldn't have been send to the GameEntityContext.
|
||||
delete entity;
|
||||
}
|
||||
}
|
||||
delete request.m_ticket;
|
||||
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -55,13 +55,18 @@ namespace AzFramework
|
||||
|
||||
void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
void SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
EntitySpawnTicket& ticket, AZStd::vector<uint32_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) override;
|
||||
void RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) override;
|
||||
void ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
void UpdateEntityAliasTypes(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
void ListEntities(
|
||||
EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
void ListIndicesAndEntities(
|
||||
@@ -70,6 +75,8 @@ namespace AzFramework
|
||||
EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override;
|
||||
void LoadBarrier(
|
||||
EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
//
|
||||
// The following function is thread safe but intended to be run from the main thread.
|
||||
@@ -78,14 +85,20 @@ namespace AzFramework
|
||||
CommandQueueStatus ProcessQueue(CommandQueuePriority priority);
|
||||
|
||||
protected:
|
||||
struct Ticket
|
||||
enum class CommandResult : bool
|
||||
{
|
||||
Executed,
|
||||
Requeue
|
||||
};
|
||||
|
||||
struct Ticket final
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(Ticket, AZ::ThreadPoolAllocator, 0);
|
||||
static constexpr uint32_t Processing = AZStd::numeric_limits<uint32_t>::max();
|
||||
|
||||
//! Map of template entity ids to their associated instance ids.
|
||||
//! Tickets can be used to spawn the same template entities multiple times, in any order, across multiple calls.
|
||||
//! Since template entities can reference other entities, this map is used to fix up those references across calls
|
||||
//! Map of prototype entity ids to their associated instance ids.
|
||||
//! Tickets can be used to spawn the same prototype entities multiple times, in any order, across multiple calls.
|
||||
//! Since prototype entities can reference other entities, this map is used to fix up those references across calls
|
||||
//! using the following policy:
|
||||
//! - Entities referencing an entity that hasn't been spawned yet will get a reference to the id that *will* be used
|
||||
//! the first time that entity will be spawned. The reference will be invalid until that entity is spawned, but
|
||||
@@ -100,14 +113,14 @@ namespace AzFramework
|
||||
AZStd::unordered_set<AZ::EntityId> m_previouslySpawned;
|
||||
|
||||
AZStd::vector<AZ::Entity*> m_spawnedEntities;
|
||||
AZStd::vector<size_t> m_spawnedEntityIndices;
|
||||
AZStd::vector<uint32_t> m_spawnedEntityIndices;
|
||||
AZ::Data::Asset<Spawnable> m_spawnable;
|
||||
uint32_t m_nextRequestId{ 0 }; //!< Next id for this ticket.
|
||||
uint32_t m_currentRequestId { 0 }; //!< The id for the command that should be executed.
|
||||
bool m_loadAll{ true };
|
||||
};
|
||||
|
||||
struct SpawnAllEntitiesCommand
|
||||
struct SpawnAllEntitiesCommand final
|
||||
{
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
@@ -116,9 +129,9 @@ namespace AzFramework
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct SpawnEntitiesCommand
|
||||
struct SpawnEntitiesCommand final
|
||||
{
|
||||
AZStd::vector<size_t> m_entityIndices;
|
||||
AZStd::vector<uint32_t> m_entityIndices;
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
AZ::SerializeContext* m_serializeContext;
|
||||
@@ -127,7 +140,7 @@ namespace AzFramework
|
||||
uint32_t m_requestId;
|
||||
bool m_referencePreviouslySpawnedEntities;
|
||||
};
|
||||
struct DespawnAllEntitiesCommand
|
||||
struct DespawnAllEntitiesCommand final
|
||||
{
|
||||
EntityDespawnCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
@@ -142,7 +155,7 @@ namespace AzFramework
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ReloadSpawnableCommand
|
||||
struct ReloadSpawnableCommand final
|
||||
{
|
||||
AZ::Data::Asset<Spawnable> m_spawnable;
|
||||
ReloadSpawnableCallback m_completionCallback;
|
||||
@@ -151,35 +164,51 @@ namespace AzFramework
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListEntitiesCommand
|
||||
struct UpdateEntityAliasTypesCommand final
|
||||
{
|
||||
AZStd::vector<EntityAliasTypeChange> m_entityAliases;
|
||||
UpdateEntityAliasTypesCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListEntitiesCommand final
|
||||
{
|
||||
ListEntitiesCallback m_listCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListIndicesEntitiesCommand
|
||||
struct ListIndicesEntitiesCommand final
|
||||
{
|
||||
ListIndicesEntitiesCallback m_listCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ClaimEntitiesCommand
|
||||
struct ClaimEntitiesCommand final
|
||||
{
|
||||
ClaimEntitiesCallback m_listCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct BarrierCommand
|
||||
struct BarrierCommand final
|
||||
{
|
||||
BarrierCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct DestroyTicketCommand
|
||||
struct LoadBarrierCommand final
|
||||
{
|
||||
BarrierCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
bool m_checkAliasSpawnables;
|
||||
};
|
||||
struct DestroyTicketCommand final
|
||||
{
|
||||
Ticket* m_ticket;
|
||||
uint32_t m_requestId;
|
||||
@@ -191,10 +220,12 @@ namespace AzFramework
|
||||
DespawnAllEntitiesCommand,
|
||||
DespawnEntityCommand,
|
||||
ReloadSpawnableCommand,
|
||||
UpdateEntityAliasTypesCommand,
|
||||
ListEntitiesCommand,
|
||||
ListIndicesEntitiesCommand,
|
||||
ClaimEntitiesCommand,
|
||||
BarrierCommand,
|
||||
LoadBarrierCommand,
|
||||
DestroyTicketCommand>;
|
||||
|
||||
struct Queue
|
||||
@@ -212,18 +243,31 @@ namespace AzFramework
|
||||
CommandQueueStatus ProcessQueue(Queue& queue);
|
||||
|
||||
AZ::Entity* CloneSingleEntity(
|
||||
const AZ::Entity& entityTemplate, EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext);
|
||||
const AZ::Entity& entityPrototype, EntityIdMap& prototypeToCloneMap, AZ::SerializeContext& serializeContext);
|
||||
AZ::Entity* CloneSingleAliasedEntity(
|
||||
const AZ::Entity& entityPrototype,
|
||||
const Spawnable::EntityAlias& alias,
|
||||
EntityIdMap& prototypeToCloneMap,
|
||||
AZ::Entity* previouslySpawnedEntity,
|
||||
AZ::SerializeContext& serializeContext);
|
||||
void AppendComponents(
|
||||
AZ::Entity& target,
|
||||
const AZ::Entity::ComponentArrayType& componentPrototypes,
|
||||
EntityIdMap& prototypeToCloneMap,
|
||||
AZ::SerializeContext& serializeContext);
|
||||
|
||||
bool ProcessRequest(SpawnAllEntitiesCommand& request);
|
||||
bool ProcessRequest(SpawnEntitiesCommand& request);
|
||||
bool ProcessRequest(DespawnAllEntitiesCommand& request);
|
||||
bool ProcessRequest(DespawnEntityCommand& request);
|
||||
bool ProcessRequest(ReloadSpawnableCommand& request);
|
||||
bool ProcessRequest(ListEntitiesCommand& request);
|
||||
bool ProcessRequest(ListIndicesEntitiesCommand& request);
|
||||
bool ProcessRequest(ClaimEntitiesCommand& request);
|
||||
bool ProcessRequest(BarrierCommand& request);
|
||||
bool ProcessRequest(DestroyTicketCommand& request);
|
||||
CommandResult ProcessRequest(SpawnAllEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(SpawnEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(DespawnAllEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(DespawnEntityCommand& request);
|
||||
CommandResult ProcessRequest(ReloadSpawnableCommand& request);
|
||||
CommandResult ProcessRequest(UpdateEntityAliasTypesCommand& request);
|
||||
CommandResult ProcessRequest(ListEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(ListIndicesEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(ClaimEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(BarrierCommand& request);
|
||||
CommandResult ProcessRequest(LoadBarrierCommand& request);
|
||||
CommandResult ProcessRequest(DestroyTicketCommand& request);
|
||||
|
||||
//! Generate a base set of original-to-new entity ID mappings to use during spawning.
|
||||
//! Since Entity references get fixed up on an entity-by-entity basis while spawning, it's important to have the complete
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace AzFramework
|
||||
|
||||
uint64_t SpawnableSystemComponent::AssignRootSpawnable(AZ::Data::Asset<Spawnable> rootSpawnable)
|
||||
{
|
||||
uint64_t generation = 0;
|
||||
uint32_t generation = 0;
|
||||
|
||||
if (m_rootSpawnableId == rootSpawnable.GetId())
|
||||
{
|
||||
@@ -87,16 +87,25 @@ namespace AzFramework
|
||||
// Suspend and resume processing in the container that completion calls aren't received until
|
||||
// everything has been setup to accept callbacks from the call.
|
||||
m_rootSpawnableContainer.Reset(rootSpawnable);
|
||||
m_rootSpawnableContainer.SpawnAllEntities();
|
||||
generation = m_rootSpawnableContainer.GetCurrentGeneration();
|
||||
AZ_TracePrintf("Spawnables", "Root spawnable set to '%s' at generation %zu.\n", rootSpawnable.GetHint().c_str(),
|
||||
generation);
|
||||
|
||||
// Don't send out the alert that the root spawnable has been assigned until the spawnable itself is ready. The common
|
||||
// use case is for handlers to do something with the information in the spawnable before the entities get spawned.
|
||||
m_rootSpawnableContainer.Alert(
|
||||
[rootSpawnable](uint32_t generation)
|
||||
{
|
||||
RootSpawnableNotificationBus::Broadcast(
|
||||
&RootSpawnableNotificationBus::Events::OnRootSpawnableAssigned, AZStd::move(rootSpawnable), generation);
|
||||
}, SpawnableEntitiesContainer::CheckIfSpawnableIsLoaded::Yes);
|
||||
m_rootSpawnableContainer.SpawnAllEntities();
|
||||
m_rootSpawnableContainer.Alert(
|
||||
[newSpawnable = AZStd::move(rootSpawnable)](uint32_t generation)
|
||||
{
|
||||
RootSpawnableNotificationBus::QueueBroadcast(
|
||||
&RootSpawnableNotificationBus::Events::OnRootSpawnableAssigned, newSpawnable, generation);
|
||||
&RootSpawnableNotificationBus::Events::OnRootSpawnableReady, AZStd::move(newSpawnable), generation);
|
||||
});
|
||||
|
||||
AZ_TracePrintf("Spawnables", "Root spawnable set to '%s' at generation %zu.\n", rootSpawnable.GetHint().c_str(), generation);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -132,6 +141,12 @@ namespace AzFramework
|
||||
AZ_TracePrintf("Spawnables", "New root spawnable '%s' assigned (generation: %i).\n", rootSpawnable.GetHint().c_str(), generation);
|
||||
}
|
||||
|
||||
void SpawnableSystemComponent::OnRootSpawnableReady(
|
||||
[[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
|
||||
{
|
||||
AZ_TracePrintf("Spawnables", "Entities from new root spawnable '%s' are ready (generation: %i).\n", rootSpawnable.GetHint().c_str(), generation);
|
||||
}
|
||||
|
||||
void SpawnableSystemComponent::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation)
|
||||
{
|
||||
AZ_TracePrintf("Spawnables", "Generation %i of the root spawnable has been released.\n", generation);
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace AzFramework
|
||||
//
|
||||
|
||||
void OnRootSpawnableAssigned(AZ::Data::Asset<Spawnable> rootSpawnable, uint32_t generation) override;
|
||||
void OnRootSpawnableReady(AZ::Data::Asset<Spawnable> rootSpawnable, uint32_t generation) override;
|
||||
void OnRootSpawnableReleased(uint32_t generation) override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -806,12 +806,20 @@ namespace AzFramework
|
||||
[[maybe_unused]] float scrollDelta,
|
||||
[[maybe_unused]] float deltaTime)
|
||||
{
|
||||
const auto pivot = m_pivotFn();
|
||||
|
||||
if (!pivot.has_value())
|
||||
{
|
||||
EndActivation();
|
||||
return targetCamera;
|
||||
}
|
||||
|
||||
if (Beginning())
|
||||
{
|
||||
// as the camera starts, record the camera we would like to end up as
|
||||
m_nextCamera.m_offset = m_offsetFn(m_pivotFn().GetDistance(targetCamera.Translation()));
|
||||
m_nextCamera.m_offset = m_offsetFn(pivot.value().GetDistance(targetCamera.Translation()));
|
||||
const auto angles =
|
||||
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), m_pivotFn())));
|
||||
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), pivot.value())));
|
||||
m_nextCamera.m_pitch = angles.GetX();
|
||||
m_nextCamera.m_yaw = angles.GetZ();
|
||||
m_nextCamera.m_pivot = targetCamera.m_pivot;
|
||||
|
||||
@@ -651,7 +651,7 @@ namespace AzFramework
|
||||
class FocusCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
using PivotFn = AZStd::function<AZ::Vector3()>;
|
||||
using PivotFn = AZStd::function<AZStd::optional<AZ::Vector3>()>;
|
||||
|
||||
FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn);
|
||||
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzFramework/Viewport/ViewportId.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/std/optional.h>
|
||||
#include <AzFramework/Viewport/ViewportId.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -20,18 +21,15 @@ namespace AZ
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class ViewportRequests
|
||||
: public AZ::EBusTraits
|
||||
class ViewportRequests : public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
|
||||
using BusIdType = ViewportId;
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
virtual ~ViewportRequests() {}
|
||||
|
||||
//! Gets the current camera's world to view matrix.
|
||||
virtual const AZ::Matrix4x4& GetCameraViewMatrix() const = 0;
|
||||
//! Sets the current camera's world to view matrix.
|
||||
@@ -44,8 +42,36 @@ namespace AzFramework
|
||||
virtual AZ::Transform GetCameraTransform() const = 0;
|
||||
//! Convenience method, sets the camera's world to view matrix from this AZ::Transform.
|
||||
virtual void SetCameraTransform(const AZ::Transform& transform) = 0;
|
||||
|
||||
protected:
|
||||
~ViewportRequests() = default;
|
||||
};
|
||||
|
||||
using ViewportRequestBus = AZ::EBus<ViewportRequests>;
|
||||
|
||||
} //namespace AzFramework
|
||||
//! The additional padding around the viewport when a viewport border is active.
|
||||
struct ViewportBorderPadding
|
||||
{
|
||||
float m_top;
|
||||
float m_bottom;
|
||||
float m_left;
|
||||
float m_right;
|
||||
};
|
||||
|
||||
//! For performing queries about the state of the viewport border.
|
||||
class ViewportBorderRequests : public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
|
||||
using BusIdType = ViewportId;
|
||||
|
||||
//! Returns if a viewport border is in effect and what the current dimensions (padding) of the border are.
|
||||
virtual AZStd::optional<ViewportBorderPadding> GetViewportBorderPadding() const = 0;
|
||||
|
||||
protected:
|
||||
~ViewportBorderRequests() = default;
|
||||
};
|
||||
|
||||
using ViewportBorderRequestBus = AZ::EBus<ViewportBorderRequests>;
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -286,6 +286,7 @@ set(FILES
|
||||
Spawnable/RootSpawnableInterface.h
|
||||
Spawnable/Spawnable.cpp
|
||||
Spawnable/Spawnable.h
|
||||
Spawnable/SpawnableAssetBus.h
|
||||
Spawnable/SpawnableAssetHandler.h
|
||||
Spawnable/SpawnableAssetHandler.cpp
|
||||
Spawnable/SpawnableEntitiesContainer.h
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <AzFramework/XcbEventHandler.h>
|
||||
#include <AzFramework/XcbInterface.h>
|
||||
|
||||
#include <xcb/xinput.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -34,6 +36,31 @@ namespace AzFramework
|
||||
return m_xcbConnection.get();
|
||||
}
|
||||
|
||||
void SetEnableXInput(xcb_connection_t* connection, bool enable) override
|
||||
{
|
||||
struct Mask
|
||||
{
|
||||
xcb_input_event_mask_t head;
|
||||
xcb_input_xi_event_mask_t mask;
|
||||
};
|
||||
const Mask mask {
|
||||
/*.head=*/{
|
||||
/*.device_id=*/XCB_INPUT_DEVICE_ALL_MASTER,
|
||||
/*.mask_len=*/1
|
||||
},
|
||||
/*.mask=*/ enable ?
|
||||
(xcb_input_xi_event_mask_t)(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_t)XCB_NONE
|
||||
};
|
||||
|
||||
const xcb_setup_t* xcbSetup = xcb_get_setup(connection);
|
||||
const xcb_screen_t* xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
|
||||
|
||||
xcb_input_xi_select_events(connection, xcbScreen->root, 1, &mask.head);
|
||||
|
||||
xcb_flush(connection);
|
||||
}
|
||||
|
||||
private:
|
||||
XcbUniquePtr<xcb_connection_t, xcb_disconnect> m_xcbConnection = nullptr;
|
||||
};
|
||||
|
||||
@@ -24,6 +24,9 @@ namespace AzFramework
|
||||
virtual ~XcbConnectionManager() = default;
|
||||
|
||||
virtual xcb_connection_t* GetXcbConnection() const = 0;
|
||||
|
||||
//! Enables/Disables XInput Raw Input events.
|
||||
virtual void SetEnableXInput(xcb_connection_t* connection, bool enable) = 0;
|
||||
};
|
||||
|
||||
class XcbConnectionManagerBusTraits
|
||||
|
||||
@@ -23,9 +23,6 @@ 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
|
||||
|
||||
+97
-180
@@ -13,21 +13,68 @@
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
xcb_window_t GetSystemCursorFocusWindow()
|
||||
xcb_window_t GetSystemCursorFocusWindow(xcb_connection_t* connection)
|
||||
{
|
||||
void* systemCursorFocusWindow = nullptr;
|
||||
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
|
||||
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
|
||||
|
||||
if (!systemCursorFocusWindow)
|
||||
if (systemCursorFocusWindow)
|
||||
{
|
||||
return XCB_NONE;
|
||||
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
|
||||
}
|
||||
|
||||
// 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.
|
||||
// EWMH-compliant window managers set the "_NET_ACTIVE_WINDOW" property
|
||||
// of the X server's root window to the currently active window. This
|
||||
// retrieves value of that property.
|
||||
|
||||
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
|
||||
// Get the atom for the _NET_ACTIVE_WINDOW property
|
||||
constexpr int propertyNameLength = 18;
|
||||
xcb_generic_error_t* error = nullptr;
|
||||
XcbStdFreePtr<xcb_intern_atom_reply_t> activeWindowAtom {xcb_intern_atom_reply(
|
||||
connection,
|
||||
xcb_intern_atom(connection, /*only_if_exists=*/ 1, propertyNameLength, "_NET_ACTIVE_WINDOW"),
|
||||
&error
|
||||
)};
|
||||
if (!activeWindowAtom || error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
|
||||
free(error);
|
||||
}
|
||||
return XCB_WINDOW_NONE;
|
||||
}
|
||||
|
||||
// Get the root window
|
||||
const xcb_window_t rootWId = xcb_setup_roots_iterator(xcb_get_setup(connection)).data->root;
|
||||
|
||||
// Fetch the value of the root window's _NET_ACTIVE_WINDOW property
|
||||
XcbStdFreePtr<xcb_get_property_reply_t> property {xcb_get_property_reply(
|
||||
connection,
|
||||
xcb_get_property(
|
||||
/*c=*/connection,
|
||||
/*_delete=*/ 0,
|
||||
/*window=*/rootWId,
|
||||
/*property=*/activeWindowAtom->atom,
|
||||
/*type=*/XCB_ATOM_WINDOW,
|
||||
/*long_offset=*/0,
|
||||
/*long_length=*/1
|
||||
),
|
||||
&error
|
||||
)};
|
||||
|
||||
if (!property || error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
|
||||
free(error);
|
||||
}
|
||||
return XCB_WINDOW_NONE;
|
||||
}
|
||||
|
||||
return *static_cast<xcb_window_t*>(xcb_get_property_value(property.get()));
|
||||
}
|
||||
|
||||
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
|
||||
@@ -39,8 +86,7 @@ namespace AzFramework
|
||||
: InputDeviceMouse::Implementation(inputDevice)
|
||||
, m_systemCursorState(SystemCursorState::Unknown)
|
||||
, m_systemCursorPositionNormalized(0.5f, 0.5f)
|
||||
, m_prevConstraintWindow(XCB_NONE)
|
||||
, m_focusWindow(XCB_NONE)
|
||||
, m_focusWindow(XCB_WINDOW_NONE)
|
||||
, m_cursorShown(true)
|
||||
{
|
||||
XcbEventHandlerBus::Handler::BusConnect();
|
||||
@@ -57,14 +103,14 @@ namespace AzFramework
|
||||
|
||||
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
|
||||
{
|
||||
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
const auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
if (!interface)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB interface not available");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
|
||||
s_xcbConnection = interface->GetXcbConnection();
|
||||
if (!s_xcbConnection)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB connection not available");
|
||||
@@ -126,7 +172,7 @@ namespace AzFramework
|
||||
|
||||
// Get window information.
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
|
||||
|
||||
if (!xcbGeometryReply)
|
||||
{
|
||||
@@ -137,7 +183,7 @@ namespace AzFramework
|
||||
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) };
|
||||
s_xcbConnection, translate_coord, nullptr) };
|
||||
|
||||
if (!xkbTranslateCoordReply)
|
||||
{
|
||||
@@ -173,11 +219,11 @@ namespace AzFramework
|
||||
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) };
|
||||
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, nullptr);
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xcbError{ 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,
|
||||
"XcbInput", !xcbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
|
||||
barrier.x1, barrier.y1);
|
||||
}
|
||||
}
|
||||
@@ -207,7 +253,7 @@ namespace AzFramework
|
||||
|
||||
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
|
||||
|
||||
xcb_generic_error_t* error = NULL;
|
||||
xcb_generic_error_t* error = nullptr;
|
||||
const XcbStdFreePtr<xcb_xfixes_query_version_reply_t> xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
|
||||
s_xcbConnection, query_cookie, &error) };
|
||||
|
||||
@@ -244,7 +290,7 @@ namespace AzFramework
|
||||
|
||||
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;
|
||||
xcb_generic_error_t* error = nullptr;
|
||||
const XcbStdFreePtr<xcb_input_xi_query_version_reply_t> xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
|
||||
s_xcbConnection, query_version_cookie, &error) };
|
||||
|
||||
@@ -268,40 +314,13 @@ namespace AzFramework
|
||||
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();
|
||||
m_focusWindow = GetSystemCursorFocusWindow(s_xcbConnection);
|
||||
|
||||
HandleCursorState(m_focusWindow, systemCursorState);
|
||||
}
|
||||
@@ -309,52 +328,10 @@ namespace AzFramework
|
||||
|
||||
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);
|
||||
const bool confined = (systemCursorState == SystemCursorState::ConstrainedAndHidden) ||
|
||||
(systemCursorState == SystemCursorState::ConstrainedAndVisible);
|
||||
const bool cursorShown = (systemCursorState == SystemCursorState::ConstrainedAndVisible) ||
|
||||
(systemCursorState == SystemCursorState::UnconstrainedAndVisible);
|
||||
|
||||
CreateBarriers(window, confined);
|
||||
ShowCursor(window, cursorShown);
|
||||
@@ -368,26 +345,26 @@ namespace AzFramework
|
||||
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) };
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
|
||||
|
||||
if (!xkbGeometryReply)
|
||||
if (!xcbGeometryReply)
|
||||
{
|
||||
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);
|
||||
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xcbGeometryReply->width);
|
||||
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xcbGeometryReply->height);
|
||||
|
||||
xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y);
|
||||
xcb_warp_pointer(s_xcbConnection, XCB_WINDOW_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)
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
|
||||
if (XCB_WINDOW_NONE == window)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -401,7 +378,7 @@ namespace AzFramework
|
||||
|
||||
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) };
|
||||
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, nullptr) };
|
||||
|
||||
if (!xkbQueryPointerReply)
|
||||
{
|
||||
@@ -409,7 +386,7 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
|
||||
|
||||
if (!xkbGeometryReply)
|
||||
{
|
||||
@@ -429,8 +406,8 @@ namespace AzFramework
|
||||
|
||||
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
|
||||
{
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow();
|
||||
if (XCB_NONE == window)
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
|
||||
if (XCB_WINDOW_NONE == window)
|
||||
{
|
||||
return AZ::Vector2::CreateZero();
|
||||
}
|
||||
@@ -455,11 +432,11 @@ namespace AzFramework
|
||||
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
|
||||
}
|
||||
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xcbError{ xcb_request_check(s_xcbConnection, cookie) };
|
||||
|
||||
if (xkbError)
|
||||
if (xcbError)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
|
||||
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xcbError->error_code);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -500,14 +477,6 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -552,78 +521,20 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
// XInput raw events are sent from the server as a XCB_GE_GENERIC
|
||||
// event. A XCB_GE_GENERIC event is typecast to a
|
||||
// xcb_ge_generic_event_t, which is distinct from a
|
||||
// xcb_generic_event_t, and exists so that X11 extensions can extend
|
||||
// the event emission beyond the size that a normal X11 event could
|
||||
// contain.
|
||||
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;
|
||||
}
|
||||
}
|
||||
HandleRawInputEvents(genericEvent);
|
||||
}
|
||||
break;
|
||||
case XCB_FOCUS_IN:
|
||||
@@ -634,6 +545,9 @@ namespace AzFramework
|
||||
m_focusWindow = focusInEvent->event;
|
||||
HandleCursorState(m_focusWindow, m_systemCursorState);
|
||||
}
|
||||
|
||||
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
interface->SetEnableXInput(interface->GetXcbConnection(), true);
|
||||
}
|
||||
break;
|
||||
case XCB_FOCUS_OUT:
|
||||
@@ -644,7 +558,10 @@ namespace AzFramework
|
||||
ProcessRawEventQueues();
|
||||
ResetInputChannelStates();
|
||||
|
||||
m_focusWindow = XCB_NONE;
|
||||
m_focusWindow = XCB_WINDOW_NONE;
|
||||
|
||||
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
interface->SetEnableXInput(interface->GetXcbConnection(), false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -65,9 +65,6 @@ namespace AzFramework
|
||||
//! \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;
|
||||
|
||||
@@ -77,9 +74,6 @@ namespace AzFramework
|
||||
//! 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);
|
||||
|
||||
@@ -98,9 +92,6 @@ namespace AzFramework
|
||||
//! 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);
|
||||
|
||||
@@ -160,7 +151,6 @@ namespace AzFramework
|
||||
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;
|
||||
@@ -171,9 +161,6 @@ namespace AzFramework
|
||||
//! 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;
|
||||
|
||||
|
||||
+74
-39
@@ -9,19 +9,71 @@
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/std/containers/array.h>
|
||||
#include <AzCore/std/tuple.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <cerrno>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
AZ_CVAR(bool, ap_tether_lifetime, true, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
"If enabled, a parent process that launches the AP will terminate the AP on exit");
|
||||
|
||||
namespace AzFramework::AssetSystem::Platform
|
||||
{
|
||||
void AllowAssetProcessorToForeground()
|
||||
{}
|
||||
|
||||
[[noreturn]] static void LaunchAssetProcessorDirectly(const AZ::IO::FixedMaxPath& assetProcessorPath, AZStd::string_view engineRoot, AZStd::string_view projectPath)
|
||||
{
|
||||
AZStd::fixed_vector<const char*, 5> args {
|
||||
assetProcessorPath.c_str(),
|
||||
"--start-hidden",
|
||||
};
|
||||
|
||||
// Add the engine path to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString engineRootArg;
|
||||
if (!engineRoot.empty())
|
||||
{
|
||||
// No need to quote these paths, this code calls exec directly and
|
||||
// does not go through shell string interpolation
|
||||
engineRootArg = AZ::IO::FixedMaxPathString{"--engine-path="} + AZ::IO::FixedMaxPathString{engineRoot};
|
||||
args.push_back(engineRootArg.data());
|
||||
}
|
||||
|
||||
// Add the active project path to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString projectPathArg;
|
||||
if (!projectPath.empty())
|
||||
{
|
||||
projectPathArg = AZ::IO::FixedMaxPathString{"--regset=/Amazon/AzCore/Bootstrap/project_path="} + AZ::IO::FixedMaxPathString{projectPath};
|
||||
args.push_back(projectPathArg.data());
|
||||
}
|
||||
|
||||
// Make sure this is at the end
|
||||
args.push_back(nullptr); // argv itself needs to be null-terminated
|
||||
|
||||
execv(args[0], const_cast<char**>(args.data()));
|
||||
|
||||
// exec* family of functions only return on error
|
||||
fprintf(stderr, "Asset Processor failed with error: %s\n", strerror(errno));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
static pid_t LaunchAssetProcessorDaemonized(const AZ::IO::FixedMaxPath& assetProcessorPath, AZStd::string_view engineRoot, AZStd::string_view projectPath)
|
||||
{
|
||||
// detach the child from parent
|
||||
setsid();
|
||||
const pid_t secondChildPid = fork();
|
||||
if (secondChildPid == 0)
|
||||
{
|
||||
LaunchAssetProcessorDirectly(assetProcessorPath, engineRoot, projectPath);
|
||||
}
|
||||
return secondChildPid;
|
||||
}
|
||||
|
||||
bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view engineRoot,
|
||||
AZStd::string_view projectPath)
|
||||
{
|
||||
@@ -40,7 +92,8 @@ namespace AzFramework::AssetSystem::Platform
|
||||
}
|
||||
}
|
||||
|
||||
pid_t firstChildPid = fork();
|
||||
const pid_t parentPid = getpid();
|
||||
const pid_t firstChildPid = fork();
|
||||
if (firstChildPid == 0)
|
||||
{
|
||||
// redirect output to dev/null so it doesn't hijack an existing console window
|
||||
@@ -53,51 +106,33 @@ namespace AzFramework::AssetSystem::Platform
|
||||
AZ::IO::FileDescriptorRedirector stderrRedirect(STDERR_FILENO);
|
||||
stderrRedirect.RedirectTo(devNull, mode);
|
||||
|
||||
// detach the child from parent
|
||||
setsid();
|
||||
pid_t secondChildPid = fork();
|
||||
if (secondChildPid == 0)
|
||||
if (ap_tether_lifetime)
|
||||
{
|
||||
AZStd::array args {
|
||||
assetProcessorPath.c_str(), assetProcessorPath.c_str(), "--start-hidden",
|
||||
static_cast<const char*>(nullptr), static_cast<const char*>(nullptr), static_cast<const char*>(nullptr)
|
||||
};
|
||||
int optionalArgPos = 3;
|
||||
|
||||
// Add the engine path to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString engineRootArg;
|
||||
if (!engineRoot.empty())
|
||||
prctl(PR_SET_PDEATHSIG, SIGTERM);
|
||||
if (getppid() != parentPid)
|
||||
{
|
||||
engineRootArg = AZ::IO::FixedMaxPathString::format(R"(--engine-path="%.*s")",
|
||||
aznumeric_cast<int>(engineRoot.size()), engineRoot.data());
|
||||
args[optionalArgPos++] = engineRootArg.data();
|
||||
_exit(1);
|
||||
}
|
||||
LaunchAssetProcessorDirectly(assetProcessorPath, engineRoot, projectPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
const pid_t secondChildPid = LaunchAssetProcessorDaemonized(assetProcessorPath, engineRoot, projectPath);
|
||||
stdoutRedirect.Reset();
|
||||
stderrRedirect.Reset();
|
||||
|
||||
// Add the active project path to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString projectPathArg;
|
||||
if (!projectPath.empty())
|
||||
{
|
||||
projectPathArg = AZ::IO::FixedMaxPathString::format(R"(--regset="/Amazon/AzCore/Bootstrap/project_path=%.*s")",
|
||||
aznumeric_cast<int>(projectPath.size()), projectPath.data());
|
||||
args[optionalArgPos++] = projectPathArg.data();
|
||||
}
|
||||
|
||||
AZStd::apply(execl, args);
|
||||
|
||||
// exec* family of functions only exit on error
|
||||
AZ_Error("AssetSystemComponent", false, "Asset Processor failed with error: %s", strerror(errno));
|
||||
_exit(1);
|
||||
// exit the transient child with proper return code
|
||||
int ret = (secondChildPid < 0) ? 1 : 0;
|
||||
_exit(ret);
|
||||
}
|
||||
|
||||
stdoutRedirect.Reset();
|
||||
stderrRedirect.Reset();
|
||||
|
||||
// exit the transient child with proper return code
|
||||
int ret = (secondChildPid < 0) ? 1 : 0;
|
||||
_exit(ret);
|
||||
}
|
||||
else if (firstChildPid > 0)
|
||||
{
|
||||
if (ap_tether_lifetime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// wait for first child to exit to ensure the second child was started
|
||||
int status = 0;
|
||||
pid_t ret = waitpid(firstChildPid, &status, 0);
|
||||
@@ -106,4 +141,4 @@ namespace AzFramework::AssetSystem::Platform
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework::AssetSystem::Platform
|
||||
|
||||
+3
-2
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <AzCore/Module/DynamicModuleHandle.h>
|
||||
#include <AzCore/PlatformIncl.h>
|
||||
#include <AzCore/std/containers/array.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
|
||||
namespace AzFramework
|
||||
@@ -234,14 +235,14 @@ namespace AzFramework
|
||||
const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER);
|
||||
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize);
|
||||
|
||||
LPBYTE rawInputBytes = new BYTE[rawInputSize];
|
||||
AZStd::array<BYTE, sizeof(RAWINPUT)> rawInputBytesArray;
|
||||
LPBYTE rawInputBytes = rawInputBytesArray.data();
|
||||
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
|
||||
|
||||
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
|
||||
AzFramework::RawInputNotificationBusWindows::Broadcast(
|
||||
&AzFramework::RawInputNotificationBusWindows::Events::OnRawInputEvent, *rawInput);
|
||||
|
||||
delete [] rawInputBytes;
|
||||
break;
|
||||
}
|
||||
case WM_CHAR:
|
||||
|
||||
@@ -41,7 +41,9 @@ namespace UnitTest
|
||||
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_application->Start({});
|
||||
|
||||
@@ -45,7 +45,9 @@ namespace UnitTest
|
||||
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_application->Start({});
|
||||
|
||||
@@ -305,7 +305,9 @@ namespace UnitTest
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
AZ::ComponentApplication::StartupParameters startupParameters;
|
||||
|
||||
@@ -48,6 +48,24 @@ namespace InputUnitTests
|
||||
AZStd::unique_ptr<InputSystemComponent> m_inputSystemComponent;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
TEST_F(InputTest, InputChannelId_ConstExpression_CopyConstructorSuccessfull)
|
||||
{
|
||||
constexpr InputChannelId testInputChannelId1("TestInputChannelId");
|
||||
constexpr InputChannelId testInputChannelId2(testInputChannelId1);
|
||||
static_assert(testInputChannelId1 == testInputChannelId2);
|
||||
EXPECT_EQ(testInputChannelId1, testInputChannelId2);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
TEST_F(InputTest, InputDeviceId_ConstExpression_CopyConstructorSuccessfull)
|
||||
{
|
||||
constexpr InputDeviceId testInputDeviceId1("TestInputDeviceId");
|
||||
constexpr InputDeviceId testInputDeviceId2(testInputDeviceId1);
|
||||
static_assert(testInputDeviceId1 == testInputDeviceId2);
|
||||
EXPECT_EQ(testInputDeviceId1, testInputDeviceId2);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
TEST_F(InputTest, InputContext_InitWithDataStruct_InitializationSuccessfull)
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace AzFramework
|
||||
|
||||
MOCK_METHOD3(
|
||||
SpawnEntities,
|
||||
void(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs));
|
||||
void(EntitySpawnTicket& ticket, AZStd::vector<uint32_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD2(DespawnAllEntities, void(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
@@ -49,6 +49,13 @@ namespace AzFramework
|
||||
ReloadSpawnable,
|
||||
void(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
UpdateEntityAliasTypes,
|
||||
void(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
ListEntities, void(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
@@ -61,6 +68,7 @@ namespace AzFramework
|
||||
void(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(Barrier, void(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs));
|
||||
MOCK_METHOD3(LoadBarrier, void(EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD1(CreateTicket, AZStd::pair<EntitySpawnTicket::Id, void*>(AZ::Data::Asset<Spawnable>&& spawnable));
|
||||
MOCK_METHOD1(DestroyTicket, void(void* ticket));
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_0_VALUE_PARAMS()) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{};
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_1_VALUE_PARAMS(p0)) {
|
||||
@@ -25,3 +32,38 @@ ACTION_TEMPLATE(ReturnMalloc,
|
||||
*value = T{ p0, p1 };
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_3_VALUE_PARAMS(p0, p1, p2)) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{ p0, p1, p2 };
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_4_VALUE_PARAMS(p0, p1, p2, p3)) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{ p0, p1, p2, p3 };
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{ p0, p1, p2, p3, p4 };
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{ p0, p1, p2, p3, p4, p5 };
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{ p0, p1, p2, p3, p4, p5, p6 };
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,82 @@ xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t co
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_request_check(c, cookie);
|
||||
}
|
||||
const xcb_setup_t* xcb_get_setup(xcb_connection_t *c)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_setup(c);
|
||||
}
|
||||
xcb_screen_iterator_t xcb_setup_roots_iterator(const xcb_setup_t* R)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_setup_roots_iterator(R);
|
||||
}
|
||||
const xcb_query_extension_reply_t* xcb_get_extension_data(xcb_connection_t* c, xcb_extension_t* ext)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_extension_data(c, ext);
|
||||
}
|
||||
int xcb_flush(xcb_connection_t *c)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_flush(c);
|
||||
}
|
||||
xcb_query_pointer_cookie_t xcb_query_pointer(xcb_connection_t* c, xcb_window_t window)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_query_pointer(c, window);
|
||||
}
|
||||
xcb_query_pointer_reply_t* xcb_query_pointer_reply(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_query_pointer_reply(c, cookie, e);
|
||||
}
|
||||
xcb_get_geometry_cookie_t xcb_get_geometry(xcb_connection_t* c, xcb_drawable_t drawable)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_geometry(c, drawable);
|
||||
}
|
||||
xcb_get_geometry_reply_t* xcb_get_geometry_reply(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_geometry_reply(c, cookie, e);
|
||||
}
|
||||
xcb_void_cookie_t xcb_warp_pointer(
|
||||
xcb_connection_t* c,
|
||||
xcb_window_t src_window,
|
||||
xcb_window_t dst_window,
|
||||
int16_t src_x,
|
||||
int16_t src_y,
|
||||
uint16_t src_width,
|
||||
uint16_t src_height,
|
||||
int16_t dst_x,
|
||||
int16_t dst_y)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_warp_pointer(c, src_window, dst_window, src_x, src_y, src_width, src_height, dst_x, dst_y);
|
||||
}
|
||||
xcb_intern_atom_cookie_t xcb_intern_atom(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_intern_atom(c, only_if_exists, name_len, name);
|
||||
}
|
||||
xcb_intern_atom_reply_t* xcb_intern_atom_reply(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_intern_atom_reply(c, cookie, e);
|
||||
}
|
||||
xcb_get_property_cookie_t xcb_get_property(
|
||||
xcb_connection_t* c,
|
||||
uint8_t _delete,
|
||||
xcb_window_t window,
|
||||
xcb_atom_t property,
|
||||
xcb_atom_t type,
|
||||
uint32_t long_offset,
|
||||
uint32_t long_length)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_property(c, _delete, window, property, type, long_offset, long_length);
|
||||
}
|
||||
xcb_get_property_reply_t* xcb_get_property_reply(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_property_reply(c, cookie, e);
|
||||
}
|
||||
void* xcb_get_property_value(const xcb_get_property_reply_t* R)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_property_value(R);
|
||||
}
|
||||
uint32_t xcb_generate_id(xcb_connection_t *c)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_generate_id(c);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// xcb-xkb
|
||||
@@ -116,4 +192,76 @@ xkb_state_component xkb_state_update_mask(
|
||||
state, depressed_mods, latched_mods, locked_mods, depressed_layout, latched_layout, locked_layout);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// xcb-xfixes
|
||||
xcb_xfixes_query_version_cookie_t xcb_xfixes_query_version(
|
||||
xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_query_version(c, client_major_version, client_minor_version);
|
||||
}
|
||||
xcb_xfixes_query_version_reply_t* xcb_xfixes_query_version_reply(
|
||||
xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_query_version_reply(c, cookie, e);
|
||||
}
|
||||
xcb_void_cookie_t xcb_xfixes_show_cursor_checked(xcb_connection_t* c, xcb_window_t window)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_show_cursor_checked(c, window);
|
||||
}
|
||||
xcb_void_cookie_t xcb_xfixes_hide_cursor_checked(xcb_connection_t* c, xcb_window_t window)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_hide_cursor_checked(c, window);
|
||||
}
|
||||
xcb_void_cookie_t xcb_xfixes_delete_pointer_barrier_checked(xcb_connection_t* c, xcb_xfixes_barrier_t barrier)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_delete_pointer_barrier_checked(c, barrier);
|
||||
}
|
||||
xcb_translate_coordinates_cookie_t xcb_translate_coordinates(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_translate_coordinates(c, src_window, dst_window, src_x, src_y);
|
||||
}
|
||||
xcb_translate_coordinates_reply_t* xcb_translate_coordinates_reply(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_translate_coordinates_reply(c, cookie, e);
|
||||
}
|
||||
xcb_void_cookie_t xcb_xfixes_create_pointer_barrier_checked(
|
||||
xcb_connection_t* c,
|
||||
xcb_xfixes_barrier_t barrier,
|
||||
xcb_window_t window,
|
||||
uint16_t x1,
|
||||
uint16_t y1,
|
||||
uint16_t x2,
|
||||
uint16_t y2,
|
||||
uint32_t directions,
|
||||
uint16_t num_devices,
|
||||
const uint16_t* devices)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_create_pointer_barrier_checked(c, barrier, window, x1, y1, x2, y2, directions, num_devices, devices);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// xcb-xinput
|
||||
xcb_input_xi_query_version_cookie_t xcb_input_xi_query_version(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_input_xi_query_version(c, major_version, minor_version);
|
||||
}
|
||||
xcb_input_xi_query_version_reply_t* xcb_input_xi_query_version_reply(
|
||||
xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_input_xi_query_version_reply(c, cookie, e);
|
||||
}
|
||||
xcb_void_cookie_t xcb_input_xi_select_events(
|
||||
xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_input_xi_select_events(c, window, num_mask, masks);
|
||||
}
|
||||
int xcb_input_raw_button_press_axisvalues_length (const xcb_input_raw_button_press_event_t *R)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_length(R);
|
||||
}
|
||||
xcb_input_fp3232_t* xcb_input_raw_button_press_axisvalues_raw(const xcb_input_raw_button_press_event_t* R)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_raw(R);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#undef explicit
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-x11.h>
|
||||
#include <xcb/xfixes.h>
|
||||
#include <xcb/xinput.h>
|
||||
|
||||
#include "Printers.h"
|
||||
|
||||
@@ -62,6 +64,37 @@ public:
|
||||
MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c));
|
||||
MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c));
|
||||
MOCK_CONST_METHOD2(xcb_request_check, xcb_generic_error_t*(xcb_connection_t* c, xcb_void_cookie_t cookie));
|
||||
MOCK_CONST_METHOD1(xcb_get_setup, const xcb_setup_t*(xcb_connection_t *c));
|
||||
MOCK_CONST_METHOD1(xcb_setup_roots_iterator, xcb_screen_iterator_t(const xcb_setup_t* R));
|
||||
MOCK_CONST_METHOD2(xcb_get_extension_data, const xcb_query_extension_reply_t*(xcb_connection_t* c, xcb_extension_t* ext));
|
||||
MOCK_CONST_METHOD1(xcb_flush, int(xcb_connection_t *c));
|
||||
MOCK_CONST_METHOD2(xcb_query_pointer, xcb_query_pointer_cookie_t(xcb_connection_t* c, xcb_window_t window));
|
||||
MOCK_CONST_METHOD3(xcb_query_pointer_reply, xcb_query_pointer_reply_t*(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD2(xcb_get_geometry, xcb_get_geometry_cookie_t(xcb_connection_t* c, xcb_drawable_t drawable));
|
||||
MOCK_CONST_METHOD3(xcb_get_geometry_reply, xcb_get_geometry_reply_t*(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD9(xcb_warp_pointer, xcb_void_cookie_t(
|
||||
xcb_connection_t* c,
|
||||
xcb_window_t src_window,
|
||||
xcb_window_t dst_window,
|
||||
int16_t src_x,
|
||||
int16_t src_y,
|
||||
uint16_t src_width,
|
||||
uint16_t src_height,
|
||||
int16_t dst_x,
|
||||
int16_t dst_y));
|
||||
MOCK_CONST_METHOD4(xcb_intern_atom, xcb_intern_atom_cookie_t(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name));
|
||||
MOCK_CONST_METHOD3(xcb_intern_atom_reply, xcb_intern_atom_reply_t*(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD7(xcb_get_property, xcb_get_property_cookie_t(
|
||||
xcb_connection_t* c,
|
||||
uint8_t _delete,
|
||||
xcb_window_t window,
|
||||
xcb_atom_t property,
|
||||
xcb_atom_t type,
|
||||
uint32_t long_offset,
|
||||
uint32_t long_length));
|
||||
MOCK_CONST_METHOD3(xcb_get_property_reply, xcb_get_property_reply_t*(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD1(xcb_get_property_value, void*(const xcb_get_property_reply_t* R));
|
||||
MOCK_CONST_METHOD1(xcb_generate_id, uint32_t(xcb_connection_t *c));
|
||||
|
||||
// xcb-xkb
|
||||
MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor));
|
||||
@@ -83,6 +116,33 @@ public:
|
||||
MOCK_CONST_METHOD4(xkb_state_key_get_utf8, int(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size));
|
||||
MOCK_CONST_METHOD7(xkb_state_update_mask, xkb_state_component(xkb_state* state, xkb_mod_mask_t depressed_mods, xkb_mod_mask_t latched_mods, xkb_mod_mask_t locked_mods, xkb_layout_index_t depressed_layout, xkb_layout_index_t latched_layout, xkb_layout_index_t locked_layout));
|
||||
|
||||
// xcb-xfixes
|
||||
MOCK_CONST_METHOD3(xcb_xfixes_query_version, xcb_xfixes_query_version_cookie_t(xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version));
|
||||
MOCK_CONST_METHOD3(xcb_xfixes_query_version_reply, xcb_xfixes_query_version_reply_t*(xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD2(xcb_xfixes_show_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
|
||||
MOCK_CONST_METHOD2(xcb_xfixes_hide_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
|
||||
MOCK_CONST_METHOD2(xcb_xfixes_delete_pointer_barrier_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_xfixes_barrier_t barrier));
|
||||
MOCK_CONST_METHOD5(xcb_translate_coordinates, xcb_translate_coordinates_cookie_t(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y));
|
||||
MOCK_CONST_METHOD3(xcb_translate_coordinates_reply, xcb_translate_coordinates_reply_t*(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD10(xcb_xfixes_create_pointer_barrier_checked, xcb_void_cookie_t(
|
||||
xcb_connection_t* c,
|
||||
xcb_xfixes_barrier_t barrier,
|
||||
xcb_window_t window,
|
||||
uint16_t x1,
|
||||
uint16_t y1,
|
||||
uint16_t x2,
|
||||
uint16_t y2,
|
||||
uint32_t directions,
|
||||
uint16_t num_devices,
|
||||
const uint16_t* devices));
|
||||
|
||||
// xcb-xinput
|
||||
MOCK_CONST_METHOD3(xcb_input_xi_query_version, xcb_input_xi_query_version_cookie_t(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version));
|
||||
MOCK_CONST_METHOD3(xcb_input_xi_query_version_reply, xcb_input_xi_query_version_reply_t*(xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD4(xcb_input_xi_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks));
|
||||
MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_length, int(const xcb_input_raw_button_press_event_t* R));
|
||||
MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_raw, xcb_input_fp3232_t*(const xcb_input_raw_button_press_event_t* R));
|
||||
|
||||
private:
|
||||
static inline MockXcbInterface* self = nullptr;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,12 @@ namespace AzFramework
|
||||
public:
|
||||
void SetUp() override;
|
||||
|
||||
template<typename T>
|
||||
static xcb_generic_event_t MakeEvent(T event)
|
||||
{
|
||||
return *reinterpret_cast<xcb_generic_event_t*>(&event);
|
||||
}
|
||||
|
||||
protected:
|
||||
testing::NiceMock<MockXcbInterface> m_interface;
|
||||
xcb_connection_t m_connection{};
|
||||
|
||||
@@ -21,12 +21,6 @@
|
||||
#include "XcbBaseTestFixture.h"
|
||||
#include "XcbTestApplication.h"
|
||||
|
||||
template<typename T>
|
||||
xcb_generic_event_t MakeEvent(T event)
|
||||
{
|
||||
return *reinterpret_cast<xcb_generic_event_t*>(&event);
|
||||
}
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
// Sets up default behavior for mock keyboard responses to xcb methods
|
||||
|
||||
@@ -0,0 +1,545 @@
|
||||
/*
|
||||
* 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 <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
|
||||
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
|
||||
#include "XcbBaseTestFixture.h"
|
||||
#include "XcbTestApplication.h"
|
||||
#include "Matchers.h"
|
||||
#include "Actions.h"
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
// Sets up default behavior for mock keyboard responses to xcb methods
|
||||
class XcbInputDeviceMouseTests
|
||||
: public XcbBaseTestFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
using testing::Eq;
|
||||
using testing::Field;
|
||||
using testing::Return;
|
||||
using testing::StrEq;
|
||||
using testing::_;
|
||||
|
||||
XcbBaseTestFixture::SetUp();
|
||||
|
||||
ON_CALL(m_interface, xcb_get_setup(&m_connection))
|
||||
.WillByDefault(Return(&s_xcbSetup));
|
||||
ON_CALL(m_interface, xcb_setup_roots_iterator(&s_xcbSetup))
|
||||
.WillByDefault(Return(xcb_screen_iterator_t{&s_xcbScreen}));
|
||||
|
||||
ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_xfixes_id))
|
||||
.WillByDefault(Return(&s_xfixesExtensionReply));
|
||||
ON_CALL(m_interface, xcb_xfixes_query_version_reply(&m_connection, _, _))
|
||||
.WillByDefault(ReturnMalloc<xcb_xfixes_query_version_reply_t>(
|
||||
/*response_type=*/(uint8_t)XCB_XFIXES_QUERY_VERSION,
|
||||
/*pad0=*/(uint8_t)0,
|
||||
/*sequence=*/(uint16_t)1,
|
||||
/*length=*/0u,
|
||||
/*major_version=*/5u,
|
||||
/*minor_version=*/0u
|
||||
));
|
||||
|
||||
ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_input_id))
|
||||
.WillByDefault(Return(&s_xfixesExtensionReply));
|
||||
ON_CALL(m_interface, xcb_input_xi_query_version_reply(&m_connection, _, _))
|
||||
.WillByDefault(ReturnMalloc<xcb_input_xi_query_version_reply_t>(
|
||||
/*response_type=*/(uint8_t)XCB_INPUT_XI_QUERY_VERSION,
|
||||
/*pad0=*/(uint8_t)0,
|
||||
/*sequence=*/(uint16_t)1,
|
||||
/*length=*/0u,
|
||||
/*major_version=*/(uint16_t)2,
|
||||
/*minor_version=*/(uint16_t)2
|
||||
));
|
||||
|
||||
// Set the default focus window
|
||||
EXPECT_CALL(m_interface, xcb_intern_atom(&m_connection, 1, 18, StrEq("_NET_ACTIVE_WINDOW")))
|
||||
.WillRepeatedly(Return(xcb_intern_atom_cookie_t{/*.sequence=*/ 1}));
|
||||
ON_CALL(m_interface, xcb_intern_atom_reply(&m_connection, Field(&xcb_intern_atom_cookie_t::sequence, Eq(1)), _))
|
||||
.WillByDefault(ReturnMalloc<xcb_intern_atom_reply_t>(
|
||||
/*response_type=*/(uint8_t)XCB_INTERN_ATOM,
|
||||
/*pad0=*/(uint8_t)0,
|
||||
/*sequence=*/(uint16_t)1,
|
||||
/*length=*/0u,
|
||||
/*xcb_atom_t=*/s_netActiveWindowAtom
|
||||
));
|
||||
ON_CALL(m_interface, xcb_get_property(&m_connection, 0, s_rootWindow, s_netActiveWindowAtom, XCB_ATOM_WINDOW, 0, 1))
|
||||
.WillByDefault(Return(xcb_get_property_cookie_t{/*.sequence=*/ s_getActiveWindowPropertySequence}));
|
||||
ON_CALL(m_interface, xcb_get_property_reply(&m_connection, Field(&xcb_get_property_cookie_t::sequence, Eq(s_getActiveWindowPropertySequence)), _))
|
||||
.WillByDefault(ReturnMalloc<xcb_get_property_reply_t>(
|
||||
/*response_type=*/(uint8_t)XCB_GET_PROPERTY,
|
||||
/*format=*/(uint8_t)0,
|
||||
/*sequence=*/(uint16_t)s_getActiveWindowPropertySequence,
|
||||
/*length=*/0u,
|
||||
/*type=*/XCB_ATOM_WINDOW,
|
||||
/*bytes_after=*/0u,
|
||||
/*value_len=*/1u
|
||||
));
|
||||
ON_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
|
||||
.WillByDefault(Return(const_cast<xcb_window_t*>(&s_nullWindow)));
|
||||
|
||||
ON_CALL(m_interface, xcb_get_geometry(&m_connection, _))
|
||||
.WillByDefault(Return(xcb_get_geometry_cookie_t{/*.sequence=*/1}));
|
||||
ON_CALL(m_interface, xcb_get_geometry_reply(&m_connection, Field(&xcb_get_geometry_cookie_t::sequence, Eq(1)), _))
|
||||
.WillByDefault(ReturnMalloc<xcb_get_geometry_reply_t>(s_defaultWindowGeometry));
|
||||
}
|
||||
|
||||
void PumpApplication()
|
||||
{
|
||||
m_application.PumpSystemEventLoopUntilEmpty();
|
||||
m_application.TickSystem();
|
||||
m_application.Tick();
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr inline uint8_t s_xinputMajorOpcode = 131;
|
||||
static constexpr inline xcb_window_t s_rootWindow = 1;
|
||||
static constexpr inline xcb_window_t s_nullWindow = XCB_WINDOW_NONE;
|
||||
static constexpr inline xcb_input_device_id_t s_virtualCorePointerId = 2;
|
||||
static constexpr inline xcb_input_device_id_t s_physicalPointerDeviceId = 3;
|
||||
static constexpr inline uint16_t s_screenWidthInPixels = 3840;
|
||||
static constexpr inline uint16_t s_screenHeightInPixels = 2160;
|
||||
static constexpr inline uint16_t s_getActiveWindowPropertySequence = 2160;
|
||||
static constexpr inline xcb_atom_t s_netActiveWindowAtom = 1;
|
||||
static constexpr inline xcb_setup_t s_xcbSetup{
|
||||
/*.status=*/1,
|
||||
/*.pad0=*/0,
|
||||
/*.protocol_major_version=*/11,
|
||||
/*.protocol_minor_version=*/0,
|
||||
};
|
||||
static inline xcb_screen_t s_xcbScreen{
|
||||
/*.root=*/s_rootWindow,
|
||||
/*.default_colormap=*/32,
|
||||
/*.white_pixel=*/16777215,
|
||||
/*.black_pixel=*/0,
|
||||
/*.current_input_masks=*/0,
|
||||
/*.width_in_pixels=*/s_screenWidthInPixels,
|
||||
/*.height_in_pixels=*/s_screenHeightInPixels,
|
||||
/*.width_in_millimeters=*/602,
|
||||
/*.height_in_millimeters=*/341,
|
||||
};
|
||||
static constexpr inline xcb_query_extension_reply_t s_xfixesExtensionReply{
|
||||
/*.response_type=*/XCB_QUERY_EXTENSION,
|
||||
/*.pad0=*/0,
|
||||
/*.sequence=*/1,
|
||||
/*.length=*/0,
|
||||
/*.present=*/1,
|
||||
};
|
||||
static constexpr inline xcb_query_extension_reply_t s_xinputExtensionReply{
|
||||
/*.response_type=*/XCB_QUERY_EXTENSION,
|
||||
/*.pad0=*/0,
|
||||
/*.sequence=*/1,
|
||||
/*.length=*/0,
|
||||
/*.present=*/1,
|
||||
/*.major_opcode=*/s_xinputMajorOpcode,
|
||||
};
|
||||
static constexpr inline xcb_get_geometry_reply_t s_defaultWindowGeometry{
|
||||
/*.response_type=*/XCB_GET_GEOMETRY,
|
||||
/*.depth=*/0,
|
||||
/*.sequence=*/1,
|
||||
/*.length=*/0,
|
||||
/*.root=*/s_rootWindow,
|
||||
/*.x=*/100,
|
||||
/*.y=*/100,
|
||||
/*.width=*/100,
|
||||
/*.height=*/100,
|
||||
/*.border_width=*/3,
|
||||
/*.pad0[2]=*/{},
|
||||
};
|
||||
XcbTestApplication m_application{
|
||||
/*enabledGamepadsCount=*/0,
|
||||
/*keyboardEnabled=*/false,
|
||||
/*motionEnabled=*/false,
|
||||
/*mouseEnabled=*/true,
|
||||
/*touchEnabled=*/false,
|
||||
/*virtualKeyboardEnabled=*/false
|
||||
};
|
||||
};
|
||||
|
||||
struct MouseButtonTestData
|
||||
{
|
||||
xcb_button_index_t m_button;
|
||||
};
|
||||
|
||||
class XcbInputDeviceMouseButtonTests
|
||||
: public XcbInputDeviceMouseTests
|
||||
, public testing::WithParamInterface<MouseButtonTestData>
|
||||
{
|
||||
public:
|
||||
static InputChannelId GetInputChannelIdForButton(const xcb_button_index_t button)
|
||||
{
|
||||
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;
|
||||
}
|
||||
return InputChannelId{};
|
||||
}
|
||||
|
||||
AZStd::array<InputChannelId, 4> GetIdleChannelIdsForButton(const xcb_button_index_t button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case XCB_BUTTON_INDEX_1:
|
||||
return { InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_2:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_3:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_4:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_5:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1 };
|
||||
}
|
||||
return AZStd::array<InputChannelId, 4>();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(XcbInputDeviceMouseButtonTests, ButtonInputChannelsUpdateStateFromXcbEvents)
|
||||
{
|
||||
using testing::Each;
|
||||
using testing::Eq;
|
||||
using testing::NotNull;
|
||||
using testing::Property;
|
||||
using testing::Return;
|
||||
|
||||
// Set the expectations for the events that will be generated
|
||||
// nullptr entries represent when the event queue is empty, and will cause
|
||||
// PumpSystemEventLoopUntilEmpty to return
|
||||
//
|
||||
// Event pointers are freed by the calling code, so these actions
|
||||
// malloc new copies
|
||||
//
|
||||
// The xcb mouse does not react to the `XCB_BUTTON_PRESS` /
|
||||
// `XCB_BUTTON_RELEASE` events, but it will still receive those events
|
||||
// from the X server.
|
||||
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_button_press_event_t{
|
||||
/*response_type=*/XCB_GE_GENERIC,
|
||||
/*extension=*/s_xinputMajorOpcode,
|
||||
/*sequence=*/4,
|
||||
/*length=*/2,
|
||||
/*event_type=*/XCB_INPUT_RAW_BUTTON_PRESS,
|
||||
/*deviceid=*/s_virtualCorePointerId,
|
||||
/*time=*/3984920,
|
||||
/*detail=*/GetParam().m_button,
|
||||
/*sourceid=*/s_physicalPointerDeviceId,
|
||||
/*valuators_len=*/2,
|
||||
/*flags=*/0,
|
||||
/*pad0[4]=*/{},
|
||||
/*full_sequence=*/4
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_button_press_event_t{
|
||||
/*response_type=*/XCB_BUTTON_PRESS,
|
||||
/*detail=*/static_cast<xcb_button_t>(GetParam().m_button),
|
||||
/*sequence=*/4,
|
||||
/*time=*/3984920,
|
||||
/*root=*/s_rootWindow,
|
||||
/*event=*/119537664,
|
||||
/*child=*/0,
|
||||
/*root_x=*/55,
|
||||
/*root_y=*/1099,
|
||||
/*event_x=*/55,
|
||||
/*event_y=*/55,
|
||||
/*state=*/0,
|
||||
/*same_screen=*/1
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_button_release_event_t{
|
||||
/*response_type=*/XCB_GE_GENERIC,
|
||||
/*extension=*/s_xinputMajorOpcode,
|
||||
/*sequence=*/4,
|
||||
/*length=*/2,
|
||||
/*event_type=*/XCB_INPUT_RAW_BUTTON_RELEASE,
|
||||
/*deviceid=*/s_virtualCorePointerId,
|
||||
/*time=*/3984964,
|
||||
/*detail=*/GetParam().m_button,
|
||||
/*sourceid=*/s_physicalPointerDeviceId,
|
||||
/*valuators_len=*/2,
|
||||
/*flags=*/0,
|
||||
/*pad0[4]=*/{},
|
||||
/*full_sequence=*/4
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_button_release_event_t{
|
||||
/*response_type=*/XCB_BUTTON_RELEASE,
|
||||
/*detail=*/static_cast<xcb_button_t>(GetParam().m_button),
|
||||
/*sequence=*/4,
|
||||
/*time=*/3984964,
|
||||
/*root=*/s_rootWindow,
|
||||
/*event=*/119537664,
|
||||
/*child=*/0,
|
||||
/*root_x=*/55,
|
||||
/*root_y=*/1099,
|
||||
/*event_x=*/55,
|
||||
/*event_y=*/55,
|
||||
/*state=*/XCB_KEY_BUT_MASK_BUTTON_1,
|
||||
/*same_screen=*/1
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
;
|
||||
|
||||
m_application.Start();
|
||||
InputSystemCursorRequestBus::Event(
|
||||
InputDeviceMouse::Id,
|
||||
&InputSystemCursorRequests::SetSystemCursorState,
|
||||
SystemCursorState::ConstrainedAndHidden);
|
||||
|
||||
const InputChannel* activeButtonChannel = InputChannelRequests::FindInputChannel(GetInputChannelIdForButton(GetParam().m_button));
|
||||
const auto inactiveButtonChannels = [this]()
|
||||
{
|
||||
const auto inactiveButtonChannelIds = GetIdleChannelIdsForButton(GetParam().m_button);
|
||||
AZStd::array<const InputChannel*, 4> channels{};
|
||||
AZStd::transform(begin(inactiveButtonChannelIds), end(inactiveButtonChannelIds), begin(channels), [](const InputChannelId& id)
|
||||
{
|
||||
return InputChannelRequests::FindInputChannel(id);
|
||||
});
|
||||
return channels;
|
||||
}();
|
||||
|
||||
ASSERT_TRUE(activeButtonChannel);
|
||||
ASSERT_THAT(inactiveButtonChannels, Each(NotNull()));
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Began));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Updated));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Ended));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
AllButtons,
|
||||
XcbInputDeviceMouseButtonTests,
|
||||
testing::Values(
|
||||
MouseButtonTestData{ XCB_BUTTON_INDEX_1 },
|
||||
MouseButtonTestData{ XCB_BUTTON_INDEX_2 },
|
||||
MouseButtonTestData{ XCB_BUTTON_INDEX_3 }
|
||||
// XCB_BUTTON_INDEX_4 and XCB_BUTTON_INDEX_5 map to positive and
|
||||
// negative scroll wheel events, which are handled as motion events
|
||||
)
|
||||
);
|
||||
|
||||
TEST_F(XcbInputDeviceMouseTests, MovementInputChannelsUpdateStateFromXcbEvents)
|
||||
{
|
||||
using testing::Each;
|
||||
using testing::Eq;
|
||||
using testing::FloatEq;
|
||||
using testing::NotNull;
|
||||
using testing::Property;
|
||||
using testing::Return;
|
||||
|
||||
// Set the expectations for the events that will be generated
|
||||
// nullptr entries represent when the event queue is empty, and will cause
|
||||
// PumpSystemEventLoopUntilEmpty to return
|
||||
//
|
||||
// Event pointers are freed by the calling code, so these actions
|
||||
// malloc new copies
|
||||
//
|
||||
// The xcb mouse does not react to the `XCB_MOTION_NOTIFY` event, but
|
||||
// it will still receive it from the X server.
|
||||
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_motion_event_t{
|
||||
/*response_type=*/XCB_GE_GENERIC,
|
||||
/*extension=*/s_xinputMajorOpcode,
|
||||
/*sequence=*/5,
|
||||
/*length=*/10,
|
||||
/*event_type=*/XCB_INPUT_RAW_MOTION,
|
||||
/*deviceid=*/s_virtualCorePointerId,
|
||||
/*time=*/0, // use the time value to identify each event
|
||||
/*detail=*/XCB_MOTION_NORMAL,
|
||||
/*sourceid=*/s_physicalPointerDeviceId,
|
||||
/*valuators_len=*/2, // number of axes that have values for this event
|
||||
/*flags=*/0,
|
||||
/*pad0[4]=*/{},
|
||||
/*full_sequence=*/5,
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_motion_notify_event_t{
|
||||
/*response_type=*/XCB_MOTION_NOTIFY,
|
||||
/*detail=*/XCB_MOTION_NORMAL,
|
||||
/*sequence=*/5,
|
||||
/*time=*/1, // use the time value to identify each event
|
||||
/*root=*/s_rootWindow,
|
||||
/*event=*/127926272,
|
||||
/*child=*/0,
|
||||
/*root_x=*/95,
|
||||
/*root_y=*/1079,
|
||||
/*event_x=*/95,
|
||||
/*event_y=*/20,
|
||||
/*state=*/0,
|
||||
/*same_screen=*/1,
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
;
|
||||
|
||||
AZStd::array axisValues
|
||||
{
|
||||
xcb_input_fp3232_t{ /*.integral=*/ 1, /*.fraction=*/0 }, // x motion
|
||||
xcb_input_fp3232_t{ /*.integral=*/ 2, /*.fraction=*/0 } // y motion
|
||||
};
|
||||
|
||||
EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_length(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
|
||||
.WillRepeatedly(testing::Return(2)); // x and y axis
|
||||
EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_raw(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
|
||||
.WillRepeatedly(testing::Return(axisValues.data())); // x and y axis
|
||||
|
||||
m_application.Start();
|
||||
InputSystemCursorRequestBus::Event(
|
||||
InputDeviceMouse::Id,
|
||||
&InputSystemCursorRequests::SetSystemCursorState,
|
||||
SystemCursorState::ConstrainedAndHidden);
|
||||
|
||||
const InputChannel* xMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::X);
|
||||
const InputChannel* yMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::Y);
|
||||
ASSERT_TRUE(xMotionChannel);
|
||||
ASSERT_TRUE(yMotionChannel);
|
||||
|
||||
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Began));
|
||||
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Began));
|
||||
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(1.0f));
|
||||
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(2.0f));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Ended));
|
||||
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Ended));
|
||||
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
}
|
||||
|
||||
struct GetCursorPositionParam
|
||||
{
|
||||
int16_t m_x;
|
||||
int16_t m_y;
|
||||
};
|
||||
|
||||
class XcbGetSystemCursorPositionTests
|
||||
: public XcbInputDeviceMouseTests
|
||||
, public testing::WithParamInterface<GetCursorPositionParam>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_P(XcbGetSystemCursorPositionTests, GetSystemCursorPositionNormalizedReturnsCorrectValue)
|
||||
{
|
||||
using testing::Eq;
|
||||
using testing::Field;
|
||||
using testing::Return;
|
||||
using testing::_;
|
||||
|
||||
xcb_window_t focusWindow = 42;
|
||||
const xcb_query_pointer_reply_t queryPointerReply{
|
||||
/*.response_type=*/XCB_QUERY_POINTER,
|
||||
/*.same_screen=*/1,
|
||||
/*.sequence=*/0,
|
||||
/*.length=*/1,
|
||||
/*.root=*/s_rootWindow,
|
||||
/*.child=*/focusWindow,
|
||||
/*.root_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
|
||||
/*.root_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
|
||||
/*.win_x=*/GetParam().m_x,
|
||||
/*.win_y=*/GetParam().m_y,
|
||||
/*.mask=*/{},
|
||||
/*.pad0[2]=*/{},
|
||||
};
|
||||
|
||||
// Querying the root window's pointer gives its absolute value
|
||||
const xcb_query_pointer_reply_t rootWindowQueryPointerReply{
|
||||
/*.response_type=*/XCB_QUERY_POINTER,
|
||||
/*.same_screen=*/1,
|
||||
/*.sequence=*/0,
|
||||
/*.length=*/1,
|
||||
/*.root=*/s_rootWindow,
|
||||
/*.child=*/s_rootWindow,
|
||||
/*.root_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
|
||||
/*.root_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
|
||||
/*.win_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
|
||||
/*.win_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
|
||||
/*.mask=*/{},
|
||||
/*.pad0[2]=*/{},
|
||||
};
|
||||
|
||||
EXPECT_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
|
||||
.WillRepeatedly(Return(&focusWindow));
|
||||
|
||||
EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, focusWindow))
|
||||
.WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/1}));
|
||||
EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 1), _))
|
||||
.WillRepeatedly(ReturnMalloc<xcb_query_pointer_reply_t>(queryPointerReply));
|
||||
|
||||
EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, s_rootWindow))
|
||||
.WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/2}));
|
||||
EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 2), _))
|
||||
.WillRepeatedly(ReturnMalloc<xcb_query_pointer_reply_t>(rootWindowQueryPointerReply));
|
||||
|
||||
m_application.Start();
|
||||
InputSystemCursorRequestBus::Event(
|
||||
InputDeviceMouse::Id,
|
||||
&InputSystemCursorRequests::SetSystemCursorState,
|
||||
SystemCursorState::ConstrainedAndHidden);
|
||||
|
||||
AZ::Vector2 systemCursorPositionNormalized = AZ::Vector2::CreateZero();
|
||||
InputSystemCursorRequestBus::EventResult(
|
||||
systemCursorPositionNormalized,
|
||||
InputDeviceMouse::Id,
|
||||
&InputSystemCursorRequests::GetSystemCursorPositionNormalized);
|
||||
|
||||
EXPECT_THAT(systemCursorPositionNormalized, ::testing::AllOf(
|
||||
testing::Property(&AZ::Vector2::GetX, testing::FloatEq(static_cast<float>(GetParam().m_x) / s_defaultWindowGeometry.width)),
|
||||
testing::Property(&AZ::Vector2::GetY, testing::FloatEq(static_cast<float>(GetParam().m_y) / s_defaultWindowGeometry.height))
|
||||
));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
AllPointerPositions,
|
||||
XcbGetSystemCursorPositionTests,
|
||||
testing::Values(
|
||||
// Default mocked window geometry sets width and height to 100, all
|
||||
// parameter values should be within [0, 100)
|
||||
GetCursorPositionParam{ 50, 50 },
|
||||
GetCursorPositionParam{ 25, 25 },
|
||||
GetCursorPositionParam{ 0, 100 }
|
||||
)
|
||||
);
|
||||
} // namespace AzFramework
|
||||
@@ -17,5 +17,6 @@ set(FILES
|
||||
XcbBaseTestFixture.cpp
|
||||
XcbBaseTestFixture.h
|
||||
XcbInputDeviceKeyboardTests.cpp
|
||||
XcbInputDeviceMouseTests.cpp
|
||||
XcbTestApplication.h
|
||||
)
|
||||
|
||||
@@ -55,6 +55,40 @@ namespace UnitTest
|
||||
AZ::EntityId m_entityReference;
|
||||
};
|
||||
|
||||
class SourceSpawnableComponent : public AZ::Component
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(SourceSpawnableComponent, "{47FF79CE-A95B-420E-8BEB-F1CC58087B87}");
|
||||
|
||||
void Activate() override {}
|
||||
void Deactivate() override {}
|
||||
|
||||
static void Reflect(AZ::ReflectContext* reflection)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
|
||||
{
|
||||
serializeContext->Class<SourceSpawnableComponent, AZ::Component>();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TargetSpawnableComponent : public AZ::Component
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(TargetSpawnableComponent, "{B4041561-63A7-4E1E-80F1-78C08D497960}");
|
||||
|
||||
void Activate() override {}
|
||||
void Deactivate() override {}
|
||||
|
||||
static void Reflect(AZ::ReflectContext* reflection)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
|
||||
{
|
||||
serializeContext->Class<TargetSpawnableComponent, AZ::Component>();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SpawnableEntitiesManagerTest : public AllocatorsFixture
|
||||
{
|
||||
public:
|
||||
@@ -66,6 +100,8 @@ namespace UnitTest
|
||||
AZ::ComponentApplication::Descriptor descriptor;
|
||||
m_application->Start(descriptor);
|
||||
m_application->RegisterComponentDescriptor(ComponentWithEntityReference::CreateDescriptor());
|
||||
m_application->RegisterComponentDescriptor(SourceSpawnableComponent::CreateDescriptor());
|
||||
m_application->RegisterComponentDescriptor(TargetSpawnableComponent::CreateDescriptor());
|
||||
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
|
||||
@@ -109,10 +145,126 @@ namespace UnitTest
|
||||
entities.reserve(numElements);
|
||||
for (size_t i=0; i<numElements; ++i)
|
||||
{
|
||||
entities.push_back(AZStd::make_unique<AZ::Entity>());
|
||||
auto entry = AZStd::make_unique<AZ::Entity>();
|
||||
entry->AddComponent(aznew SourceSpawnableComponent());
|
||||
entities.push_back(AZStd::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AzFramework::Spawnable> CreateTargetSpawnable(size_t numElements)
|
||||
{
|
||||
auto target = aznew AzFramework::Spawnable(
|
||||
AZ::Data::AssetId(AZ::Uuid("{716CD8C3-0BA8-4F32-B579-0EC7C967796F}")), AZ::Data::AssetData::AssetStatus::Ready);
|
||||
|
||||
AzFramework::Spawnable::EntityList& entities = target->GetEntities();
|
||||
entities.reserve(numElements);
|
||||
for (size_t i = 0; i < numElements; ++i)
|
||||
{
|
||||
auto entry = AZStd::make_unique<AZ::Entity>();
|
||||
entry->AddComponent(aznew TargetSpawnableComponent());
|
||||
entities.push_back(AZStd::move(entry));
|
||||
}
|
||||
|
||||
return AZ::Data::Asset<AzFramework::Spawnable>(target, AZ::Data::AssetLoadBehavior::NoLoad);
|
||||
}
|
||||
|
||||
template<size_t AliasCount>
|
||||
void InsertEntityAliases(
|
||||
const AZStd::array<uint32_t, AliasCount>& sourceIds,
|
||||
const AZStd::array<uint32_t, AliasCount>& targetIds,
|
||||
const AZStd::array<AzFramework::Spawnable::EntityAliasType, AliasCount>& aliasTypes,
|
||||
AZ::Data::Asset<AzFramework::Spawnable>* target = nullptr)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
|
||||
for (uint32_t i = 0; i < AliasCount; ++i)
|
||||
{
|
||||
if (target)
|
||||
{
|
||||
visitor.AddAlias(*target, AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], false);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ::Data::Asset<AzFramework::Spawnable> spawnable(
|
||||
AZ::Data::AssetId(AZ::Uuid("{4CBEC17A-52D6-42D5-9037-F4C05B9CE1D9}"), i), azrtti_typeid<AzFramework::Spawnable>());
|
||||
visitor.AddAlias(AZStd::move(spawnable), AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool AreAllEntitiesReplaced(AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
for (const AZ::Entity* entity : entities)
|
||||
{
|
||||
if (entity)
|
||||
{
|
||||
if (entity->FindComponent<SourceSpawnableComponent>() != nullptr ||
|
||||
entity->FindComponent<TargetSpawnableComponent>() == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsEveryOtherEntityAReplacement(AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
bool onAlternative = true;
|
||||
for (const AZ::Entity* entity : entities)
|
||||
{
|
||||
if (entity)
|
||||
{
|
||||
if (onAlternative)
|
||||
{
|
||||
if (entity->FindComponent<SourceSpawnableComponent>() == nullptr ||
|
||||
entity->FindComponent<TargetSpawnableComponent>() != nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entity->FindComponent<SourceSpawnableComponent>() != nullptr ||
|
||||
entity->FindComponent<TargetSpawnableComponent>() == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
onAlternative = !onAlternative;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool AreAllMerged(AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
for (const AZ::Entity* entity : entities)
|
||||
{
|
||||
if (entity)
|
||||
{
|
||||
if (entity->FindComponent<SourceSpawnableComponent>() == nullptr ||
|
||||
entity->FindComponent<TargetSpawnableComponent>() == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CreateRecursiveHierarchy()
|
||||
{
|
||||
AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities();
|
||||
@@ -245,6 +397,30 @@ namespace UnitTest
|
||||
TestApplication* m_application { nullptr };
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Constructors
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, EntitySpawnTicket_Move_Works)
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket1(*m_spawnableAsset);
|
||||
AzFramework::EntitySpawnTicket ticket2(*m_spawnableAsset);
|
||||
|
||||
const AzFramework::EntitySpawnTicket::Id ticket1Id = ticket1.GetId();
|
||||
const AzFramework::EntitySpawnTicket::Id ticket2Id = ticket2.GetId();
|
||||
|
||||
AzFramework::EntitySpawnTicket ticketMoveConstructor(AZStd::move(ticket1));
|
||||
EXPECT_TRUE(ticketMoveConstructor.IsValid());
|
||||
EXPECT_EQ(ticketMoveConstructor.GetId(), ticket1Id);
|
||||
|
||||
AzFramework::EntitySpawnTicket ticketMoveOperator;
|
||||
ticketMoveOperator = AZStd::move(ticket2);
|
||||
EXPECT_TRUE(ticketMoveOperator.IsValid());
|
||||
EXPECT_EQ(ticketMoveOperator.GetId(), ticket2Id);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// SpawnAllEntitities
|
||||
//
|
||||
@@ -366,24 +542,6 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, EntitySpawnTicket_Move_Works)
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket1(*m_spawnableAsset);
|
||||
AzFramework::EntitySpawnTicket ticket2(*m_spawnableAsset);
|
||||
|
||||
const AzFramework::EntitySpawnTicket::Id ticket1Id = ticket1.GetId();
|
||||
const AzFramework::EntitySpawnTicket::Id ticket2Id = ticket2.GetId();
|
||||
|
||||
AzFramework::EntitySpawnTicket ticketMoveConstructor(AZStd::move(ticket1));
|
||||
EXPECT_TRUE(ticketMoveConstructor.IsValid());
|
||||
EXPECT_EQ(ticketMoveConstructor.GetId(), ticket1Id);
|
||||
|
||||
AzFramework::EntitySpawnTicket ticketMoveOperator;
|
||||
ticketMoveOperator = AZStd::move(ticket2);
|
||||
EXPECT_TRUE(ticketMoveOperator.IsValid());
|
||||
EXPECT_EQ(ticketMoveOperator.GetId(), ticket2Id);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
@@ -393,6 +551,135 @@ namespace UnitTest
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithDisabled_NoEntitiesSpawned)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
InsertEntityAliases<NumEntities>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable,
|
||||
Spawnable::EntityAliasType::Disable });
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(0, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_SomeAliasesWithDisabled_RegularEntitiesAreSpawned)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 8;
|
||||
FillSpawnable(NumEntities);
|
||||
InsertEntityAliases<2>({ 1, 3 }, { 1, 3 }, { Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable });
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(6, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithReplace_EntitiesSpawnedFromTarget)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace },
|
||||
&target);
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allReplaced = false;
|
||||
auto callback = [&spawnedEntitiesCount, &allReplaced](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allReplaced = AreAllEntitiesReplaced(entities);
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(4, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allReplaced);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
|
||||
Spawnable::EntityAliasType::Additional },
|
||||
&target);
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allAdded = false;
|
||||
auto callback = [&spawnedEntitiesCount, &allAdded](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allAdded = IsEveryOtherEntityAReplacement(entities);
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(8, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allAdded);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge,
|
||||
Spawnable::EntityAliasType::Merge },
|
||||
&target);
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allMerged = false;
|
||||
auto callback = [&spawnedEntitiesCount, &allMerged](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allMerged = AreAllMerged(entities);
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(4, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allMerged);
|
||||
}
|
||||
|
||||
//
|
||||
// SpawnEntities
|
||||
@@ -403,7 +690,7 @@ namespace UnitTest
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 2, 3, 1 };
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
@@ -423,7 +710,7 @@ namespace UnitTest
|
||||
static constexpr size_t NumEntities = 1;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 0 };
|
||||
AZStd::vector<uint32_t> indices = { 0, 0 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback =
|
||||
@@ -444,7 +731,7 @@ namespace UnitTest
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 2, 3, 1 };
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback =
|
||||
@@ -467,7 +754,7 @@ namespace UnitTest
|
||||
FillSpawnable(NumEntities);
|
||||
CreateSingleParent();
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 1, 2, 3 };
|
||||
AZStd::vector<uint32_t> indices = { 0, 1, 2, 3 };
|
||||
AZStd::vector<AZ::EntityId> parents;
|
||||
|
||||
auto callback = [&parents](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
@@ -499,7 +786,7 @@ namespace UnitTest
|
||||
FillSpawnable(NumEntities);
|
||||
CreateSingleParent();
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 1, 2, 3 };
|
||||
AZStd::vector<uint32_t> indices = { 0, 1, 2, 3 };
|
||||
AZStd::vector<AZ::EntityId> parents;
|
||||
|
||||
auto callback =
|
||||
@@ -754,6 +1041,148 @@ namespace UnitTest
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithDisabled_NoEntitiesSpawned)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
InsertEntityAliases<NumEntities>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable,
|
||||
Spawnable::EntityAliasType::Disable });
|
||||
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(0, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_SomeAliasesWithDisabled_RegularEntitiesAreSpawned)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
FillSpawnable(8);
|
||||
InsertEntityAliases<3>(
|
||||
{ 1, 3, 6 }, { 1, 3, 6 },
|
||||
{ Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable });
|
||||
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1, 2, 3, 0, 1, 6, 4, 5, 7, 4, 1, 0, 6 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(9, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithReplace_EntitiesSpawnedFromTarget)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace },
|
||||
&target);
|
||||
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allReplaced = false;
|
||||
auto callback = [&spawnedEntitiesCount, &allReplaced](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allReplaced = AreAllEntitiesReplaced(entities);
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(4, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allReplaced);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
|
||||
Spawnable::EntityAliasType::Additional },
|
||||
&target);
|
||||
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allAdded = false;
|
||||
auto callback =
|
||||
[&spawnedEntitiesCount, &allAdded](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allAdded = IsEveryOtherEntityAReplacement(entities);
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(8, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allAdded);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge,
|
||||
Spawnable::EntityAliasType::Merge },
|
||||
&target);
|
||||
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allMerged = false;
|
||||
auto callback = [&spawnedEntitiesCount, &allMerged](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allMerged = AreAllMerged(entities);
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(4, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allMerged);
|
||||
}
|
||||
|
||||
//
|
||||
// DespawnAllEntities
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* 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/Casting/numeric_cast.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class SpawnableTest : public AllocatorsFixture
|
||||
{
|
||||
public:
|
||||
static constexpr size_t DefaultEntityAliasTestCount = 8;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AllocatorsFixture::SetUp();
|
||||
|
||||
m_spawnable = aznew AzFramework::Spawnable();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
delete m_spawnable;
|
||||
m_spawnable = nullptr;
|
||||
|
||||
AllocatorsFixture::TearDown();
|
||||
}
|
||||
|
||||
void InsertEntities(size_t count)
|
||||
{
|
||||
AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities();
|
||||
entities.reserve(entities.size() + count);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
entities.emplace_back(AZStd::make_unique<AZ::Entity>());
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t Count>
|
||||
void InsertEntityAliases(
|
||||
const AZStd::array<uint32_t, Count>& sourceIds,
|
||||
const AZStd::array<uint32_t, Count>& targetIds,
|
||||
const AZStd::array<AzFramework::Spawnable::EntityAliasType, Count>& aliasTypes,
|
||||
bool queueLoad = false)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
|
||||
for (uint32_t i = 0; i < Count; ++i)
|
||||
{
|
||||
AZ::Data::Asset<AzFramework::Spawnable> spawnable(
|
||||
AZ::Data::AssetId(AZ::Uuid("{4CBEC17A-52D6-42D5-9037-F4C05B9CE1D9}"), i), azrtti_typeid<AzFramework::Spawnable>());
|
||||
visitor.AddAlias(spawnable, AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], queueLoad);
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t Count>
|
||||
void InsertEntityAliases(bool queueLoad)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
|
||||
AZStd::array<uint32_t, Count> ids;
|
||||
for (uint32_t i=0; i<aznumeric_cast<uint32_t>(Count); ++i)
|
||||
{
|
||||
ids[i] = i;
|
||||
}
|
||||
|
||||
AZStd::array<AzFramework::Spawnable::EntityAliasType, Count> aliasTypes;
|
||||
for (uint32_t i = 0; i < aznumeric_cast<uint32_t>(Count); ++i)
|
||||
{
|
||||
aliasTypes[i] = Spawnable::EntityAliasType::Replace;
|
||||
}
|
||||
|
||||
InsertEntityAliases<Count>(ids, ids, aliasTypes, queueLoad);
|
||||
}
|
||||
|
||||
template<size_t Count>
|
||||
void InsertEntityAliases()
|
||||
{
|
||||
InsertEntityAliases<Count>(false);
|
||||
}
|
||||
|
||||
protected:
|
||||
AzFramework::Spawnable* m_spawnable;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// TryGetAliasesConst
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, TryGetAliasesConst_GetVisitor_VisitorDataIsAvailable)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst();
|
||||
EXPECT_TRUE(visitor.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsNotReadShared_VisitorDataIsNotAvailable)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor readWriteVisitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(readWriteVisitor.IsValid());
|
||||
|
||||
AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst();
|
||||
EXPECT_FALSE(visitor.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsAlreadyReadShared_VisitorDataIsAvailable)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasConstVisitor readVisitor = m_spawnable->TryGetAliasesConst();
|
||||
ASSERT_TRUE(readVisitor.IsValid());
|
||||
|
||||
AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst();
|
||||
EXPECT_TRUE(visitor.IsValid());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// TryGetAliases
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, TryGetAliases_GetVisitor_VisitorDataIsAvailable)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
EXPECT_TRUE(visitor.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsAlreadyShared_VisitorDataNotIsAvailable)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasConstVisitor readVisitor = m_spawnable->TryGetAliasesConst();
|
||||
ASSERT_TRUE(readVisitor.IsValid());
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
EXPECT_FALSE(visitor.IsValid());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// EntityAliasVisitor
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
// HasAliases
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_HasAliases_EmptyAliasList_ReturnsFalse)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_FALSE(visitor.HasAliases());
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_HasAliases_FilledInAliasList_ReturnsTrue)
|
||||
{
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>();
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_TRUE(visitor.HasAliases());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Optimize
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_SortEntityAliases_AliasesAreSortedBySourceAndTargetId)
|
||||
{
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>();
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
// Optimize doesn't need to be explicitly called because the setup of the aliases will cause the alias list to be sorted and optimized.
|
||||
|
||||
uint32_t sourceIndex = 0;
|
||||
uint32_t targetIndex = 0;
|
||||
for (const AzFramework::Spawnable::EntityAlias& alias : visitor)
|
||||
{
|
||||
if (alias.m_sourceIndex != sourceIndex)
|
||||
{
|
||||
ASSERT_LE(sourceIndex, alias.m_sourceIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_LE(targetIndex, alias.m_targetIndex);
|
||||
}
|
||||
sourceIndex = alias.m_sourceIndex;
|
||||
targetIndex = alias.m_targetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
SpawnableTest, EntityAliasVisitor_Optimize_RemoveUnused_OnlySecondToLastAliasRemains)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disable,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disable,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_EQ(1, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()->m_aliasType);
|
||||
EXPECT_EQ(6, visitor.begin()->m_targetIndex);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_AddAdditional_ThreeAdditionalAliasesAreAdded)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 0, 0, 0, 1, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
|
||||
Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
|
||||
Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_EQ(11, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()->m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[5].m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[7].m_aliasType);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_OriginalsOnly_AliasListIsEmpty)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
|
||||
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
|
||||
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_MixedOriginals_AllOriginalsRemoved)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 0, 0, 1, 1, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
|
||||
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Original,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_EQ(2, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Disable, visitor.begin()->m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()[1].m_aliasType);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_MergeAfterOriginal_NoAdditionalOriginalIsInserted)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 0, 1, 1, 2, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
|
||||
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_EQ(4, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()->m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Merge, visitor.begin()[1].m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()[2].m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Merge, visitor.begin()[3].m_aliasType);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// UpdateAliasType
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliasType_AllToOriginal_NoAliasesAfterOptimization)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
for (uint32_t i = 0; i < 8; ++i)
|
||||
{
|
||||
visitor.UpdateAliasType(i, Spawnable::EntityAliasType::Original);
|
||||
}
|
||||
|
||||
for (const Spawnable::EntityAlias& alias : visitor)
|
||||
{
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, alias.m_aliasType);
|
||||
}
|
||||
|
||||
visitor.Optimize();
|
||||
|
||||
EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// UpdateAliases
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliases_AllToOriginal_NoAliasesAfterOptimization)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
auto callback =
|
||||
[](Spawnable::EntityAliasType& aliasType, bool& /*queueLoad*/, const AZ::Data::Asset<Spawnable>& /*aliasedSpawnable*/,
|
||||
const AZ::Crc32 /*tag*/, const uint32_t /*sourceIndex*/, const uint32_t /*targetIndex*/)
|
||||
{
|
||||
aliasType = Spawnable::EntityAliasType::Original;
|
||||
};
|
||||
visitor.UpdateAliases(AZStd::move(callback));
|
||||
|
||||
for (const Spawnable::EntityAlias& alias : visitor)
|
||||
{
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, alias.m_aliasType);
|
||||
}
|
||||
|
||||
visitor.Optimize();
|
||||
|
||||
EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliases_FilterByTag_OnlyOneAliasUpdated)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
bool correctTag = false;
|
||||
size_t numberOfUpdates = 0;
|
||||
auto callback = [&correctTag, &numberOfUpdates](Spawnable::EntityAliasType& aliasType, bool& /*queueLoad*/,
|
||||
const AZ::Data::Asset<Spawnable>& /*aliasedSpawnable*/, const AZ::Crc32 tag, const uint32_t /*sourceIndex*/,
|
||||
const uint32_t /*targetIndex*/)
|
||||
{
|
||||
correctTag = (tag == AZ::Crc32(3));
|
||||
numberOfUpdates++;
|
||||
aliasType = Spawnable::EntityAliasType::Original;
|
||||
};
|
||||
visitor.UpdateAliases(AZ::Crc32(3), AZStd::move(callback));
|
||||
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[3].m_aliasType);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// AreAllSpawnablesReady
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_AreAllSpawnablesReady_CheckFakeLoadedAssets_ReturnsTrue)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>();
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_TRUE(visitor.AreAllSpawnablesReady());
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_AreAllSpawnablesReady_CheckFakeNotLoadedAssets_ReturnsFalse)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(true);
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_FALSE(visitor.AreAllSpawnablesReady());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ListTargetSpawnables
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_ListTargetSpawnables_ListAllTargetAssets_AllTargetsListed)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>();
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
size_t count = 0;
|
||||
bool correctAssets = true;
|
||||
auto callback = [&count, &correctAssets](const AZ::Data::Asset<Spawnable>& targetSpawnable)
|
||||
{
|
||||
correctAssets = correctAssets && (targetSpawnable.GetId().m_subId == count);
|
||||
count++;
|
||||
};
|
||||
visitor.ListTargetSpawnables(callback);
|
||||
|
||||
EXPECT_EQ(8, count);
|
||||
EXPECT_TRUE(correctAssets);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_ListTargetSpawnables_ListTaggedTargetAssets_OneAssetListed)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>();
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
size_t count = 0;
|
||||
bool correctAsset = false;
|
||||
auto callback = [&count, &correctAsset](const AZ::Data::Asset<Spawnable>& targetSpawnable)
|
||||
{
|
||||
correctAsset = (targetSpawnable.GetId().m_subId == 3);
|
||||
count++;
|
||||
};
|
||||
visitor.ListTargetSpawnables(AZ::Crc32(3), callback);
|
||||
|
||||
EXPECT_EQ(1, count);
|
||||
EXPECT_TRUE(correctAsset);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ListSpawnablesRequiringLoad
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_ListSpawnablesRequiringLoad_AllSetToLoaded_AllTargetsListed)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>(true);
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
size_t count = 0;
|
||||
bool correctAssets = true;
|
||||
auto callback = [&count, &correctAssets](const AZ::Data::Asset<Spawnable>& targetSpawnable)
|
||||
{
|
||||
correctAssets = correctAssets && (targetSpawnable.GetId().m_subId == count);
|
||||
count++;
|
||||
};
|
||||
visitor.ListSpawnablesRequiringLoad(callback);
|
||||
|
||||
EXPECT_EQ(8, count);
|
||||
EXPECT_TRUE(correctAssets);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_ListSpawnablesRequiringLoad_AllSetToNotLoaded_NoTargetsListed)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>(false);
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
size_t count = 0;
|
||||
auto callback = [&count](const AZ::Data::Asset<Spawnable>& /*targetSpawnable*/)
|
||||
{
|
||||
count++;
|
||||
};
|
||||
visitor.ListSpawnablesRequiringLoad(callback);
|
||||
|
||||
EXPECT_EQ(0, count);
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -10,6 +10,7 @@ set(FILES
|
||||
Main.cpp
|
||||
Spawnable/SpawnableEntitiesInterfaceTests.cpp
|
||||
Spawnable/SpawnableEntitiesManagerTests.cpp
|
||||
Spawnable/SpawnableTests.cpp
|
||||
ArchiveCompressionTests.cpp
|
||||
ArchiveTests.cpp
|
||||
BehaviorEntityTests.cpp
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace AzGameFramework
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
|
||||
#endif
|
||||
|
||||
// Used the lowercase the platform name since the bootstrap.game.<config>.<platform>.setreg is being loaded
|
||||
// Used the lowercase the platform name since the bootstrap.game.<config>.setreg is being loaded
|
||||
// from the asset cache root where all the files are in lowercased from regardless of the filesystem case-sensitivity
|
||||
static constexpr char filename[] = "bootstrap.game." AZ_BUILD_CONFIGURATION_TYPE ".setreg";
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
<Member Type="AzNetworking::DisconnectReason" Name="disconnectReason" Init="AzNetworking::DisconnectReason::None" />
|
||||
</Packet>
|
||||
|
||||
<Packet Name="HeartbeatPacket" Desc="This packet is used to keep an established connection alive" />
|
||||
<Packet Name="HeartbeatPacket" Desc="This packet is used to keep an established connection alive">
|
||||
<Member Type="bool" Name="requestResponse" Init="false" />
|
||||
</Packet>
|
||||
|
||||
<Packet Name="FragmentedPacket" Desc="This packet is used to segment a packet that exceeds a connections MTU">
|
||||
<Member Type="AzNetworking::SequenceId" Name="unfragmentedSequence" Init="AzNetworking::InvalidSequenceId" />
|
||||
|
||||
@@ -122,10 +122,4 @@ namespace AzNetworking
|
||||
m_timeoutItemMap.erase(itemTimeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts)
|
||||
{
|
||||
TimeoutHandler handler([&timeoutHandler](TimeoutQueue::TimeoutItem& item) { return timeoutHandler.HandleTimeout(item); });
|
||||
UpdateTimeouts(handler, maxTimeouts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ namespace AzNetworking
|
||||
Delete
|
||||
};
|
||||
|
||||
class ITimeoutHandler;
|
||||
|
||||
//! @class TimeoutQueue
|
||||
//! @brief class for managing timeout items.
|
||||
class TimeoutQueue
|
||||
@@ -70,11 +68,6 @@ namespace AzNetworking
|
||||
using TimeoutHandler = AZStd::function<TimeoutResult(TimeoutQueue::TimeoutItem&)>;
|
||||
void UpdateTimeouts(const TimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1);
|
||||
|
||||
//! Updates timeouts for all items, invokes timeout handlers if required.
|
||||
//! @param timeoutHandler listener instance to call back on for timeouts
|
||||
//! @param maxTimeouts the maximum number of timeouts to process before breaking iteration
|
||||
void UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1);
|
||||
|
||||
private:
|
||||
|
||||
struct TimeoutQueueItem
|
||||
@@ -94,19 +87,6 @@ namespace AzNetworking
|
||||
TimeoutItemMap m_timeoutItemMap;
|
||||
TimeoutItemQueue m_timeoutItemQueue;
|
||||
};
|
||||
|
||||
//! @class ITimeoutHandler
|
||||
//! @brief interface class for managing timeout items.
|
||||
class ITimeoutHandler
|
||||
{
|
||||
public:
|
||||
virtual ~ITimeoutHandler() = default;
|
||||
|
||||
//! Handler callback for timed out items.
|
||||
//! @param item containing registered timeout details
|
||||
//! @return ETimeoutResult for whether to re-register or discard the timeout params
|
||||
virtual TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#include <AzNetworking/DataStructures/TimeoutQueue.inl>
|
||||
|
||||
@@ -27,13 +27,11 @@ namespace AzNetworking
|
||||
ConnectionId connectionId,
|
||||
const IpAddress& remoteAddress,
|
||||
TcpNetworkInterface& networkInterface,
|
||||
TcpSocket& socket,
|
||||
TimeoutId timeoutId
|
||||
TcpSocket& socket
|
||||
)
|
||||
: IConnection(connectionId, remoteAddress)
|
||||
, m_networkInterface(networkInterface)
|
||||
, m_socket(socket.CloneAndTakeOwnership())
|
||||
, m_timeoutId(timeoutId)
|
||||
, m_state(m_socket->IsOpen() ? ConnectionState::Connecting : ConnectionState::Disconnected)
|
||||
, m_connectionRole(ConnectionRole::Acceptor)
|
||||
, m_registeredSocketFd(InvalidSocketFd)
|
||||
@@ -163,13 +161,6 @@ namespace AzNetworking
|
||||
break;
|
||||
}
|
||||
|
||||
TimeoutQueue::TimeoutItem* timeoutItem = m_networkInterface.m_connectionTimeoutQueue.RetrieveItem(GetTimeoutId());
|
||||
if (timeoutItem == nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
timeoutItem->UpdateTimeoutTime(startTimeMs);
|
||||
|
||||
NetworkOutputSerializer serializer(buffer.GetBuffer(), static_cast<uint32_t>(buffer.GetSize()));
|
||||
if (m_state == ConnectionState::Connecting)
|
||||
{
|
||||
|
||||
@@ -38,14 +38,12 @@ namespace AzNetworking
|
||||
//! @param remoteAddress IP address of the remote endpoint
|
||||
//! @param networkInterface TcpNetworkInterface that owns this connection instance
|
||||
//! @param socket TCP socket to take ownership of and use for sending and receiving data
|
||||
//! @param timeoutId timeout identifier of this connection instance
|
||||
TcpConnection
|
||||
(
|
||||
ConnectionId connectionId,
|
||||
const IpAddress& remoteAddress,
|
||||
TcpNetworkInterface& networkInterface,
|
||||
TcpSocket& socket,
|
||||
TimeoutId timeoutId
|
||||
TcpSocket& socket
|
||||
);
|
||||
|
||||
//! Construct a new socket with optional encryption, used when initiating a new connection
|
||||
@@ -69,14 +67,6 @@ namespace AzNetworking
|
||||
//! @return the TcpSocket bound to this TcpConnection
|
||||
TcpSocket* GetTcpSocket() const;
|
||||
|
||||
//! Sets the timeout identifier for this TcpConnection.
|
||||
//! @param timeoutId the timeout identifier to use for this TcpConnection
|
||||
void SetTimeoutId(TimeoutId timeoutId);
|
||||
|
||||
//! Returns the timeout identifier for this TcpConnection.
|
||||
//! @return the timeout identifier for this TcpConnection
|
||||
TimeoutId GetTimeoutId() const;
|
||||
|
||||
//! Returns true if this connection instance is in an open state, and is capable of actively sending and receiving packets.
|
||||
//! @return boolean true if this connection instance is in an open state
|
||||
bool IsOpen() const;
|
||||
@@ -142,7 +132,6 @@ namespace AzNetworking
|
||||
AZStd::unique_ptr<TcpSocket> m_socket;
|
||||
AZStd::unique_ptr<ICompressor> m_compressor;
|
||||
|
||||
TimeoutId m_timeoutId;
|
||||
PacketId m_lastSentPacketId = InvalidPacketId;
|
||||
ConnectionState m_state = ConnectionState::Disconnected;
|
||||
ConnectionRole m_connectionRole = ConnectionRole::Connector;
|
||||
|
||||
@@ -15,16 +15,6 @@ namespace AzNetworking
|
||||
return m_socket.get();
|
||||
}
|
||||
|
||||
inline void TcpConnection::SetTimeoutId(TimeoutId timeoutId)
|
||||
{
|
||||
m_timeoutId = timeoutId;
|
||||
}
|
||||
|
||||
inline TimeoutId TcpConnection::GetTimeoutId() const
|
||||
{
|
||||
return m_timeoutId;
|
||||
}
|
||||
|
||||
inline bool TcpConnection::IsOpen() const
|
||||
{
|
||||
return m_socket->IsOpen();
|
||||
|
||||
@@ -21,16 +21,11 @@ namespace AzNetworking
|
||||
static const bool net_TcpUseEncryption = false;
|
||||
#endif
|
||||
|
||||
AZ_CVAR(bool, net_TcpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Tcp connections");
|
||||
AZ_CVAR(AZ::TimeMs, net_TcpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Tcp connection heartbeat frequency");
|
||||
AZ_CVAR(AZ::TimeMs, net_TcpDefaultTimeoutMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Tcp connection");
|
||||
|
||||
TcpNetworkInterface::TcpNetworkInterface(AZ::Name name, IConnectionListener& connectionListener, TrustZone trustZone, TcpListenThread& listenThread)
|
||||
: m_name(name)
|
||||
, m_trustZone(trustZone)
|
||||
, m_connectionListener(connectionListener)
|
||||
, m_listenThread(listenThread)
|
||||
, m_timeoutMs(net_TcpDefaultTimeoutMs)
|
||||
{
|
||||
;
|
||||
}
|
||||
@@ -98,8 +93,6 @@ namespace AzNetworking
|
||||
}
|
||||
|
||||
AZLOG_INFO("Adding new socket %d", static_cast<int32_t>(tcpSocket->GetSocketFd()));
|
||||
const TimeoutId newTimeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast<uint64_t>(tcpSocket->GetSocketFd()), net_TcpHeartbeatTimeMs);
|
||||
connection->SetTimeoutId(newTimeoutId);
|
||||
connection->SendReliablePacket(CorePackets::InitiateConnectionPacket());
|
||||
m_connectionListener.OnConnect(connection.get());
|
||||
m_connectionSet.AddConnection(AZStd::move(connection));
|
||||
@@ -110,12 +103,6 @@ namespace AzNetworking
|
||||
{
|
||||
const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs();
|
||||
|
||||
// Time out any stale connections
|
||||
{
|
||||
ConnectionTimeoutFunctor functor(*this);
|
||||
m_connectionTimeoutQueue.UpdateTimeouts(functor);
|
||||
}
|
||||
|
||||
AcceptNewConnections();
|
||||
|
||||
auto readCallback = [this, startTimeMs](SocketFd socketFd) { HandleConnectionRecv(socketFd, startTimeMs); };
|
||||
@@ -258,8 +245,7 @@ namespace AzNetworking
|
||||
return;
|
||||
}
|
||||
AZLOG(NET_TcpTraffic, "Adding new socket %d", static_cast<int32_t>(tcpSocket.GetSocketFd()));
|
||||
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast<uint64_t>(tcpSocket.GetSocketFd()), m_timeoutMs);
|
||||
AZStd::unique_ptr<TcpConnection> connection = AZStd::make_unique<TcpConnection>(connectionId, remoteAddress, *this, tcpSocket, timeoutId);
|
||||
AZStd::unique_ptr<TcpConnection> connection = AZStd::make_unique<TcpConnection>(connectionId, remoteAddress, *this, tcpSocket);
|
||||
AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Acceptor, "Invalid role for connection");
|
||||
GetConnectionListener().OnConnect(connection.get());
|
||||
m_connectionSet.AddConnection(AZStd::move(connection));
|
||||
@@ -286,7 +272,6 @@ namespace AzNetworking
|
||||
m_pendingRemoves.resize_no_construct(0);
|
||||
}
|
||||
|
||||
|
||||
TcpNetworkInterface::PendingConnection::PendingConnection(SocketFd socketFd, uint32_t remoteIpAddress, uint16_t remotePort, uint16_t listenPort)
|
||||
: m_socketFd(socketFd)
|
||||
, m_remoteIpAddress(remoteIpAddress)
|
||||
@@ -295,34 +280,4 @@ namespace AzNetworking
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
TcpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(TcpNetworkInterface& networkInterface)
|
||||
: m_networkInterface(networkInterface)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
TimeoutResult TcpNetworkInterface::ConnectionTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item)
|
||||
{
|
||||
const SocketFd socketFd = static_cast<SocketFd>(item.m_userData);
|
||||
TcpConnection* tcpConnection = m_networkInterface.m_connectionSet.GetConnection(socketFd);
|
||||
|
||||
if (tcpConnection == nullptr)
|
||||
{
|
||||
// We've already deleted this connection
|
||||
return TimeoutResult::Delete;
|
||||
}
|
||||
|
||||
if (tcpConnection->GetConnectionRole() == ConnectionRole::Connector)
|
||||
{
|
||||
tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket());
|
||||
}
|
||||
else if (net_TcpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 }))
|
||||
{
|
||||
tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
|
||||
return TimeoutResult::Delete;
|
||||
}
|
||||
|
||||
return TimeoutResult::Refresh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,16 +137,6 @@ namespace AzNetworking
|
||||
|
||||
AZ_DISABLE_COPY_MOVE(TcpNetworkInterface);
|
||||
|
||||
struct ConnectionTimeoutFunctor final
|
||||
: public ITimeoutHandler
|
||||
{
|
||||
ConnectionTimeoutFunctor(TcpNetworkInterface& networkInterface);
|
||||
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(ConnectionTimeoutFunctor);
|
||||
TcpNetworkInterface& m_networkInterface;
|
||||
};
|
||||
|
||||
struct PendingRemove
|
||||
{
|
||||
SocketFd m_socketFd;
|
||||
@@ -162,7 +152,6 @@ namespace AzNetworking
|
||||
TcpSocketManager m_tcpSocketManager;
|
||||
AZ::ThreadSafeDeque<PendingConnection> m_pendingConnections;
|
||||
AZStd::vector<PendingRemove> m_pendingRemoves;
|
||||
TimeoutQueue m_connectionTimeoutQueue;
|
||||
TcpListenThread& m_listenThread;
|
||||
|
||||
friend class TcpConnection; // For access to private RequestDisconnect() method
|
||||
|
||||
@@ -79,7 +79,8 @@ namespace AzNetworking
|
||||
AZLOG(NET_Acks, "Unacked packet count exceeded, sending client heartbeat (curr %u : max %u)", m_unackedPacketCount, static_cast<uint32_t>(net_UdpMaxUnackedPacketCount));
|
||||
// This simply times out unreliable chunks that haven't completed within our timeout delay
|
||||
m_fragmentQueue.Update();
|
||||
SendUnreliablePacket(CorePackets::HeartbeatPacket());
|
||||
// This heartbeat is sent to minimize the time the remote endpoint spends waiting for ack vector replication, we don't require a response
|
||||
SendUnreliablePacket(CorePackets::HeartbeatPacket(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +290,11 @@ namespace AzNetworking
|
||||
{
|
||||
return PacketDispatchResult::Failure;
|
||||
}
|
||||
// Do nothing, we've already processed our ack packets
|
||||
if (packet.GetRequestResponse())
|
||||
{
|
||||
// We're replying to a heartbeat request, we don't want a response
|
||||
SendUnreliablePacket(CorePackets::HeartbeatPacket(false));
|
||||
}
|
||||
return PacketDispatchResult::Success;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -136,12 +136,12 @@ namespace AzNetworking
|
||||
AZ_DISABLE_COPY_MOVE(UdpConnection);
|
||||
|
||||
UdpNetworkInterface& m_networkInterface;
|
||||
UdpPacketTracker m_packetTracker;
|
||||
UdpReliableQueue m_reliableQueue;
|
||||
UdpFragmentQueue m_fragmentQueue;
|
||||
ConnectionState m_state = ConnectionState::Disconnected;
|
||||
ConnectionRole m_connectionRole = ConnectionRole::Connector;
|
||||
DtlsEndpoint m_dtlsEndpoint;
|
||||
UdpPacketTracker m_packetTracker;
|
||||
UdpReliableQueue m_reliableQueue;
|
||||
UdpFragmentQueue m_fragmentQueue;
|
||||
ConnectionState m_state = ConnectionState::Disconnected;
|
||||
ConnectionRole m_connectionRole = ConnectionRole::Connector;
|
||||
DtlsEndpoint m_dtlsEndpoint;
|
||||
|
||||
AZ::TimeMs m_lastSentPacketMs;
|
||||
uint32_t m_unackedPacketCount = 0;
|
||||
|
||||
@@ -20,7 +20,13 @@ namespace AzNetworking
|
||||
|
||||
void UdpFragmentQueue::Update()
|
||||
{
|
||||
m_timeoutQueue.UpdateTimeouts(*this);
|
||||
m_timeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item)
|
||||
{
|
||||
const SequenceId fragmentSequence = static_cast<SequenceId>(item.m_userData & 0xFF);
|
||||
AZLOG(NET_FragmentQueue, "Timing out unreliable fragmented packet %u", static_cast<uint32_t>(fragmentSequence));
|
||||
m_packetFragments.erase(fragmentSequence);
|
||||
return TimeoutResult::Delete;
|
||||
});
|
||||
}
|
||||
|
||||
void UdpFragmentQueue::Reset()
|
||||
@@ -163,12 +169,4 @@ namespace AzNetworking
|
||||
|
||||
return handledPacket;
|
||||
}
|
||||
|
||||
TimeoutResult UdpFragmentQueue::HandleTimeout(TimeoutQueue::TimeoutItem& item)
|
||||
{
|
||||
const SequenceId fragmentSequence = static_cast<SequenceId>(item.m_userData & 0xFF);
|
||||
AZLOG(NET_FragmentQueue, "Timing out unreliable fragmented packet %u", static_cast<uint32_t>(fragmentSequence));
|
||||
m_packetFragments.erase(fragmentSequence);
|
||||
return TimeoutResult::Delete;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace AzNetworking
|
||||
//! @class UdpFragmentQueue
|
||||
//! @brief Class for reconstructing packet chunks into the original unsegmented packet.
|
||||
class UdpFragmentQueue
|
||||
: public ITimeoutHandler
|
||||
{
|
||||
|
||||
public:
|
||||
@@ -51,11 +50,6 @@ namespace AzNetworking
|
||||
|
||||
private:
|
||||
|
||||
//! Handler callback for timed out items.
|
||||
//! @param item containing registered timeout details
|
||||
//! @return ETimeoutResult for whether to re-register or discard the timeout params
|
||||
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
|
||||
|
||||
TimeoutQueue m_timeoutQueue;
|
||||
SequenceGenerator m_sequenceGenerator;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace AzNetworking
|
||||
|
||||
AZ_CVAR(bool, net_UdpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Udp connections");
|
||||
AZ_CVAR(AZ::TimeMs, net_UdpPacketTimeSliceMs, AZ::TimeMs{ 8 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The number of milliseconds to allow for packet processing");
|
||||
AZ_CVAR(AZ::TimeMs, net_UdpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Udp connection heartbeat frequency");
|
||||
AZ_CVAR(uint32_t, net_UdpUnackedHeartbeats, 5, nullptr, AZ::ConsoleFunctorFlags::Null, "The number of heartbeats to attempt to send to keep a connection alive before giving up");
|
||||
AZ_CVAR(AZ::TimeMs, net_UdpDefaultTimeoutMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Udp connection");
|
||||
AZ_CVAR(AZ::TimeMs, net_MinPacketTimeoutMs, AZ::TimeMs{ 200 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Minimum time to wait before timing out an unacked packet");
|
||||
AZ_CVAR(int32_t, net_MaxTimeoutsPerFrame, 1000, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Maximum number of packet timeouts to allow to process in a single frame");
|
||||
@@ -139,7 +139,8 @@ namespace AzNetworking
|
||||
}
|
||||
|
||||
const ConnectionId connectionId = m_connectionSet.GetNextConnectionId();
|
||||
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(connectionId), m_timeoutMs);
|
||||
const AZ::TimeMs timeoutTimeMs = m_timeoutMs / static_cast<AZ::TimeMs>(static_cast<int32_t>(net_UdpUnackedHeartbeats));
|
||||
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(connectionId), timeoutTimeMs);
|
||||
|
||||
AZStd::unique_ptr<UdpConnection> connection = AZStd::make_unique<UdpConnection>(connectionId, remoteAddress, *this, ConnectionRole::Connector);
|
||||
UdpPacketEncodingBuffer dtlsData;
|
||||
@@ -277,6 +278,7 @@ namespace AzNetworking
|
||||
}
|
||||
|
||||
timeoutItem->UpdateTimeoutTime(startTimeMs);
|
||||
connection->m_timeoutCounter = 0;
|
||||
|
||||
PacketDispatchResult handledPacket = PacketDispatchResult::Failure;
|
||||
if (header.GetPacketType() < aznumeric_cast<PacketType>(CorePackets::PacketType::MAX))
|
||||
@@ -319,16 +321,10 @@ namespace AzNetworking
|
||||
const AZ::TimeMs receiveTimeMs = AZ::GetElapsedTimeMs() - startTimeMs;
|
||||
|
||||
// Time out any stale client connections
|
||||
{
|
||||
ConnectionTimeoutFunctor functor(*this);
|
||||
m_connectionTimeoutQueue.UpdateTimeouts(functor);
|
||||
}
|
||||
m_connectionTimeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item) { return HandleConnectionTimeout(item); });
|
||||
|
||||
// Time out any packets that haven't been acked within our timeout window
|
||||
{
|
||||
PacketTimeoutFunctor functor(*this);
|
||||
m_packetTimeoutQueue.UpdateTimeouts(functor, static_cast<int32_t>(net_MaxTimeoutsPerFrame));
|
||||
}
|
||||
m_packetTimeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item) { return HandlePacketTimeout(item); }, static_cast<int32_t>(net_MaxTimeoutsPerFrame));
|
||||
|
||||
// Delete any connections we've disconnected
|
||||
for (RemovedConnection& removedConnection : m_removedConnections)
|
||||
@@ -709,21 +705,14 @@ namespace AzNetworking
|
||||
{
|
||||
// Packets involved in handshake are InitiateConnection, ConnectionHandshake and FragmentedPackets of ConnectionHandshake
|
||||
return packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket) ||
|
||||
packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::ConnectionHandshakePacket) ||
|
||||
(packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::FragmentedPacket) && endpoint.IsConnecting());
|
||||
packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::ConnectionHandshakePacket) ||
|
||||
(packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::FragmentedPacket) && endpoint.IsConnecting());
|
||||
}
|
||||
|
||||
|
||||
UdpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface)
|
||||
: m_networkInterface(networkInterface)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
TimeoutResult UdpNetworkInterface::ConnectionTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item)
|
||||
TimeoutResult UdpNetworkInterface::HandleConnectionTimeout(TimeoutQueue::TimeoutItem& item)
|
||||
{
|
||||
const ConnectionId connectionId = ConnectionId(aznumeric_cast<uint32_t>(item.m_userData));
|
||||
UdpConnection* udpConnection = static_cast<UdpConnection*>(m_networkInterface.m_connectionSet.GetConnection(connectionId));
|
||||
UdpConnection* udpConnection = static_cast<UdpConnection*>(m_connectionSet.GetConnection(connectionId));
|
||||
|
||||
if (udpConnection == nullptr)
|
||||
{
|
||||
@@ -731,22 +720,23 @@ namespace AzNetworking
|
||||
return TimeoutResult::Delete;
|
||||
}
|
||||
|
||||
if (udpConnection->GetConnectionState() == ConnectionState::Connecting)
|
||||
if ((udpConnection->GetConnectionState() == ConnectionState::Connecting)
|
||||
&& udpConnection->GetDtlsEndpoint().IsConnecting())
|
||||
{
|
||||
if (udpConnection->GetDtlsEndpoint().IsConnecting())
|
||||
{
|
||||
// DTLS prefers we resend data lost over the wire with fresh SSL IDs so account for that here
|
||||
UdpPacketEncodingBuffer dtlsData;
|
||||
udpConnection->ProcessHandshakeData(dtlsData);
|
||||
return TimeoutResult::Refresh;
|
||||
}
|
||||
// DTLS prefers we resend data lost over the wire with fresh SSL IDs so account for that here
|
||||
UdpPacketEncodingBuffer dtlsData;
|
||||
udpConnection->ProcessHandshakeData(dtlsData);
|
||||
return TimeoutResult::Refresh;
|
||||
}
|
||||
|
||||
if (udpConnection->GetConnectionRole() == ConnectionRole::Connector)
|
||||
if ((udpConnection->GetConnectionRole() == ConnectionRole::Connector)
|
||||
&& (udpConnection->m_timeoutCounter < net_UdpUnackedHeartbeats))
|
||||
{
|
||||
udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket());
|
||||
// Set the request response flag to true since we want a response to keep the connection alive
|
||||
udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket(true));
|
||||
++udpConnection->m_timeoutCounter;
|
||||
}
|
||||
else if (net_UdpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 }))
|
||||
else if (net_UdpTimeoutConnections && (GetTimeoutMs() > AZ::TimeMs{ 0 }))
|
||||
{
|
||||
udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
|
||||
return TimeoutResult::Delete;
|
||||
@@ -755,19 +745,13 @@ namespace AzNetworking
|
||||
return TimeoutResult::Refresh;
|
||||
}
|
||||
|
||||
UdpNetworkInterface::PacketTimeoutFunctor::PacketTimeoutFunctor(UdpNetworkInterface& networkInterface)
|
||||
: m_networkInterface(networkInterface)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
TimeoutResult UdpNetworkInterface::PacketTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item)
|
||||
TimeoutResult UdpNetworkInterface::HandlePacketTimeout(TimeoutQueue::TimeoutItem& item)
|
||||
{
|
||||
ConnectionId connectionId;
|
||||
PacketId packetId;
|
||||
ReliabilityType reliability;
|
||||
DecodeTimeoutId(item.m_userData, connectionId, packetId, reliability);
|
||||
UdpConnection* connection = static_cast<UdpConnection*>(m_networkInterface.m_connectionSet.GetConnection(connectionId));
|
||||
UdpConnection* connection = static_cast<UdpConnection*>(m_connectionSet.GetConnection(connectionId));
|
||||
|
||||
if (connection == nullptr)
|
||||
{
|
||||
@@ -782,16 +766,14 @@ namespace AzNetworking
|
||||
case PacketTimeoutResult::Acked:
|
||||
// Packet was already acked, just discard this timeout entry
|
||||
return TimeoutResult::Delete;
|
||||
|
||||
case PacketTimeoutResult::Pending:
|
||||
// Packet timed out before we received any info about it's sequence from the remote endpoint
|
||||
// The connection latency may have increased, and our Rtt metrics may still be adjusting..
|
||||
// Just throw it back into the timeout queue
|
||||
return TimeoutResult::Refresh;
|
||||
|
||||
case PacketTimeoutResult::Lost:
|
||||
// Packet timed out and was not acked, so we consider it lost
|
||||
m_networkInterface.m_connectionListener.OnPacketLost(connection, packetId);
|
||||
m_connectionListener.OnPacketLost(connection, packetId);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -149,34 +149,24 @@ namespace AzNetworking
|
||||
//! @param endpoint whether the disconnection was initiated locally or remotely
|
||||
void RequestDisconnect(UdpConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint);
|
||||
|
||||
//! Internal helper to check if a packet's type is for connection handshake
|
||||
//! Internal helper to check if a packet's type is for connection handshake.
|
||||
//! @param endpoint DTLS endpoint participating in the handshake
|
||||
//! @param packetType type of the packet
|
||||
//! @return if the packet is for handshake
|
||||
bool IsHandshakePacket(const DtlsEndpoint& endpoint, AzNetworking::PacketType packetType) const;
|
||||
|
||||
//! Internal helper to manage connection timeout behaviour.
|
||||
//! @param item the timeout item corresponding to the timed out connection
|
||||
//! @return whether to delete or persist the timeout item
|
||||
TimeoutResult HandleConnectionTimeout(TimeoutQueue::TimeoutItem& item);
|
||||
|
||||
//! Internal helper to manage packet timeout behaviour.
|
||||
//! @param item the timeout item corresponding to the timed out packet
|
||||
//! @return whether to delete or persist the timeout item
|
||||
TimeoutResult HandlePacketTimeout(TimeoutQueue::TimeoutItem& item);
|
||||
|
||||
AZ_DISABLE_COPY_MOVE(UdpNetworkInterface);
|
||||
|
||||
struct ConnectionTimeoutFunctor final
|
||||
: public ITimeoutHandler
|
||||
{
|
||||
ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface);
|
||||
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(ConnectionTimeoutFunctor);
|
||||
UdpNetworkInterface& m_networkInterface;
|
||||
};
|
||||
|
||||
struct PacketTimeoutFunctor final
|
||||
: public ITimeoutHandler
|
||||
{
|
||||
PacketTimeoutFunctor(UdpNetworkInterface& networkInterface);
|
||||
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PacketTimeoutFunctor);
|
||||
UdpNetworkInterface& m_networkInterface;
|
||||
};
|
||||
|
||||
AZ::Name m_name;
|
||||
TrustZone m_trustZone;
|
||||
uint16_t m_port = 0;
|
||||
|
||||
@@ -2785,7 +2785,7 @@ namespace AzQtComponents
|
||||
RepaintFloatingIndicators();
|
||||
}
|
||||
break;
|
||||
case QEvent::WindowDeactivate:
|
||||
case QEvent::WindowBlocked:
|
||||
// If our main window is deactivated while we are in the middle of
|
||||
// a docking drag operation (e.g. popup dialog for new level), we
|
||||
// should cancel our drag operation because the mouse release event
|
||||
|
||||
@@ -140,6 +140,11 @@ namespace AZ
|
||||
|
||||
void GemTestEnvironment::TeardownEnvironment()
|
||||
{
|
||||
for (AZ::ComponentDescriptor* descriptor : m_parameters->m_componentDescriptors)
|
||||
{
|
||||
m_application->UnregisterComponentDescriptor(descriptor);
|
||||
}
|
||||
|
||||
const AZ::Entity::ComponentArrayType& components = m_gemEntity->GetComponents();
|
||||
for (auto itComponent = components.rbegin(); itComponent != components.rend(); ++itComponent)
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace AZ
|
||||
|
||||
/// A test environment which is intended to facilitate writing unit tests which require components from a gem.
|
||||
class GemTestEnvironment
|
||||
: public UnitTest::TraceBusHook
|
||||
: public ::UnitTest::TraceBusHook
|
||||
{
|
||||
public:
|
||||
GemTestEnvironment();
|
||||
|
||||
+2
-2
@@ -40,9 +40,9 @@ namespace AzToolsFramework
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QModelIndex parent(const QModelIndex& child) const override;
|
||||
QModelIndex sibling(int row, int column, const QModelIndex& idx) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role /* = Qt::DisplayRole */) const override;
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace AzToolsFramework
|
||||
private slots:
|
||||
void SourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
private:
|
||||
AZ::u64 m_numberOfItemsDisplayed = 0;
|
||||
AZ::u64 m_numberOfItemsDisplayed = 50;
|
||||
int m_displayedItemsCounter = 0;
|
||||
QPointer<AssetBrowserFilterModel> m_filterModel;
|
||||
QMap<int, QModelIndex> m_indexMap;
|
||||
|
||||
+4
@@ -58,6 +58,10 @@ namespace AzToolsFramework
|
||||
//! @return The highest closed entity container id if any, or entityId otherwise.
|
||||
virtual AZ::EntityId FindHighestSelectableEntity(AZ::EntityId entityId) const = 0;
|
||||
|
||||
//! Triggers the OnContainerEntityStatusChanged notifications for all registered containers,
|
||||
//! allowing listeners to update correctly.
|
||||
virtual void RefreshAllContainerEntities(AzFramework::EntityContextId entityContextId) const = 0;
|
||||
|
||||
//! Clears all open state information for Container Entities for the EntityContextId provided.
|
||||
//! Used when context is switched, for example in the case of a new root prefab being loaded
|
||||
//! in place of an old one.
|
||||
|
||||
+9
@@ -142,6 +142,15 @@ namespace AzToolsFramework
|
||||
Clear(editorEntityContextId);
|
||||
}
|
||||
|
||||
void ContainerEntitySystemComponent::RefreshAllContainerEntities([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
|
||||
{
|
||||
for (AZ::EntityId containerEntityId : m_containers)
|
||||
{
|
||||
ContainerEntityNotificationBus::Broadcast(
|
||||
&ContainerEntityNotificationBus::Events::OnContainerEntityStatusChanged, containerEntityId, m_openContainers.contains(containerEntityId));
|
||||
}
|
||||
}
|
||||
|
||||
ContainerEntityOperationResult ContainerEntitySystemComponent::Clear(AzFramework::EntityContextId entityContextId)
|
||||
{
|
||||
// We don't yet support multiple entity contexts, so only clear the default.
|
||||
|
||||
+1
@@ -47,6 +47,7 @@ namespace AzToolsFramework
|
||||
ContainerEntityOperationResult SetContainerOpen(AZ::EntityId entityId, bool open) override;
|
||||
bool IsContainerOpen(AZ::EntityId entityId) const override;
|
||||
AZ::EntityId FindHighestSelectableEntity(AZ::EntityId entityId) const override;
|
||||
void RefreshAllContainerEntities(AzFramework::EntityContextId entityContextId) const override;
|
||||
ContainerEntityOperationResult Clear(AzFramework::EntityContextId entityContextId) override;
|
||||
bool IsUnderClosedContainerEntity(AZ::EntityId entityId) const override;
|
||||
|
||||
|
||||
+11
-11
@@ -581,15 +581,15 @@ namespace AzToolsFramework
|
||||
{
|
||||
if (m_rootInstance && m_playInEditorData.m_isEnabled)
|
||||
{
|
||||
AZ_Assert(m_playInEditorData.m_entities.IsSet(),
|
||||
AZ_Assert(
|
||||
m_playInEditorData.m_entities.IsSet(),
|
||||
"Invalid Game Mode Entities Container encountered after play-in-editor stopped. "
|
||||
"Confirm that the container was initialized correctly");
|
||||
|
||||
m_playInEditorData.m_entities.DespawnAllEntities();
|
||||
m_playInEditorData.m_entities.Alert(
|
||||
[assets = AZStd::move(m_playInEditorData.m_assets),
|
||||
deactivatedEntities = AZStd::move(m_playInEditorData.m_deactivatedEntities)]
|
||||
([[maybe_unused]]uint32_t generation) mutable
|
||||
deactivatedEntities = AZStd::move(m_playInEditorData.m_deactivatedEntities)]([[maybe_unused]] uint32_t generation) mutable
|
||||
{
|
||||
auto end = deactivatedEntities.rend();
|
||||
for (auto it = deactivatedEntities.rbegin(); it != end; ++it)
|
||||
@@ -614,15 +614,15 @@ namespace AzToolsFramework
|
||||
AzFramework::GameEntityContextEventBus::Broadcast(&AzFramework::GameEntityContextEventBus::Events::OnGameEntitiesReset);
|
||||
});
|
||||
m_playInEditorData.m_entities.Clear();
|
||||
}
|
||||
|
||||
// Game entity cleanup is queued onto the next tick via the DespawnEntities call.
|
||||
// To avoid both game entities and Editor entities active at the same time
|
||||
// we flush the tick queue to ensure the game entities are cleared first.
|
||||
// The Alert callback that follows the DespawnEntities call will then reactivate the editor entities
|
||||
// This should be considered temporary as a move to a less rigid event sequence that supports async entity clean up
|
||||
// is the desired direction forward.
|
||||
AZ::TickBus::ExecuteQueuedEvents();
|
||||
// Game entity cleanup is queued onto the next tick via the DespawnEntities call.
|
||||
// To avoid both game entities and Editor entities active at the same time
|
||||
// we flush the tick queue to ensure the game entities are cleared first.
|
||||
// The Alert callback that follows the DespawnEntities call will then reactivate the editor entities
|
||||
// This should be considered temporary as a move to a less rigid event sequence that supports async entity clean up
|
||||
// is the desired direction forward.
|
||||
AZ::TickBus::ExecuteQueuedEvents();
|
||||
}
|
||||
|
||||
m_playInEditorData.m_isEnabled = false;
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace AzToolsFramework
|
||||
|
||||
void Instance::SetLinkId(LinkId linkId)
|
||||
{
|
||||
m_linkId = AZStd::move(linkId);
|
||||
m_linkId = linkId;
|
||||
}
|
||||
|
||||
LinkId Instance::GetLinkId() const
|
||||
@@ -154,23 +154,26 @@ namespace AzToolsFramework
|
||||
|
||||
bool Instance::AddEntity(AZ::Entity& entity)
|
||||
{
|
||||
EntityAlias newEntityAlias = GenerateEntityAlias();
|
||||
return AddEntity(entity, newEntityAlias);
|
||||
return AddEntity(entity, GenerateEntityAlias());
|
||||
}
|
||||
|
||||
bool Instance::AddEntity(AZStd::unique_ptr<AZ::Entity>&& entity)
|
||||
{
|
||||
return AddEntity(AZStd::move(entity), GenerateEntityAlias());
|
||||
}
|
||||
|
||||
bool Instance::AddEntity(AZ::Entity& entity, EntityAlias entityAlias)
|
||||
{
|
||||
if (!RegisterEntity(entity.GetId(), entityAlias))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return
|
||||
RegisterEntity(entity.GetId(), entityAlias) &&
|
||||
m_entities.emplace(AZStd::move(entityAlias), &entity).second;
|
||||
}
|
||||
|
||||
if (!m_entities.emplace(AZStd::make_pair(entityAlias, &entity)).second)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
bool Instance::AddEntity(AZStd::unique_ptr<AZ::Entity>&& entity, EntityAlias entityAlias)
|
||||
{
|
||||
return
|
||||
RegisterEntity(entity->GetId(), entityAlias) &&
|
||||
m_entities.emplace(AZStd::move(entityAlias), AZStd::move(entity)).second;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> Instance::DetachEntity(const AZ::EntityId& entityId)
|
||||
@@ -228,6 +231,23 @@ namespace AzToolsFramework
|
||||
m_entities.clear();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> Instance::ReplaceEntity(AZStd::unique_ptr<AZ::Entity>&& entity, EntityAliasView alias)
|
||||
{
|
||||
AZStd::unique_ptr<AZ::Entity> result;
|
||||
auto it = m_entities.find(alias);
|
||||
if (it != m_entities.end())
|
||||
{
|
||||
// Swap entity ids as these need to remain stable
|
||||
AZ::EntityId originalId = it->second->GetId();
|
||||
it->second->SetId(entity->GetId());
|
||||
entity->SetId(originalId);
|
||||
|
||||
result = AZStd::move(it->second);
|
||||
it->second = AZStd::move(entity);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Instance::RemoveNestedEntities(
|
||||
const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter)
|
||||
{
|
||||
@@ -377,7 +397,12 @@ namespace AzToolsFramework
|
||||
return entityAliases;
|
||||
}
|
||||
|
||||
void Instance::GetNestedEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback)
|
||||
size_t Instance::GetEntityAliasCount() const
|
||||
{
|
||||
return m_entities.size();
|
||||
}
|
||||
|
||||
void Instance::GetNestedEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback) const
|
||||
{
|
||||
GetEntityIds(callback);
|
||||
|
||||
@@ -387,7 +412,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::GetEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback)
|
||||
void Instance::GetEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback) const
|
||||
{
|
||||
for (auto&&[entityAlias, entityId] : m_templateToInstanceEntityIdMap)
|
||||
{
|
||||
@@ -398,6 +423,17 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::GetEntityIdToAlias(const AZStd::function<bool(AZ::EntityId, EntityAliasView)>& callback) const
|
||||
{
|
||||
for (auto&& [entityAlias, entityId] : m_templateToInstanceEntityIdMap)
|
||||
{
|
||||
if (!callback(entityId, entityAlias))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Instance::GetEntities_Impl(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
|
||||
{
|
||||
for (auto& [entityAlias, entity] : m_entities)
|
||||
@@ -514,24 +550,81 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
EntityAliasOptionalReference Instance::GetEntityAlias(const AZ::EntityId& id)
|
||||
EntityAliasOptionalReference Instance::GetEntityAlias(AZ::EntityId id)
|
||||
{
|
||||
if (m_instanceToTemplateEntityIdMap.count(id))
|
||||
{
|
||||
return m_instanceToTemplateEntityIdMap[id];
|
||||
}
|
||||
|
||||
return AZStd::nullopt;
|
||||
auto it = m_instanceToTemplateEntityIdMap.find(id);
|
||||
return it != m_instanceToTemplateEntityIdMap.end() ? EntityAliasOptionalReference(it->second)
|
||||
: EntityAliasOptionalReference(AZStd::nullopt);
|
||||
}
|
||||
|
||||
AZ::EntityId Instance::GetEntityId(const EntityAlias& alias)
|
||||
EntityAliasView Instance::GetEntityAlias(AZ::EntityId id) const
|
||||
{
|
||||
if (m_templateToInstanceEntityIdMap.count(alias))
|
||||
auto it = m_instanceToTemplateEntityIdMap.find(id);
|
||||
return it != m_instanceToTemplateEntityIdMap.end() ? EntityAliasView(it->second) : EntityAliasView();
|
||||
}
|
||||
|
||||
AZStd::pair<Instance*, EntityAliasView> Instance::FindInstanceAndAlias(AZ::EntityId entity)
|
||||
{
|
||||
auto it = m_instanceToTemplateEntityIdMap.find(entity);
|
||||
if (it != m_instanceToTemplateEntityIdMap.end())
|
||||
{
|
||||
return m_templateToInstanceEntityIdMap[alias];
|
||||
return AZStd::pair<Instance*, EntityAliasView>(this, it->second);
|
||||
}
|
||||
|
||||
return AZ::EntityId();
|
||||
else
|
||||
{
|
||||
for (auto&& [_, instance] : m_nestedInstances)
|
||||
{
|
||||
AZStd::pair<Instance*, EntityAliasView> next = instance->FindInstanceAndAlias(entity);
|
||||
if (next.first != nullptr)
|
||||
{
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AZStd::pair<Instance*, EntityAliasView>(nullptr, "");
|
||||
}
|
||||
|
||||
AZStd::pair<const Instance*, EntityAliasView> Instance::FindInstanceAndAlias(AZ::EntityId entity) const
|
||||
{
|
||||
return const_cast<Instance*>(this)->FindInstanceAndAlias(entity);
|
||||
}
|
||||
|
||||
EntityOptionalReference Instance::GetEntity(const EntityAlias& alias)
|
||||
{
|
||||
auto it = m_entities.find(alias);
|
||||
return it != m_entities.end() ? EntityOptionalReference(*it->second) : EntityOptionalReference(AZStd::nullopt);
|
||||
}
|
||||
|
||||
EntityOptionalConstReference Instance::GetEntity(const EntityAlias& alias) const
|
||||
{
|
||||
auto it = m_entities.find(alias);
|
||||
return it != m_entities.end() ? EntityOptionalConstReference(*it->second) : EntityOptionalConstReference(AZStd::nullopt);
|
||||
}
|
||||
|
||||
AZ::EntityId Instance::GetEntityId(const EntityAlias& alias) const
|
||||
{
|
||||
auto it = m_templateToInstanceEntityIdMap.find(alias);
|
||||
return it != m_templateToInstanceEntityIdMap.end() ? it->second : AZ::EntityId();
|
||||
}
|
||||
|
||||
AZ::EntityId Instance::GetEntityIdFromAliasPath(AliasPathView relativeAliasPath) const
|
||||
{
|
||||
const Instance* instance = this;
|
||||
AliasPathView path = relativeAliasPath.ParentPath();
|
||||
for (auto it : path)
|
||||
{
|
||||
InstanceOptionalConstReference child = instance->FindNestedInstance(it.Native());
|
||||
if (child.has_value())
|
||||
{
|
||||
instance = &(child->get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return AZ::EntityId();
|
||||
}
|
||||
}
|
||||
|
||||
return instance->GetEntityId(relativeAliasPath.Filename().Native());
|
||||
}
|
||||
|
||||
AZStd::vector<InstanceAlias> Instance::GetNestedInstanceAliases(TemplateId templateId) const
|
||||
@@ -572,6 +665,32 @@ namespace AzToolsFramework
|
||||
return aliasPathResult;
|
||||
}
|
||||
|
||||
AliasPath Instance::GetAliasPathRelativeToInstance(const AZ::EntityId& entity) const
|
||||
{
|
||||
AliasPath result = AliasPath(s_aliasPathSeparator);
|
||||
auto&& [instance, alias] = FindInstanceAndAlias(entity);
|
||||
if (instance)
|
||||
{
|
||||
AZStd::vector<const Instance*> instanceChain;
|
||||
|
||||
while (instance && instance != this)
|
||||
{
|
||||
instanceChain.push_back(instance);
|
||||
instance = instance->m_parent;
|
||||
}
|
||||
|
||||
for (auto it = instanceChain.rbegin(); it != instanceChain.rend(); ++it)
|
||||
{
|
||||
result.Append((*it)->m_alias);
|
||||
}
|
||||
return result.Append(alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
EntityAlias Instance::GenerateEntityAlias()
|
||||
{
|
||||
return AZStd::string::format("Entity_%s", AZ::Entity::MakeId().ToString().c_str());
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace AzToolsFramework
|
||||
using AliasPath = AZ::IO::Path;
|
||||
using AliasPathView = AZ::IO::PathView;
|
||||
using EntityAlias = AZStd::string;
|
||||
using EntityAliasView = AZStd::string_view;
|
||||
using InstanceAlias = AZStd::string;
|
||||
|
||||
class Instance;
|
||||
@@ -83,9 +84,17 @@ namespace AzToolsFramework
|
||||
void SetContainerEntityName(AZStd::string_view containerName);
|
||||
|
||||
bool AddEntity(AZ::Entity& entity);
|
||||
bool AddEntity(AZStd::unique_ptr<AZ::Entity>&& entity);
|
||||
bool AddEntity(AZ::Entity& entity, EntityAlias entityAlias);
|
||||
bool AddEntity(AZStd::unique_ptr<AZ::Entity>&& entity, EntityAlias entityAlias);
|
||||
AZStd::unique_ptr<AZ::Entity> DetachEntity(const AZ::EntityId& entityId);
|
||||
void DetachEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
|
||||
/**
|
||||
* Replaces the entity stored under the provided alias with a new one.
|
||||
*
|
||||
* @return The original entity or a nullptr if not found.
|
||||
*/
|
||||
AZStd::unique_ptr<AZ::Entity> ReplaceEntity(AZStd::unique_ptr<AZ::Entity>&& entity, EntityAliasView alias);
|
||||
|
||||
/**
|
||||
* Detaches all entities in the instance hierarchy.
|
||||
@@ -109,13 +118,15 @@ namespace AzToolsFramework
|
||||
* @return The list of EntityAliases
|
||||
*/
|
||||
AZStd::vector<EntityAlias> GetEntityAliases();
|
||||
size_t GetEntityAliasCount() const;
|
||||
|
||||
/**
|
||||
* Gets the ids for the entities in the Instance DOM. Can recursively trace all nested instances.
|
||||
*/
|
||||
void GetNestedEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback);
|
||||
void GetNestedEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback) const;
|
||||
|
||||
void GetEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback);
|
||||
void GetEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback) const;
|
||||
void GetEntityIdToAlias(const AZStd::function<bool(AZ::EntityId, EntityAliasView)>& callback) const;
|
||||
|
||||
/**
|
||||
* Gets the entities in the Instance DOM. Can recursively trace all nested instances.
|
||||
@@ -131,14 +142,33 @@ namespace AzToolsFramework
|
||||
*
|
||||
* @return entityAlias via optional
|
||||
*/
|
||||
AZStd::optional<AZStd::reference_wrapper<EntityAlias>> GetEntityAlias(const AZ::EntityId& id);
|
||||
EntityAliasOptionalReference GetEntityAlias(AZ::EntityId id);
|
||||
EntityAliasView GetEntityAlias(AZ::EntityId id) const;
|
||||
/**
|
||||
* Searches for the entity in this instance and its nested instances.
|
||||
*
|
||||
* @return The instance that owns the entity and the alias under which the entity is known.
|
||||
* If the entity isn't found then the instance will be null and the alias empty.
|
||||
*/
|
||||
AZStd::pair<Instance*, EntityAliasView> FindInstanceAndAlias(AZ::EntityId entity);
|
||||
AZStd::pair<const Instance*, EntityAliasView> FindInstanceAndAlias(AZ::EntityId entity) const;
|
||||
|
||||
EntityOptionalReference GetEntity(const EntityAlias& alias);
|
||||
EntityOptionalConstReference GetEntity(const EntityAlias& alias) const;
|
||||
|
||||
/**
|
||||
* Gets the id for a given EnitityAlias in the Instance DOM.
|
||||
*
|
||||
* @return entityId, invalid ID if not found
|
||||
*/
|
||||
AZ::EntityId GetEntityId(const EntityAlias& alias);
|
||||
AZ::EntityId GetEntityId(const EntityAlias& alias) const;
|
||||
|
||||
/**
|
||||
* Retrieves the entity id from an alias path that's relative to this instance.
|
||||
*
|
||||
* @return entityId, invalid ID if not found
|
||||
*/
|
||||
AZ::EntityId GetEntityIdFromAliasPath(AliasPathView relativeAliasPath) const;
|
||||
|
||||
|
||||
/**
|
||||
@@ -180,6 +210,7 @@ namespace AzToolsFramework
|
||||
|
||||
static EntityAlias GenerateEntityAlias();
|
||||
AliasPath GetAbsoluteInstanceAliasPath() const;
|
||||
AliasPath GetAliasPathRelativeToInstance(const AZ::EntityId& entity) const;
|
||||
|
||||
static InstanceAlias GenerateInstanceAlias();
|
||||
|
||||
|
||||
+1
-2
@@ -46,11 +46,10 @@ namespace AzToolsFramework
|
||||
//! Updates the template links (updating instances) for the given template and triggers propagation on its instances.
|
||||
//! @param providedPatch The patch to apply to the template.
|
||||
//! @param templateId The id of the template to update.
|
||||
//! @param immediate An optional flag whether to apply the patch immediately (needed for Undo/Redos) or wait until next system tick.
|
||||
//! @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshes as part of propagation.
|
||||
//! Defaults to nullopt, which means that all instances will be refreshed.
|
||||
//! @return True if the template was patched correctly, false if the operation failed.
|
||||
virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
|
||||
virtual void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) = 0;
|
||||
|
||||
|
||||
+2
-2
@@ -156,7 +156,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
|
||||
bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace AzToolsFramework
|
||||
(result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::PartialSkip),
|
||||
"Some of the patches were not successfully applied.");
|
||||
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true);
|
||||
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, immediate, instanceToExclude);
|
||||
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, instanceToExclude);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ namespace AzToolsFramework
|
||||
|
||||
InstanceOptionalReference GetTopMostInstanceInHierarchy(AZ::EntityId entityId) override;
|
||||
|
||||
bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt) override;
|
||||
|
||||
void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) override;
|
||||
|
||||
|
||||
+2
-7
@@ -52,7 +52,7 @@ namespace AzToolsFramework
|
||||
AZ::Interface<InstanceUpdateExecutorInterface>::Unregister(this);
|
||||
}
|
||||
|
||||
void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate, InstanceOptionalReference instanceToExclude)
|
||||
void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
auto findInstancesResult =
|
||||
m_templateInstanceMapperInterface->FindInstancesOwnedByTemplate(instanceTemplateId);
|
||||
@@ -66,7 +66,7 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
Instance* instanceToExcludePtr = nullptr;
|
||||
const Instance* instanceToExcludePtr = nullptr;
|
||||
if (instanceToExclude.has_value())
|
||||
{
|
||||
instanceToExcludePtr = &(instanceToExclude->get());
|
||||
@@ -79,11 +79,6 @@ namespace AzToolsFramework
|
||||
m_instancesUpdateQueue.emplace_back(instance);
|
||||
}
|
||||
}
|
||||
|
||||
if (immediate)
|
||||
{
|
||||
UpdateTemplateInstancesInQueue();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceUpdateExecutor::RemoveTemplateInstanceFromQueue(const Instance* instance)
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ namespace AzToolsFramework
|
||||
|
||||
explicit InstanceUpdateExecutor(int instanceCountToUpdateInBatch = 0);
|
||||
|
||||
void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt) override;
|
||||
bool UpdateTemplateInstancesInQueue() override;
|
||||
virtual void RemoveTemplateInstanceFromQueue(const Instance* instance) override;
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ namespace AzToolsFramework
|
||||
virtual ~InstanceUpdateExecutorInterface() = default;
|
||||
|
||||
// Add all Instances of Template with given Id into a queue for updating them later.
|
||||
virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
|
||||
// Update Instances in the waiting queue.
|
||||
virtual bool UpdateTemplateInstancesInQueue() = 0;
|
||||
|
||||
@@ -86,6 +86,45 @@ namespace AzToolsFramework::Prefab
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PrefabFocusOperationResult PrefabFocusHandler::FocusOnParentOfFocusedPrefab(
|
||||
[[maybe_unused]] AzFramework::EntityContextId entityContextId)
|
||||
{
|
||||
// If only one instance is in the hierarchy, this operation is invalid
|
||||
size_t hierarchySize = m_instanceFocusHierarchy.size();
|
||||
if (hierarchySize <= 1)
|
||||
{
|
||||
return AZ::Failure(
|
||||
AZStd::string("Prefab Focus Handler: Could not complete FocusOnParentOfFocusedPrefab operation while focusing on the root."));
|
||||
}
|
||||
|
||||
// Retrieve parent of currently focused prefab.
|
||||
InstanceOptionalReference parentInstance = m_instanceFocusHierarchy[hierarchySize - 2];
|
||||
|
||||
// Use container entity of parent Instance for focus operations.
|
||||
AZ::EntityId entityId = parentInstance->get().GetContainerEntityId();
|
||||
|
||||
// Initialize Undo Batch object
|
||||
ScopedUndoBatch undoBatch("Edit Prefab");
|
||||
|
||||
// Clear selection
|
||||
{
|
||||
const EntityIdList selectedEntities = EntityIdList{};
|
||||
auto selectionUndo = aznew SelectionCommand(selectedEntities, "Clear Selection");
|
||||
selectionUndo->SetParent(undoBatch.GetUndoBatch());
|
||||
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, selectedEntities);
|
||||
}
|
||||
|
||||
// Edit Prefab
|
||||
{
|
||||
auto editUndo = aznew PrefabFocusUndo("Edit Prefab");
|
||||
editUndo->Capture(entityId);
|
||||
editUndo->SetParent(undoBatch.GetUndoBatch());
|
||||
FocusOnPrefabInstanceOwningEntityId(entityId);
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
|
||||
{
|
||||
if (index < 0 || index >= m_instanceFocusHierarchy.size())
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace AzToolsFramework::Prefab
|
||||
|
||||
// PrefabFocusPublicInterface overrides ...
|
||||
PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override;
|
||||
PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) override;
|
||||
PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override;
|
||||
AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const override;
|
||||
bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override;
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace AzToolsFramework::Prefab
|
||||
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
|
||||
virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0;
|
||||
|
||||
//! Set the focused prefab instance to the parent of the currently focused prefab instance. Supports undo/redo.
|
||||
virtual PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) = 0;
|
||||
|
||||
//! Set the focused prefab instance to the instance at position index of the current path. Supports undo/redo.
|
||||
//! @param index The index of the instance in the current path that we want the prefab system to focus on.
|
||||
virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0;
|
||||
|
||||
@@ -777,7 +777,7 @@ namespace AzToolsFramework
|
||||
linkUpdate->SetParent(undoBatch);
|
||||
linkUpdate->Capture(patch, linkId);
|
||||
|
||||
linkUpdate->Redo(parentInstance);
|
||||
linkUpdate->Redo(parentInstance->get());
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::Internal_HandleEntityChange(
|
||||
@@ -788,8 +788,7 @@ namespace AzToolsFramework
|
||||
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
|
||||
state->SetParent(undoBatch);
|
||||
state->Capture(beforeState, afterState, entityId);
|
||||
|
||||
state->Redo(instance);
|
||||
state->Redo(instance->get());
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::Internal_HandleInstanceChange(
|
||||
@@ -1061,7 +1060,7 @@ namespace AzToolsFramework
|
||||
DuplicateNestedEntitiesInInstance(commonOwningInstance->get(),
|
||||
entities, instanceDomAfter, duplicatedEntityAndInstanceIds, duplicateEntityAliasMap);
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication", false);
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication");
|
||||
command->SetParent(undoBatch.GetUndoBatch());
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
|
||||
command->Redo();
|
||||
@@ -1167,6 +1166,60 @@ namespace AzToolsFramework
|
||||
|
||||
ScopedUndoBatch undoBatch("Delete Selected");
|
||||
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities");
|
||||
|
||||
Prefab::PrefabDom instanceDomBefore;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
|
||||
|
||||
if (deleteDescendants)
|
||||
{
|
||||
AZStd::vector<AZ::Entity*> entities;
|
||||
AZStd::vector<Instance*> instances;
|
||||
|
||||
PrefabOperationResult retrieveEntitiesAndInstancesOutcome =
|
||||
RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
||||
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
||||
}
|
||||
|
||||
for (AZ::Entity* entity : entities)
|
||||
{
|
||||
commonOwningInstance->get().DetachEntity(entity->GetId()).release();
|
||||
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId());
|
||||
}
|
||||
|
||||
for (auto& nestedInstance : instances)
|
||||
{
|
||||
AZStd::unique_ptr<Instance> outInstance =
|
||||
commonOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
||||
RemoveLink(outInstance, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
||||
outInstance.reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (AZ::EntityId entityId : entityIdsNoFocusContainer)
|
||||
{
|
||||
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
// If this is the container entity, it actually represents the instance so get its owner
|
||||
if (owningInstance->get().GetContainerEntityId() == entityId)
|
||||
{
|
||||
auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
|
||||
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
||||
}
|
||||
else
|
||||
{
|
||||
commonOwningInstance->get().DetachEntity(entityId);
|
||||
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Prefab::PrefabDom instanceDomAfter;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, commonOwningInstance->get());
|
||||
|
||||
// In order to undo DeleteSelected, we have to create a selection command which selects the current selection
|
||||
// and then add the deletion as children.
|
||||
// Commands always execute themselves first and then their children (when going forwards)
|
||||
@@ -1174,81 +1227,22 @@ namespace AzToolsFramework
|
||||
EntityIdList selectedEntities;
|
||||
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
|
||||
SelectionCommand* selCommand = aznew SelectionCommand(selectedEntities, "Delete Entities");
|
||||
selCommand->SetParent(undoBatch.GetUndoBatch());
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:RunRedo");
|
||||
selCommand->RunRedo();
|
||||
|
||||
// We insert a "deselect all" command before we delete the entities. This ensures the delete operations aren't changing
|
||||
// selection state, which triggers expensive UI updates. By deselecting up front, we are able to do those expensive
|
||||
// UI updates once at the start instead of once for each entity.
|
||||
{
|
||||
EntityIdList deselection;
|
||||
SelectionCommand* deselectAllCommand = aznew SelectionCommand(deselection, "Deselect Entities");
|
||||
deselectAllCommand->SetParent(selCommand);
|
||||
}
|
||||
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities");
|
||||
|
||||
Prefab::PrefabDom instanceDomBefore;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
|
||||
|
||||
if (deleteDescendants)
|
||||
{
|
||||
AZStd::vector<AZ::Entity*> entities;
|
||||
AZStd::vector<Instance*> instances;
|
||||
|
||||
PrefabOperationResult retrieveEntitiesAndInstancesOutcome =
|
||||
RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
||||
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
||||
}
|
||||
|
||||
for (AZ::Entity* entity : entities)
|
||||
{
|
||||
commonOwningInstance->get().DetachEntity(entity->GetId()).release();
|
||||
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId());
|
||||
}
|
||||
|
||||
for (auto& nestedInstance : instances)
|
||||
{
|
||||
AZStd::unique_ptr<Instance> outInstance = commonOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
||||
RemoveLink(outInstance, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
||||
outInstance.reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (AZ::EntityId entityId : entityIdsNoFocusContainer)
|
||||
{
|
||||
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
// If this is the container entity, it actually represents the instance so get its owner
|
||||
if (owningInstance->get().GetContainerEntityId() == entityId)
|
||||
{
|
||||
auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
|
||||
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
||||
}
|
||||
else
|
||||
{
|
||||
commonOwningInstance->get().DetachEntity(entityId);
|
||||
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Prefab::PrefabDom instanceDomAfter;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, commonOwningInstance->get());
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance deletion");
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
|
||||
command->SetParent(selCommand);
|
||||
}
|
||||
|
||||
selCommand->SetParent(undoBatch.GetUndoBatch());
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:RunRedo");
|
||||
selCommand->RunRedo();
|
||||
}
|
||||
EntityIdList deselection;
|
||||
SelectionCommand* deselectAllCommand = aznew SelectionCommand(deselection, "Deselect Entities");
|
||||
deselectAllCommand->SetParent(undoBatch.GetUndoBatch());
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance deletion");
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
|
||||
command->SetParent(undoBatch.GetUndoBatch());
|
||||
command->Redo(commonOwningInstance->get());
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
@@ -1333,12 +1327,12 @@ namespace AzToolsFramework
|
||||
Prefab::PrefabDom instanceDomAfter;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance);
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment", false);
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment");
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId);
|
||||
command->SetParent(undoBatch.GetUndoBatch());
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DetachPrefab:RunRedo");
|
||||
command->RunRedo();
|
||||
command->Redo(parentInstance);
|
||||
}
|
||||
|
||||
instancePtr->DetachNestedInstances(
|
||||
|
||||
@@ -160,10 +160,8 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
|
||||
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
UpdatePrefabInstances(templateId, immediate, instanceToExclude);
|
||||
|
||||
auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId);
|
||||
if (templateIdToLinkIdsIterator != m_templateToLinkIdsMap.end())
|
||||
{
|
||||
@@ -174,6 +172,7 @@ namespace AzToolsFramework
|
||||
templateIdToLinkIdsIterator->second.end()));
|
||||
UpdateLinkedInstances(linkIdsToUpdateQueue);
|
||||
}
|
||||
UpdatePrefabInstances(templateId, instanceToExclude);
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom)
|
||||
@@ -191,9 +190,9 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
|
||||
void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, immediate, instanceToExclude);
|
||||
m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, instanceToExclude);
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::UpdateLinkedInstances(AZStd::queue<LinkIds>& linkIdsQueue)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user