Merging latest development

Signed-off-by: kberg-amzn <karlberg@amazon.com>
This commit is contained in:
kberg-amzn
2021-10-14 19:17:10 -07:00
1528 changed files with 33581 additions and 22343 deletions
@@ -77,11 +77,15 @@
namespace AzFramework
{
namespace ApplicationInternal
{
static constexpr const char s_prefabSystemKey[] = "/Amazon/Preferences/EnablePrefabSystem";
static constexpr const char s_prefabWipSystemKey[] = "/Amazon/Preferences/EnablePrefabSystemWipFeatures";
static constexpr const char s_legacySlicesAssertKey[] = "/Amazon/Preferences/ShouldAssertForLegacySlicesUsage";
static constexpr const char* DeprecatedFileIOAliasesRoot = "/O3DE/AzCore/FileIO/DeprecatedAliases";
static constexpr const char* DeprecatedFileIOAliasesOldAliasKey = "OldAlias";
static constexpr const char* DeprecatedFileIOAliasesNewAliasKey = "NewAlias";
}
Application::Application()
@@ -563,6 +567,68 @@ namespace AzFramework
}
}
struct DeprecatedAliasesKeyVisitor
: AZ::SettingsRegistryInterface::Visitor
{
using VisitResponse = AZ::SettingsRegistryInterface::VisitResponse;
using VisitAction = AZ::SettingsRegistryInterface::VisitAction;
using Type = AZ::SettingsRegistryInterface::Type;
using AZ::SettingsRegistryInterface::Visitor::Visit;
VisitResponse Traverse(AZStd::string_view path, AZStd::string_view,
VisitAction action, Type type) override
{
if (action == AZ::SettingsRegistryInterface::VisitAction::Begin)
{
if (type == AZ::SettingsRegistryInterface::Type::Array)
{
m_parentArrayPath = path;
}
// Strip off last path segment from json path and check if is a child element of the array
if (AZ::StringFunc::TokenizeLast(path, '/');
m_parentArrayPath == path)
{
m_aliases.emplace_back();
}
}
else if (action == AZ::SettingsRegistryInterface::VisitAction::End)
{
if (type == AZ::SettingsRegistryInterface::Type::Array)
{
m_parentArrayPath = AZStd::string{};
}
}
return AZ::SettingsRegistryInterface::VisitResponse::Continue;
}
void Visit(AZStd::string_view, AZStd::string_view valueName, Type, AZStd::string_view value) override
{
if (!m_aliases.empty())
{
if (valueName == ApplicationInternal::DeprecatedFileIOAliasesOldAliasKey)
{
m_aliases.back().m_oldAlias = value;
}
else if (valueName == ApplicationInternal::DeprecatedFileIOAliasesNewAliasKey)
{
m_aliases.back().m_newAlias = value;
}
}
}
struct AliasPair
{
AZStd::string m_oldAlias;
AZStd::string m_newAlias;
};
AZStd::vector<AliasPair> m_aliases;
private:
AZStd::string m_parentArrayPath;
};
static void CreateUserCache(const AZ::IO::FixedMaxPath& cacheUserPath, AZ::IO::FileIOBase& fileIoBase)
{
@@ -610,9 +676,8 @@ namespace AzFramework
void Application::SetFileIOAliases()
{
if (m_archiveFileIO)
if (auto fileIoBase = m_archiveFileIO.get(); fileIoBase)
{
auto fileIoBase = m_archiveFileIO.get();
// Set up the default file aliases based on the settings registry
fileIoBase->SetAlias("@engroot@", GetEngineRoot());
fileIoBase->SetAlias("@projectroot@", GetEngineRoot());
@@ -620,29 +685,20 @@ namespace AzFramework
{
AZ::IO::FixedMaxPath pathAliases;
if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder))
{
fileIoBase->SetAlias("@projectcache@", pathAliases.c_str());
}
pathAliases.clear();
if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
{
fileIoBase->SetAlias("@assets@", pathAliases.c_str());
fileIoBase->SetAlias("@projectplatformcache@", pathAliases.c_str());
fileIoBase->SetAlias("@root@", pathAliases.c_str()); // Deprecated Use @projectplatformcache@
fileIoBase->SetAlias("@products@", pathAliases.c_str());
}
pathAliases.clear();
if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
{
fileIoBase->SetAlias("@engroot@", pathAliases.c_str());
fileIoBase->SetAlias("@devroot@", pathAliases.c_str()); // Deprecated - Use @engroot@
}
pathAliases.clear();
if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath))
{
fileIoBase->SetAlias("@devassets@", pathAliases.c_str()); // Deprecated - Use @projectsourceassets@
fileIoBase->SetAlias("@projectroot@", pathAliases.c_str());
fileIoBase->SetAlias("@projectsourceassets@", (pathAliases / "Assets").c_str());
}
}
@@ -663,6 +719,15 @@ namespace AzFramework
}
fileIoBase->SetAlias("@log@", projectLogPath.c_str());
fileIoBase->CreatePath(projectLogPath.c_str());
DeprecatedAliasesKeyVisitor visitor;
if (m_settingsRegistry->Visit(visitor, ApplicationInternal::DeprecatedFileIOAliasesRoot))
{
for (const auto& [oldAlias, newAlias] : visitor.m_aliases)
{
fileIoBase->SetDeprecatedAlias(oldAlias, newAlias);
}
}
}
}
@@ -1121,7 +1121,7 @@ namespace AZ::IO
if (AZ::IO::FixedMaxPath pathBindRoot; !AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(pathBindRoot, szBindRoot))
{
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(pathBindRoot, "@assets@");
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(pathBindRoot, "@products@");
desc.m_pathBindRoot = pathBindRoot.LexicallyNormal().String();
}
else
@@ -1807,9 +1807,9 @@ namespace AZ::IO
if (m_eRecordFileOpenList != IArchive::RFOM_Disabled)
{
// we only want to record ASSET access
// assets are identified as files that are relative to the resolved @assets@ alias path
// assets are identified as files that are relative to the resolved @products@ alias path
auto fileIoBase = AZ::IO::FileIOBase::GetInstance();
const char* aliasValue = fileIoBase->GetAlias("@assets@");
const char* aliasValue = fileIoBase->GetAlias("@products@");
if (AZ::IO::FixedMaxPath resolvedFilePath;
fileIoBase->ResolvePath(resolvedFilePath, szFilename)
@@ -546,6 +546,16 @@ namespace AZ::IO
realUnderlyingFileIO->GetAlias(alias);
}
void ArchiveFileIO::SetDeprecatedAlias(AZStd::string_view oldAlias, AZStd::string_view newAlias)
{
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
if (!realUnderlyingFileIO)
{
return;
}
realUnderlyingFileIO->SetDeprecatedAlias(oldAlias, newAlias);
}
AZStd::optional<AZ::u64> ArchiveFileIO::ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const
{
if ((!inOutBuffer) || (bufferLength == 0))
@@ -63,6 +63,7 @@ namespace AZ::IO
IO::Result FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback) override;
void SetAlias(const char* alias, const char* path) override;
void ClearAlias(const char* alias) override;
void SetDeprecatedAlias(AZStd::string_view oldAlias, AZStd::string_view newAlias) override;
AZStd::optional<AZ::u64> ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const override;
bool ConvertToAlias(AZ::IO::FixedMaxPath& convertedPath, const AZ::IO::PathView& path) const override;
using FileIOBase::ConvertToAlias;
@@ -186,8 +186,8 @@ namespace AZ::IO
{
// filter out the stuff which does not match.
// the problem here is that szDir might be something like "@assets@/levels/*"
// but our archive might be mounted at the root, or at some other folder at like "@assets@" or "@assets@/levels/mylevel"
// the problem here is that szDir might be something like "@products@/levels/*"
// but our archive might be mounted at the root, or at some other folder at like "@products@" or "@products@/levels/mylevel"
// so there's really no way to filter out opening the pack and looking at the files inside.
// however, the bind root is not part of the inner zip entry name either
// and the ZipDir::FindFile actually expects just the chopped off piece.
@@ -202,22 +202,22 @@ namespace AZ::IO
// Example:
// "@assets@\\levels\\*" <--- szDir
// "@assets@\\" <--- mount point
// "@products@\\levels\\*" <--- szDir
// "@products@\\" <--- mount point
// ~~~~~~~~~~~ Common part
// "levels\\*" <---- remainder that is not in common
// "" <--- mount point remainder. In this case, we should scan the contents of the pak for the remainder
// Example:
// "@assets@\\levels\\*" <--- szDir
// "@assets@\\levels\\mylevel\\" <--- mount point (its level.pak)
// "@products@\\levels\\*" <--- szDir
// "@products@\\levels\\mylevel\\" <--- mount point (its level.pak)
// ~~~~~~~~~~~~~~~~~~ common part
// "*" <---- remainder that is not in common
// "mylevel\\" <--- mount point remainder.
// example:
// "@assets@\\levels\\otherlevel\\*" <--- szDir
// "@assets@\\levels\\mylevel\\" <--- mount point (its level.pak)
// "@products@\\levels\\otherlevel\\*" <--- szDir
// "@products@\\levels\\mylevel\\" <--- mount point (its level.pak)
// "otherlevel\\*" <---- remainder
// "mylevel\\" <--- mount point remainder.
@@ -249,7 +249,7 @@ namespace AZ::IO
// which means we may search inside the pack.
ScanInZip(it->pZip.get(), sourcePathRemainder.Native());
}
}
}
@@ -94,7 +94,7 @@ namespace AZ::IO::Internal
}
AZStd::smatch matches;
const AZStd::regex lodRegex("@assets@\\\\(.*)_lod[0-9]+(\\.cgfm?)");
const AZStd::regex lodRegex("@products@\\\\(.*)_lod[0-9]+(\\.cgfm?)");
if (!AZStd::regex_match(szPath, matches, lodRegex) || matches.size() != 3)
{
// The current file is not a valid LOD file
@@ -725,7 +725,7 @@ namespace AzFramework
if (!info.m_relativePath.empty())
{
const char* devAssetRoot = fileIO->GetAlias("@devassets@");
const char* devAssetRoot = fileIO->GetAlias("@projectroot@");
if (devAssetRoot)
{
AZ::Data::AssetStreamInfo streamInfo;
@@ -133,6 +133,8 @@ namespace AzFramework
behaviorContext->Class<BehaviorEntity>("Entity")
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
->Attribute(AZ::Script::Attributes::ConstructorOverride, &Internal::BehaviorEntityScriptConstructor)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "entity")
->Constructor()
->Constructor<AZ::EntityId>()
->Constructor<AZ::Entity*>()
@@ -61,7 +61,7 @@ namespace AzFramework
AZ::IO::Path& gemAbsPath = gemInfo.m_absoluteSourcePaths.emplace_back(value);
// Resolve any file aliases first - Do not use ResolvePath() as that assumes
// any relative path is underneath the @assets@ alias
// any relative path is underneath the @products@ alias
if (auto fileIoBase = AZ::IO::FileIOBase::GetInstance(); fileIoBase != nullptr)
{
AZ::IO::FixedMaxPath replacedAliasPath;
@@ -29,6 +29,10 @@ namespace AzFramework
AZStd::vector<AZ::IO::Path> m_absoluteSourcePaths; //!< Where the gem's source path folder are located(as an absolute path)
static constexpr const char* GetGemAssetFolder() { return "Assets"; }
static constexpr const char* GetGemRegistryFolder()
{
return "Registry";
}
};
//! Returns a list of GemInfo of all the gems that are active for the for the specified game project.
@@ -12,10 +12,12 @@
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/std/containers/fixed_unordered_set.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/string/string_view.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
#include <cctype>
namespace AZ
@@ -292,7 +294,7 @@ namespace AZ
void LocalFileIO::CheckInvalidWrite([[maybe_unused]] const char* path)
{
#if defined(AZ_ENABLE_TRACING)
const char* assetAliasPath = GetAlias("@assets@");
const char* assetAliasPath = GetAlias("@products@");
if (path && assetAliasPath)
{
const AZ::IO::PathView pathView(path);
@@ -478,17 +480,15 @@ namespace AZ
return false;
}
if (IsAbsolutePath(path))
if (AZ::IO::PathView(path).HasRootPath())
{
size_t pathLen = strlen(path);
if (pathLen + 1 < resolvedPathSize)
{
azstrncpy(resolvedPath, resolvedPathSize, path, pathLen + 1);
//see if the absolute path uses @assets@ or @root@, if it does lowercase the relative part
[[maybe_unused]] bool lowercasePath = LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@assets@"))
|| LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@root@"))
|| LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@projectplatformcache@"));
//see if the absolute path matches the resolved value of @products@, if it does lowercase the relative part
LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@products@"));
ToUnixSlashes(resolvedPath, resolvedPathSize);
return true;
@@ -499,34 +499,39 @@ namespace AZ
}
}
char rootedPathBuffer[AZ_MAX_PATH_LEN] = {0};
constexpr AZStd::string_view productAssetAlias = "@products@";
// Add plus one for the path separator: <alias>/<path>
constexpr size_t MaxPathSizeWithProductAssetAlias = AZ::IO::MaxPathLength + productAssetAlias.size() + 1;
using RootedPathString = AZStd::fixed_string<MaxPathSizeWithProductAssetAlias>;
RootedPathString rootedPathBuffer;
const char* rootedPath = path;
// if the path does not begin with an alias, then it is assumed to begin with @assets@
// if the path does not begin with an alias, then it is assumed to begin with @products@
if (path[0] != '@')
{
if (GetAlias("@assets@"))
if (GetAlias("@products@"))
{
const int rootLength = 9;// strlen("@assets@/")
azstrncpy(rootedPathBuffer, AZ_MAX_PATH_LEN, "@assets@/", rootLength);
size_t pathLen = strlen(path);
size_t rootedPathBufferlength = rootLength + pathLen + 1;// +1 for null terminator
if (rootedPathBufferlength > resolvedPathSize)
if (const size_t requiredSize = productAssetAlias.size() + strlen(path) + 1;
requiredSize > rootedPathBuffer.capacity())
{
AZ_Assert(rootedPathBufferlength < resolvedPathSize, "Constructed path length is wrong:%s", rootedPathBuffer);//path constructed is wrong
size_t remainingSize = resolvedPathSize - rootLength - 1;// - 1 for null terminator
azstrncpy(rootedPathBuffer + rootLength, AZ_MAX_PATH_LEN, path, remainingSize);
rootedPathBuffer[resolvedPathSize - 1] = '\0';
AZ_Error("FileIO", false, "Prepending the %.*s alias to the input path results in a path longer than the"
" AZ::IO::MaxPathLength + the alias size of %zu. The size of the potential failed path is %zu",
AZ_STRING_ARG(productAssetAlias), rootedPathBuffer.capacity(), requiredSize)
}
else
{
azstrncpy(rootedPathBuffer + rootLength, AZ_MAX_PATH_LEN - rootLength, path, pathLen + 1);
rootedPathBuffer = RootedPathString::format("%.*s/%s", AZ_STRING_ARG(productAssetAlias), path);
}
}
else
{
ConvertToAbsolutePath(path, rootedPathBuffer, AZ_MAX_PATH_LEN);
if (ConvertToAbsolutePath(path, rootedPathBuffer.data(), rootedPathBuffer.capacity()))
{
// Recalculate the internal string length
rootedPathBuffer.resize_no_construct(AZStd::char_traits<char>::length(rootedPathBuffer.data()));
}
}
rootedPath = rootedPathBuffer;
rootedPath = rootedPathBuffer.c_str();
}
if (ResolveAliases(rootedPath, resolvedPath, resolvedPathSize))
@@ -561,11 +566,57 @@ namespace AZ
const char* LocalFileIO::GetAlias(const char* key) const
{
const auto it = m_aliases.find(key);
if (it != m_aliases.end())
if (const auto it = m_aliases.find(key); it != m_aliases.end())
{
return it->second.c_str();
}
else if (const auto deprecatedIt = m_deprecatedAliases.find(key);
deprecatedIt != m_deprecatedAliases.end())
{
AZ_Error("FileIO", false, R"(Alias "%s" is deprecated. Please use alias "%s" instead)",
key, deprecatedIt->second.c_str());
AZStd::string_view aliasValue = deprecatedIt->second;
// Contains the list of aliases resolved so far
// If max_size is hit, than an error is logged and nullptr is returned
using VisitedAliasSet = AZStd::fixed_unordered_set<AZStd::string_view, 8, 8>;
VisitedAliasSet visitedAliasSet;
while (aliasValue.starts_with("@"))
{
if (visitedAliasSet.contains(aliasValue))
{
AZ_Error("FileIO", false, "Cycle found with for alias %.*s when trying to resolve deprecated alias %s",
AZ_STRING_ARG(aliasValue), key);
return nullptr;
}
if(visitedAliasSet.size() == visitedAliasSet.max_size())
{
AZ_Error("FileIO", false, "Unable to resolve path to deprecated alias %s within %zu steps",
key, visitedAliasSet.max_size());
return nullptr;
}
// Add the current alias value to the visited set
visitedAliasSet.emplace(aliasValue);
// Check if the alias value corresponds to another alias
if (auto resolvedIter = m_aliases.find(aliasValue); resolvedIter != m_aliases.end())
{
aliasValue = resolvedIter->second;
}
else if (resolvedIter = m_deprecatedAliases.find(aliasValue);
resolvedIter != m_deprecatedAliases.end())
{
aliasValue = resolvedIter->second;
}
else
{
return nullptr;
}
}
return aliasValue.data();
}
return nullptr;
}
@@ -574,6 +625,11 @@ namespace AZ
m_aliases.erase(key);
}
void LocalFileIO::SetDeprecatedAlias(AZStd::string_view oldAlias, AZStd::string_view newAlias)
{
m_deprecatedAliases[oldAlias] = newAlias;
}
AZStd::optional<AZ::u64> LocalFileIO::ConvertToAliasBuffer(char* outBuffer, AZ::u64 outBufferLength, AZStd::string_view inBuffer) const
{
size_t longestMatch = 0;
@@ -675,7 +731,9 @@ namespace AZ
: string_view_pair{};
size_t requiredResolvedPathSize = pathView.size() - aliasKey.size() + aliasValue.size() + 1;
AZ_Assert(path != resolvedPath && resolvedPathSize >= requiredResolvedPathSize, "Resolved path is incorrect");
AZ_Assert(path != resolvedPath, "ResolveAliases does not support inplace update of the path");
AZ_Assert(resolvedPathSize >= requiredResolvedPathSize, "Resolved path size %llu not large enough. It needs to be %zu",
resolvedPathSize, requiredResolvedPathSize);
// we assert above, but we also need to properly handle the case when the resolvedPath buffer size
// is too small to copy the source into.
if (path == resolvedPath || (resolvedPathSize < requiredResolvedPathSize))
@@ -699,13 +757,9 @@ namespace AZ
resolvedPath[resolvedPathLen] = '\0';
// If the path started with one of the "asset cache" path aliases, lowercase the path
const char* assetAliasPath = GetAlias("@assets@");
const char* rootAliasPath = GetAlias("@root@");
const char* projectPlatformCacheAliasPath = GetAlias("@projectplatformcache@");
const char* projectPlatformCacheAliasPath = GetAlias("@products@");
const bool lowercasePath = (assetAliasPath != nullptr && AZ::StringFunc::StartsWith(resolvedPath, assetAliasPath)) ||
(rootAliasPath != nullptr && AZ::StringFunc::StartsWith(resolvedPath, rootAliasPath)) ||
(projectPlatformCacheAliasPath != nullptr && AZ::StringFunc::StartsWith(resolvedPath, projectPlatformCacheAliasPath));
const bool lowercasePath = projectPlatformCacheAliasPath != nullptr && AZ::StringFunc::StartsWith(resolvedPath, projectPlatformCacheAliasPath);
if (lowercasePath)
{
@@ -822,5 +876,10 @@ namespace AZ
return pathStr + "/";
}
bool LocalFileIO::ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const
{
return AZ::Utils::ConvertToAbsolutePath(path, absolutePath, maxLength);
}
} // namespace IO
} // namespace AZ
@@ -61,6 +61,8 @@ namespace AZ
void SetAlias(const char* alias, const char* path) override;
void ClearAlias(const char* alias) override;
const char* GetAlias(const char* alias) const override;
void SetDeprecatedAlias(AZStd::string_view oldAlias, AZStd::string_view newAlias) override;
AZStd::optional<AZ::u64> ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const override;
bool ConvertToAlias(AZ::IO::FixedMaxPath& convertedPath, const AZ::IO::PathView& path) const override;
using FileIOBase::ConvertToAlias;
@@ -71,7 +73,7 @@ namespace AZ
bool GetFilename(HandleType fileHandle, char* filename, AZ::u64 filenameSize) const override;
bool ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const;
private:
SystemFile* GetFilePointerFromHandle(HandleType fileHandle);
@@ -79,7 +81,6 @@ namespace AZ
AZStd::optional<AZ::u64> ConvertToAliasBuffer(char* outBuffer, AZ::u64 outBufferLength, AZStd::string_view inBuffer) const;
bool ResolveAliases(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const;
bool IsAbsolutePath(const char* path) const;
bool LowerIfBeginsWith(char* inOutBuffer, AZ::u64 bufferLen, const char* alias) const;
@@ -91,6 +92,7 @@ namespace AZ
AZStd::atomic<HandleType> m_nextHandle;
AZStd::unordered_map<HandleType, SystemFile> m_openFiles;
AZStd::unordered_map<AZStd::string, AZStd::string> m_aliases;
AZStd::unordered_map<AZStd::string, AZStd::string> m_deprecatedAliases;
void CheckInvalidWrite(const char* path);
};
@@ -49,14 +49,14 @@ namespace AZ
s_IOLog.append(m_name);
s_IOLog.append("\r\n");
}
void Append(const char* line)
{
s_IOLog.append(AZStd::string::format("%u ", m_fileOperation));
s_IOLog.append(line);
s_IOLog.append("\r\n");
}
~LogCall()
{
s_IOLog.append(AZStd::string::format("%u End ", m_fileOperation));
@@ -251,7 +251,7 @@ namespace AZ
REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Size(filePath=%s) size request failed. return Error", filePath).c_str());
return ResultCode::Error;
}
size = response.m_size;
REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Size(filePath=%s) size=%u. return Success", filePath, size).c_str());
return ResultCode::Success;
@@ -793,6 +793,12 @@ namespace AZ
REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ClearAlias(alias=%s)", alias?alias:"nullptr").c_str());
}
void NetworkFileIO::SetDeprecatedAlias([[maybe_unused]] AZStd::string_view oldAlias, [[maybe_unused]] AZStd::string_view newAlias)
{
REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::SetDeprecatedAlias(oldAlias=%.*s, newAlias=%.*s)",
AZ_STRING_ARG(oldAlias), AZ_STRING_ARG(newAlias)).c_str());
}
AZStd::optional<AZ::u64> NetworkFileIO::ConvertToAlias(char* inOutBuffer, [[maybe_unused]] AZ::u64 bufferLength) const
{
REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ConvertToAlias(inOutBuffer=%s, bufferLength=%u)", inOutBuffer?inOutBuffer:"nullptr", bufferLength).c_str());
@@ -927,7 +933,7 @@ namespace AZ
{
m_cacheLookaheadPos = filePosition - CacheStartFilePosition();
}
void RemoteFileCache::SyncCheck()
{
#ifdef REMOTEFILEIO_SYNC_CHECK
@@ -955,7 +961,7 @@ namespace AZ
AZ_TracePrintf(RemoteFileCacheChannel, "RemoteFileCache::SyncCheck(m_fileHandle=%u) tell request failed.", m_fileHandle);
REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileCache::SyncCheck(m_fileHandle=%u) tell request failed.", m_fileHandle).c_str());
}
if (responce.m_offset != m_filePosition)
{
AZ_TracePrintf(RemoteFileCacheChannel, "RemoteFileCache::SyncCheck(m_fileHandle=%u) failed!!! m_filePosition=%u tell=%u", m_fileHandle, m_filePosition, responce.m_offset);
@@ -1028,7 +1034,7 @@ namespace AZ
{
REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Close(fileHandle=%u)", fileHandle).c_str());
Result returnValue = NetworkFileIO::Close(fileHandle);
if (returnValue == ResultCode::Success)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
@@ -1160,7 +1166,7 @@ namespace AZ
REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Read(fileHandle=%u, buffer=OUT, size=%u, failOnFewerThanSizeBytesRead=%s, bytesRead=OUT)", fileHandle, size, failOnFewerThanSizeBytesRead ? "True" : "False").c_str());
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard);
RemoteFileCache& cache = GetCache(fileHandle);
AZ::u64 remainingBytesToRead = size;
AZ::u64 bytesReadFromCache = 0;
AZ::u64 remainingBytesInCache = cache.RemainingBytes();
@@ -1263,7 +1269,7 @@ namespace AZ
RemoteFileCache& cache = GetCache(fileHandle);
if (cache.m_cacheLookaheadBuffer.size() && cache.RemainingBytes())
{
// find out where we are
// find out where we are
AZ::u64 seekPosition = cache.CacheFilePosition();
// note, seeks are predicted, and do not ask for a response.
@@ -1361,6 +1367,14 @@ namespace AZ
}
}
void RemoteFileIO::SetDeprecatedAlias(AZStd::string_view oldAlias, AZStd::string_view newAlias)
{
if (m_excludedFileIO)
{
m_excludedFileIO->SetDeprecatedAlias(oldAlias, newAlias);
}
}
AZStd::optional<AZ::u64> RemoteFileIO::ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const
{
return m_excludedFileIO ? m_excludedFileIO->ConvertToAlias(inOutBuffer, bufferLength) : strlen(inOutBuffer);
@@ -102,6 +102,7 @@ namespace AZ
Result FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback) override;
void SetAlias(const char* alias, const char* path) override;
void ClearAlias(const char* alias) override;
void SetDeprecatedAlias(AZStd::string_view oldAlias, AZStd::string_view newAlias) override;
AZStd::optional<AZ::u64> ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const override;
bool ConvertToAlias(AZ::IO::FixedMaxPath& convertedPath, const AZ::IO::PathView& path) const override;
using FileIOBase::ConvertToAlias;
@@ -194,6 +195,7 @@ namespace AZ
void SetAlias(const char* alias, const char* path) override;
const char* GetAlias(const char* alias) const override;
void ClearAlias(const char* alias) override;
void SetDeprecatedAlias(AZStd::string_view oldAlias, AZStd::string_view newAlias) override;
AZStd::optional<AZ::u64> ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const override;
bool ConvertToAlias(AZ::IO::FixedMaxPath& convertedPath, const AZ::IO::PathView& path) const override;
using FileIOBase::ConvertToAlias;
@@ -35,6 +35,7 @@ namespace AzFramework
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputMappingAnd::Config::GetNameLabelOverride)
->DataElement(AZ::Edit::UIHandlers::Default, &Config::m_sourceInputChannelNames, "Source Input Channel Names",
"The source input channel names that will be mapped to the output input channel name.")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
@@ -35,6 +35,7 @@ namespace AzFramework
->Attribute(AZ::Edit::Attributes::NameLabelOverride, &InputMappingOr::Config::GetNameLabelOverride)
->DataElement(AZ::Edit::UIHandlers::Default, &Config::m_sourceInputChannelNames, "Source Input Channel Names",
"The source input channel names that will be mapped to the output input channel name.")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
@@ -20,6 +20,7 @@
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/SettingsRegistry.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
@@ -190,6 +191,25 @@ namespace AzFramework
////////////////////////////////////////////////////////////////////////////////////////////////
void InputSystemComponent::Activate()
{
const auto* settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
AZ::u64 value = 0;
if (settingsRegistry->Get(value, "/O3DE/InputSystem/MouseMovementSampleRateHertz"))
{
m_mouseMovementSampleRateHertz = aznumeric_caster(value);
}
if (settingsRegistry->Get(value, "/O3DE/InputSystem/GamepadsEnabled"))
{
m_gamepadsEnabled = aznumeric_caster(value);
}
settingsRegistry->Get(m_keyboardEnabled, "/O3DE/InputSystem/KeyboardEnabled");
settingsRegistry->Get(m_motionEnabled, "/O3DE/InputSystem/MotionEnabled");
settingsRegistry->Get(m_mouseEnabled, "/O3DE/InputSystem/MouseEnabled");
settingsRegistry->Get(m_touchEnabled, "/O3DE/InputSystem/TouchEnabled");
settingsRegistry->Get(m_virtualKeyboardEnabled, "/O3DE/InputSystem/VirtualKeyboardEnabled");
}
// Create all enabled input devices
CreateEnabledInputDevices();
@@ -0,0 +1,63 @@
/*
* 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/RTTI/ReflectContext.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Matchmaking/MatchmakingRequests.h>
namespace AzFramework
{
//! IMatchmakingRequests
//! Pure virtual session interface class to abstract the details of session handling from application code.
class IMatchmakingRequests
{
public:
AZ_RTTI(IMatchmakingRequests, "{BC0B74DA-A448-4F40-9B50-9D73142829D5}");
IMatchmakingRequests() = default;
virtual ~IMatchmakingRequests() = default;
// Registers a player's acceptance or rejection of a proposed matchmaking.
// @param acceptMatchRequest The request of AcceptMatch operation
virtual void AcceptMatch(const AcceptMatchRequest& acceptMatchRequest) = 0;
// Create a game match for a group of players.
// @param startMatchmakingRequest The request of StartMatchmaking operation
// @return A unique identifier for a matchmaking ticket
virtual AZStd::string StartMatchmaking(const StartMatchmakingRequest& startMatchmakingRequest) = 0;
// Cancels a matchmaking ticket that is currently being processed.
// @param stopMatchmakingRequest The request of StopMatchmaking operation
virtual void StopMatchmaking(const StopMatchmakingRequest& stopMatchmakingRequest) = 0;
};
//! IMatchmakingAsyncRequests
//! Async version of IMatchmakingRequests
class IMatchmakingAsyncRequests
{
public:
AZ_RTTI(ISessionAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}");
IMatchmakingAsyncRequests() = default;
virtual ~IMatchmakingAsyncRequests() = default;
// AcceptMatch Async
// @param acceptMatchRequest The request of AcceptMatch operation
virtual void AcceptMatchAsync(const AcceptMatchRequest& acceptMatchRequest) = 0;
// StartMatchmaking Async
// @param startMatchmakingRequest The request of StartMatchmaking operation
virtual void StartMatchmakingAsync(const StartMatchmakingRequest& startMatchmakingRequest) = 0;
// StopMatchmaking Async
// @param stopMatchmakingRequest The request of StopMatchmaking operation
virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0;
};
} // namespace AzFramework
@@ -0,0 +1,62 @@
/*
* 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/string/string.h>
namespace AzFramework
{
//! MatchmakingAsyncRequestNotifications
//! The notifications correspond to matchmaking async requests
class MatchmakingAsyncRequestNotifications
: public AZ::EBusTraits
{
public:
// Safeguard handler for multi-threaded use case
using MutexType = AZStd::recursive_mutex;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
virtual void OnAcceptMatchAsyncComplete() = 0;
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
virtual void OnStopMatchmakingAsyncComplete() = 0;
};
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
//! MatchmakingNotifications
//! The matchmaking notifications to listen for performing required operations
class MatchAcceptanceNotifications
: public AZ::EBusTraits
{
public:
// Safeguard handler for multi-threaded use case
using MutexType = AZStd::recursive_mutex;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnMatchAcceptance is fired when DescribeMatchmaking ticket status is REQUIRES_ACCEPTANCE
virtual void OnMatchAcceptance() = 0;
};
using MatchAcceptanceNotificationBus = AZ::EBus<MatchAcceptanceNotifications>;
} // namespace AzFramework
@@ -0,0 +1,78 @@
/*
* 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/RTTI/ReflectContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Matchmaking/MatchmakingRequests.h>
namespace AzFramework
{
void AcceptMatchRequest::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AcceptMatchRequest>()
->Version(0)
->Field("acceptMatch", &AcceptMatchRequest::m_acceptMatch)
->Field("playerIds", &AcceptMatchRequest::m_playerIds)
->Field("ticketId", &AcceptMatchRequest::m_ticketId);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<AcceptMatchRequest>("AcceptMatchRequest", "The container for AcceptMatch request parameters")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &AcceptMatchRequest::m_acceptMatch, "AcceptMatch",
"Player response to accept or reject match")
->DataElement(AZ::Edit::UIHandlers::Default, &AcceptMatchRequest::m_playerIds, "PlayerIds",
"A list of unique identifiers for players delivering the response")
->DataElement(AZ::Edit::UIHandlers::Default, &AcceptMatchRequest::m_ticketId, "TicketId",
"A unique identifier for a matchmaking ticket");
}
}
}
void StartMatchmakingRequest::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<StartMatchmakingRequest>()
->Version(0)
->Field("ticketId", &StartMatchmakingRequest::m_ticketId);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<StartMatchmakingRequest>("StartMatchmakingRequest", "The container for StartMatchmaking request parameters")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &StartMatchmakingRequest::m_ticketId, "TicketId",
"A unique identifier for a matchmaking ticket");
}
}
}
void StopMatchmakingRequest::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<StopMatchmakingRequest>()
->Version(0)
->Field("ticketId", &StopMatchmakingRequest::m_ticketId);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<StopMatchmakingRequest>("StopMatchmakingRequest", "The container for StopMatchmaking request parameters")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &StopMatchmakingRequest::m_ticketId, "TicketId",
"A unique identifier for a matchmaking ticket");
}
}
}
} // namespace AzFramework
@@ -0,0 +1,67 @@
/*
* 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/RTTI/RTTI.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
class ReflectContext;
}
namespace AzFramework
{
//! AcceptMatchRequest
//! The container for AcceptMatch request parameters.
struct AcceptMatchRequest
{
AZ_RTTI(AcceptMatchRequest, "{AD289D76-CEE2-424F-847E-E62AA83B7D79}");
static void Reflect(AZ::ReflectContext* context);
AcceptMatchRequest() = default;
virtual ~AcceptMatchRequest() = default;
// Player response to accept or reject match
bool m_acceptMatch;
// A list of unique identifiers for players delivering the response
AZStd::vector<AZStd::string> m_playerIds;
// A unique identifier for a matchmaking ticket
AZStd::string m_ticketId;
};
//! StartMatchmakingRequest
//! The container for StartMatchmaking request parameters.
struct StartMatchmakingRequest
{
AZ_RTTI(StartMatchmakingRequest, "{70B47776-E8E7-4993-BEC3-5CAEC3D48E47}");
static void Reflect(AZ::ReflectContext* context);
StartMatchmakingRequest() = default;
virtual ~StartMatchmakingRequest() = default;
// A unique identifier for a matchmaking ticket
AZStd::string m_ticketId;
};
//! StopMatchmakingRequest
//! The container for StopMatchmaking request parameters.
struct StopMatchmakingRequest
{
AZ_RTTI(StopMatchmakingRequest, "{6132E293-65EF-4DC2-A8A0-00269697229D}");
static void Reflect(AZ::ReflectContext* context);
StopMatchmakingRequest() = default;
virtual ~StopMatchmakingRequest() = default;
// A unique identifier for a matchmaking ticket
AZStd::string m_ticketId;
};
} // namespace AzFramework
@@ -10,98 +10,11 @@
#include <AzCore/EBus/EBus.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzFramework/Session/SessionRequests.h>
namespace AzFramework
{
struct SessionConfig;
//! CreateSessionRequest
//! The container for CreateSession request parameters.
struct CreateSessionRequest
{
AZ_RTTI(CreateSessionRequest, "{E39C2A45-89C9-4CFB-B337-9734DC798930}");
static void Reflect(AZ::ReflectContext* context);
CreateSessionRequest() = default;
virtual ~CreateSessionRequest() = default;
// A unique identifier for a player or entity creating the session.
AZStd::string m_creatorId;
// A collection of custom properties for a session.
AZStd::unordered_map<AZStd::string, AZStd::string> m_sessionProperties;
// A descriptive label that is associated with a session.
AZStd::string m_sessionName;
// The maximum number of players that can be connected simultaneously to the session.
uint64_t m_maxPlayer = 0;
};
//! SearchSessionsRequest
//! The container for SearchSessions request parameters.
struct SearchSessionsRequest
{
AZ_RTTI(SearchSessionsRequest, "{B49207A8-8549-4ADB-B7D9-D7A4932F9B4B}");
static void Reflect(AZ::ReflectContext* context);
SearchSessionsRequest() = default;
virtual ~SearchSessionsRequest() = default;
// String containing the search criteria for the session search. If no filter expression is included, the request returns results
// for all active sessions.
AZStd::string m_filterExpression;
// Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order.
AZStd::string m_sortExpression;
// The maximum number of results to return.
uint8_t m_maxResult = 0;
// A token that indicates the start of the next sequential page of results.
AZStd::string m_nextToken;
};
//! SearchSessionsResponse
//! The container for SearchSession request results.
struct SearchSessionsResponse
{
AZ_RTTI(SearchSessionsResponse, "{F93DE7DC-D381-4E08-8A3B-0B08F7C38714}");
static void Reflect(AZ::ReflectContext* context);
SearchSessionsResponse() = default;
virtual ~SearchSessionsResponse() = default;
// A collection of sessions that match the search criteria and sorted in specific order.
AZStd::vector<SessionConfig> m_sessionConfigs;
// A token that indicates the start of the next sequential page of results.
AZStd::string m_nextToken;
};
//! JoinSessionRequest
//! The container for JoinSession request parameters.
struct JoinSessionRequest
{
AZ_RTTI(JoinSessionRequest, "{519769E8-3CDE-4385-A0D7-24DBB3685657}");
static void Reflect(AZ::ReflectContext* context);
JoinSessionRequest() = default;
virtual ~JoinSessionRequest() = default;
// A unique identifier for the session.
AZStd::string m_sessionId;
// A unique identifier for a player. Player IDs are developer-defined.
AZStd::string m_playerId;
// Developer-defined information related to a player.
AZStd::string m_playerData;
};
//! ISessionRequests
//! Pure virtual session interface class to abstract the details of session handling from application code.
class ISessionRequests
@@ -22,6 +22,7 @@ namespace AzFramework
->Field("terminationTime", &SessionConfig::m_terminationTime)
->Field("creatorId", &SessionConfig::m_creatorId)
->Field("sessionProperties", &SessionConfig::m_sessionProperties)
->Field("matchmakingData", &SessionConfig::m_matchmakingData)
->Field("sessionId", &SessionConfig::m_sessionId)
->Field("sessionName", &SessionConfig::m_sessionName)
->Field("dnsName", &SessionConfig::m_dnsName)
@@ -46,6 +47,8 @@ namespace AzFramework
"CreatorId", "A unique identifier for a player or entity creating the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionProperties,
"SessionProperties", "A collection of custom properties for a session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_matchmakingData,
"MatchmakingData", "The matchmaking process information that was used to create the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionId,
"SessionId", "A unique identifier for the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionName,
@@ -35,6 +35,9 @@ namespace AzFramework
// A collection of custom properties for a session.
AZStd::unordered_map<AZStd::string, AZStd::string> m_sessionProperties;
// The matchmaking process information that was used to create the session.
AZStd::string m_matchmakingData;
// A unique identifier for the session.
AZStd::string m_sessionId;
@@ -41,6 +41,11 @@ namespace AzFramework
// OnDestroySessionBegin is fired at the beginning of session termination
// @return The result of all OnDestroySessionBegin notifications
virtual bool OnDestroySessionBegin() = 0;
// OnUpdateSessionBegin is fired at the beginning of session update
// @param sessionConfig The properties to describe a session
// @param updateReason The reason for session update
virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0;
};
using SessionNotificationBus = AZ::EBus<SessionNotifications>;
} // namespace AzFramework
@@ -6,9 +6,10 @@
*
*/
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Session/ISessionRequests.h>
#include <AzFramework/Session/SessionRequests.h>
#include <AzFramework/Session/SessionConfig.h>
namespace AzFramework
@@ -0,0 +1,107 @@
/*
* 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/RTTI/RTTI.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
class ReflectContext;
}
namespace AzFramework
{
struct SessionConfig;
//! CreateSessionRequest
//! The container for CreateSession request parameters.
struct CreateSessionRequest
{
AZ_RTTI(CreateSessionRequest, "{E39C2A45-89C9-4CFB-B337-9734DC798930}");
static void Reflect(AZ::ReflectContext* context);
CreateSessionRequest() = default;
virtual ~CreateSessionRequest() = default;
// A unique identifier for a player or entity creating the session.
AZStd::string m_creatorId;
// A collection of custom properties for a session.
AZStd::unordered_map<AZStd::string, AZStd::string> m_sessionProperties;
// A descriptive label that is associated with a session.
AZStd::string m_sessionName;
// The maximum number of players that can be connected simultaneously to the session.
uint64_t m_maxPlayer = 0;
};
//! SearchSessionsRequest
//! The container for SearchSessions request parameters.
struct SearchSessionsRequest
{
AZ_RTTI(SearchSessionsRequest, "{B49207A8-8549-4ADB-B7D9-D7A4932F9B4B}");
static void Reflect(AZ::ReflectContext* context);
SearchSessionsRequest() = default;
virtual ~SearchSessionsRequest() = default;
// String containing the search criteria for the session search. If no filter expression is included, the request returns results
// for all active sessions.
AZStd::string m_filterExpression;
// Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order.
AZStd::string m_sortExpression;
// The maximum number of results to return.
uint8_t m_maxResult = 0;
// A token that indicates the start of the next sequential page of results.
AZStd::string m_nextToken;
};
//! SearchSessionsResponse
//! The container for SearchSession request results.
struct SearchSessionsResponse
{
AZ_RTTI(SearchSessionsResponse, "{F93DE7DC-D381-4E08-8A3B-0B08F7C38714}");
static void Reflect(AZ::ReflectContext* context);
SearchSessionsResponse() = default;
virtual ~SearchSessionsResponse() = default;
// A collection of sessions that match the search criteria and sorted in specific order.
AZStd::vector<SessionConfig> m_sessionConfigs;
// A token that indicates the start of the next sequential page of results.
AZStd::string m_nextToken;
};
//! JoinSessionRequest
//! The container for JoinSession request parameters.
struct JoinSessionRequest
{
AZ_RTTI(JoinSessionRequest, "{519769E8-3CDE-4385-A0D7-24DBB3685657}");
static void Reflect(AZ::ReflectContext* context);
JoinSessionRequest() = default;
virtual ~JoinSessionRequest() = default;
// A unique identifier for the session.
AZStd::string m_sessionId;
// A unique identifier for a player. Player IDs are developer-defined.
AZStd::string m_playerId;
// Developer-defined information related to a player.
AZStd::string m_playerData;
};
} // namespace AzFramework
@@ -61,6 +61,10 @@ namespace AzFramework
//! be deleted and the spawnable asset to be released. This call is automatically done when
//! AssignRootSpawnable is called while a root spawnable is assigned.
virtual void ReleaseRootSpawnable() = 0;
//! Force processing all SpawnableEntitiesManager requests immediately
//! This is useful when loading a different level while SpawnableEntitiesManager still has
//! pending requests
virtual void ProcessSpawnableQueue() = 0;
};
using RootSpawnableInterface = AZ::Interface<RootSpawnableDefinition>;
@@ -45,8 +45,7 @@ namespace AzFramework
void SpawnableSystemComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
{
m_entitiesManager.ProcessQueue(
SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular);
ProcessSpawnableQueue();
RootSpawnableNotificationBus::ExecuteQueuedEvents();
}
@@ -121,6 +120,12 @@ namespace AzFramework
m_rootSpawnableId = AZ::Data::AssetId();
}
void SpawnableSystemComponent::ProcessSpawnableQueue()
{
m_entitiesManager.ProcessQueue(
SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular);
}
void SpawnableSystemComponent::OnRootSpawnableAssigned([[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable,
[[maybe_unused]] uint32_t generation)
{
@@ -161,6 +166,8 @@ namespace AzFramework
void SpawnableSystemComponent::Deactivate()
{
ProcessSpawnableQueue();
m_registryChangeHandler.Disconnect();
AZ::TickBus::Handler::BusDisconnect();
@@ -75,6 +75,7 @@ namespace AzFramework
uint64_t AssignRootSpawnable(AZ::Data::Asset<Spawnable> rootSpawnable) override;
void ReleaseRootSpawnable() override;
void ProcessSpawnableQueue() override;
//
// RootSpawnbleNotificationBus
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Aabb.h>
@@ -17,16 +18,38 @@ namespace AzFramework
{
namespace SurfaceData
{
namespace Constants
{
static const char* s_unassignedTagName = "(unassigned)";
}
struct SurfaceTagWeight
{
AZ_TYPE_INFO(SurfaceTagWeight, "{EA14018E-E853-4BF5-8E13-D83BB99A54CC}");
AZ::Crc32 m_surfaceType;
float m_weight; //! A Value in the range [0.0f .. 1.0f]
AZ::Crc32 m_surfaceType = AZ::Crc32(Constants::s_unassignedTagName);
float m_weight = 0.0f; //! A Value in the range [0.0f .. 1.0f]
//! Don't call this directly. TerrainDataRequests::Reflect is doing it already.
static void Reflect(AZ::ReflectContext* context);
};
struct SurfaceTagWeightComparator
{
bool operator()(const SurfaceTagWeight& tagWeight1, const SurfaceTagWeight& tagWeight2) const
{
if (!AZ::IsClose(tagWeight1.m_weight, tagWeight2.m_weight))
{
return tagWeight1.m_weight > tagWeight2.m_weight;
}
else
{
return tagWeight1.m_surfaceType > tagWeight2.m_surfaceType;
}
}
};
using OrderedSurfaceTagWeightSet = AZStd::set<SurfaceTagWeight, SurfaceTagWeightComparator>;
} //namespace SurfaceData
namespace Terrain
@@ -75,8 +98,28 @@ namespace AzFramework
//! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain HOLE then *terrainExistsPtr will be set to false,
//! otherwise *terrainExistsPtr will be set to true.
virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeight(AZ::Vector3 position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromVector2(const AZ::Vector2& inPosition, Sampler sampleFilter = Sampler::DEFAULT, bool* terrainExistsPtr = nullptr) const = 0;
virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromFloats(float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
//! Given an XY coordinate, return the set of surface types and weights. The Vector3 input position version is defined to ignore
//! the input Z value.
virtual void GetSurfaceWeights(
const AZ::Vector3& inPosition,
SurfaceData::OrderedSurfaceTagWeightSet& outSurfaceWeights,
Sampler sampleFilter = Sampler::DEFAULT,
bool* terrainExistsPtr = nullptr) const = 0;
virtual void GetSurfaceWeightsFromVector2(
const AZ::Vector2& inPosition,
SurfaceData::OrderedSurfaceTagWeightSet& outSurfaceWeights,
Sampler sampleFilter = Sampler::DEFAULT,
bool* terrainExistsPtr = nullptr) const = 0;
virtual void GetSurfaceWeightsFromFloats(
float x,
float y,
SurfaceData::OrderedSurfaceTagWeightSet& outSurfaceWeights,
Sampler sampleFilter = Sampler::DEFAULT,
bool* terrainExistsPtr = nullptr) const = 0;
//! Convenience function for low level systems that can't do a reverse lookup from Crc to string. Everyone else should use GetMaxSurfaceWeight or GetMaxSurfaceWeightFromFloats.
//! Not available in the behavior context.
//! Returns nullptr if the position is inside a hole or outside of the terrain boundaries.
@@ -533,8 +533,8 @@ namespace AzFramework
m_translateCameraInputChannelIds = translateCameraInputChannelIds;
}
PivotCameraInput::PivotCameraInput(const InputChannelId& pivotChannelId)
: m_pivotChannelId(pivotChannelId)
OrbitCameraInput::OrbitCameraInput(const InputChannelId& orbitChannelId)
: m_orbitChannelId(orbitChannelId)
{
m_pivotFn = []([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
{
@@ -542,11 +542,11 @@ namespace AzFramework
};
}
bool PivotCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta)
bool OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta)
{
if (const auto* input = AZStd::get_if<DiscreteInputEvent>(&event))
{
if (input->m_channelId == m_pivotChannelId)
if (input->m_channelId == m_orbitChannelId)
{
if (input->m_state == InputChannel::State::Began)
{
@@ -561,13 +561,13 @@ namespace AzFramework
if (Active())
{
return m_pivotCameras.HandleEvents(event, cursorDelta, scrollDelta);
return m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta);
}
return !Idle();
}
Camera PivotCameraInput::StepCamera(
Camera OrbitCameraInput::StepCamera(
const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime)
{
Camera nextCamera = targetCamera;
@@ -581,12 +581,12 @@ namespace AzFramework
if (Active())
{
MovePivotDetached(nextCamera, m_pivotFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY()));
nextCamera = m_pivotCameras.StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime);
nextCamera = m_orbitCameras.StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime);
}
if (Ending())
{
m_pivotCameras.Reset();
m_orbitCameras.Reset();
nextCamera.m_pivot = nextCamera.Translation();
nextCamera.m_offset = AZ::Vector3::CreateZero();
@@ -595,12 +595,12 @@ namespace AzFramework
return nextCamera;
}
void PivotCameraInput::SetPivotInputChannelId(const InputChannelId& pivotChanneId)
void OrbitCameraInput::SetOrbitInputChannelId(const InputChannelId& orbitChanneId)
{
m_pivotChannelId = pivotChanneId;
m_orbitChannelId = orbitChanneId;
}
PivotDollyScrollCameraInput::PivotDollyScrollCameraInput()
OrbitDollyScrollCameraInput::OrbitDollyScrollCameraInput()
{
m_scrollSpeedFn = []() constexpr
{
@@ -608,7 +608,7 @@ namespace AzFramework
};
}
bool PivotDollyScrollCameraInput::HandleEvents(
bool OrbitDollyScrollCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta)
{
if (const auto* scroll = AZStd::get_if<ScrollEvent>(&event))
@@ -619,36 +619,45 @@ namespace AzFramework
return !Idle();
}
static Camera PivotDolly(const Camera& targetCamera, const float delta)
static Camera OrbitDolly(const Camera& targetCamera, const float delta)
{
Camera nextCamera = targetCamera;
const auto pivotDirection = targetCamera.m_offset.GetNormalized();
nextCamera.m_offset -= pivotDirection * delta;
const auto pivotDot = targetCamera.m_offset.Dot(nextCamera.m_offset);
const auto distance = nextCamera.m_offset.GetLength() * AZ::GetSign(pivotDot);
const auto minDistance = 0.01f;
if (distance < minDistance || pivotDot < 0.0f)
// handle case where pivot and offset may be the same to begin with
// choose negative y-axis for offset to default to moving the camera backwards from the pivot (standard centered pivot behavior)
const auto pivotDirection = [&targetCamera]
{
nextCamera.m_offset = pivotDirection * minDistance;
if (const auto offsetLength = targetCamera.m_offset.GetLength(); AZ::IsCloseMag(offsetLength, 0.0f))
{
return -AZ::Vector3::CreateAxisY();
}
else
{
return targetCamera.m_offset / offsetLength;
}
}();
nextCamera.m_offset -= pivotDirection * delta;
if (pivotDirection.Dot(nextCamera.m_offset) < 0.0f)
{
nextCamera.m_offset = pivotDirection * 0.001f;
}
return nextCamera;
}
Camera PivotDollyScrollCameraInput::StepCamera(
Camera OrbitDollyScrollCameraInput::StepCamera(
const Camera& targetCamera,
[[maybe_unused]] const ScreenVector& cursorDelta,
const float scrollDelta,
[[maybe_unused]] const float deltaTime)
{
const auto nextCamera = PivotDolly(targetCamera, aznumeric_cast<float>(scrollDelta) * m_scrollSpeedFn());
const auto nextCamera = OrbitDolly(targetCamera, aznumeric_cast<float>(scrollDelta) * m_scrollSpeedFn());
EndActivation();
return nextCamera;
}
PivotDollyMotionCameraInput::PivotDollyMotionCameraInput(const InputChannelId& dollyChannelId)
OrbitDollyMotionCameraInput::OrbitDollyMotionCameraInput(const InputChannelId& dollyChannelId)
: m_dollyChannelId(dollyChannelId)
{
m_motionSpeedFn = []() constexpr
@@ -657,28 +666,28 @@ namespace AzFramework
};
}
bool PivotDollyMotionCameraInput::HandleEvents(
bool OrbitDollyMotionCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta)
{
HandleActivationEvents(event, m_dollyChannelId, cursorDelta, m_clickDetector, *this);
return CameraInputUpdatingAfterMotion(*this);
}
Camera PivotDollyMotionCameraInput::StepCamera(
Camera OrbitDollyMotionCameraInput::StepCamera(
const Camera& targetCamera,
const ScreenVector& cursorDelta,
[[maybe_unused]] const float scrollDelta,
[[maybe_unused]] const float deltaTime)
{
return PivotDolly(targetCamera, aznumeric_cast<float>(cursorDelta.m_y) * m_motionSpeedFn());
return OrbitDolly(targetCamera, aznumeric_cast<float>(cursorDelta.m_y) * m_motionSpeedFn());
}
void PivotDollyMotionCameraInput::SetDollyInputChannelId(const InputChannelId& dollyChannelId)
void OrbitDollyMotionCameraInput::SetDollyInputChannelId(const InputChannelId& dollyChannelId)
{
m_dollyChannelId = dollyChannelId;
}
ScrollTranslationCameraInput::ScrollTranslationCameraInput()
LookScrollTranslationCameraInput::LookScrollTranslationCameraInput()
{
m_scrollSpeedFn = []() constexpr
{
@@ -686,7 +695,7 @@ namespace AzFramework
};
}
bool ScrollTranslationCameraInput::HandleEvents(
bool LookScrollTranslationCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta)
{
if (const auto* scroll = AZStd::get_if<ScrollEvent>(&event))
@@ -697,7 +706,7 @@ namespace AzFramework
return !Idle();
}
Camera ScrollTranslationCameraInput::StepCamera(
Camera LookScrollTranslationCameraInput::StepCamera(
const Camera& targetCamera,
[[maybe_unused]] const ScreenVector& cursorDelta,
const float scrollDelta,
@@ -771,6 +780,73 @@ namespace AzFramework
return camera;
}
FocusCameraInput::FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn)
: m_focusChannelId(focusChannelId)
, m_offsetFn(offsetFn)
{
}
bool FocusCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto* input = AZStd::get_if<DiscreteInputEvent>(&event))
{
if (input->m_channelId == m_focusChannelId && input->m_state == InputChannel::State::Began)
{
BeginActivation();
}
}
return !Idle();
}
Camera FocusCameraInput::StepCamera(
const Camera& targetCamera,
[[maybe_unused]] const ScreenVector& cursorDelta,
[[maybe_unused]] float scrollDelta,
[[maybe_unused]] float deltaTime)
{
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()));
const auto angles =
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), m_pivotFn())));
m_nextCamera.m_pitch = angles.GetX();
m_nextCamera.m_yaw = angles.GetZ();
m_nextCamera.m_pivot = targetCamera.m_pivot;
}
// end the behavior when the camera is in alignment
if (AZ::IsCloseMag(targetCamera.m_pitch, m_nextCamera.m_pitch) && AZ::IsCloseMag(targetCamera.m_yaw, m_nextCamera.m_yaw))
{
EndActivation();
}
return m_nextCamera;
}
void FocusCameraInput::SetPivotFn(PivotFn pivotFn)
{
m_pivotFn = AZStd::move(pivotFn);
}
void FocusCameraInput::SetFocusInputChannelId(const InputChannelId& focusChannelId)
{
m_focusChannelId = focusChannelId;
}
bool CustomCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta)
{
return m_handleEventsFn(*this, event, cursorDelta, scrollDelta);
}
Camera CustomCameraInput::StepCamera(
const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime)
{
return m_stepCameraFn(*this, targetCamera, cursorDelta, scrollDelta, deltaTime);
}
InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize)
{
const auto& inputChannelId = inputChannel.GetInputChannelId();
@@ -30,8 +30,11 @@ namespace AzFramework
AZ::Vector3 EulerAngles(const AZ::Matrix3x3& orientation);
//! A simple camera representation using spherical coordinates as input (pitch, yaw, pivot and offset).
//! The cameras transform and view can be obtained through accessor functions that use the internal
//! The camera's transform and view can be obtained through accessor functions that use the internal
//! spherical coordinates to calculate the position and orientation.
//! @note Modifying m_pivot directly and leaving m_offset as zero will produce a free look camera effect, giving
//! m_offset a value (e.g. in negative Y only) will produce an orbit camera effect, modifying X and Z of m_offset
//! will further alter the camera translation in relation to m_pivot so it appears off center.
struct Camera
{
AZ::Vector3 m_pivot = AZ::Vector3::CreateZero(); //!< Pivot point to rotate about (modified in world space).
@@ -291,7 +294,7 @@ namespace AzFramework
Cameras m_cameras; //!< Represents a collection of camera inputs that together provide a camera controller.
private:
ScreenVector m_motionDelta; //!< The delta used for look/pivot/pan (rotation + translation) - two dimensional.
ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional.
CursorState m_cursorState; //!< The current and previous position of the cursor (used to calculate movement delta).
float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional.
bool m_handlingEvents = false; //!< Is the camera system currently handling events (events are consumed and not propagated).
@@ -316,7 +319,7 @@ namespace AzFramework
return AZStd::fmod(yaw + AZ::Constants::TwoPi, AZ::Constants::TwoPi);
}
//! A camera input to handle motion deltas that can rotate or pivot the camera.
//! A camera input to handle motion deltas that can change the orientation of the camera (update pitch and yaw).
class RotateCameraInput : public CameraInput
{
public:
@@ -348,15 +351,16 @@ namespace AzFramework
//! PanAxes build function that will return a pair of pan axes depending on the camera orientation.
using PanAxesFn = AZStd::function<PanAxes(const Camera& camera)>;
//! PanAxes to use while in 'look' camera behavior (free look).
//! PanAxes to use while in 'look' or 'orbit' camera behavior.
inline PanAxes LookPan(const Camera& camera)
{
const AZ::Matrix3x3 orientation = camera.Rotation();
return { orientation.GetBasisX(), orientation.GetBasisZ() };
}
//! PanAxes to use while in 'pivot' camera behavior.
inline PanAxes PivotPan(const Camera& camera)
//! Optional PanAxes to use while in 'orbit' camera behavior.
//! @note This will move the camera in the local X/Y plane instead of usual X/Z plane.
inline PanAxes OrbitPan(const Camera& camera)
{
const AZ::Matrix3x3 orientation = camera.Rotation();
@@ -370,14 +374,23 @@ namespace AzFramework
return { basisX, basisY };
}
//! TranslationDeltaFn is used by PanCameraInput and TranslateCameraInput
//! @note Choose the appropriate function if the behavior should be operating as a free look camera (TranslatePivotLook)
//! or an orbit camera (TranslateOffsetOrbit).
using TranslationDeltaFn = AZStd::function<void(Camera& camera, const AZ::Vector3& delta)>;
inline void TranslatePivot(Camera& camera, const AZ::Vector3& delta)
//! Update the pivot camera position.
//! @note delta will need to have been transformed to world space, e.g. To move the camera right, (1, 0, 0) must
//! first be transformed by the orientation of the camera before being applied to m_pivot.
inline void TranslatePivotLook(Camera& camera, const AZ::Vector3& delta)
{
camera.m_pivot += delta;
}
inline void TranslateOffset(Camera& camera, const AZ::Vector3& delta)
//! Update the offset camera position.
//! @note delta still needs to be transformed to world space (as with TranslatePivotLook) but internally this is undone
//! to be performed in local space when being applied to m_offset.
inline void TranslateOffsetOrbit(Camera& camera, const AZ::Vector3& delta)
{
camera.m_offset += camera.View().TransformVector(delta);
}
@@ -409,7 +422,7 @@ namespace AzFramework
//! Axes to use while translating the camera.
using TranslationAxesFn = AZStd::function<AZ::Matrix3x3(const Camera& camera)>;
//! TranslationAxes to use while in 'look' camera behavior (free look).
//! TranslationAxes to use while in 'look' or 'orbit' camera behavior.
inline AZ::Matrix3x3 LookTranslation(const Camera& camera)
{
const AZ::Matrix3x3 orientation = camera.Rotation();
@@ -421,8 +434,8 @@ namespace AzFramework
return AZ::Matrix3x3::CreateFromColumns(basisX, basisY, basisZ);
}
//! TranslationAxes to use while in 'pivot' camera behavior.
inline AZ::Matrix3x3 PivotTranslation(const Camera& camera)
//! Optional TranslationAxes to use while in 'orbit' camera behavior.
inline AZ::Matrix3x3 OrbitTranslation(const Camera& camera)
{
const AZ::Matrix3x3 orientation = camera.Rotation();
@@ -535,11 +548,11 @@ namespace AzFramework
bool m_boost = false; //!< Is the translation speed currently being multiplied/scaled upwards.
};
//! A camera input to handle discrete scroll events that can modify the camera pivot distance.
class PivotDollyScrollCameraInput : public CameraInput
//! A camera input to handle discrete scroll events that can modify the camera offset.
class OrbitDollyScrollCameraInput : public CameraInput
{
public:
PivotDollyScrollCameraInput();
OrbitDollyScrollCameraInput();
// CameraInput overrides ...
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
@@ -548,11 +561,11 @@ namespace AzFramework
AZStd::function<float()> m_scrollSpeedFn;
};
//! A camera input to handle motion deltas that can modify the camera pivot distance.
class PivotDollyMotionCameraInput : public CameraInput
//! A camera input to handle motion deltas that can modify the camera offset.
class OrbitDollyMotionCameraInput : public CameraInput
{
public:
explicit PivotDollyMotionCameraInput(const InputChannelId& dollyChannelId);
explicit OrbitDollyMotionCameraInput(const InputChannelId& dollyChannelId);
// CameraInput overrides ...
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
@@ -569,10 +582,10 @@ namespace AzFramework
};
//! A camera input to handle discrete scroll events that can scroll (translate) the camera along its forward axis.
class ScrollTranslationCameraInput : public CameraInput
class LookScrollTranslationCameraInput : public CameraInput
{
public:
ScrollTranslationCameraInput();
LookScrollTranslationCameraInput();
// CameraInput overrides ...
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
@@ -583,40 +596,96 @@ namespace AzFramework
//! A camera input that doubles as its own set of camera inputs.
//! It is 'exclusive', so does not overlap with other sibling camera inputs - it runs its own set of camera inputs as 'children'.
class PivotCameraInput : public CameraInput
class OrbitCameraInput : public CameraInput
{
public:
using PivotFn = AZStd::function<AZ::Vector3(const AZ::Vector3& position, const AZ::Vector3& direction)>;
explicit PivotCameraInput(const InputChannelId& pivotChannelId);
explicit OrbitCameraInput(const InputChannelId& orbitChannelId);
// CameraInput overrides ...
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
bool Exclusive() const override;
void SetPivotInputChannelId(const InputChannelId& pivotChanneId);
void SetOrbitInputChannelId(const InputChannelId& orbitChanneId);
Cameras m_pivotCameras; //!< The camera inputs to run when this camera input is active (only these will run as it is exclusive).
Cameras m_orbitCameras; //!< The camera inputs to run when this camera input is active (only these will run as it is exclusive).
//! Override the default behavior for how a pivot point is calculated.
void SetPivotFn(PivotFn pivotFn);
private:
InputChannelId m_pivotChannelId; //!< Input channel to begin the pivot camera input.
PivotFn m_pivotFn; //!< The pivot position to use for this pivot camera (how is the pivot point calculated/retrieved).
InputChannelId m_orbitChannelId; //!< Input channel to begin the orbit camera input.
PivotFn m_pivotFn; //!< The pivot position to use for this orbit camera (how is the pivot point calculated/retrieved).
};
inline void PivotCameraInput::SetPivotFn(PivotFn pivotFn)
inline void OrbitCameraInput::SetPivotFn(PivotFn pivotFn)
{
m_pivotFn = AZStd::move(pivotFn);
}
inline bool PivotCameraInput::Exclusive() const
inline bool OrbitCameraInput::Exclusive() const
{
return true;
}
//! Callback to use for FocusCameraInput when a free look camera is being used.
//! @note This is when offset is zero.
inline AZ::Vector3 FocusLook(float)
{
return AZ::Vector3::CreateZero();
}
//! Callback to use for FocusCameraInput when a orbit camera is being used.
//! @note This is when offset is non zero.
inline AZ::Vector3 FocusOrbit(const float length)
{
return AZ::Vector3::CreateAxisY(-length);
}
using FocusOffsetFn = AZStd::function<AZ::Vector3(float)>;
//! A focus behavior to align the camera view to the position returned by the pivot function.
//! @note This only alters the camera orientation, the translation is unaffected.
class FocusCameraInput : public CameraInput
{
public:
using PivotFn = AZStd::function<AZ::Vector3()>;
FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn);
// CameraInput overrides ...
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
//! Override the default behavior for how a pivot point is calculated.
void SetPivotFn(PivotFn pivotFn);
void SetFocusInputChannelId(const InputChannelId& focusChannelId);
private:
InputChannelId m_focusChannelId; //!< Input channel to begin the focus camera input.
Camera m_nextCamera;
PivotFn m_pivotFn;
FocusOffsetFn m_offsetFn;
};
//! Provides a CameraInput type that can be implemented without needing to create a new type deriving from CameraInput.
//! This can be very useful for specific use cases that are less generally applicable.
class CustomCameraInput : public CameraInput
{
public:
// CameraInput overrides ...
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
//! HandleEvents delegates directly to m_handleEventsFn.
AZStd::function<bool(CameraInput&, const InputEvent&, const ScreenVector&, float)> m_handleEventsFn;
//! StepCamera delegates directly to m_stepCameraFn.
AZStd::function<Camera(CameraInput&, const Camera&, const ScreenVector&, float, float)> m_stepCameraFn;
};
//! Map from a generic InputChannel event to a camera specific InputEvent.
InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize);
} // namespace AzFramework
@@ -167,6 +167,10 @@ set(FILES
Logging/MissingAssetLogger.cpp
Logging/MissingAssetLogger.h
Logging/MissingAssetNotificationBus.h
Matchmaking/IMatchmakingRequests.h
Matchmaking/MatchmakingRequests.cpp
Matchmaking/MatchmakingRequests.h
Matchmaking/MatchmakingNotifications.h
Scene/Scene.h
Scene/Scene.inl
Scene/Scene.cpp
@@ -181,8 +185,9 @@ set(FILES
Script/ScriptRemoteDebugging.cpp
Script/ScriptRemoteDebugging.h
Session/ISessionHandlingRequests.h
Session/ISessionRequests.cpp
Session/ISessionRequests.h
Session/SessionRequests.cpp
Session/SessionRequests.h
Session/SessionConfig.cpp
Session/SessionConfig.h
Session/SessionNotifications.h
@@ -29,7 +29,6 @@ ly_add_target(
AZ::AzCore
PUBLIC
AZ::GridMate
3rdParty::zlib
3rdParty::zstd
3rdParty::lz4
)
@@ -13,7 +13,6 @@
#include <AzCore/Android/Utils.h>
#include <AzCore/IO/IOUtils.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/functional.h>
#include <android/api-level.h>
@@ -42,10 +41,10 @@ namespace AZ
{
Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{
char resolvedSourcePath[AZ_MAX_PATH_LEN];
char resolvedDestPath[AZ_MAX_PATH_LEN];
ResolvePath(sourceFilePath, resolvedSourcePath, AZ_MAX_PATH_LEN);
ResolvePath(destinationFilePath, resolvedDestPath, AZ_MAX_PATH_LEN);
char resolvedSourcePath[AZ::IO::MaxPathLength];
char resolvedDestPath[AZ::IO::MaxPathLength];
ResolvePath(sourceFilePath, resolvedSourcePath, AZ::IO::MaxPathLength);
ResolvePath(destinationFilePath, resolvedDestPath, AZ::IO::MaxPathLength);
if (AZ::Android::Utils::IsApkPath(sourceFilePath) || AZ::Android::Utils::IsApkPath(destinationFilePath))
{
@@ -77,18 +76,17 @@ namespace AZ
{
ANDROID_IO_PROFILE_SECTION_ARGS("FindFiles:%s", filePath);
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
char resolvedPath[AZ::IO::MaxPathLength];
ResolvePath(filePath, resolvedPath, AZ::IO::MaxPathLength);
AZStd::string pathWithoutSlash = RemoveTrailingSlash(resolvedPath);
bool isInAPK = AZ::Android::Utils::IsApkPath(pathWithoutSlash.c_str());
AZ::IO::FixedMaxPath tempBuffer;
if (isInAPK)
{
AZ::IO::FixedMaxPath strippedPath = AZ::Android::Utils::StripApkPrefix(pathWithoutSlash.c_str());
char tempBuffer[AZ_MAX_PATH_LEN] = {0};
AZ::Android::APKFileHandler::ParseDirectory(strippedPath.c_str(), [&](const char* name)
{
AZStd::string_view filenameView = name;
@@ -98,10 +96,9 @@ namespace AZ
AZStd::string foundFilePath = CheckForTrailingSlash(resolvedPath);
foundFilePath += name;
// if aliased, de-alias!
azstrcpy(tempBuffer, AZ_MAX_PATH_LEN, foundFilePath.c_str());
ConvertToAlias(tempBuffer, AZ_MAX_PATH_LEN);
ConvertToAlias(tempBuffer, AZ::IO::PathView{ foundFilePath });
if (!callback(tempBuffer))
if (!callback(tempBuffer.c_str()))
{
return false;
}
@@ -115,10 +112,6 @@ namespace AZ
if (dir != nullptr)
{
// because the absolute path might actually be SHORTER than the alias ("c:/r/dev" -> "@devroot@"), we need to
// use a static buffer here.
char tempBuffer[AZ_MAX_PATH_LEN];
// clear the errno state so we can distinguish between errors and end of stream
errno = 0;
struct dirent* entry = readdir(dir);
@@ -133,10 +126,9 @@ namespace AZ
AZStd::string foundFilePath = CheckForTrailingSlash(resolvedPath);
foundFilePath += entry->d_name;
// if aliased, de-alias!
azstrcpy(tempBuffer, AZ_MAX_PATH_LEN, foundFilePath.c_str());
ConvertToAlias(tempBuffer, AZ_MAX_PATH_LEN);
ConvertToAlias(tempBuffer, AZ::IO::PathView{ foundFilePath });
if (!callback(tempBuffer))
if (!callback(tempBuffer.c_str()))
{
break;
}
@@ -163,8 +155,8 @@ namespace AZ
Result LocalFileIO::CreatePath(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
char resolvedPath[AZ::IO::MaxPathLength];
ResolvePath(filePath, resolvedPath, AZ::IO::MaxPathLength);
if (AZ::Android::Utils::IsApkPath(resolvedPath))
{
@@ -201,33 +193,5 @@ namespace AZ
mkdir(pathBuffer.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
return IsDirectory(resolvedPath) ? ResultCode::Success : ResultCode::Error;
}
bool LocalFileIO::IsAbsolutePath(const char* path) const
{
return path && path[0] == '/';
}
bool LocalFileIO::ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const
{
if (AZ::Android::Utils::IsApkPath(path))
{
azstrncpy(absolutePath, maxLength, path, maxLength);
return true;
}
AZ_Assert(maxLength >= AZ_MAX_PATH_LEN, "Path length is larger than AZ_MAX_PATH_LEN");
if (!IsAbsolutePath(path))
{
// note that realpath fails if the path does not exist and actually changes the return value
// to be the actual place that FAILED, which we don't want.
// if we fail, we'd prefer to fall through and at least use the original path.
const char* result = realpath(path, absolutePath);
if (result)
{
return true;
}
}
azstrcpy(absolutePath, maxLength, path);
return IsAbsolutePath(absolutePath);
}
} // namespace IO
}//namespace AZ
@@ -10,7 +10,7 @@
#include <dirent.h>
#include <unistd.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/functional.h>
namespace AZ
@@ -19,11 +19,11 @@ namespace AZ
{
Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{
char resolvedSourceFilePath[AZ_MAX_PATH_LEN] = {0};
ResolvePath(sourceFilePath, resolvedSourceFilePath, AZ_MAX_PATH_LEN);
char resolvedSourceFilePath[AZ::IO::MaxPathLength] = {0};
ResolvePath(sourceFilePath, resolvedSourceFilePath, AZ::IO::MaxPathLength);
char resolvedDestinationFilePath[AZ_MAX_PATH_LEN] = {0};
ResolvePath(destinationFilePath, resolvedDestinationFilePath, AZ_MAX_PATH_LEN);
char resolvedDestinationFilePath[AZ::IO::MaxPathLength] = {0};
ResolvePath(destinationFilePath, resolvedDestinationFilePath, AZ::IO::MaxPathLength);
// Use standard C++ method of file copy.
{
@@ -45,17 +45,15 @@ namespace AZ
Result LocalFileIO::FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback)
{
char resolvedPath[AZ_MAX_PATH_LEN] = {0};
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
char resolvedPath[AZ::IO::MaxPathLength] = {0};
ResolvePath(filePath, resolvedPath, AZ::IO::MaxPathLength);
AZStd::string withoutSlash = RemoveTrailingSlash(resolvedPath);
DIR* dir = opendir(withoutSlash.c_str());
if (dir != nullptr)
{
// because the absolute path might actually be SHORTER than the alias ("c:/r/dev" -> "@devroot@"), we need to
// use a static buffer here.
char tempBuffer[AZ_MAX_PATH_LEN];
AZ::IO::FixedMaxPath tempBuffer;
errno = 0;
struct dirent* entry = readdir(dir);
@@ -70,10 +68,9 @@ namespace AZ
AZStd::string foundFilePath = CheckForTrailingSlash(resolvedPath);
foundFilePath += entry->d_name;
// if aliased, dealias!
azstrcpy(tempBuffer, AZ_MAX_PATH_LEN, foundFilePath.c_str());
ConvertToAlias(tempBuffer, AZ_MAX_PATH_LEN);
ConvertToAlias(tempBuffer, AZ::IO::PathView{ foundFilePath });
if (!callback(tempBuffer))
if (!callback(tempBuffer.c_str()))
{
break;
}
@@ -92,8 +89,8 @@ namespace AZ
Result LocalFileIO::CreatePath(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN] = {0};
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
char resolvedPath[AZ::IO::MaxPathLength] = {0};
ResolvePath(filePath, resolvedPath, AZ::IO::MaxPathLength);
// create all paths up to that directory.
// its not an error if the path exists.
@@ -125,28 +122,5 @@ namespace AZ
mkdir(buf.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
return IsDirectory(resolvedPath) ? ResultCode::Success : ResultCode::Error;
}
bool LocalFileIO::IsAbsolutePath(const char* path) const
{
return path && path[0] == '/';
}
bool LocalFileIO::ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const
{
AZ_Assert(maxLength >= AZ_MAX_PATH_LEN, "Path length is larger than AZ_MAX_PATH_LEN");
if (!IsAbsolutePath(path))
{
// note that realpath fails if the path does not exist and actually changes the return value
// to be the actual place that FAILED, which we don't want.
// if we fail, we'd prefer to fall through and at least use the original path.
const char* result = realpath(path, absolutePath);
if (result)
{
return true;
}
}
azstrcpy(absolutePath, maxLength, path);
return IsAbsolutePath(absolutePath);
}
} // namespace IO
} // namespace AZ
@@ -47,7 +47,7 @@ namespace AZ
if (hFind != INVALID_HANDLE_VALUE)
{
// because the absolute path might actually be SHORTER than the alias ("c:/r/dev" -> "@devroot@"), we need to
// because the absolute path might actually be SHORTER than the alias ("D:/o3de" -> "@engroot@"), we need to
// use a static buffer here.
char tempBuffer[AZ_MAX_PATH_LEN];
do
@@ -133,36 +133,5 @@ namespace AZ
return SystemFile::CreateDir(buf.c_str()) ? ResultCode::Success : ResultCode::Error;
}
bool LocalFileIO::ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const
{
char* result = _fullpath(absolutePath, path, maxLength);
size_t len = ::strlen(absolutePath);
if (len > 0)
{
// strip trailing slash
if (absolutePath[len - 1] == '/' || absolutePath[len - 1] == '\\')
{
absolutePath[len - 1] = 0;
}
// For some reason, at least on windows, _fullpath returns a lowercase drive letter even though other systems like Qt, use upper case.
if (len > 2)
{
if (absolutePath[1] == ':')
{
absolutePath[0] = (char)toupper(absolutePath[0]);
}
}
}
return result != nullptr;
}
bool LocalFileIO::IsAbsolutePath(const char* path) const
{
char drive[16] = { 0 };
_splitpath_s(path, drive, 16, nullptr, 0, nullptr, 0, nullptr, 0);
return strlen(drive) > 0;
}
} // namespace IO
}//namespace AZ
@@ -20,6 +20,15 @@
namespace AzFramework
{
// xcb-xkb does not provide a generic event type, so we define our own.
// These fields are enough to get to the xkbType field, which can then be
// read to typecast the event to the right concrete type.
struct XcbXkbGenericEventT
{
uint8_t response_type;
uint8_t xkbType;
};
XcbInputDeviceKeyboard::XcbInputDeviceKeyboard(InputDeviceKeyboard& inputDevice)
: InputDeviceKeyboard::Implementation(inputDevice)
{
@@ -39,19 +48,22 @@ namespace AzFramework
return;
}
XcbStdFreePtr<xcb_xkb_use_extension_reply_t> xkbUseExtensionReply{
xcb_xkb_use_extension_reply(connection, xcb_xkb_use_extension(connection, 1, 0), nullptr)
};
if (!xkbUseExtensionReply)
int initializeXkbExtensionSuccess = xkb_x11_setup_xkb_extension(
connection,
1,
0,
XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
nullptr,
nullptr,
&m_xkbEventCode,
nullptr
);
if (!initializeXkbExtensionSuccess)
{
AZ_Warning("ApplicationLinux", false, "Failed to initialize the xkb extension");
return;
}
if (!xkbUseExtensionReply->supported)
{
AZ_Warning("ApplicationLinux", false, "The X server does not support the xkb extension");
return;
}
m_coreDeviceId = xkb_x11_get_core_keyboard_device_id(connection);
@@ -59,6 +71,43 @@ namespace AzFramework
m_xkbKeymap.reset(xkb_x11_keymap_new_from_device(m_xkbContext.get(), connection, m_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS));
m_xkbState.reset(xkb_x11_state_new_from_device(m_xkbKeymap.get(), connection, m_coreDeviceId));
const uint16_t affectMap =
XCB_XKB_MAP_PART_KEY_TYPES
| XCB_XKB_MAP_PART_KEY_SYMS
| XCB_XKB_MAP_PART_MODIFIER_MAP
| XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS
| XCB_XKB_MAP_PART_KEY_ACTIONS
| XCB_XKB_MAP_PART_KEY_BEHAVIORS
| XCB_XKB_MAP_PART_VIRTUAL_MODS
| XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP
;
const uint16_t selectedEvents =
XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY
| XCB_XKB_EVENT_TYPE_MAP_NOTIFY
| XCB_XKB_EVENT_TYPE_STATE_NOTIFY
;
XcbStdFreePtr<xcb_generic_error_t> error{xcb_request_check(
connection,
xcb_xkb_select_events(
connection,
/* deviceSpec = */ XCB_XKB_ID_USE_CORE_KBD,
/* affectWhich = */ selectedEvents,
/* clear = */ 0,
/* selectAll = */ selectedEvents,
/* affectMap = */ affectMap,
/* map = */ affectMap,
/* details = */ nullptr
)
)};
if (error)
{
AZ_Warning("ApplicationLinux", false, "failed to select notify events from XKB");
return;
}
m_initialized = true;
}
@@ -70,15 +119,17 @@ namespace AzFramework
bool XcbInputDeviceKeyboard::HasTextEntryStarted() const
{
return false;
return m_hasTextEntryStarted;
}
void XcbInputDeviceKeyboard::TextEntryStart(const InputDeviceKeyboard::VirtualKeyboardOptions& options)
{
m_hasTextEntryStarted = true;
}
void XcbInputDeviceKeyboard::TextEntryStop()
{
m_hasTextEntryStarted = false;
}
void XcbInputDeviceKeyboard::TickInputDevice()
@@ -93,30 +144,45 @@ namespace AzFramework
return;
}
switch (event->response_type & ~0x80)
const auto responseType = event->response_type & ~0x80;
if (responseType == XCB_KEY_PRESS)
{
case XCB_KEY_PRESS:
{
auto* keyPress = reinterpret_cast<xcb_key_press_event_t*>(event);
const auto* keyPress = reinterpret_cast<xcb_key_press_event_t*>(event);
{
auto text = TextFromKeycode(m_xkbState.get(), keyPress->detail);
if (!text.empty())
{
QueueRawTextEvent(AZStd::move(text));
}
}
const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail);
if (key)
if (const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail))
{
QueueRawKeyEvent(*key, true);
}
break;
}
case XCB_KEY_RELEASE:
else if (responseType == XCB_KEY_RELEASE)
{
auto* keyRelease = reinterpret_cast<xcb_key_release_event_t*>(event);
const auto* keyRelease = reinterpret_cast<xcb_key_release_event_t*>(event);
const InputChannelId* key = InputChannelFromKeyEvent(keyRelease->detail);
if (key)
{
QueueRawKeyEvent(*key, false);
}
break;
}
else if (responseType == m_xkbEventCode)
{
const auto* xkbEvent = reinterpret_cast<XcbXkbGenericEventT*>(event);
switch (xkbEvent->xkbType)
{
case XCB_XKB_STATE_NOTIFY:
{
const auto* stateNotifyEvent = reinterpret_cast<xcb_xkb_state_notify_event_t*>(event);
UpdateState(stateNotifyEvent);
break;
}
}
}
}
@@ -268,4 +334,34 @@ namespace AzFramework
default: return nullptr;
}
}
AZStd::string XcbInputDeviceKeyboard::TextFromKeycode(xkb_state* state, xkb_keycode_t code)
{
// Find out how much of a buffer we need
const size_t size = xkb_state_key_get_utf8(state, code, nullptr, 0);
if (!size)
{
return {};
}
// xkb_state_key_get_utf8 will null-terminate the resulting string, and
// will truncate the result to `size - 1` if there is not enough space
// for the null byte. The first call returns the size of the resulting
// string without including the null byte. AZStd::string internally
// includes space for the null byte, but that is not included in its
// `size()`. xkb_state_key_get_utf8 will always set `buf[size - 1] =
// 0`, so add 1 to `chars.size()` to include that internal null byte in
// the string.
AZStd::string chars;
chars.resize_no_construct(size);
xkb_state_key_get_utf8(state, code, chars.data(), chars.size() + 1);
return chars;
}
void XcbInputDeviceKeyboard::UpdateState(const xcb_xkb_state_notify_event_t* state)
{
if (m_initialized)
{
xkb_state_update_mask(m_xkbState.get(), state->baseMods, state->latchedMods, state->lockedMods, state->baseGroup, state->latchedGroup, state->lockedGroup);
}
}
} // namespace AzFramework
@@ -13,6 +13,8 @@
#include <xcb/xcb.h>
#include <xkbcommon/xkbcommon.h>
struct xcb_xkb_state_notify_event_t;
namespace AzFramework
{
class XcbInputDeviceKeyboard
@@ -37,10 +39,16 @@ namespace AzFramework
private:
[[nodiscard]] const InputChannelId* InputChannelFromKeyEvent(xcb_keycode_t code) const;
static AZStd::string TextFromKeycode(xkb_state* state, xkb_keycode_t code);
void UpdateState(const xcb_xkb_state_notify_event_t* state);
XcbUniquePtr<xkb_context, xkb_context_unref> m_xkbContext;
XcbUniquePtr<xkb_keymap, xkb_keymap_unref> m_xkbKeymap;
XcbUniquePtr<xkb_state, xkb_state_unref> m_xkbState;
int m_coreDeviceId{-1};
uint8_t m_xkbEventCode{0};
bool m_initialized{false};
bool m_hasTextEntryStarted{false};
};
} // namespace AzFramework
@@ -10,6 +10,7 @@
#include <AzFramework/Windowing/NativeWindow.h>
#include <AzFramework/XcbNativeWindow.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbInterface.h>
#include <xcb/xcb.h>
@@ -7,26 +7,24 @@
*/
#include <AzCore/PlatformIncl.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
namespace AZ
namespace AZ::IO
{
namespace IO
Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{
AZ::IO::FixedMaxPath resolvedSourcePath;
ResolvePath(resolvedSourcePath, sourceFilePath);
AZ::IO::FixedMaxPath resolvedDestPath;
ResolvePath(resolvedDestPath, destinationFilePath);
Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{
char resolvedSourcePath[AZ_MAX_PATH_LEN];
ResolvePath(sourceFilePath, resolvedSourcePath, AZ_MAX_PATH_LEN);
char resolvedDestPath[AZ_MAX_PATH_LEN];
ResolvePath(destinationFilePath, resolvedDestPath, AZ_MAX_PATH_LEN);
AZStd::fixed_wstring<AZ::IO::MaxPathLength> resolvedSourcePathW;
AZStd::fixed_wstring<AZ::IO::MaxPathLength> resolvedDestPathW;
AZStd::to_wstring(resolvedSourcePathW, resolvedSourcePath.Native());
AZStd::to_wstring(resolvedDestPathW, resolvedDestPath.Native());
if (::CopyFileA(resolvedSourcePath, resolvedDestPath, false) == 0)
{
return ResultCode::Error;
}
return ResultCode::Success;
}
} // namespace IO
}//namespace AZ
return ::CopyFileW(resolvedSourcePathW.c_str(), resolvedDestPathW.c_str(), false) != 0 ? ResultCode::Success : ResultCode::Error;
}
}//namespace AZ::IO
@@ -112,9 +112,10 @@ namespace AzFramework
void ApplicationIos::PumpSystemEventLoopUntilEmpty()
{
SInt32 result;
const CFTimeInterval MaxSecondsInRunLoop = 0.001; // One millisecond
do
{
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, DBL_EPSILON, TRUE);
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, MaxSecondsInRunLoop, TRUE);
}
while (result == kCFRunLoopRunHandledSource);
}
@@ -26,7 +26,7 @@ protected:
}
if (auto fileIoBase = AZ::IO::FileIOBase::GetInstance(); fileIoBase != nullptr)
{
fileIoBase->SetAlias("@assets@", m_tempDirectory.GetDirectory());
fileIoBase->SetAlias("@products@", m_tempDirectory.GetDirectory());
}
}
@@ -50,7 +50,7 @@ namespace UnitTest
m_application->Start({});
// 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
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
}
@@ -262,7 +262,7 @@ namespace UnitTest
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath_withSubfolders.c_str()));
EXPECT_TRUE(archive->OpenPack("@assets@", testArchivePath_withSubfolders.c_str()));
EXPECT_TRUE(archive->OpenPack("@products@", testArchivePath_withSubfolders.c_str()));
EXPECT_TRUE(archive->IsFileExist(fileInArchiveFile));
}
@@ -353,7 +353,7 @@ namespace UnitTest
// and be able to IMMEDIATELY
// * read the file in the subfolder
// * enumerate the folders (including that subfolder) even though they are 'virtual', not real folders on physical media
// * all of the above even though the mount point for the archive is @assets@ wheras the physical pack lives in @usercache@
// * all of the above even though the mount point for the archive is @products@ wheras the physical pack lives in @usercache@
// finally, we're going to repeat the above test but with files mounted with subfolders
// so for example, the pack will contain levelinfo.xml at the root of it
// but it will be mounted at a subfolder (levels/mylevel).
@@ -388,7 +388,7 @@ namespace UnitTest
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath_withSubfolders));
EXPECT_TRUE(archive->OpenPack("@assets@", testArchivePath_withSubfolders));
EXPECT_TRUE(archive->OpenPack("@products@", testArchivePath_withSubfolders));
// ---- BARRAGE OF TESTS
EXPECT_TRUE(archive->IsFileExist("levels\\mylevel\\levelinfo.xml"));
EXPECT_TRUE(archive->IsFileExist("levels//mylevel//levelinfo.xml"));
@@ -484,7 +484,7 @@ namespace UnitTest
pArchive.reset();
EXPECT_TRUE(IsPackValid(testArchivePath_withMountPoint));
EXPECT_TRUE(archive->OpenPack("@assets@\\uniquename\\mylevel2", testArchivePath_withMountPoint));
EXPECT_TRUE(archive->OpenPack("@products@\\uniquename\\mylevel2", testArchivePath_withMountPoint));
// ---- BARRAGE OF TESTS
EXPECT_TRUE(archive->IsFileExist("uniquename\\mylevel2\\levelinfo.xml"));
@@ -543,7 +543,7 @@ namespace UnitTest
archive->ClosePack(testArchivePath_withMountPoint);
// --- test to make sure that when you iterate only the first component is found, so bury it deep and ask for the root
EXPECT_TRUE(archive->OpenPack("@assets@\\uniquename\\mylevel2\\mylevel3\\mylevel4", testArchivePath_withMountPoint));
EXPECT_TRUE(archive->OpenPack("@products@\\uniquename\\mylevel2\\mylevel3\\mylevel4", testArchivePath_withMountPoint));
found_mylevel_folder = false;
handle = archive->FindFirst("uniquename\\*");
@@ -574,9 +574,9 @@ namespace UnitTest
found_mylevel_folder = false;
// now make sure no red herrings appear
// for example, if a file is mounted at "@assets@\\uniquename\\mylevel2\\mylevel3\\mylevel4"
// and the file "@assets@\\somethingelse" is requested it should not be found
// in addition if the file "@assets@\\uniquename\\mylevel3" is requested it should not be found
// for example, if a file is mounted at "@products@\\uniquename\\mylevel2\\mylevel3\\mylevel4"
// and the file "@products@\\somethingelse" is requested it should not be found
// in addition if the file "@products@\\uniquename\\mylevel3" is requested it should not be found
handle = archive->FindFirst("somethingelse\\*");
EXPECT_FALSE(static_cast<bool>(handle));
@@ -610,7 +610,7 @@ namespace UnitTest
cpfio.Remove(genericArchiveFileName);
// create the asset alias directory
cpfio.CreatePath("@assets@");
cpfio.CreatePath("@products@");
// create generic file
@@ -635,11 +635,11 @@ namespace UnitTest
pArchive.reset();
EXPECT_TRUE(IsPackValid(genericArchiveFileName));
EXPECT_TRUE(archive->OpenPack("@assets@", genericArchiveFileName));
EXPECT_TRUE(archive->OpenPack("@products@", genericArchiveFileName));
// ---- BARRAGE OF TESTS
EXPECT_TRUE(cpfio.Exists("testfile.xml"));
EXPECT_TRUE(cpfio.Exists("@assets@/testfile.xml")); // this should be hte same file
EXPECT_TRUE(cpfio.Exists("@products@/testfile.xml")); // this should be hte same file
EXPECT_TRUE(!cpfio.Exists("@log@/testfile.xml"));
EXPECT_TRUE(!cpfio.Exists("@usercache@/testfile.xml"));
EXPECT_TRUE(cpfio.Exists("@log@/unittesttemp/realfileforunittest.xml"));
@@ -685,9 +685,9 @@ namespace UnitTest
EXPECT_EQ(ResultCode::Success, cpfio.Close(normalFileHandle));
EXPECT_TRUE(!cpfio.IsDirectory("testfile.xml"));
EXPECT_TRUE(cpfio.IsDirectory("@assets@"));
EXPECT_TRUE(cpfio.IsDirectory("@products@"));
EXPECT_TRUE(cpfio.IsReadOnly("testfile.xml"));
EXPECT_TRUE(cpfio.IsReadOnly("@assets@/testfile.xml"));
EXPECT_TRUE(cpfio.IsReadOnly("@products@/testfile.xml"));
EXPECT_TRUE(!cpfio.IsReadOnly("@log@/unittesttemp/realfileforunittest.xml"));
@@ -714,10 +714,10 @@ namespace UnitTest
// find files test.
AZ::IO::FixedMaxPath resolvedTestFilePath;
EXPECT_TRUE(cpfio.ResolvePath(resolvedTestFilePath, AZ::IO::PathView("@assets@/testfile.xml")));
EXPECT_TRUE(cpfio.ResolvePath(resolvedTestFilePath, AZ::IO::PathView("@products@/testfile.xml")));
bool foundIt = false;
// note that this file exists only in the archive.
cpfio.FindFiles("@assets@", "*.xml", [&foundIt, &cpfio, &resolvedTestFilePath](const char* foundName)
cpfio.FindFiles("@products@", "*.xml", [&foundIt, &cpfio, &resolvedTestFilePath](const char* foundName)
{
AZ::IO::FixedMaxPath resolvedFoundPath;
EXPECT_TRUE(cpfio.ResolvePath(resolvedFoundPath, AZ::IO::PathView(foundName)));
@@ -734,10 +734,10 @@ namespace UnitTest
// The following test is disabled because it will trigger an AZ_ERROR which will affect the outcome of this entire test
// EXPECT_NE(ResultCode::Success, cpfio.Remove("@assets@/testfile.xml")); // may not delete archive files
// EXPECT_NE(ResultCode::Success, cpfio.Remove("@products@/testfile.xml")); // may not delete archive files
// make sure it works with and without alias:
EXPECT_TRUE(cpfio.Exists("@assets@/testfile.xml"));
EXPECT_TRUE(cpfio.Exists("@products@/testfile.xml"));
EXPECT_TRUE(cpfio.Exists("testfile.xml"));
EXPECT_TRUE(cpfio.Exists("@log@/unittesttemp/realfileforunittest.xml"));
@@ -788,22 +788,22 @@ namespace UnitTest
EXPECT_TRUE(archive->ClosePack(realNameBuf));
// change its actual location:
EXPECT_TRUE(archive->OpenPack("@assets@", realNameBuf));
EXPECT_TRUE(archive->IsFileExist("@assets@/foundit.dat"));
EXPECT_TRUE(archive->OpenPack("@products@", realNameBuf));
EXPECT_TRUE(archive->IsFileExist("@products@/foundit.dat"));
EXPECT_FALSE(archive->IsFileExist("@usercache@/foundit.dat")); // do not find it in the previous location!
EXPECT_FALSE(archive->IsFileExist("@assets@/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk));
EXPECT_FALSE(archive->IsFileExist("@assets@/notfoundit.dat"));
EXPECT_FALSE(archive->IsFileExist("@products@/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk));
EXPECT_FALSE(archive->IsFileExist("@products@/notfoundit.dat"));
EXPECT_TRUE(archive->ClosePack(realNameBuf));
// try sub-folders
EXPECT_TRUE(archive->OpenPack("@assets@/mystuff", realNameBuf));
EXPECT_TRUE(archive->IsFileExist("@assets@/mystuff/foundit.dat"));
EXPECT_FALSE(archive->IsFileExist("@assets@/foundit.dat")); // do not find it in the previous locations!
EXPECT_TRUE(archive->OpenPack("@products@/mystuff", realNameBuf));
EXPECT_TRUE(archive->IsFileExist("@products@/mystuff/foundit.dat"));
EXPECT_FALSE(archive->IsFileExist("@products@/foundit.dat")); // do not find it in the previous locations!
EXPECT_FALSE(archive->IsFileExist("@usercache@/foundit.dat")); // do not find it in the previous locations!
EXPECT_FALSE(archive->IsFileExist("@assets@/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk));
EXPECT_FALSE(archive->IsFileExist("@assets@/mystuff/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk));
EXPECT_FALSE(archive->IsFileExist("@assets@/notfoundit.dat")); // non-existent file
EXPECT_FALSE(archive->IsFileExist("@assets@/mystuff/notfoundit.dat")); // non-existent file
EXPECT_FALSE(archive->IsFileExist("@products@/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk));
EXPECT_FALSE(archive->IsFileExist("@products@/mystuff/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk));
EXPECT_FALSE(archive->IsFileExist("@products@/notfoundit.dat")); // non-existent file
EXPECT_FALSE(archive->IsFileExist("@products@/mystuff/notfoundit.dat")); // non-existent file
EXPECT_TRUE(archive->ClosePack(realNameBuf));
}
@@ -861,7 +861,7 @@ namespace UnitTest
AZ::IO::FileIOBase* ioBase = AZ::IO::FileIOBase::GetInstance();
ASSERT_NE(nullptr, ioBase);
const char* assetsPath = ioBase->GetAlias("@assets@");
const char* assetsPath = ioBase->GetAlias("@products@");
ASSERT_NE(nullptr, assetsPath);
auto stringToAdd = AZ::IO::Path(assetsPath) / "textures" / "test.dds";
@@ -872,7 +872,7 @@ namespace UnitTest
// it normalizes the string, so the slashes flip and everything is lowercased.
AZ::IO::FixedMaxPath resolvedAddedPath;
AZ::IO::FixedMaxPath resolvedResourcePath;
EXPECT_TRUE(ioBase->ReplaceAlias(resolvedAddedPath, "@assets@/textures/test.dds"));
EXPECT_TRUE(ioBase->ReplaceAlias(resolvedAddedPath, "@products@/textures/test.dds"));
EXPECT_TRUE(ioBase->ReplaceAlias(resolvedResourcePath, reslist->GetFirst()));
EXPECT_EQ(resolvedAddedPath, resolvedResourcePath);
reslist->Clear();
@@ -53,31 +53,31 @@ namespace UnitTest
};
m_firstPersonTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(
m_translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivot);
m_translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivotLook);
m_pivotCamera = AZStd::make_shared<AzFramework::PivotCameraInput>(m_pivotChannelId);
m_pivotCamera->SetPivotFn(
m_orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>(m_orbitChannelId);
m_orbitCamera->SetPivotFn(
[this](const AZ::Vector3&, const AZ::Vector3&)
{
return m_pivot;
});
auto pivotRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Left);
auto orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::InputDeviceMouse::Button::Left);
// set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth.
pivotRotateCamera->m_rotateSpeedFn = []()
orbitRotateCamera->m_rotateSpeedFn = []()
{
return 0.001f;
};
auto pivotTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(
m_translateCameraInputChannelIds, AzFramework::PivotTranslation, AzFramework::TranslateOffset);
auto orbitTranslateCamera = AZStd::make_shared<AzFramework::TranslateCameraInput>(
m_translateCameraInputChannelIds, AzFramework::OrbitTranslation, AzFramework::TranslateOffsetOrbit);
m_pivotCamera->m_pivotCameras.AddCamera(pivotRotateCamera);
m_pivotCamera->m_pivotCameras.AddCamera(pivotTranslateCamera);
m_orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
m_orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
m_cameraSystem->m_cameras.AddCamera(m_firstPersonRotateCamera);
m_cameraSystem->m_cameras.AddCamera(m_firstPersonTranslateCamera);
m_cameraSystem->m_cameras.AddCamera(m_pivotCamera);
m_cameraSystem->m_cameras.AddCamera(m_orbitCamera);
// these tests rely on using motion delta, not cursor positions (default is true)
AzFramework::ed_cameraSystemUseCursor = false;
@@ -87,7 +87,7 @@ namespace UnitTest
{
AzFramework::ed_cameraSystemUseCursor = true;
m_pivotCamera.reset();
m_orbitCamera.reset();
m_firstPersonRotateCamera.reset();
m_firstPersonTranslateCamera.reset();
@@ -97,11 +97,11 @@ namespace UnitTest
AllocatorsTestFixture::TearDown();
}
AzFramework::InputChannelId m_pivotChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l");
AzFramework::InputChannelId m_orbitChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l");
AzFramework::TranslateCameraInputChannelIds m_translateCameraInputChannelIds;
AZStd::shared_ptr<AzFramework::RotateCameraInput> m_firstPersonRotateCamera;
AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_firstPersonTranslateCamera;
AZStd::shared_ptr<AzFramework::PivotCameraInput> m_pivotCamera;
AZStd::shared_ptr<AzFramework::OrbitCameraInput> m_orbitCamera;
AZ::Vector3 m_pivot = AZ::Vector3::CreateZero();
//! This is approximately Pi/2 * 1000 - this can be used to rotate the camera 90 degrees (pitch or yaw based
@@ -109,17 +109,17 @@ namespace UnitTest
inline static const int PixelMotionDelta = 1570;
};
TEST_F(CameraInputFixture, BeginAndEndPivotCameraInputConsumesCorrectEvents)
TEST_F(CameraInputFixture, BeginAndEndOrbitCameraInputConsumesCorrectEvents)
{
// begin pivot camera
// begin orbit camera
const bool consumed1 = HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceKeyboard::Key::ModifierAltL,
AzFramework::InputChannel::State::Began });
// begin listening for pivot rotate (click detector) - event is not consumed
// begin listening for orbit rotate (click detector) - event is not consumed
const bool consumed2 = HandleEventAndUpdate(
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began });
// begin pivot rotate (mouse has moved sufficient distance to initiate)
// begin orbit rotate (mouse has moved sufficient distance to initiate)
const bool consumed3 = HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ 5 });
// end pivot (mouse up) - event is not consumed
// end orbit (mouse up) - event is not consumed
const bool consumed4 = HandleEventAndUpdate(
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended });
@@ -260,10 +260,10 @@ namespace UnitTest
EXPECT_TRUE(activationEnded);
}
TEST_F(CameraInputFixture, PivotCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenPivoting)
TEST_F(CameraInputFixture, OrbitCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenOrbiting)
{
// create pathological lookAtFn that just returns the same position as the camera
m_pivotCamera->SetPivotFn(
m_orbitCamera->SetPivotFn(
[](const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
{
return position;
@@ -275,7 +275,7 @@ namespace UnitTest
AZ::Transform::CreateFromQuaternionAndTranslation(
AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), expectedCameraPosition));
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
// verify the camera yaw has not changed and pivot point matches the expected camera position
using ::testing::FloatNear;
@@ -321,14 +321,14 @@ namespace UnitTest
EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero()));
}
TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta)
TEST_F(CameraInputFixture, OrbitRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta)
{
const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-20.0f);
m_targetCamera.m_pivot = cameraStartingPosition;
m_pivot = AZ::Vector3::CreateAxisY(-10.0f);
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta });
@@ -344,14 +344,14 @@ namespace UnitTest
EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f));
}
TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesYawOffsetByNinetyDegreesWithRequiredPixelDelta)
TEST_F(CameraInputFixture, OrbitRotateCameraInputRotatesYawOffsetByNinetyDegreesWithRequiredPixelDelta)
{
const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f);
m_targetCamera.m_pivot = cameraStartingPosition;
m_pivot = AZ::Vector3(10.0f, -10.0f, 0.0f);
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(
AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ -PixelMotionDelta });
@@ -802,6 +802,51 @@ namespace UnitTest
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
TEST_F(AliasTest, GetAlias_LogsError_WhenAccessingDeprecatedAlias_Succeeds)
{
AZ::IO::LocalFileIO local;
AZ::IO::FixedMaxPathString aliasFolder;
EXPECT_TRUE(local.ConvertToAbsolutePath("/temp", aliasFolder.data(), aliasFolder.capacity()));
aliasFolder.resize_no_construct(AZStd::char_traits<char>::length(aliasFolder.data()));
local.SetAlias("@test@", aliasFolder.c_str());
local.SetDeprecatedAlias("@deprecated@", "@test@");
local.SetDeprecatedAlias("@deprecatednonexistent@", "@nonexistent@");
local.SetDeprecatedAlias("@deprecatedsecond@", "@deprecated@");
local.SetDeprecatedAlias("@deprecatednonaliaspath@", aliasFolder);
AZ_TEST_START_TRACE_SUPPRESSION;
const char* testAlias = local.GetAlias("@test@");
ASSERT_NE(nullptr, testAlias);
EXPECT_EQ(AZ::IO::PathView(aliasFolder), AZ::IO::PathView(testAlias));
AZ_TEST_STOP_TRACE_SUPPRESSION(0);
// Validate that accessing Deprecated Alias results in AZ_Error
AZ_TEST_START_TRACE_SUPPRESSION;
testAlias = local.GetAlias("@deprecated@");
ASSERT_NE(nullptr, testAlias);
EXPECT_EQ(AZ::IO::PathView(aliasFolder), AZ::IO::PathView(testAlias));
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
AZ_TEST_START_TRACE_SUPPRESSION;
testAlias = local.GetAlias("@deprecatednonexistent@");
EXPECT_EQ(nullptr, testAlias);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
AZ_TEST_START_TRACE_SUPPRESSION;
testAlias = local.GetAlias("@deprecatedsecond@");
ASSERT_NE(nullptr, testAlias);
EXPECT_EQ(AZ::IO::PathView(aliasFolder), AZ::IO::PathView(testAlias));
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
AZ_TEST_START_TRACE_SUPPRESSION;
testAlias = local.GetAlias("@deprecatednonaliaspath@");
ASSERT_NE(nullptr, testAlias);
EXPECT_EQ(AZ::IO::PathView(aliasFolder), AZ::IO::PathView(testAlias));
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
class SmartMoveTests
: public FolderFixture
{
@@ -27,7 +27,7 @@ namespace UnitTest
const char DummyFile[] = "dummy.txt";
const char AnotherDummyFile[] = "Foo/Dummy.txt";
const char DummyPattern[] = R"(^(.+)_([a-z]+)\..+$)";
const char MatchingPatternFile[] = "Foo/dummy_abc.txt";
const char NonMatchingPatternFile[] = "Foo/dummy_a8c.txt";
@@ -75,7 +75,7 @@ namespace UnitTest
: public AllocatorsFixture
{
public:
void SetUp() override
{
AllocatorsFixture::SetUp();
@@ -89,7 +89,7 @@ namespace UnitTest
const char* testAssetRoot = m_tempDirectory.GetDirectory();
// 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
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
@@ -98,7 +98,7 @@ namespace UnitTest
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_data->m_localFileIO.get());
AZ::IO::FileIOBase::GetInstance()->SetAlias("@assets@", testAssetRoot);
AZ::IO::FileIOBase::GetInstance()->SetAlias("@products@", testAssetRoot);
m_data->m_excludeFileQueryManager = AZStd::make_unique<FileTagQueryManagerTest>(FileTagType::Exclude);
m_data->m_includeFileQueryManager = AZStd::make_unique<FileTagQueryManagerTest>(FileTagType::Include);
@@ -114,7 +114,7 @@ namespace UnitTest
AZStd::vector<AZStd::string> includedWildcardTags = { DummyFileTags[DummyFileTagIndex::GIdx] };
EXPECT_TRUE(m_data->m_fileTagManager.AddFilePatternTags(DummyWildcard, FilePatternType::Wildcard, FileTagType::Include, includedWildcardTags).IsSuccess());
AzFramework::StringFunc::Path::Join(testAssetRoot, AZStd::string::format("%s.%s", ExcludeFile, FileTagAsset::Extension()).c_str(), m_data->m_excludeFile);
AzFramework::StringFunc::Path::Join(testAssetRoot, AZStd::string::format("%s.%s", IncludeFile, FileTagAsset::Extension()).c_str(), m_data->m_includeFile);
@@ -184,7 +184,7 @@ namespace UnitTest
TEST_F(FileTagTest, FileTags_QueryByAbsoluteFilePath_Valid)
{
AZStd::string absoluteDummyFilePath = DummyFile;
EXPECT_TRUE(AzFramework::StringFunc::AssetDatabasePath::Join("@assets@", absoluteDummyFilePath.c_str(), absoluteDummyFilePath));
EXPECT_TRUE(AzFramework::StringFunc::AssetDatabasePath::Join("@products@", absoluteDummyFilePath.c_str(), absoluteDummyFilePath));
AZStd::set<AZStd::string> tags = m_data->m_excludeFileQueryManager->GetTags(absoluteDummyFilePath);
@@ -196,7 +196,7 @@ namespace UnitTest
ASSERT_EQ(tags.size(), 0);
AZStd::string absoluteAnotherDummyFilePath = AnotherDummyFile;
EXPECT_TRUE(AzFramework::StringFunc::AssetDatabasePath::Join("@assets@", absoluteAnotherDummyFilePath.c_str(), absoluteAnotherDummyFilePath));
EXPECT_TRUE(AzFramework::StringFunc::AssetDatabasePath::Join("@products@", absoluteAnotherDummyFilePath.c_str(), absoluteAnotherDummyFilePath));
tags = m_data->m_includeFileQueryManager->GetTags(absoluteAnotherDummyFilePath);
ASSERT_EQ(tags.size(), 2);
@@ -213,7 +213,7 @@ namespace UnitTest
// Set the customized alias
AZStd::string customizedAliasFilePath;
const char* assetsAlias = AZ::IO::FileIOBase::GetInstance()->GetAlias("@assets@");
const char* assetsAlias = AZ::IO::FileIOBase::GetInstance()->GetAlias("@products@");
AzFramework::StringFunc::AssetDatabasePath::Join(assetsAlias, "foo", customizedAliasFilePath);
AZ::IO::FileIOBase::GetInstance()->SetAlias("@customizedalias@", customizedAliasFilePath.c_str());
@@ -305,7 +305,7 @@ namespace UnitTest
m_data->m_excludeFileQueryManager->ClearData();
EXPECT_TRUE(m_data->m_excludeFileQueryManager->Load(m_data->m_excludeFile));
AZStd::set<AZStd::string> outputTags = m_data->m_excludeFileQueryManager->GetTags(MatchingWildcardFile);
EXPECT_EQ(outputTags.size(), 2);
@@ -6,81 +6,19 @@
*
*/
#include <AzCore/IO/FileIO.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzTest/Utils.h>
#include <AzFramework/Application/Application.h>
namespace UnitTest
{
using namespace AZ;
class FileIOBaseRAII
{
public:
FileIOBaseRAII(AZ::IO::FileIOBase& fileIO)
: m_prevFileIO(AZ::IO::FileIOBase::GetInstance())
{
AZ::IO::FileIOBase::SetInstance(&fileIO);
}
~FileIOBaseRAII()
{
AZ::IO::FileIOBase::SetInstance(m_prevFileIO);
}
private:
AZ::IO::FileIOBase* m_prevFileIO;
};
class GenAppDescriptors
: public AllocatorsTestFixture
{
public:
void run()
{
struct Config
{
const char* platformName;
const char* configName;
const char* libSuffix;
};
ComponentApplication app;
SerializeContext serializeContext;
AZ::ComponentApplication::Descriptor::Reflect(&serializeContext, &app);
AZ::Entity::Reflect(&serializeContext);
DynamicModuleDescriptor::Reflect(&serializeContext);
AZ::Entity dummySystemEntity(AZ::SystemEntityId, "SystemEntity");
const Config config = {"Platform", "Config", "libSuffix"};
AZ::ComponentApplication::Descriptor descriptor;
if (config.libSuffix && config.libSuffix[0])
{
FakePopulateModules(descriptor, config.libSuffix);
}
const AZStd::string filename = AZStd::string::format("LYConfig_%s%s.xml", config.platformName, config.configName);
IO::FileIOStream stream(filename.c_str(), IO::OpenMode::ModeWrite);
ObjectStream* objStream = ObjectStream::Create(&stream, serializeContext, ObjectStream::ST_XML);
bool descWriteOk = objStream->WriteClass(&descriptor);
(void)descWriteOk;
AZ_Warning("ComponentApplication", descWriteOk, "Failed to write memory descriptor to application descriptor file %s!", filename.c_str());
bool entityWriteOk = objStream->WriteClass(&dummySystemEntity);
(void)entityWriteOk;
AZ_Warning("ComponentApplication", entityWriteOk, "Failed to write system entity to application descriptor file %s!", filename.c_str());
bool flushOk = objStream->Finalize();
(void)flushOk;
AZ_Warning("ComponentApplication", flushOk, "Failed finalizing application descriptor file %s!", filename.c_str());
}
void FakePopulateModules(AZ::ComponentApplication::Descriptor& desc, const char* libSuffix)
{
static const char* modules[] =
@@ -100,10 +38,44 @@ namespace UnitTest
}
};
TEST_F(GenAppDescriptors, Test)
TEST_F(GenAppDescriptors, WriteDescriptor_ToXML_Succeeds)
{
AZ::IO::LocalFileIO fileIO;
FileIOBaseRAII restoreFileIOScope(fileIO);
run();
struct Config
{
const char* platformName;
const char* configName;
const char* libSuffix;
};
AzFramework::Application app;
AZ::SerializeContext serializeContext;
AZ::ComponentApplication::Descriptor::Reflect(&serializeContext, &app);
AZ::Entity::Reflect(&serializeContext);
AZ::DynamicModuleDescriptor::Reflect(&serializeContext);
AZ::Entity dummySystemEntity(AZ::SystemEntityId, "SystemEntity");
const Config config = {"Platform", "Config", "libSuffix"};
AZ::ComponentApplication::Descriptor descriptor;
if (config.libSuffix && config.libSuffix[0])
{
FakePopulateModules(descriptor, config.libSuffix);
}
AZ::Test::ScopedAutoTempDirectory tempDirectory;
const auto filename = AZ::IO::Path(tempDirectory.GetDirectory()) /
AZStd::string::format("LYConfig_%s%s.xml", config.platformName, config.configName);
AZ::IO::FileIOStream stream(filename.c_str(), AZ::IO::OpenMode::ModeWrite);
auto objStream = AZ::ObjectStream::Create(&stream, serializeContext, AZ::ObjectStream::ST_XML);
const bool descWriteOk = objStream->WriteClass(&descriptor);
EXPECT_TRUE(descWriteOk) << "Failed to write memory descriptor to application descriptor file " << filename.c_str() << "!";
const bool entityWriteOk = objStream->WriteClass(&dummySystemEntity);
EXPECT_TRUE(entityWriteOk) << "Failed to write system entity to application descriptor file " << filename.c_str() << "!";
const bool flushOk = objStream->Finalize();
EXPECT_TRUE(flushOk) << "Failed finalizing application descriptor file " << filename.c_str() << "!";
}
}
@@ -0,0 +1,17 @@
/*
* 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 <gmock/gmock.h>
#include <AzCore/std/string/string.h>
inline testing::PolymorphicMatcher<testing::internal::StrEqualityMatcher<AZStd::string>> StrEq(const AZStd::string& str)
{
return ::testing::MakePolymorphicMatcher(testing::internal::StrEqualityMatcher<AZStd::string>(str, true, true));
}
@@ -28,6 +28,10 @@ xcb_generic_event_t* xcb_poll_for_event(xcb_connection_t* c)
{
return MockXcbInterface::Instance()->xcb_poll_for_event(c);
}
xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t cookie)
{
return MockXcbInterface::Instance()->xcb_request_check(c, cookie);
}
// ----------------------------------------------------------------------------
// xcb-xkb
@@ -39,6 +43,10 @@ xcb_xkb_use_extension_reply_t* xcb_xkb_use_extension_reply(xcb_connection_t* c,
{
return MockXcbInterface::Instance()->xcb_xkb_use_extension_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_xkb_select_events(xcb_connection_t* c, xcb_xkb_device_spec_t deviceSpec, uint16_t affectWhich, uint16_t clear, uint16_t selectAll, uint16_t affectMap, uint16_t map, const void* details)
{
return MockXcbInterface::Instance()->xcb_xkb_select_events(c, deviceSpec, affectWhich, clear, selectAll, affectMap, map, details);
}
// ----------------------------------------------------------------------------
// xkb-x11
@@ -46,7 +54,7 @@ int32_t xkb_x11_get_core_keyboard_device_id(xcb_connection_t* connection)
{
return MockXcbInterface::Instance()->xkb_x11_get_core_keyboard_device_id(connection);
}
struct xkb_keymap* xkb_x11_keymap_new_from_device(struct xkb_context* context, xcb_connection_t* connection, int32_t device_id, enum xkb_keymap_compile_flags flags)
xkb_keymap* xkb_x11_keymap_new_from_device(xkb_context* context, xcb_connection_t* connection, int32_t device_id, xkb_keymap_compile_flags flags)
{
return MockXcbInterface::Instance()->xkb_x11_keymap_new_from_device(context, connection, device_id, flags);
}
@@ -54,28 +62,58 @@ xkb_state* xkb_x11_state_new_from_device(xkb_keymap* keymap, xcb_connection_t* c
{
return MockXcbInterface::Instance()->xkb_x11_state_new_from_device(keymap, connection, device_id);
}
int xkb_x11_setup_xkb_extension(
xcb_connection_t* connection,
uint16_t major_xkb_version,
uint16_t minor_xkb_version,
xkb_x11_setup_xkb_extension_flags flags,
uint16_t* major_xkb_version_out,
uint16_t* minor_xkb_version_out,
uint8_t* base_event_out,
uint8_t* base_error_out)
{
return MockXcbInterface::Instance()->xkb_x11_setup_xkb_extension(
connection, major_xkb_version, minor_xkb_version, flags, major_xkb_version_out, minor_xkb_version_out, base_event_out,
base_error_out);
}
// ----------------------------------------------------------------------------
// xkbcommon
xkb_context* xkb_context_new(enum xkb_context_flags flags)
xkb_context* xkb_context_new(xkb_context_flags flags)
{
return MockXcbInterface::Instance()->xkb_context_new(flags);
}
void xkb_context_unref(xkb_context *context)
void xkb_context_unref(xkb_context* context)
{
return MockXcbInterface::Instance()->xkb_context_unref(context);
}
void xkb_keymap_unref(xkb_keymap *keymap)
void xkb_keymap_unref(xkb_keymap* keymap)
{
return MockXcbInterface::Instance()->xkb_keymap_unref(keymap);
}
void xkb_state_unref(xkb_state *state)
void xkb_state_unref(xkb_state* state)
{
return MockXcbInterface::Instance()->xkb_state_unref(state);
}
xkb_keysym_t xkb_state_key_get_one_sym(xkb_state *state, xkb_keycode_t key)
xkb_keysym_t xkb_state_key_get_one_sym(xkb_state* state, xkb_keycode_t key)
{
return MockXcbInterface::Instance()->xkb_state_key_get_one_sym(state, key);
}
int xkb_state_key_get_utf8(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size)
{
return MockXcbInterface::Instance()->xkb_state_key_get_utf8(state, key, buffer, size);
}
xkb_state_component xkb_state_update_mask(
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)
{
return MockXcbInterface::Instance()->xkb_state_update_mask(
state, depressed_mods, latched_mods, locked_mods, depressed_layout, latched_layout, locked_layout);
}
}
@@ -17,6 +17,7 @@
#include <xcb/xkb.h>
#undef explicit
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
#include "Printers.h"
@@ -35,6 +36,7 @@ struct xkb_keymap
struct xkb_state
{
xkb_mod_mask_t m_modifiers{};
};
class MockXcbInterface
@@ -48,7 +50,7 @@ public:
MockXcbInterface(MockXcbInterface&&) = delete;
MockXcbInterface& operator=(const MockXcbInterface&) = delete;
MockXcbInterface& operator=(MockXcbInterface&&) = delete;
~MockXcbInterface()
virtual ~MockXcbInterface()
{
self = nullptr;
}
@@ -59,22 +61,27 @@ public:
MOCK_CONST_METHOD2(xcb_connect, xcb_connection_t*(const char* displayname, int* screenp));
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));
// 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));
MOCK_CONST_METHOD3(xcb_xkb_use_extension_reply, xcb_xkb_use_extension_reply_t*(xcb_connection_t* c, xcb_xkb_use_extension_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD8(xcb_xkb_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_xkb_device_spec_t deviceSpec, uint16_t affectWhich, uint16_t clear, uint16_t selectAll, uint16_t affectMap, uint16_t map, const void* details));
// xkb-x11
MOCK_CONST_METHOD1(xkb_x11_get_core_keyboard_device_id, int32_t(xcb_connection_t* connection));
MOCK_CONST_METHOD4(xkb_x11_keymap_new_from_device, xkb_keymap*(xkb_context* context, xcb_connection_t* connection, int32_t device_id, xkb_keymap_compile_flags flags));
MOCK_CONST_METHOD3(xkb_x11_state_new_from_device, xkb_state*(xkb_keymap* keymap, xcb_connection_t* connection, int32_t device_id));
MOCK_CONST_METHOD8(xkb_x11_setup_xkb_extension, int(xcb_connection_t* connection, uint16_t major_xkb_version, uint16_t minor_xkb_version, xkb_x11_setup_xkb_extension_flags flags, uint16_t* major_xkb_version_out, uint16_t* minor_xkb_version_out, uint8_t* base_event_out, uint8_t* base_error_out));
// xkbcommon
MOCK_CONST_METHOD1(xkb_context_new, xkb_context*(xkb_context_flags flags));
MOCK_CONST_METHOD1(xkb_context_unref, void(xkb_context* context));
MOCK_CONST_METHOD1(xkb_keymap_unref, void(xkb_keymap* keymap));
MOCK_CONST_METHOD1(xkb_state_unref, void(xkb_state* state));
MOCK_CONST_METHOD2(xkb_state_key_get_one_sym, xkb_keysym_t(xkb_state *state, xkb_keycode_t key));
MOCK_CONST_METHOD2(xkb_state_key_get_one_sym, xkb_keysym_t(xkb_state* state, xkb_keycode_t key));
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));
private:
static inline MockXcbInterface* self = nullptr;
@@ -0,0 +1,25 @@
/*
* 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 "XcbBaseTestFixture.h"
namespace AzFramework
{
void XcbBaseTestFixture::SetUp()
{
using testing::Return;
using testing::_;
testing::Test::SetUp();
EXPECT_CALL(m_interface, xcb_connect(_, _))
.WillOnce(Return(&m_connection));
EXPECT_CALL(m_interface, xcb_disconnect(&m_connection))
.Times(1);
}
} // namespace AzFramework
@@ -0,0 +1,29 @@
/*
* 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 <gtest/gtest.h>
#include <xcb/xcb.h>
#include "MockXcbInterface.h"
namespace AzFramework
{
// Sets up mock behavior for the xcb library, providing an xcb_connection_t that is returned from a call to xcb_connect
class XcbBaseTestFixture
: public testing::Test
{
public:
void SetUp() override;
protected:
testing::NiceMock<MockXcbInterface> m_interface;
xcb_connection_t m_connection{};
};
} // namespace AzFramework
@@ -6,6 +6,7 @@
*
*/
#include <gmock/gmock-actions.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
@@ -13,8 +14,12 @@
#include <AzFramework/XcbApplication.h>
#include <AzFramework/XcbInputDeviceKeyboard.h>
#include <AzFramework/Input/Buses/Notifications/InputTextNotificationBus.h>
#include "MockXcbInterface.h"
#include "Matchers.h"
#include "Actions.h"
#include "XcbBaseTestFixture.h"
#include "XcbTestApplication.h"
template<typename T>
xcb_generic_event_t MakeEvent(T event)
@@ -24,26 +29,136 @@ xcb_generic_event_t MakeEvent(T event)
namespace AzFramework
{
TEST(XcbInputDeviceKeyboard, InputChannelsUpdateStateFromXcbEvents)
// Sets up default behavior for mock keyboard responses to xcb methods
class XcbInputDeviceKeyboardTests
: public XcbBaseTestFixture
{
using testing::Return;
public:
void SetUp() override
{
using testing::Return;
using testing::SetArgPointee;
using testing::_;
XcbBaseTestFixture::SetUp();
EXPECT_CALL(m_interface, xkb_context_new(XKB_CONTEXT_NO_FLAGS))
.WillOnce(Return(&m_xkbContext));
EXPECT_CALL(m_interface, xkb_context_unref(&m_xkbContext))
.Times(1);
EXPECT_CALL(m_interface, xkb_x11_keymap_new_from_device(&m_xkbContext, &m_connection, s_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS))
.WillOnce(Return(&m_xkbKeymap));
EXPECT_CALL(m_interface, xkb_keymap_unref(&m_xkbKeymap))
.Times(1);
EXPECT_CALL(m_interface, xkb_x11_state_new_from_device(&m_xkbKeymap, &m_connection, s_coreDeviceId))
.WillOnce(Return(&m_xkbState));
EXPECT_CALL(m_interface, xkb_state_unref(&m_xkbState))
.Times(1);
ON_CALL(m_interface, xkb_x11_setup_xkb_extension(&m_connection, 1, 0, XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, _, _, _, _))
.WillByDefault(DoAll(
SetArgPointee<6>(s_xkbEventCode), // Set the "base_event_out" argument to the xkbEventCode, the value to identify XKB events
Return(1)
));
constexpr unsigned int xcbXkbSelectEventsSequence = 342;
ON_CALL(m_interface, xcb_xkb_select_events(&m_connection, _, _, _, _, _, _, _))
.WillByDefault(Return(xcb_void_cookie_t{/*.sequence = */ xcbXkbSelectEventsSequence}));
ON_CALL(m_interface, xcb_request_check(&m_connection, testing::Field(&xcb_void_cookie_t::sequence, testing::Eq(xcbXkbSelectEventsSequence))))
.WillByDefault(Return(nullptr)); // indicates success
ON_CALL(m_interface, xkb_x11_get_core_keyboard_device_id(&m_connection))
.WillByDefault(Return(s_coreDeviceId));
ON_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey))
.WillByDefault(Return(XKB_KEY_a))
;
ON_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForShiftLKey))
.WillByDefault(Return(XKB_KEY_Shift_L))
;
ON_CALL(m_interface, xkb_state_update_mask(&m_xkbState, _, _, _, _, _, _))
.WillByDefault(testing::Invoke(this, &XcbInputDeviceKeyboardTests::UpdateStateMask));
ON_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForAKey, nullptr, 0))
.WillByDefault(Return(1));
ON_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithoutShift, s_keycodeForAKey, _, 2))
.WillByDefault(DoAll(
SetArgPointee<2>('a'),
Return(1)
));
ON_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithShift, s_keycodeForAKey, _, 2))
.WillByDefault(DoAll(
SetArgPointee<2>('A'),
Return(1)
));
ON_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForShiftLKey, nullptr, 0))
.WillByDefault(Return(0));
}
private:
xkb_state_component UpdateStateMask(
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)
{
state->m_modifiers = depressed_mods | locked_mods;
return {};
}
protected:
xkb_context m_xkbContext{};
xkb_keymap m_xkbKeymap{};
xkb_state m_xkbState{};
const testing::Matcher<xkb_state*> m_matchesStateWithoutShift = testing::AllOf(&m_xkbState, testing::Field(&xkb_state::m_modifiers, 0));
const testing::Matcher<xkb_state*> m_matchesStateWithShift = testing::AllOf(&m_xkbState, testing::Field(&xkb_state::m_modifiers, XCB_MOD_MASK_SHIFT));
static constexpr int32_t s_coreDeviceId{1};
static constexpr uint8_t s_xkbEventCode{85};
static constexpr xcb_keycode_t s_keycodeForAKey{38};
static constexpr xcb_keycode_t s_keycodeForShiftLKey{50};
XcbTestApplication m_application{
/*enabledGamepadsCount=*/0,
/*keyboardEnabled=*/true,
/*motionEnabled=*/false,
/*mouseEnabled=*/false,
/*touchEnabled=*/false,
/*virtualKeyboardEnabled=*/false
};
};
class InputTextNotificationListener
: public InputTextNotificationBus::Handler
{
public:
InputTextNotificationListener()
{
BusConnect();
}
MOCK_METHOD2(OnInputTextEvent, void(const AZStd::string& /*textUTF8*/, bool& /*o_hasBeenConsumed*/));
};
TEST_F(XcbInputDeviceKeyboardTests, InputChannelsUpdateStateFromXcbEvents)
{
using testing::DoAll;
using testing::Eq;
using testing::Return;
using testing::SetArgPointee;
using testing::_;
MockXcbInterface interface;
xcb_connection_t connection{};
xkb_context xkbContext{};
xkb_keymap xkbKeymap{};
xkb_state xkbState{};
const int32_t coreDeviceId{1};
constexpr xcb_keycode_t keycodeForAKey = 38;
const AZStd::array events
{
MakeEvent(xcb_key_press_event_t{
/*.response_type = */ XCB_KEY_PRESS,
/*.detail = */ keycodeForAKey,
/*.detail = */ s_keycodeForAKey,
/*.sequence = */ 0,
/*.time = */ 0,
/*.root = */ 0,
@@ -59,7 +174,7 @@ namespace AzFramework
}),
MakeEvent(xcb_key_release_event_t{
/*.response_type = */ XCB_KEY_RELEASE,
/*.detail = */ keycodeForAKey,
/*.detail = */ s_keycodeForAKey,
/*.sequence = */ 0,
/*.time = */ 0,
/*.root = */ 0,
@@ -75,71 +190,249 @@ namespace AzFramework
}),
};
EXPECT_CALL(interface, xcb_connect(_, _))
.WillOnce(Return(&connection));
EXPECT_CALL(interface, xcb_disconnect(&connection))
.Times(1);
EXPECT_CALL(interface, xkb_context_new(XKB_CONTEXT_NO_FLAGS))
.WillOnce(Return(&xkbContext));
EXPECT_CALL(interface, xkb_context_unref(&xkbContext))
.Times(1);
EXPECT_CALL(interface, xkb_x11_keymap_new_from_device(&xkbContext, &connection, coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS))
.WillOnce(Return(&xkbKeymap));
EXPECT_CALL(interface, xkb_keymap_unref(&xkbKeymap))
.Times(1);
EXPECT_CALL(interface, xkb_x11_state_new_from_device(&xkbKeymap, &connection, coreDeviceId))
.WillOnce(Return(&xkbState));
EXPECT_CALL(interface, xkb_state_unref(&xkbState))
.Times(1);
EXPECT_CALL(interface, xcb_xkb_use_extension(&connection, 1, 0));
EXPECT_CALL(interface, xcb_xkb_use_extension_reply(&connection, _, _))
.WillOnce(ReturnMalloc<xcb_xkb_use_extension_reply_t>(
/* .response_type =*/static_cast<uint8_t>(XCB_XKB_USE_EXTENSION),
/* .supported =*/ static_cast<uint8_t>(1))
);
EXPECT_CALL(interface, xkb_x11_get_core_keyboard_device_id(&connection))
.WillRepeatedly(Return(coreDeviceId));
// 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 we malloc new copies
// here
EXPECT_CALL(interface, xcb_poll_for_event(&connection))
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[0]))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[1]))
.WillOnce(Return(nullptr))
;
EXPECT_CALL(interface, xkb_state_key_get_one_sym(&xkbState, keycodeForAKey))
.WillOnce(Return(XKB_KEY_a))
.WillOnce(Return(XKB_KEY_a))
;
EXPECT_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey))
.Times(2);
Application application;
application.Start({}, {});
m_application.Start();
const InputChannel* inputChannel = InputChannelRequests::FindInputChannel(InputDeviceKeyboard::Key::AlphanumericA);
ASSERT_TRUE(inputChannel);
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Idle));
application.PumpSystemEventLoopUntilEmpty();
application.TickSystem();
application.Tick();
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Began));
application.PumpSystemEventLoopUntilEmpty();
application.TickSystem();
application.Tick();
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Ended));
}
application.Stop();
TEST_F(XcbInputDeviceKeyboardTests, TextEnteredFromXcbKeyPressEvents)
{
using testing::DoAll;
using testing::Eq;
using testing::Return;
using testing::SetArgPointee;
using testing::_;
// press a
// release a
// press shift
// press a
// release a
// release shift
const AZStd::array events
{
MakeEvent(xcb_key_press_event_t{
/*.response_type = */ XCB_KEY_PRESS,
/*.detail = */ s_keycodeForAKey,
/*.sequence = */ 0,
/*.time = */ 0,
/*.root = */ 0,
/*.event = */ 0,
/*.child = */ 0,
/*.root_x = */ 0,
/*.root_y = */ 0,
/*.event_x = */ 0,
/*.event_y = */ 0,
/*.state = */ 0,
/*.same_screen = */ 0,
/*.pad0 = */ 0
}),
MakeEvent(xcb_key_release_event_t{
/*.response_type = */ XCB_KEY_RELEASE,
/*.detail = */ s_keycodeForAKey,
/*.sequence = */ 0,
/*.time = */ 0,
/*.root = */ 0,
/*.event = */ 0,
/*.child = */ 0,
/*.root_x = */ 0,
/*.root_y = */ 0,
/*.event_x = */ 0,
/*.event_y = */ 0,
/*.state = */ 0,
/*.same_screen = */ 0,
/*.pad0 = */ 0
}),
// Pressing a modifier key will generate a key press event followed
// by a state notify event
MakeEvent(xcb_key_press_event_t{
/*.response_type = */ XCB_KEY_PRESS,
/*.detail = */ s_keycodeForShiftLKey,
/*.sequence = */ 0,
/*.time = */ 0,
/*.root = */ 0,
/*.event = */ 0,
/*.child = */ 0,
/*.root_x = */ 0,
/*.root_y = */ 0,
/*.event_x = */ 0,
/*.event_y = */ 0,
/*.state = */ 0,
/*.same_screen = */ 0,
/*.pad0 = */ 0
}),
MakeEvent(xcb_xkb_state_notify_event_t{
/*.response_type = */ s_xkbEventCode,
/*.xkbType = */ XCB_XKB_STATE_NOTIFY,
/*.sequence = */ 0,
/*.time = */ 0,
/*.deviceID = */ s_coreDeviceId,
/*.mods = */ XCB_MOD_MASK_SHIFT,
/*.baseMods = */ XCB_MOD_MASK_SHIFT,
/*.latchedMods = */ 0,
/*.lockedMods = */ 0,
/*.group = */ 0,
/*.baseGroup = */ 0,
/*.latchedGroup = */ 0,
/*.lockedGroup = */ 0,
/*.compatState = */ XCB_MOD_MASK_SHIFT,
/*.grabMods = */ XCB_MOD_MASK_SHIFT,
/*.compatGrabMods = */ XCB_MOD_MASK_SHIFT,
/*.lookupMods = */ XCB_MOD_MASK_SHIFT,
/*.compatLoockupMods = */ XCB_MOD_MASK_SHIFT,
/*.ptrBtnState = */ 0,
/*.changed = */ 0,
/*.keycode = */ s_keycodeForShiftLKey,
/*.eventType = */ XCB_KEY_PRESS,
/*.requestMajor = */ 0,
/*.requestMinor = */ 0,
}),
MakeEvent(xcb_key_press_event_t{
/*.response_type = */ XCB_KEY_PRESS,
/*.detail = */ s_keycodeForAKey,
/*.sequence = */ 0,
/*.time = */ 0,
/*.root = */ 0,
/*.event = */ 0,
/*.child = */ 0,
/*.root_x = */ 0,
/*.root_y = */ 0,
/*.event_x = */ 0,
/*.event_y = */ 0,
/*.state = */ 0,
/*.same_screen = */ 0,
/*.pad0 = */ 0
}),
MakeEvent(xcb_key_release_event_t{
/*.response_type = */ XCB_KEY_RELEASE,
/*.detail = */ s_keycodeForAKey,
/*.sequence = */ 0,
/*.time = */ 0,
/*.root = */ 0,
/*.event = */ 0,
/*.child = */ 0,
/*.root_x = */ 0,
/*.root_y = */ 0,
/*.event_x = */ 0,
/*.event_y = */ 0,
/*.state = */ 0,
/*.same_screen = */ 0,
/*.pad0 = */ 0
}),
MakeEvent(xcb_key_release_event_t{
/*.response_type = */ XCB_KEY_RELEASE,
/*.detail = */ s_keycodeForShiftLKey,
/*.sequence = */ 0,
/*.time = */ 0,
/*.root = */ 0,
/*.event = */ 0,
/*.child = */ 0,
/*.root_x = */ 0,
/*.root_y = */ 0,
/*.event_x = */ 0,
/*.event_y = */ 0,
/*.state = */ 0,
/*.same_screen = */ 0,
/*.pad0 = */ 0
}),
MakeEvent(xcb_xkb_state_notify_event_t{
/*.response_type = */ s_xkbEventCode,
/*.xkbType = */ XCB_XKB_STATE_NOTIFY,
/*.sequence = */ 0,
/*.time = */ 0,
/*.deviceID = */ s_coreDeviceId,
/*.mods = */ 0,
/*.baseMods = */ 0,
/*.latchedMods = */ 0,
/*.lockedMods = */ 0,
/*.group = */ 0,
/*.baseGroup = */ 0,
/*.latchedGroup = */ 0,
/*.lockedGroup = */ 0,
/*.compatState = */ 0,
/*.grabMods = */ 0,
/*.compatGrabMods = */ 0,
/*.lookupMods = */ 0,
/*.compatLoockupMods = */ 0,
/*.ptrBtnState = */ 0,
/*.changed = */ 0,
/*.keycode = */ s_keycodeForShiftLKey,
/*.eventType = */ XCB_KEY_RELEASE,
/*.requestMajor = */ 0,
/*.requestMinor = */ 0,
}),
};
// 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 we malloc new copies
// here
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[0])) // press a
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[1])) // release a
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[2])) // press shift
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[3])) // state notify shift is down
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[4])) // press a
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[5])) // release a
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[6])) // release shift
.WillOnce(ReturnMalloc<xcb_generic_event_t>(events[7])) // state notify shift is up
.WillRepeatedly(Return(nullptr))
;
EXPECT_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForAKey, nullptr, 0))
.Times(2);
EXPECT_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithoutShift, s_keycodeForAKey, _, 2))
.Times(1);
EXPECT_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithShift, s_keycodeForAKey, _, 2))
.Times(1);
EXPECT_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForShiftLKey, nullptr, 0))
.Times(1);
InputTextNotificationListener textListener;
EXPECT_CALL(textListener, OnInputTextEvent(StrEq("a"), _)).Times(1);
EXPECT_CALL(textListener, OnInputTextEvent(StrEq("A"), _)).Times(1);
m_application.Start();
for (int i = 0; i < 4; ++i)
{
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
}
}
} // 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/Settings/SettingsRegistry.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Application/Application.h>
namespace AzFramework
{
class XcbTestApplication
: public Application
{
public:
XcbTestApplication(AZ::u64 enabledGamepadsCount, bool keyboardEnabled, bool motionEnabled, bool mouseEnabled, bool touchEnabled, bool virtualKeyboardEnabled)
{
auto* settingsRegistry = AZ::SettingsRegistry::Get();
settingsRegistry->Set("/O3DE/InputSystem/GamepadsEnabled", enabledGamepadsCount);
settingsRegistry->Set("/O3DE/InputSystem/KeyboardEnabled", keyboardEnabled);
settingsRegistry->Set("/O3DE/InputSystem/MotionEnabled", motionEnabled);
settingsRegistry->Set("/O3DE/InputSystem/MouseEnabled", mouseEnabled);
settingsRegistry->Set("/O3DE/InputSystem/TouchEnabled", touchEnabled);
settingsRegistry->Set("/O3DE/InputSystem/VirtualKeyboardEnabled", virtualKeyboardEnabled);
}
void Start(const Descriptor& descriptor = {}, const StartupParameters& startupParameters = {}) override
{
Application::Start(descriptor, startupParameters);
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
}
};
} // namespace AzFramework
@@ -9,9 +9,13 @@
set(FILES
Actions.h
Main.cpp
Matchers.h
MockXcbInterface.cpp
MockXcbInterface.h
Printers.cpp
Printers.h
XcbBaseTestFixture.cpp
XcbBaseTestFixture.h
XcbInputDeviceKeyboardTests.cpp
XcbTestApplication.h
)