Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
*.xml
@@ -0,0 +1,436 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <errno.h> // for EACCES
#include <AzCore/Android/APKFileHandler.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/IO/Path/Path.h>
//Note: Switching on verbose logging will give you a lot of detailed information about what files are being read from the APK
// but there is a likelihood it could cause logcat to terminate with a 'buffer full' error. Restarting logcat will resume logging
// but you may lose information
#define VERBOSE_IO_LOGGING 0
#if VERBOSE_IO_LOGGING
#define FILE_IO_LOG(...) AZ_Printf("LMBR", __VA_ARGS__)
#else
#define FILE_IO_LOG(...)
#endif
namespace AZ
{
namespace Android
{
AZ::EnvironmentVariable<APKFileHandler> APKFileHandler::s_instance;
bool APKFileHandler::Create()
{
if (!s_instance)
{
s_instance = AZ::Environment::CreateVariable<APKFileHandler>(AZ::AzTypeInfo<APKFileHandler>::Name());
}
if (s_instance->IsReady()) // already created in a different module
{
return true;
}
return s_instance->Initialize();
}
void APKFileHandler::Destroy()
{
s_instance.Reset();
}
bool APKFileHandler::ShouldLoadFileToMemory(const char* filePath)
{
if (!filePath)
{
return false;
}
for (const AZStd::string& fileName : m_memFileNames)
{
if (strstr(filePath, fileName.c_str()))
{
return true;
}
}
return false;
}
MemoryBuffer* APKFileHandler::GetInMemoryFileBuffer(void* asset)
{
for (auto it = m_memFileBuffers.begin(); it != m_memFileBuffers.end(); it++)
{
if (it->m_asset == asset)
{
return &(*it);
}
}
return nullptr;
}
void APKFileHandler::RemoveInMemoryFileBuffer(void* asset)
{
for (auto it = m_memFileBuffers.begin(); it != m_memFileBuffers.end(); it++)
{
if (it->m_asset == asset)
{
m_memFileBuffers.erase(it);
break;
}
}
}
FILE* APKFileHandler::Open(const char* filename, const char* mode, AZ::u64& size)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK Open");
FILE* fileHandle = nullptr;
if (mode[0] != 'w')
{
FILE_IO_LOG("******* Attempting to open file in APK:[%s] ", filename);
AAsset* asset = nullptr;
bool loadFileToMemory = Get().ShouldLoadFileToMemory(filename);
int assetMode = loadFileToMemory ? AASSET_MODE_BUFFER : AASSET_MODE_UNKNOWN;
asset = AAssetManager_open(Utils::GetAssetManager(), Utils::StripApkPrefix(filename), assetMode);
if (asset != nullptr)
{
// the pointer returned by funopen will allow us to use fread, fseek etc
fileHandle = funopen(asset, APKFileHandler::Read, APKFileHandler::Write, APKFileHandler::Seek, APKFileHandler::Close);
if (loadFileToMemory)
{
MemoryBuffer buf;
buf.m_buffer = (char*)AAsset_getBuffer(asset);
buf.m_totalSize = AAsset_getLength(asset);
buf.m_asset = asset;
if (buf.m_buffer)
{
Get().m_memFileBuffers.push_back(buf);
}
else
{
AZ_Assert(false, "Failed to load %s to memory", filename)
}
}
// the file pointer we return from funopen can't be used to get the length of the file so we need to capture that info while we have the AAsset pointer available
size = static_cast<AZ::u64>(AAsset_getLength64(asset));
FILE_IO_LOG("File loaded successfully");
}
else
{
FILE_IO_LOG("####### Failed to open file in APK:[%s] ", filename);
}
}
return fileHandle;
}
int APKFileHandler::Read(void* asset, char* buffer, int size)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK Read");
APKFileHandler& apkHandler = Get();
if (apkHandler.m_numBytesToRead < size && apkHandler.m_numBytesToRead > 0)
{
size = apkHandler.m_numBytesToRead;
}
apkHandler.m_numBytesToRead -= size;
MemoryBuffer* buf = apkHandler.GetInMemoryFileBuffer(asset);
if (buf)
{
const char* tempBuf = buf->m_buffer + buf->m_offset;
memcpy(buffer, tempBuf, size);
return size;
}
return AAsset_read(static_cast<AAsset*>(asset), buffer, static_cast<size_t>(size));
}
int APKFileHandler::Write(void* asset, const char* buffer, int size)
{
return EACCES;
}
fpos_t APKFileHandler::Seek(void* asset, fpos_t offset, int origin)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK Seek");
MemoryBuffer* buf = Get().GetInMemoryFileBuffer(asset);
if (buf)
{
if (origin == SEEK_SET)
{
buf->m_offset = offset;
}
else if (origin == SEEK_CUR)
{
buf->m_offset += offset;
}
else if (origin == SEEK_END)
{
buf->m_offset = buf->m_totalSize - offset;
}
if (buf->m_offset > buf->m_totalSize)
{
buf->m_offset = buf->m_totalSize;
}
if (buf->m_offset < 0)
{
buf->m_offset = 0;
}
return buf->m_offset;
}
return AAsset_seek(static_cast<AAsset*>(asset), offset, origin);
}
int APKFileHandler::Close(void* asset)
{
Get().RemoveInMemoryFileBuffer(asset);
AAsset_close(static_cast<AAsset*>(asset));
return 0;
}
int APKFileHandler::FileLength(const char* filename)
{
AZ::u64 size = 0;
FILE* asset = Open(filename, "r", size);
if (asset != nullptr)
{
fclose(asset);
}
return static_cast<int>(size);
}
AZ::IO::Result APKFileHandler::ParseDirectory(const char* path, FindDirsCallbackType findCallback)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK ParseDirectory");
FILE_IO_LOG("********* About to search for file in [%s] ******* ", path);
APKFileHandler& apkHandler = Get();
DirectoryCache::const_iterator it = apkHandler.m_cachedDirectories.find(path);
if (it == apkHandler.m_cachedDirectories.end())
{
// The NDK version of the Asset Manager only returns files and not directories so we must use the Java version to get all the data we need
JNIEnv* jniEnv = JNI::GetEnv();
if (!jniEnv)
{
return AZ::IO::ResultCode::Error;
}
auto newDirectory = apkHandler.m_cachedDirectories.emplace(path, StringVector());
jstring dirPath = jniEnv->NewStringUTF(path);
jobjectArray javaFileListObject = apkHandler.m_javaInstance->InvokeStaticObjectMethod<jobjectArray>("GetFilesAndDirectoriesInPath", dirPath);
jniEnv->DeleteLocalRef(dirPath);
int numObjects = jniEnv->GetArrayLength(javaFileListObject);
bool parseResults = true;
for (int i = 0; i < numObjects; i++)
{
if (!parseResults)
{
break;
}
jstring str = static_cast<jstring>(jniEnv->GetObjectArrayElement(javaFileListObject, i));
const char* entryName = jniEnv->GetStringUTFChars(str, 0);
newDirectory.first->second.push_back(StringType(entryName));
parseResults = findCallback(entryName);
jniEnv->ReleaseStringUTFChars(str, entryName);
jniEnv->DeleteLocalRef(str);
}
jniEnv->DeleteGlobalRef(javaFileListObject);
}
else
{
bool parseResults = true;
for (int i = 0; i < it->second.size(); i++)
{
if (!parseResults)
{
break;
}
parseResults = findCallback(it->second[i].c_str());
}
}
return AZ::IO::ResultCode::Success;
}
bool APKFileHandler::IsDirectory(const char* path)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK IsDir");
APKFileHandler& apkHandler = Get();
DirectoryCache::const_iterator it = apkHandler.m_cachedDirectories.find(path);
if (it == apkHandler.m_cachedDirectories.end())
{
JNIEnv* jniEnv = JNI::GetEnv();
if (!jniEnv)
{
return false;
}
jstring dirPath = jniEnv->NewStringUTF(path);
jboolean isDir = apkHandler.m_javaInstance->InvokeStaticBooleanMethod("IsDirectory", dirPath);
jniEnv->DeleteLocalRef(dirPath);
FILE_IO_LOG("########### [%s] %s a directory ######### ", path, retVal ? "IS" : "IS NOT");
return (isDir == JNI_TRUE);
}
else
{
return (it->second.size() > 0);
}
}
bool APKFileHandler::DirectoryOrFileExists(const char* path)
{
ANDROID_IO_PROFILE_SECTION_ARGS("APK DirOrFileexists");
AZ::IO::PathView insideApkPathView(Utils::StripApkPrefix(path));
AZ::IO::FixedMaxPathString filename{ insideApkPathView.Filename().Native() };
AZ::IO::FixedMaxPathString pathToFile{ insideApkPathView.ParentPath().Native() };
bool foundFile = false;
ParseDirectory(pathToFile.c_str(), [&](const char* name)
{
if (strcasecmp(name, filename.c_str()) == 0)
{
foundFile = true;
}
return true;
});
FILE_IO_LOG("########### Directory or file [%s] %s exist ######### ", filename.c_str(), foundFile ? "DOES" : "DOES NOT");
return foundFile;
}
void APKFileHandler::SetNumBytesToRead(const size_t numBytesToRead)
{
// WARNING: This isn't a thread safe way of handling this problem, LY-65478 will fix it
APKFileHandler& apkHandler = Get();
apkHandler.m_numBytesToRead = numBytesToRead;
}
void APKFileHandler::SetLoadFilesToMemory(const char* fileNames)
{
AZStd::string names(fileNames);
size_t pos = 0;
bool stringProcessed = false;
APKFileHandler& apkHandler = Get();
while (!stringProcessed)
{
size_t newPos = names.find_first_of(',', pos);
size_t len = 0;
if (newPos == AZStd::string::npos)
{
len = newPos;
stringProcessed = true;
}
else
{
len = newPos - pos;
}
AZStd::string fileName = names.substr(pos, len);
pos = newPos + 1;
apkHandler.m_memFileNames.push_back(fileName);
}
}
APKFileHandler::APKFileHandler()
: m_javaInstance()
, m_cachedDirectories()
, m_numBytesToRead(0)
{
}
APKFileHandler::~APKFileHandler()
{
m_memFileBuffers.set_capacity(0);
m_memFileNames.set_capacity(0);
if (s_instance)
{
AZ_Assert(s_instance.IsOwner(), "The Android APK file handler instance is being destroyed by someone other than the owner.");
}
}
APKFileHandler& APKFileHandler::Get()
{
if (!s_instance)
{
s_instance = AZ::Environment::FindVariable<APKFileHandler>(AZ::AzTypeInfo<APKFileHandler>::Name());
AZ_Assert(s_instance, "The Android APK file handler is NOT ready for use! Call Create first!");
}
return *s_instance;
}
bool APKFileHandler::Initialize()
{
JniObject* apkHandler = aznew JniObject("com/amazon/lumberyard/io/APKHandler", "APKHandler");
if (!apkHandler)
{
return false;
}
m_javaInstance.reset(apkHandler);
m_javaInstance->RegisterStaticMethod("IsDirectory", "(Ljava/lang/String;)Z");
m_javaInstance->RegisterStaticMethod("GetFilesAndDirectoriesInPath", "(Ljava/lang/String;)[Ljava/lang/String;");
#if VERBOSE_IO_LOGGING
m_javaInstance->RegisterStaticField("s_debug", "Z");
m_javaInstance->SetStaticBooleanField("s_debug", JNI_TRUE);
#endif
return true;
}
bool APKFileHandler::IsReady() const
{
return (m_javaInstance != nullptr);
}
} // namespace Android
} // namespace AZ
@@ -0,0 +1,170 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Android/JNI/JNI.h>
#include <AzCore/Android/JNI/Object_fwd.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Module/Environment.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/osstring.h>
//NOTE: When running with the RAD Telemetry Gem enabled, a lot of the file IO methods will be captured for analysis.
// However developers who want to profile performance of their game when using APK's containing assets can enable the flag
// below to instrument their game in even more detail
#define AZ_ENABLED_VERBOSE_ANDROID_IO_PROFILING 0
#if AZ_ENABLED_VERBOSE_ANDROID_IO_PROFILING
#include <AzCore/Debug/Profiler.h>
#define ANDROID_IO_PROFILE_SECTION AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore)
#define ANDROID_IO_PROFILE_SECTION_ARGS(...) AZ_PROFILE_SCOPE_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, __VA_ARGS__)
#else
#define ANDROID_IO_PROFILE_SECTION
#define ANDROID_IO_PROFILE_SECTION_ARGS(...)
#endif
namespace AZ
{
namespace Android
{
struct MemoryBuffer
{
const char* m_buffer;
AAsset* m_asset;
int m_totalSize;
int m_offset;
MemoryBuffer()
{
m_offset = 0;
m_totalSize = 0;
m_buffer = nullptr;
m_asset = nullptr;
}
};
class APKFileHandler
{
public:
AZ_TYPE_INFO(APKFileHandler, "{D16233A2-A183-40FE-8CF4-ABE8D53AB5B5}")
AZ_CLASS_ALLOCATOR(APKFileHandler, AZ::OSAllocator, 0);
typedef AZStd::function<bool(const char*)> FindDirsCallbackType;
//! The preferred entry point for the construction of the global APKFileHandler instance
static bool Create();
//! Public accessor to destroy the APKFileHandler global instance
static void Destroy();
//! Opens a file using the native assets manager and maps standard c file i/o
//! \param filename The full path to the file
//! \param mode Access mode of the file to be opened, will ignore write operations
//! \param size[out] Returns the size of the file in bytes
//! \return A standard c file handle
static FILE* Open(const char* filename, const char* mode, AZ::u64& size);
//! Reads \p size bytes of a given open file. Is mapped to fread when a file is opened
//! \param asset Raw pointer to the AAsset
//! \param buffer Data blob to read into
//! \param size Number of bytes to read. When called from fread redirect, value could be ignored in favor of
//! using the internal cached version to ensure we are reading only the necessary number of bytes,
//! otherwise we would be reading more than necessary as the redirected seems to only pass in 1024.
//! \return The number of bytes read, zero on EOF, or < 0 on error.
static int Read(void* asset, char* buffer, int size);
//! Writing to files inside an APK is unsupported. Is mapped to fwrite when a file is opened in order to correctly
//! return an access error.
//! \return EACCES
static int Write(void* asset, const char* buffer, int size);
//! Same as, and is mapped to fseek when a file is opened.
static fpos_t Seek(void* asset, fpos_t offset, int origin);
//! Closes the file handle and frees it's allocated resources. Is mapped to fclose when a file is opened.
//! \return 0
static int Close(void* asset);
//! Get the size, in bytes, of a file
//! \param filename The full path to the file
//! \return The size of the file, in bytes. Returns
static int FileLength(const char* filename);
//! Uses JNI to cache the contents of a given directory at \p path while providing each entry to \p findCallback
//! \param path The full path to the desired directory
//! \param findCallback Callback used to find a specific file within a given directory
//! \return If the directory was already cached \ref AZ::IO::ResultCode::Success if the directory was already cached or
static AZ::IO::Result ParseDirectory(const char* path, FindDirsCallbackType findCallback);
//! Check to see if a given path is a directory or not
static bool IsDirectory(const char* path);
//! Checks to see if a path (file or directory) exists
static bool DirectoryOrFileExists(const char* path);
//! Set the correct number of bytes to be read when calls to fread are redirected to \ref APKFileHandler::Read
static void SetNumBytesToRead(const size_t numBytesToRead);
//! Set the names of the files that should be loaded to memory
static void SetLoadFilesToMemory(const char* fileNames);
APKFileHandler();
~APKFileHandler();
private:
MemoryBuffer* GetInMemoryFileBuffer(void* asset);
void RemoveInMemoryFileBuffer(void* asset);
bool ShouldLoadFileToMemory(const char* filePath);
typedef JNI::Internal::Object<AZ::OSAllocator> JniObject;
typedef AZ::OSStdAllocator StdAllocatorType;
typedef AZ::OSString StringType;
typedef AZStd::vector<StringType, StdAllocatorType> StringVector;
typedef AZStd::unordered_map<StringType, StringVector, AZStd::hash<StringType>, AZStd::equal_to<StringType>, StdAllocatorType> DirectoryCache;
//! Internal accessor to the global APKFileHandler instance
static APKFileHandler& Get();
AZ_DISABLE_COPY_MOVE(APKFileHandler);
bool Initialize();
bool IsReady() const;
static AZ::EnvironmentVariable<APKFileHandler> s_instance; //!< Reference to the global APK file handler object, created in the AndroidEnv
AZStd::vector<MemoryBuffer> m_memFileBuffers;
AZStd::vector<AZStd::string> m_memFileNames;
AZStd::unique_ptr<JniObject> m_javaInstance; //!< JNI instance of the com.amazon.lumberyard.io.APKHandler Java object
DirectoryCache m_cachedDirectories; //!< Cache of directories and their respective files already found through previous JNI calls
size_t m_numBytesToRead; //!< Temp cache of the correct number of bytes to read when fread is called on an asset
};
} // namespace Android
} // namespace AZ
@@ -0,0 +1,420 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/Trace.h>
#include <AzCore/Android/AndroidEnv.h>
#include <AzCore/Android/APKFileHandler.h>
#include <AzCore/Android/JNI/Object.h>
#include <android/configuration.h>
namespace AZ
{
namespace Android
{
static const char* s_loadClassMethodName = "loadClass";
pthread_key_t AndroidEnv::s_jniEnvKey;
AZ::EnvironmentVariable<AndroidEnv*> AndroidEnv::s_instance;
// ----
// AndroidEnv (public)
// ----
////////////////////////////////////////////////////////////////
// static
AndroidEnv* AndroidEnv::Get()
{
if (!s_instance)
{
s_instance = AZ::Environment::FindVariable<AndroidEnv*>(AZ::AzTypeInfo<AndroidEnv>::Name());
AZ_Assert(s_instance, "The Android environment is NOT ready for use! Call Create first!");
}
return *s_instance;
}
////////////////////////////////////////////////////////////////
// static
bool AndroidEnv::Create(const Descriptor& descriptor)
{
if (!s_instance)
{
s_instance = AZ::Environment::CreateVariable<AndroidEnv*>(AZ::AzTypeInfo<AndroidEnv>::Name());
(*s_instance) = aznew AndroidEnv();
}
if ((*s_instance)->IsReady()) // already created in a different module
{
return true;
}
return (*s_instance)->Initialize(descriptor);
}
////////////////////////////////////////////////////////////////
// static
void AndroidEnv::Destroy()
{
if (s_instance)
{
if (s_instance.IsOwner())
{
(*s_instance)->Cleanup();
delete (*s_instance);
}
s_instance.Reset();
}
else
{
AZ_Assert(false, "The Android environment is NOT ready for use! Call Create first!");
}
}
// ----
////////////////////////////////////////////////////////////////
JNIEnv* AndroidEnv::GetJniEnv() const
{
JNIEnv* jniEnv = static_cast<JNIEnv*>(pthread_getspecific(s_jniEnvKey));
if (!jniEnv)
{
jint status = m_jvm->GetEnv((void **) &jniEnv, JNI_VERSION_1_6);
if (status == JNI_EDETACHED)
{
AZ_TracePrintf("AndroidEnv", "JNI Env not attached to the VM");
if (m_jvm->AttachCurrentThread(&jniEnv, NULL) != JNI_OK)
{
AZ_Assert(false, "Failed to attach tread to the JVM");
return nullptr;
}
}
pthread_setspecific(s_jniEnvKey, jniEnv);
}
return jniEnv;
}
////////////////////////////////////////////////////////////////
const char* AndroidEnv::GetObbFileName(bool mainFile) const
{
return (mainFile ? m_mainObbFileName.c_str() : m_patchObbFileName.c_str());
}
////////////////////////////////////////////////////////////////
void AndroidEnv::UpdateConfiguration()
{
if (m_ownsConfiguration)
{
AConfiguration_fromAssetManager(m_configuration, m_assetManager);
}
}
////////////////////////////////////////////////////////////////
jclass AndroidEnv::LoadClass(const char *classPath)
{
JNIEnv* jniEnv = GetJniEnv();
if (!jniEnv)
{
return nullptr;
}
jstring classString = jniEnv->NewStringUTF(classPath);
if (!classString || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to convert cstring %s to jstring", classPath);
jniEnv->ExceptionDescribe();
return nullptr;
}
jclass returnClass = m_classLoader->InvokeObjectMethod<jclass>(s_loadClassMethodName, classString);
jniEnv->DeleteLocalRef(classString);
return returnClass;
}
// ----
// AndroidEnv (private)
// ----
////////////////////////////////////////////////////////////////
// static
void AndroidEnv::DestroyJniEnv(void *threadData)
{
JNIEnv* jniEnv = static_cast<JNIEnv*>(threadData);
if (jniEnv)
{
JavaVM *javaVm = nullptr;
jniEnv->GetJavaVM(&javaVm);
javaVm->DetachCurrentThread();
pthread_setspecific(s_jniEnvKey, nullptr);
}
}
// ----
////////////////////////////////////////////////////////////////
AndroidEnv::AndroidEnv()
: m_jvm(nullptr)
, m_activityRef(nullptr)
, m_activityClass(nullptr)
, m_classLoader()
, m_getClassNameMethod(nullptr)
, m_getSimpleClassNameMethod(nullptr)
, m_assetManager(nullptr)
, m_configuration(nullptr)
, m_window(nullptr)
, m_appPrivateStoragePath()
, m_appPublicStoragePath()
, m_obbStoragePath()
, m_mainObbFileName()
, m_patchObbFileName()
, m_packageName()
, m_appVersionCode(0)
, m_ownsActivityRef(false)
, m_ownsConfiguration(false)
, m_isReady(false)
, m_isRunning(false)
{
}
////////////////////////////////////////////////////////////////
AndroidEnv::~AndroidEnv()
{
if (s_instance)
{
AZ_Assert(s_instance.IsOwner(), "The Android Environment instance is being destroyed by someone other than the owner.");
}
}
////////////////////////////////////////////////////////////////
bool AndroidEnv::Initialize(const Descriptor& descriptor)
{
m_jvm = descriptor.m_jvm;
m_assetManager = descriptor.m_assetManager;
m_configuration = descriptor.m_configuration;
m_appPrivateStoragePath = descriptor.m_appPrivateStoragePath;
m_appPublicStoragePath = descriptor.m_appPublicStoragePath;
m_obbStoragePath = descriptor.m_obbStoragePath;
if (!m_configuration)
{
m_configuration = AConfiguration_new();
AConfiguration_fromAssetManager(m_configuration, m_assetManager);
m_ownsConfiguration = true;
}
int result = pthread_key_create(&s_jniEnvKey, DestroyJniEnv);
if (result)
{
AZ_Assert(false, "Something went wrong calling pthread_key_create... Error code: %d", result);
return false;
}
JNIEnv* jniEnv = GetJniEnv();
if (!jniEnv)
{
AZ_Error("AndroidEnv", false, "Failed to get JNIEnv* on thread to initialize the AndroidEnv instance");
return false;
}
if (!LoadClassNameMethods(jniEnv))
{
return false;
}
jobjectRefType refType = jniEnv->GetObjectRefType(descriptor.m_activityRef);
if (refType == JNIGlobalRefType)
{
m_activityRef = descriptor.m_activityRef;
}
else if (refType == JNILocalRefType)
{
m_activityRef = static_cast<jclass>(jniEnv->NewGlobalRef(descriptor.m_activityRef));
if (!m_activityRef || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to construct a global reference to the activity instance");
HANDLE_JNI_EXCEPTION(jniEnv);
return false;
}
m_ownsActivityRef = true;
}
else
{
AZ_Error("AndroidEnv", false, "Unable to use 'activityRef' argument for global ref construction");
return false;
}
jclass activityClass = jniEnv->GetObjectClass(m_activityRef);
m_activityClass = static_cast<jclass>(jniEnv->NewGlobalRef(activityClass));
if (!m_activityClass || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to construct a global reference to the activity class");
HANDLE_JNI_EXCEPTION(jniEnv);
jniEnv->DeleteLocalRef(activityClass);
return false;
}
jniEnv->DeleteLocalRef(activityClass);
if (!CacheActivityData(jniEnv))
{
return false;
}
if (m_obbStoragePath.empty())
{
AZ::OSString relPath = AZ::OSString::format("/data/%s/files", m_packageName.c_str());
AZ_Assert(m_appPublicStoragePath.find(relPath) != AZ::OSString::npos,
"Public application storage path appears to be invalid. The OBB path may be incorrect and lead to unexpected results.");
AZ::OSString publicAndroidRoot = m_appPublicStoragePath.substr(0, m_appPublicStoragePath.length() - relPath.length());
m_obbStoragePath = AZ::OSString::format("%s/obb/%s", publicAndroidRoot.c_str(), m_packageName.c_str());
}
m_mainObbFileName = AZ::OSString::format("main.%d.%s.obb", m_appVersionCode, m_packageName.c_str());
m_patchObbFileName = AZ::OSString::format("patch.%d.%s.obb", m_appVersionCode, m_packageName.c_str());
AZ_TracePrintf("AndroidEnv", "Application private storage path = %s", m_appPrivateStoragePath.c_str());
AZ_TracePrintf("AndroidEnv", "Application public storage path = %s", m_appPublicStoragePath.c_str());
AZ_TracePrintf("AndroidEnv", "Application OBB path = %s", m_obbStoragePath.c_str());
AZ_TracePrintf("AndroidEnv", "Main OBB file name = %s", m_mainObbFileName.c_str());
AZ_TracePrintf("AndroidEnv", "Patch OBB file name = %s", m_patchObbFileName.c_str());
if (!APKFileHandler::Create())
{
AZ_Error("AndroidEnv", false, "Failed to construct the global APK file handler");
return false;
}
m_isReady = true;
return true;
}
////////////////////////////////////////////////////////////////
void AndroidEnv::Cleanup()
{
if (m_ownsActivityRef)
{
JNI::DeleteRef(m_activityRef);
}
JNI::DeleteRef(m_activityClass);
if (m_ownsConfiguration)
{
AConfiguration_delete(m_configuration);
}
APKFileHandler::Destroy();
}
////////////////////////////////////////////////////////////////
bool AndroidEnv::LoadClassNameMethods(JNIEnv* jniEnv)
{
const char* javaClassPath = "java/lang/Class";
const char* getNameMethodName = "getName";
const char* getSimpleNameMethodName = "getSimpleName";
const char* getNameMethodSignature = "()Ljava/lang/String;";
// since we are requesting a system class, it should be safe to use FindClass instead
// of the ClassLoader.
jclass javaClass = jniEnv->FindClass(javaClassPath);
if (!javaClass || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to find class %s from the JNI environment", javaClassPath);
HANDLE_JNI_EXCEPTION(jniEnv);
return false;
}
m_getClassNameMethod = jniEnv->GetMethodID(javaClass, getNameMethodName, getNameMethodSignature);
if (!m_getClassNameMethod || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to find method %s with signature %s in class %s", getNameMethodName, getNameMethodSignature, javaClassPath);
HANDLE_JNI_EXCEPTION(jniEnv);
jniEnv->DeleteLocalRef(javaClass);
return false;
}
m_getSimpleClassNameMethod = jniEnv->GetMethodID(javaClass, getSimpleNameMethodName, getNameMethodSignature);
if (!m_getSimpleClassNameMethod || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to find method %s with signature %s in class %s", getSimpleNameMethodName, getNameMethodSignature, javaClassPath);
HANDLE_JNI_EXCEPTION(jniEnv);
jniEnv->DeleteLocalRef(javaClass);
return false;
}
jniEnv->DeleteLocalRef(javaClass);
return true;
}
////////////////////////////////////////////////////////////////
bool AndroidEnv::CacheActivityData(JNIEnv* jniEnv)
{
JniObject activityObject(m_activityClass, m_activityRef);
activityObject.RegisterMethod("GetPackageName", "()Ljava/lang/String;");
activityObject.RegisterMethod("GetAppVersionCode", "()I");
activityObject.RegisterMethod("getClassLoader", "()Ljava/lang/ClassLoader;");
m_packageName = activityObject.InvokeStringMethod("GetPackageName");
m_appVersionCode = activityObject.InvokeIntMethod("GetAppVersionCode");
// construct the global class loader object
jobject classLoaderRef = activityObject.InvokeObjectMethod<jobject>("getClassLoader");
if (!classLoaderRef)
{
AZ_Error("AndroidEnv", false, "Failed to retrieve the class loader from the activity");
HANDLE_JNI_EXCEPTION(jniEnv);
return false;
}
jclass localClassLoaderClass = jniEnv->GetObjectClass(classLoaderRef);
if (!localClassLoaderClass || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to get jclass from ClassLoader");
HANDLE_JNI_EXCEPTION(jniEnv);
return false;
}
jclass classLoaderClass = static_cast<jclass>(jniEnv->NewGlobalRef(localClassLoaderClass));
if (!classLoaderClass || jniEnv->ExceptionCheck())
{
AZ_Error("AndroidEnv", false, "Failed to create a global reference to the class loader");
HANDLE_JNI_EXCEPTION(jniEnv);
jniEnv->DeleteLocalRef(localClassLoaderClass);
return false;
}
jniEnv->DeleteLocalRef(localClassLoaderClass);
m_classLoader.reset(aznew JniObject(classLoaderClass, classLoaderRef, true));
m_classLoader->RegisterMethod(s_loadClassMethodName, "(Ljava/lang/String;)Ljava/lang/Class;");
return true;
}
}
}
@@ -0,0 +1,222 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Module/Environment.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/osstring.h>
#include <jni.h>
#include <pthread.h>
struct AAssetManager;
struct ANativeWindow;
struct AConfiguration;
namespace AZ
{
namespace Android
{
namespace JNI
{
namespace Internal
{
template<typename Allocator>
class Object;
template<typename StringType>
class ClassName;
} // namespace Internal
} // namespace JNI
class AndroidEnv
{
public:
AZ_TYPE_INFO(AndroidEnv, "{E51A8876-7A26-4CB1-BA88-394A128728C7}")
AZ_CLASS_ALLOCATOR(AndroidEnv, AZ::OSAllocator, 0);
//! Creation POD for the AndroidEnv
struct Descriptor
{
Descriptor()
: m_jvm(nullptr)
, m_activityRef(nullptr)
, m_assetManager(nullptr)
, m_configuration(nullptr)
, m_appPrivateStoragePath()
, m_appPublicStoragePath()
, m_obbStoragePath()
{
}
JavaVM* m_jvm; //!< Global pointer to the Java virtual machine
jobject m_activityRef; //!< Local or global reference to the activity instance
AAssetManager* m_assetManager; //!< Global pointer to the Android asset manager, used for APK file i/o
AConfiguration* m_configuration; //!< Global pointer to the configuration of the device, e.g. orientation, screen density, locale, etc.
AZ::OSString m_appPrivateStoragePath; //!< Access restricted location. E.G. /data/data/<package_name>/files
AZ::OSString m_appPublicStoragePath; //!< Public storage specifically for the application. E.G. <public_storage>/Android/data/<package_name>/files
AZ::OSString m_obbStoragePath; //!< Public storage specifically for the application's obb files. E.G. <public_storage>/Android/obb/<package_name>/files
};
//! Public accessor to the global AndroidEnv instance
static AndroidEnv* Get();
//! The preferred entry point for the construction of the global AndroidEnv instance
static bool Create(const Descriptor& descriptor);
//! Public accessor to destroy the AndroidEnv global instance
static void Destroy();
// ----
//! Request a thread specific JNIEnv pointer from the JVM.
//! \return A pointer to the JNIEnv on the current thread.
JNIEnv* GetJniEnv() const;
//! Request the global reference to the activity class
jclass GetActivityClassRef() const { return m_activityClass; }
//! Request the global reference to the activity instance
jobject GetActivityRef() const { return m_activityRef; }
//! Get the global pointer to the Android asset manager, which is used for APK file i/o.
AAssetManager* GetAssetManager() const { return m_assetManager; }
//! Get the global pointer to the device/application configuration,
AConfiguration* GetConfiguration() const { return m_configuration; }
//! Set the global pointer to the Android window surface.
void SetWindow(ANativeWindow* window) { m_window = window; }
//! Get the global pointer to the Android window surface
ANativeWindow* GetWindow() const { return m_window; }
//! Get the hidden internal storage, typically this is where the application is installed on the device.
//! e.g. /data/data/<package_name/files
const char* GetAppPrivateStoragePath() const { return m_appPrivateStoragePath.c_str(); }
//! Get the application specific directory for public storage.
//! e.g. <public_storage>/Android/data/<package_name/files
const char* GetAppPublicStoragePath() const { return m_appPublicStoragePath.c_str(); }
//! Get the application specific directory for obb files.
//! e.g. <public_storage>/Android/obb/<package_name/files
const char* GetObbStoragePath() const { return m_obbStoragePath.c_str(); }
//! Get the dot separated package name for the current application.
//! e.g. com.lumberyard.samples for SamplesProject
const char* GetPackageName() const { return m_packageName.c_str(); }
//! Get the app version code (android:versionCode in the manifest).
int GetAppVersionCode() const { return m_appVersionCode; }
//! Get the filename of the obb. This doesn't include the path to the obb folder.
const char* GetObbFileName(bool mainFile) const;
//! Check if the AndroidEnv has been initialized
bool IsReady() const { return m_isReady; }
//! Set wheather or not the application should be running
void SetIsRunning(bool isRunning) { m_isRunning = isRunning; }
//! Check if the application has been backgrounded (false) or not (true)
bool IsRunning() const { return m_isRunning; }
//! If the AndroidEnv owns the native configuration, it will be updated with the latest configuration
//! information, otherwise nothing will happen.
void UpdateConfiguration();
//! Loads a Java class as opposed to attempting to find a loaded class from the call stack.
//! \param classPath The fully qualified forward slash separated Java class path.
//! \return A global reference to the desired jclass. Caller is responsible for making a
//! call to DeleteGlobalJniRef when the jclass is no longer needed.
jclass LoadClass(const char* classPath);
private:
template<typename StringType>
friend class JNI::Internal::ClassName;
typedef JNI::Internal::Object<OSAllocator> JniObject; //!< Internal usage of \ref AZ::Android::JNI::Internal::Object that uses the OSAllocator
//! Callback for when a thread exists to detach the jni env from the thread
//! \param threadData Expected to be the JNIEnv pointer
static void DestroyJniEnv(void* threadData);
// ----
AndroidEnv();
~AndroidEnv();
AZ_DISABLE_COPY_MOVE(AndroidEnv);
//! Public global accessor to the android application environment
//! \param descriptor
bool Initialize(const Descriptor& descriptor);
//! Handle the deletion of the global jni references
void Cleanup();
//! Finds the java/lang/Class jclass to get the method IDs to getName and getSimpleName
//! \return True if successfully, False otherwise
bool LoadClassNameMethods(JNIEnv* jniEnv);
//! Calls some java methods on the activity instance and constructs the class loader
//! \return True if successfully, False otherwise
bool CacheActivityData(JNIEnv* jniEnv);
// ----
static pthread_key_t s_jniEnvKey; //!< Thread key for accessing the thread specific jni env pointers
static AZ::EnvironmentVariable<AndroidEnv*> s_instance; //!< Reference to the global object, created in the main function (AndroidLauncher)
JavaVM* m_jvm; //!< Mostly used for [de/a]ttaching JNIEnv pointers to threads
jobject m_activityRef; //!< Reference to the global instance of the current activity object, used for instance method invocation, field access
jclass m_activityClass; //!< Reference to the global instance of the current activity class, used for method / field extraction, static method invocation
AZStd::unique_ptr<JniObject> m_classLoader; //!< Class loader instance, used for finding Java classes on any thread
jmethodID m_getClassNameMethod; //!< Method ID for getName from java/lang/Class which returns a fully qualified dot separated Java class path
jmethodID m_getSimpleClassNameMethod; //!< Method ID for getSimpleName from java/lang/Class which returns just the class name from a Java class path
AAssetManager* m_assetManager; //!< Global pointer to the Android asset manager, used for APK file i/o
AConfiguration* m_configuration; //!< Global pointer to the configuration of the device, e.g. orientation, screen density, locale, etc.
ANativeWindow* m_window; //!< Global pointer to the window surface created by Android, used for creating GL contexts
AZ::OSString m_appPrivateStoragePath; //!< Access restricted location. E.G. /data/data/<package_name>/files
AZ::OSString m_appPublicStoragePath; //!< Public storage specifically for the application. E.G. <public_storage>/Android/data/<package_name>/files
AZ::OSString m_obbStoragePath; //!< Public storage specifically for the application's obb files. E.G. <public_storage>/Android/obb/<package_name>/files
AZ::OSString m_mainObbFileName; //!< File name for the main OBB
AZ::OSString m_patchObbFileName; //!< File name for the patch OBB
AZ::OSString m_packageName; //!< The dot separated package id of the application
int m_appVersionCode; //!< The version code of the app (android:versionCode in the AndroidManifest.xml)
bool m_ownsActivityRef; //!< For when a local activity ref is passed into the construction and needs to be cleaned up
bool m_ownsConfiguration; //!< For when no configuration is passed into the construction and needs to be cleaned up
bool m_isReady; //!< Set only once the object has been successfully constructed
bool m_isRunning; //!< Internal flag indicating if the application is running, mainly used to determine if we shoudl be blocking on the event pump while paused
};
} // namespace Android
} // namespace AZ
@@ -0,0 +1,89 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <android/api-level.h>
#include <AzCore/Android/Utils.h>
// the following defines provide cross compatibility between NDK and header versions as they
// were only officially added to the unified headers in NDK r14
#ifndef __ANDROID_API_K__
#define __ANDROID_API_K__ 19
#endif
#ifndef __ANDROID_API_L__
#define __ANDROID_API_L__ 21
#endif
#ifndef __ANDROID_API_L_MR1__
#define __ANDROID_API_L_MR1__ 22
#endif
#ifndef __ANDROID_API_M__
#define __ANDROID_API_M__ 23
#endif
#ifndef __ANDROID_API_N__
#define __ANDROID_API_N__ 24
#endif
#ifndef __ANDROID_API_N_MR1__
#define __ANDROID_API_N_MR1__ 25
#endif
#ifndef __ANDROID_API_O__
#define __ANDROID_API_O__ 26
#endif
#ifndef __ANDROID_API_O_MR1__
#define __ANDROID_API_O_MR1__ 27
#endif
#ifndef __ANDROID_API_P__
#define __ANDROID_API_P__ 28
#endif
#ifndef __ANDROID_API_Q__
#define __ANDROID_API_Q__ 29
#endif
namespace AZ
{
namespace Android
{
//! Supported API level codes for runtime checks
enum class ApiLevel : unsigned char
{
KitKat = __ANDROID_API_K__,
Lollipop = __ANDROID_API_L__,
Lollipop_mr1 = __ANDROID_API_L_MR1__,
Marshmallow = __ANDROID_API_M__,
Nougat = __ANDROID_API_N__,
Nougat_mr1 = __ANDROID_API_N_MR1__,
Oreo = __ANDROID_API_O__,
Oreo_mr1 = __ANDROID_API_O_MR1__,
Pie = __ANDROID_API_P__,
Ten = __ANDROID_API_Q__,
};
//! Request the OS runtime API level of the device
AZ_INLINE ApiLevel GetRuntimeApiLevel()
{
AConfiguration* config = Utils::GetConfiguration();
ApiLevel sdkVersion = static_cast<ApiLevel>(AConfiguration_getSdkVersion(config));
AZ_Assert(sdkVersion >= ApiLevel::KitKat, "The Android runtime API level detected (%d) is unsupported", sdkVersion);
return sdkVersion;
}
}
}
@@ -0,0 +1,92 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Debug/Trace.h>
#include <AzCore/Android/AndroidEnv.h>
#include <AzCore/Android/JNI/Internal/JStringUtils.h>
namespace AZ { namespace Android { namespace JNI { namespace Internal
{
//! \brief Utility for getting the Java class names
//! \tparam StringType The type of string that should be return during generation. Defaults to AZStd::string
template<typename StringType = AZStd::string>
class ClassName
{
public:
//! Get the fully qualified forward slash separated Java class path of Java class ref.
//! e.g. android.app.NativeActivity ==> android/app/NativeActivity
//! \param classRef A valid reference to a java class
//! \return A copy of the class name
static StringType GetName(jclass classRef)
{
StringType className;
AndroidEnv* androidEnv = AndroidEnv::Get();
AZ_Assert(androidEnv, "Attempting to use the AndroidEnv before it's created");
if (androidEnv)
{
className = GetNameImpl(classRef, androidEnv->m_getClassNameMethod);
}
return className;
}
//! Get just the name of the Java class from a Java class ref.
//! e.g. android.app.NativeActivity ==> NativeActivity
//! \param classRef A valid reference to a java class
//! \return A copy of the class name
static StringType GetSimpleName(jclass classRef)
{
StringType className;
AndroidEnv* androidEnv = AndroidEnv::Get();
AZ_Assert(androidEnv, "Attempting to use the AndroidEnv before it's created");
if (androidEnv)
{
className = GetNameImpl(classRef, androidEnv->m_getSimpleClassNameMethod);
}
return className;
}
private:
static StringType GetNameImpl(jclass classRef, jmethodID methodId)
{
JNIEnv* jniEnv = GetEnv();
if (!jniEnv)
{
AZ_Error("JNI::ClassName", false, "Failed to get JNIEnv* on thread on call to GetClassNameImpl");
return StringType();
}
jstring rawStringValue = static_cast<jstring>(jniEnv->CallObjectMethod(classRef, methodId));
if (!rawStringValue || jniEnv->ExceptionCheck())
{
AZ_Error("JNI::ClassName", false, "Failed to invoke a GetName variant method on class Unknown");
HANDLE_JNI_EXCEPTION(jniEnv);
return StringType();
}
StringType className = ConvertJstringToStringImpl<StringType>(rawStringValue);
jniEnv->DeleteLocalRef(rawStringValue);
return className;
}
};
} // namespace Internal
} // namespace JNI
} // namespace Android
} // namespace AZ
@@ -0,0 +1,37 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <jni.h>
namespace AZ { namespace Android { namespace JNI { namespace Internal
{
//! Converts a jstring to a string type
//! \param stringValue A local or global reference to a jstring object
//! \return A copy of the converted string
template<typename StringType>
StringType ConvertJstringToStringImpl(jstring stringValue);
//! Converts a string to a jstring
//! \param stringValue The native string value to be converted
//! \return A global reference to the converted jstring. The caller is responsible for
//! deleting it when no longer needed
template<typename StringType>
jstring ConvertStringToJstringImpl(const StringType& stringValue);
} // namespace Internal
} // namespace JNI
} // namespace Android
} // namespace AZ
#include <AzCore/Android/JNI/Internal/JStringUtils_impl.h>
@@ -0,0 +1,83 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/Trace.h>
#include <AzCore/Android/JNI/JNI.h>
namespace AZ { namespace Android { namespace JNI { namespace Internal
{
//! Converts a jstring to a string type
//! \param stringValue A local or global reference to a jstring object
//! \return A copy of the converted string
template<typename StringType>
StringType ConvertJstringToStringImpl(jstring stringValue)
{
JNIEnv* jniEnv = GetEnv();
if (!jniEnv)
{
AZ_Error("AZ::Android::JNI", false, "Failed to get JNIEnv* on thread for jstring conversion");
return StringType();
}
const char* convertedStringValue = jniEnv->GetStringUTFChars(stringValue, nullptr);
if (!convertedStringValue || jniEnv->ExceptionCheck())
{
AZ_Error("AZ::Android::JNI", false, "Failed to convert a jstring to cstring");
HANDLE_JNI_EXCEPTION(jniEnv);
return StringType();
}
StringType localCopy(convertedStringValue);
jniEnv->ReleaseStringUTFChars(stringValue, convertedStringValue);
return localCopy;
}
//! Converts a string to a jstring
//! \param stringValue The native string value to be converted
//! \return A global reference to the converted jstring. The caller is responsible for
//! deleting it when no longer needed
template<typename StringType>
AZ_INLINE jstring ConvertStringToJstringImpl(const StringType& stringValue)
{
JNIEnv* jniEnv = GetEnv();
if (!jniEnv)
{
AZ_Error("AZ::Android::JNI", false, "Failed to get JNIEnv* on thread for jstring conversion");
return nullptr;
}
jstring localRef = jniEnv->NewStringUTF(stringValue.c_str());
if (!localRef || jniEnv->ExceptionCheck())
{
AZ_Error("AZ::Android::JNI", false, "Failed to convert the cstring to jstring");
HANDLE_JNI_EXCEPTION(jniEnv);
return nullptr;
}
jstring globalRef = static_cast<jstring>(jniEnv->NewGlobalRef(localRef));
if (!globalRef || jniEnv->ExceptionCheck())
{
AZ_Error("AZ::Android::JNI", false, "Failed to create a global reference to the return jstring");
HANDLE_JNI_EXCEPTION(jniEnv);
jniEnv->DeleteLocalRef(localRef);
return nullptr;
}
jniEnv->DeleteLocalRef(localRef);
return globalRef;
}
} // namespace Internal
} // namespace JNI
} // namespace Android
} // namespace AZ
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,204 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/Trace.h>
#include <AzCore/Android/JNI/Internal/ClassName.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
namespace Internal
{
template<typename StringType>
StringType GetTypeSignature(jobject value)
{
StringType signature("");
AZ_Error("JNI::Signature", value, "Call to GetTypeSignature with null jobject");
if (value)
{
JNIEnv* jniEnv = GetEnv();
AZ_Assert(jniEnv, "Failed to get JNIEnv* on thread for get signature call");
if (jniEnv)
{
jclass objectClass = jniEnv->GetObjectClass(value);
StringType typeSig = ClassName<StringType>::GetName(objectClass);
signature.reserve(typeSig.size() + 3);
signature.append("L");
signature.append(typeSig);
signature.append(";");
jniEnv->DeleteLocalRef(objectClass);
AZStd::replace(signature.begin(), signature.end(), '.', '/');
}
}
return signature;
}
template<typename StringType>
StringType GetTypeSignature(jobjectArray value)
{
StringType signature("");
AZ_Error("JNI::Signature", value, "Call to GetTypeSignature with null jobjectArray");
if (value)
{
JNIEnv* jniEnv = GetEnv();
AZ_Assert(jniEnv, "Failed to get JNIEnv* on thread for get signature call");
if (jniEnv)
{
jobject element = jniEnv->GetObjectArrayElement(value, 0);
if (!element || jniEnv->ExceptionCheck())
{
AZ_Error("JNI::Signature", false, "Unable to determine jobject array type");
HANDLE_JNI_EXCEPTION(jniEnv);
}
else
{
signature.append("[");
signature.append(GetTypeSignature<StringType>(element));
jniEnv->DeleteLocalRef(element);
}
}
}
return signature;
}
template<typename StringType, typename Type>
bool CompareTypeSignature(const StringType& baseSignature, Type param)
{
return (baseSignature.compare(GetTypeSignature(param)) == 0);
}
template<typename StringType>
bool CompareTypeSignature(const StringType& baseSignature, jobject param)
{
bool result = (!baseSignature.empty() && param);
if (result)
{
// check if the baseSignature is malformed e.g. doesn't start with 'L' or end with ';'
if (baseSignature[0] != 'L' || baseSignature[baseSignature.length() - 1] != ';')
{
return false;
}
// strip the preceding 'L' and trailing ';' from the class path
StringType classPath = baseSignature.substr(1, baseSignature.length() - 2);
if (JNIEnv* jniEnv = GetEnv())
{
// since it's valid to pass a derived java class through JNI we will need
// to check if the argument is an instance of the specified signature to
// accurately validate the signature
jclass signatureClass = LoadClass(classPath.c_str());
if (!signatureClass)
{
AZ_Assert(false, "Unable to load class in signature %s", classPath.c_str());
return false;
}
result = (jniEnv->IsInstanceOf(param, signatureClass) == JNI_TRUE);
DeleteRef(signatureClass);
}
else
{
result = false;
}
}
return result;
}
template<typename StringType>
bool CompareTypeSignature(const StringType& baseSignature, jobjectArray param)
{
bool result = (!baseSignature.empty() && param);
if (result)
{
// check if the baseSignature is malformed e.g. doesn't start with '['
if (baseSignature[0] != '[')
{
return false;
}
// strip the preceding '['
StringType typeSignature = baseSignature.substr(1, baseSignature.length() - 1);
if (JNIEnv* jniEnv = GetEnv())
{
jobject javaObject = jniEnv->GetObjectArrayElement(static_cast<jobjectArray>(param), 0);
result = CompareTypeSignature(typeSignature, javaObject);
DeleteRef(javaObject);
}
else
{
result = false;
}
}
return result;
}
} // namespace Internal
template<typename StringType>
template<typename Type, typename... Args>
bool Signature<StringType>::ValidateImpl(Type firstParam, Args&&... parameters)
{
const char* signature = m_signature.c_str();
const char* currentSignature = &(signature[m_currentIndex]);
int paramLength;
// extract the fully qualified class path for java objects
if (currentSignature[0] == 'L' || (strncmp(currentSignature, "[L", 2) == 0))
{
int endIndex = m_signature.find(';', m_currentIndex);
if (endIndex == StringType::npos)
{
AZ_Assert(false, "The base signature supplied (%s) for validation is malformed", m_signature.c_str());
return false;
}
paramLength = (endIndex - m_currentIndex) + 1; // +1 to include the trailing semicolon
}
// otherwise just extract the primitive type char(s)
else
{
paramLength = ((currentSignature[0] == '[') ? 2 : 1); // primitive types are 1 character signatures, arrays are 2
}
// extract the parameter signature and compare the value
StringType paramSignature = m_signature.substr(m_currentIndex, paramLength);
if (!Internal::CompareTypeSignature(paramSignature, firstParam))
{
return false;
}
m_currentIndex = m_currentIndex + paramLength;
if (m_currentIndex >= m_signatureLength)
{
return false;
}
return ValidateImpl(AZStd::forward<Args>(parameters)...);
}
} // namespace JNI
} // namespace Android
} // namespace AZ
@@ -0,0 +1,109 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Android/JNI/JNI.h>
#include <AzCore/Android/JNI/Internal/JStringUtils.h>
#include <AzCore/Android/JNI/Internal/ClassName.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
////////////////////////////////////////////////////////////////
JNIEnv* GetEnv()
{
return AndroidEnv::Get()->GetJniEnv();
}
////////////////////////////////////////////////////////////////
jclass LoadClass(const char* classPath)
{
return AndroidEnv::Get()->LoadClass(classPath);
}
////////////////////////////////////////////////////////////////
AZStd::string GetClassName(jclass classRef)
{
return Internal::ClassName<AZStd::string>::GetName(classRef);
}
////////////////////////////////////////////////////////////////
AZStd::string GetSimpleClassName(jclass classRef)
{
return Internal::ClassName<AZStd::string>::GetSimpleName(classRef);
}
////////////////////////////////////////////////////////////////
AZStd::string ConvertJstringToString(jstring stringValue)
{
return Internal::ConvertJstringToStringImpl<AZStd::string>(stringValue);
}
////////////////////////////////////////////////////////////////
jstring ConvertStringToJstring(const AZStd::string& stringValue)
{
return Internal::ConvertStringToJstringImpl(stringValue);
}
////////////////////////////////////////////////////////////////
int GetRefType(jobject javaRef)
{
int refType = JNIInvalidRefType;
if (javaRef)
{
JNIEnv* jniEnv = GetEnv();
AZ_Assert(jniEnv, "Unable to get JNIEnv pointer to determine JNI reference type.");
if (jniEnv)
{
refType = jniEnv->GetObjectRefType(javaRef);
}
}
return refType;
}
////////////////////////////////////////////////////////////////
void DeleteRef(jobject javaRef)
{
if (javaRef)
{
JNIEnv* jniEnv = GetEnv();
AZ_Assert(jniEnv, "Unable to get JNIEnv pointer to free JNI reference.");
if (jniEnv)
{
jobjectRefType refType = jniEnv->GetObjectRefType(javaRef);
switch (refType)
{
case JNIGlobalRefType:
jniEnv->DeleteGlobalRef(javaRef);
break;
case JNILocalRefType:
jniEnv->DeleteLocalRef(javaRef);
break;
case JNIWeakGlobalRefType:
jniEnv->DeleteWeakGlobalRef(javaRef);
break;
default:
AZ_Error("AZ::Android::JNI", false, "Unknown or invalid reference type detected.");
break;
}
}
}
}
}
}
}
@@ -0,0 +1,92 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/string/string.h>
#include <jni.h>
#include <android/asset_manager.h>
#define HANDLE_JNI_EXCEPTION(jniEnv) \
jniEnv->ExceptionDescribe(); \
jniEnv->ExceptionClear();
#if defined(AZ_DEBUG_BUILD)
#define JNI_SIGNATURE_VALIDATION
#endif
// redefine the JNI_FALSE and JNI_TRUE macros to ensure their correct types are represented when using them
#if defined(JNI_FALSE)
#undef JNI_FALSE
#define JNI_FALSE jboolean(0)
#endif // defined(JNI_FALSE)
#if defined(JNI_TRUE)
#undef JNI_TRUE
#define JNI_TRUE jboolean(1)
#endif // defined(JNI_TRUE)
namespace AZ
{
namespace Android
{
namespace JNI
{
//! Request a thread specific JNIEnv pointer from the Android environment.
//! \return A pointer to the JNIEnv on the current thread.
JNIEnv* GetEnv();
//! Loads a Java class as opposed to attempting to find a loaded class from the call stack.
//! \param classPath The fully qualified forward slash separated Java class path.
//! \return A global reference to the desired jclass. Caller is responsible for making a
//! call do DeleteGlobalJniRef when the jclass is no longer needed.
jclass LoadClass(const char* classPath);
//! Get the fully qualified forward slash separated Java class path of Java class ref.
//! e.g. android.app.NativeActivity ==> android/app/NativeActivity
//! \param classRef A valid reference to a java class
//! \return A copy of the class name
AZStd::string GetClassName(jclass classRef);
//! Get just the name of the Java class from a Java class ref.
//! e.g. android.app.NativeActivity ==> NativeActivity
//! \param classRef A valid reference to a java class
//! \return A copy of the class name
AZStd::string GetSimpleClassName(jclass classRef);
//! Converts a jstring to a AZStd::string
//! \param stringValue A local or global reference to a jstring object
//! \return A copy of the converted string
AZStd::string ConvertJstringToString(jstring stringValue);
//! Converts a string to a jstring
//! \param stringValue The native string value to be converted
//! \return A global reference to the converted jstring. The caller is responsible for
//! deleting it when no longer needed
jstring ConvertStringToJstring(const AZStd::string& stringValue);
//! Gets the reference type of the Java object. Can be Local, Global or Weak Global.
//! \param javaRef Raw Java object reference, can be null.
//! \return The result of GetObjectRefType as long as the object is valid,
//! otherwise JNIInvalidRefType.
int GetRefType(jobject javaRef);
//! Deletes a JNI object/class reference. Will handle local, global and weak global references.
//! \param javaRef Raw java object reference.
void DeleteRef(jobject javaRef);
}
}
}
@@ -0,0 +1,488 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/osstring.h>
#include <AzCore/std/typetraits/is_convertible.h>
#include <AzCore/std/typetraits/conditional.h>
#include <AzCore/std/utils.h>
#include <AzCore/Android/JNI/JNI.h>
#if defined(JNI_SIGNATURE_VALIDATION)
#include <AzCore/Android/JNI/Signature.h>
#include <AzCore/Outcome/Outcome.h>
#endif
namespace AZ { namespace Android
{
namespace JNI
{
namespace Internal
{
//! Utility to allow easier managing of JNI reference when hosting Java classes and objects
//! in native code. Provides the same functionality that is available when manipulating JNI
//! references directly with the raw JNIEnv pointer.
//! \tparam Allocator The type of allocator used for both it self and all it's internal
//! allocations. Defaults to AZ::SystemAllocator
template<typename Allocator = AZ::SystemAllocator>
class Object final
{
private:
//! special case if we are using the SystemAllocator to use the default AZStd::allocator instead of wrapping
//! the allocator in AZStdAlloc. This way the known string types (AZStd::string and AZ::OSString) are correctly
//! typedefed internally.
typedef typename AZStd::conditional<AZStd::is_same<Allocator, AZ::SystemAllocator>::value, AZStd::allocator, AZStdAlloc<Allocator>>::type AZStdAllocator;
public:
typedef AZStd::basic_string<char, AZStd::char_traits<char>, AZStdAllocator> string_type;
typedef AZStd::vector<JNINativeMethod, AZStdAllocator> vector_type;
AZ_CLASS_ALLOCATOR(Object<Allocator>, Allocator, 0);
//! Creates a custom jni object wrapper from a java class path. This JNI object
//! will take owner ship of all global refs used internally
//! \param classPath The full java class path for the object to be loaded
//! \param className The name of the java class, mostly use for logging purposes
explicit Object(const char* classPath, const char* className = nullptr);
//! Creates a JNI object wrapper based on an existing global ref
//! \param classRef The global reference to the jclass for the object
//! \param objectRef The global reference to the instance object
//! \param takeOwnership [Optional] Tell the object to clean up the argument global refs when destroyed
Object(jclass classRef, jobject objectRef, bool takeOwnership = false);
//! Automatically cleans up any global JNI reference with the JVM
~Object();
//! Register a non-static Java method with the associated Java object instance. These methods can only
//! be invoked with a valid jobject reference.
//! \param methodName The exact name of the java method to register
//! \param methodSignature The argument/return signature of the java method.
//! See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432 for more details
//! \return True if the method was registered successfully, False otherwise
bool RegisterMethod(const char* methodName, const char* methodSignature);
//! Register a static Java method with the associated Java class reference. These methods
//! can be invoked as long as the class reference is valid.
//! \param methodName The exact name of the java method to register
//! \param methodSignature The argument/return signature of the Java method.
//! See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432 for more details
//! \return True if the method was registered successfully, False otherwise
bool RegisterStaticMethod(const char* methodName, const char* methodSignature);
//! Register native callback with Java 'native' method.
//! \param nativeMethods All the native methods to register with the Java class.
//! \return True if all the methods were registered successfully, False otherwise
bool RegisterNativeMethods(vector_type nativeMethods);
//! Register a instance member field with the object.
//! \param fieldName The exact name of the java field to register.
//! \param fieldSignature The type signature of the Java field.
//! See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432 for more details
//! \return True if the field was registered successfully, False otherwise.
bool RegisterField(const char* fieldName, const char* fieldSignature);
//! Register a static member field with the object.
//! \param fieldName The exact name of the java static field to register.
//! \param fieldSignature The type signature of the Java static field.
//! See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432 for more details
//! \return True if the static field was registered successfully, False otherwise.
bool RegisterStaticField(const char* fieldName, const char* fieldSignature);
//! Creates a global reference to an java instance object
//! \param constructorSignature The function signature of the constructor desired for creating the object
//! See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16432 for more details
//! \param parameters All the arguments required for the function call
//! \return True if the global instance was created successfully, False otherwise
template<typename... Args>
bool CreateInstance(const char* constructorSignature, Args&&... parameters);
//! Destroys the global instance of the java object only. Static method calls can still be made, while instance methods will
//! fail until a new instance is constructed through CreateInstance
void DestroyInstance();
//!@{
//! All the Invoke<TYPE>Method functions are for calling registered instance methods on
//! a java object where <TYPE> is the return type of the java method.
//! \param methodName The exact name of the java method to call
//! \param parameters All the arguments required for the function call
template<typename... Args>
void InvokeVoidMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jboolean InvokeBooleanMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jbyte InvokeByteMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jchar InvokeCharMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jshort InvokeShortMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jint InvokeIntMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jlong InvokeLongMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jfloat InvokeFloatMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jdouble InvokeDoubleMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
string_type InvokeStringMethod(const char* methodName, Args&&... parameters);
//!@}
//! Call a java instance method that returns a customs java object such as an String, Array
//! or other java class type. This function is restricted to types derived from _jobject.
//! The return value will be a global reference and the caller is responsible for deleting
//! through Jni::DeleteGlobalRef when no longer needed.
template<typename ReturnType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ReturnType, jobject>::value, ReturnType>::type
InvokeObjectMethod(const char* methodName, Args&&... parameters);
//!@{
//! All the InvokeStatic<TYPE>Method functions are for calling registered static methods on
//! a java object where <TYPE> is the return type of the java method.
//! \param methodName The exact name of the java method to call
//! \param parameters All the arguments required for the function call
template<typename... Args>
void InvokeStaticVoidMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jboolean InvokeStaticBooleanMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jbyte InvokeStaticByteMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jchar InvokeStaticCharMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jshort InvokeStaticShortMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jint InvokeStaticIntMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jlong InvokeStaticLongMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jfloat InvokeStaticFloatMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
jdouble InvokeStaticDoubleMethod(const char* methodName, Args&&... parameters);
template<typename... Args>
string_type InvokeStaticStringMethod(const char* methodName, Args&&... parameters);
//!@}
//! Call a java static method that returns a customs java object such as an String, Array or
//! other java class type. This function is restricted to types derived from _jobject.
//! The return value will be a global reference and the caller is responsible for deleting
//! through Jni::DeleteGlobalRef when no longer needed.
template<typename ReturnType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ReturnType, jobject>::value, ReturnType>::type
InvokeStaticObjectMethod(const char* methodName, Args&&... parameters);
//!@{
//! All the Set<TYPE>Field functions are for setting a registered instance member on
//! a java object where <TYPE> is the type of the instance member.
//! \param fieldName The exact name of the java instance member to set.
//! \param value The new value to set the instance member.
void SetBooleanField(const char* fieldName, jboolean value);
void SetByteField(const char* fieldName, jbyte value);
void SetCharField(const char* fieldName, jchar value);
void SetShortField(const char* fieldName, jshort value);
void SetIntField(const char* fieldName, jint value);
void SetLongField(const char* fieldName, jlong value);
void SetFloatField(const char* fieldName, jfloat value);
void SetDoubleField(const char* fieldName, jdouble value);
void SetStringField(const char* fieldName, const string_type& value);
//!@}
//! Set a custom java object instance field. Restricted to types derived from _jobject,
//! such as jstring, jarray, etc.
template<typename ValueType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ValueType, jobject>::value>::type
SetObjectField(const char* fieldName, ValueType value);
//!@{
//! All the Get<TYPE>Field functions are for getting a registered instance member on
//! a java object where <TYPE> is the type of the instance member.
//! \param fieldName The exact name of the java instance member to get.
jboolean GetBooleanField(const char* fieldName);
jbyte GetByteField(const char* fieldName);
jchar GetCharField(const char* fieldName);
jshort GetShortField(const char* fieldName);
jint GetIntField(const char* fieldName);
jlong GetLongField(const char* fieldName);
jfloat GetFloatField(const char* fieldName);
jdouble GetDoubleField(const char* fieldName);
string_type GetStringField(const char* fieldName);
//!@}
//! Get a custom java object instance field. Restricted to types derived from _jobject,
//! such as jstring, jarray, etc. A global refernece will be returned and the caller is
//! responsible for deleting through Jni::DeleteGlobalRef when no longer needed.
template<typename ReturnType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ReturnType, jobject>::value, ReturnType>::type
GetObjectField(const char* fieldName);
//!@{
//! All the Set<TYPE>Field functions are for setting a registered instance member on
//! a java object where <TYPE> is the type of the instance member.
//! \param fieldName The exact name of the java instance member to set.
//! \param value The new value to set the static member.
void SetStaticBooleanField(const char* fieldName, jboolean value);
void SetStaticByteField(const char* fieldName, jbyte value);
void SetStaticCharField(const char* fieldName, jchar value);
void SetStaticShortField(const char* fieldName, jshort value);
void SetStaticIntField(const char* fieldName, jint value);
void SetStaticLongField(const char* fieldName, jlong value);
void SetStaticFloatField(const char* fieldName, jfloat value);
void SetStaticDoubleField(const char* fieldName, jdouble value);
void SetStaticStringField(const char* fieldName, const string_type& value);
//!@}
//! Set a custom java object static field. Restricted to types derived from _jobject,
//! such as jstring, jarray, etc.
template<typename ValueType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ValueType, jobject>::value>::type
SetStaticObjectField(const char* fieldName, ValueType value);
//!@{
//! All the Get<TYPE>Field functions are for getting a registered static member on
//! a java object where <TYPE> is the type of the static member.
//! \param fieldName The exact name of the java instance member to get.
jboolean GetStaticBooleanField(const char* fieldName);
jbyte GetStaticByteField(const char* fieldName);
jchar GetStaticCharField(const char* fieldName);
jshort GetStaticShortField(const char* fieldName);
jint GetStaticIntField(const char* fieldName);
jlong GetStaticLongField(const char* fieldName);
jfloat GetStaticFloatField(const char* fieldName);
jdouble GetStaticDoubleField(const char* fieldName);
string_type GetStaticStringField(const char* fieldName);
//!@}
//! Get a custom java object static field. Restricted to types derived from _jobject,
//! such as jstring, jarray, etc. A global reference will be returned and the caller is
//! responsible for deleting through Jni::DeleteGlobalRef when no longer needed.
template<typename ReturnType, typename... Args>
typename AZStd::enable_if<AZStd::is_convertible<ReturnType, jobject>::value, ReturnType>::type
GetStaticObjectField(const char* fieldName);
private:
//! Simple structure containing core information about a registered Java method
struct JMethodCache
{
jmethodID m_methodId; //!< Pointer to the method reference on the JVM
#if defined(JNI_SIGNATURE_VALIDATION)
string_type m_methodName; //!< Name of the Java method, mostly used for debug logs
string_type m_argumentSignature; //!< Java type signature of all method arguments e.g. (int, String) => ILjava/lang/String;
string_type m_returnSignature; //!< Java type signature of the return value
#endif
};
//! Simple structure containing core information about a registered Java field
struct JFieldCache
{
jfieldID m_fieldId; //!< Pointer to the field reference on the JVM
#if defined(JNI_SIGNATURE_VALIDATION)
string_type m_fieldName; //!< Name of the Java field, mostly used for debug logs
string_type m_signature; //!< Java type signature of the field
#endif
};
typedef AZStd::shared_ptr<JMethodCache> JMethodCachePtr;
typedef AZStd::shared_ptr<JFieldCache> JFieldCachePtr;
template<typename ValueType>
using CacheMap = AZStd::unordered_map<string_type, ValueType, AZStd::hash<string_type>, AZStd::equal_to<string_type>, AZStdAllocator>;
typedef CacheMap<JMethodCachePtr> JMethodMap;
typedef CacheMap<JFieldCachePtr> JFieldMap;
template<typename ReturnType>
using JniMethodCallback = AZStd::function<ReturnType(JNIEnv*, jobject, jmethodID)>;
template<typename ReturnType>
using JniStaticMethodCallback = AZStd::function<ReturnType(JNIEnv*, jclass, jmethodID)>;
template<typename ReturnType>
using JniFieldCallback = AZStd::function<ReturnType(JNIEnv*, jobject, jfieldID)>;
template<typename ReturnType>
using JniStaticFieldCallback = AZStd::function<ReturnType(JNIEnv*, jclass, jfieldID)>;
#if defined(JNI_SIGNATURE_VALIDATION)
using SignatureOutcome = AZ::Outcome<void, string_type>;
typedef Signature<string_type> SigUtil;
#endif
// ----
//! Helper to find a register instance method
JMethodCachePtr GetMethod(const string_type& methodName) const;
//! Helper to find a register static method
JMethodCachePtr GetStaticMethod(const string_type& methodName) const;
//! Helper to find a register instance field
JFieldCachePtr GetField(const string_type& fieldName) const;
//! Helper to find a register static field
JFieldCachePtr GetStaticField(const string_type& fieldName) const;
#if defined(JNI_SIGNATURE_VALIDATION)
//! Helper to extract the argument and return signatures from a complete method signature
//! and set the respective properties within the specified JMethodCache pointer.
//! \param methodCache JMethodCache pointer to set
//! \param methodName The exact name of the java method
//! \param methodSignature The full method signature
void SetMethodSignature(JMethodCachePtr methodCache, const char* methodName, const char* methodSignature);
//! Helper to extract the argument and return signatures from a complete method signature
//! and set the respective properties within the specified JMethodCache pointer.
//! \param fieldCache JFieldCache pointer to set
//! \param fieldName The exact name of the java field
//! \param signature The signature, or type, of the java field
void SetFieldSignature(JFieldCachePtr fieldCache, const char* fieldName, const char* signature);
//! Performs a full signature validation for all types in Args
//! \param baseSignature The base signature used to register the JNI method or field
//! \param parameters Pending arguments to a JNI call needing validation
//! \return \ref AZ::Success if all parameters pass validation, \ref AZ::Failure<string_type> containing the
//! error message otherwise
template<typename... Args>
SignatureOutcome ValidateSignature(const string_type& baseSignature, Args&&... parameters);
//! Performs a partial signature validation when \p Type is jobject or jobjectArray, and a full signature validation
//! for other types. Primarily used for basic validation of return type values when the requested type
//! can't be deduced without making the JNI call first.
//! \param baseSignature The base signature used to register the JNI method or field
//! \param param Pending type to a JNI call needing validation
//! \return \ref AZ::Success if all parameters pass validation, \ref AZ::Failure<string_type> containing the
//! error message otherwise
template<typename Type>
SignatureOutcome ValidateSignaturePartial(const string_type& baseSignature, Type param);
#endif // defined(JNI_SIGNATURE_VALIDATION)
//! Helper for invoking a primitive type instance method on the JNI object
//! \param methodName The name of the instance method to invoke
//! \param jniCallback Lambda wrapper to the actual JNIEnv::Call<type>Method
//! \param parameters Java method call arguments list
//! \return The primitive return value from the Java method
template<typename ReturnType, typename... Args>
ReturnType InvokePrimitiveTypeMethodInternal(const char* methodName, JniMethodCallback<ReturnType> jniCallback, Args&&... parameters);
//! Helper for invoking a primitive type instance method on the JNI object
//! \param methodName The name of the instance method to invoke
//! \param jniCallback Lambda wrapper to the actual JNIEnv::CallStatic<type>Method
//! \param parameters Java method call arguments list
//! \return The primitive return value from the Java method
template<typename ReturnType, typename... Args>
ReturnType InvokePrimitiveTypeStaticMethodInternal(const char* methodName, JniStaticMethodCallback<ReturnType> jniCallback, Args&&... parameters);
//! Helper for setting a primitive type instance field
//! \param fieldName The name of the instance field to set
//! \param jniCallback Lambda wrapper to the actual JNIEnv::Set<type>Field
//! \param value New value of the instance field
template<typename ValueType>
void SetPrimitiveTypeFieldInternal(const char* fieldName, JniFieldCallback<void> jniCallback, ValueType value);
//! Helper for getting a primitive type instance field
//! \param fieldName The name of the instance field to set
//! \param jniCallback Lambda wrapper to the actual JNIEnv::Get<type>Field
//! \return The current value of the primitive instance field
template<typename ReturnType>
ReturnType GetPrimitiveTypeFieldInternal(const char* fieldName, JniFieldCallback<ReturnType> jniCallback);
//! Helper for setting a primitive type static field
//! \param fieldName The name of the static field to set
//! \param jniCallback Lambda wrapper to the actual JNIEnv::SetStatic<type>Field
//! \param value New value of the static field
template<typename ValueType>
void SetPrimitiveTypeStaticFieldInternal(const char* fieldName, JniStaticFieldCallback<void> jniCallback, ValueType value);
//! Helper for getting a primitive type static field
//! \param fieldName The name of the static field to set
//! \param jniCallback Lambda wrapper to the actual JNIEnv::GetStatic<type>Field
//! \return The current value of the primitive static field
template<typename ReturnType>
ReturnType GetPrimitiveTypeStaticFieldInternal(const char* fieldName, JniStaticFieldCallback<ReturnType> jniCallback);
// ----
string_type m_className; //!< The simple name of the Java class, used for debugging
AZStdAllocator m_stdAllocator; //!< Allocator instance used for allocating the JMethodCachePtr/JFieldCachePtr shared pointers
jclass m_classRef; //!< A global reference to the java class, used for method/filed extraction, static method invocation
jobject m_objectRef; //!< A global reference to the java object instance, used for instance method invocation, field access
JMethodMap m_methods; //!< Container of all the instance methods currently registered for the java class
JMethodMap m_staticMethods; //!< Container of all the static methods currently registered for the java class
JFieldMap m_fields; //!< Container of all the instance fields currently registered for the java class
JFieldMap m_staticFields; //!< Container of all the static fields currently registered for the java class
bool m_ownsGlobalRefs; //!< Should the global references be destroyed automatically or manually
bool m_instanceConstructed; //!< Can we invoke instance methods, has CreateInstance ben called
};
} // namespace Internal
//! \brief The default \ref AZ::Android::JNI::Internal::Object type which uses the SystemAllocator
typedef Internal::Object<AZ::SystemAllocator> Object;
} // namespace JNI
} // namespace Android
} // namespace AZ
#include <AzCore/Android/JNI/Internal/Object_impl.h>
@@ -0,0 +1,33 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
namespace Internal
{
template<typename Allocator>
class Object;
}
//! \brief The default \ref AZ::Android::JNI::Internal::Object type which uses the SystemAllocator
typedef Internal::Object<AZ::SystemAllocator> Object;
}
}
}
@@ -0,0 +1,273 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/osstring.h>
#include <AzCore/std/utils.h>
#include <AzCore/Android/JNI/JNI.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
namespace Internal
{
//! \brief Templated interface for getting specific JNI type signatures. This is intentionally left empty
//! to enforce usage to only known template specializations.
//! \tparam Type The JNI type desired for the signature request
//! \tparam StringType The type of string that should be returned. Defaults to 'const char*'
//! \return String containing the type specific JNI signature
template<typename Type, typename StringType = const char*>
StringType GetTypeSignature(Type);
///!@{
//! \brief Known type specializations for \ref AZ::Android::JNI::Internal::GetTypeSignature.
template<> inline const char* GetTypeSignature(jboolean) { return "Z"; }
template<> inline const char* GetTypeSignature(bool) { return "Z"; }
template<> inline const char* GetTypeSignature(jbooleanArray) { return "[Z"; }
template<> inline const char* GetTypeSignature(jbyte) { return "B"; }
template<> inline const char* GetTypeSignature(jbyteArray) { return "[B"; }
template<> inline const char* GetTypeSignature(jchar) { return "C"; }
template<> inline const char* GetTypeSignature(jcharArray) { return "[C"; }
template<> inline const char* GetTypeSignature(jshort) { return "S"; }
template<> inline const char* GetTypeSignature(jshortArray) { return "[S"; }
template<> inline const char* GetTypeSignature(jint) { return "I"; }
template<> inline const char* GetTypeSignature(jintArray) { return "[I"; }
template<> inline const char* GetTypeSignature(jlong) { return "J"; }
template<> inline const char* GetTypeSignature(jlongArray) { return "[J"; }
template<> inline const char* GetTypeSignature(jfloat) { return "F"; }
template<> inline const char* GetTypeSignature(jfloatArray) { return "[F"; }
template<> inline const char* GetTypeSignature(jdouble) { return "D"; }
template<> inline const char* GetTypeSignature(jdoubleArray) { return "[D"; }
template<> inline const char* GetTypeSignature(jstring) { return "Ljava/lang/String;"; }
template<> inline const char* GetTypeSignature(jclass) { return "Ljava/lang/Class;"; }
template<typename StringType>
StringType GetTypeSignature(jobject value);
template<typename StringType>
StringType GetTypeSignature(jobjectArray value);
//!@}
//! \brief Templated interface for comparing JNI type signatures. This leverages
//! \ref AZ::Android::JNI::Internal::GetTypeSignature under the hood
//! to weed out unsupported types
//! \tparam StringType The type of string that should used for base comparison
//! \tparam Type The JNI type desired for the signature verification
//! \param baseSignature The string representation of the expected type \p param should be
//! \param param The raw JNI type to be validated
//! \return True if \p param is an acceptable type for the type specified in \p baseSignature,
//! false otherwise
template<typename StringType, typename Type>
bool CompareTypeSignature(const StringType& baseSignature, Type param);
///!@{
//! \brief Explicit definitions for jobject and jobjectArray need to be defined in order to support
//! subclass validation for Java classes
template<typename StringType>
bool CompareTypeSignature(const StringType& baseSignature, jobject param);
template<typename StringType>
bool CompareTypeSignature(const StringType& baseSignature, jobjectArray param);
//!@}
}
//! \brief Utility for generating and validating JNI signatures
//! \tparam StringType The type of string used internally for generation and validation. Defaults to AZStd::string
template<typename StringType = AZStd::string>
class Signature
{
public:
//! \brief Required for handling cases when an empty set of variadic arguments are forwarded from
//! \ref AZ::Android::JNI::GetSignature calls
//! \return An empty string
static StringType Generate()
{
Signature sig;
return sig.m_signature;
}
//! \brief Gets the signature from n-number of parameters
//! \param parameters Variables only used to forward their types on to \ref AZ::Android::JNI::Signature::GenerateImpl
//! \return String containing a fully qualified Java signature
template<typename... Args>
static StringType Generate(Args&&... parameters)
{
Signature sig;
sig.GenerateImpl(AZStd::forward<Args>(parameters)...);
return sig.m_signature;
}
//! \brief Required for handling cases when an empty set of variadic arguments are forwarded from
//! \ref AZ::Android::JNI::ValidateSignature calls
//! \param baseSignature The string representation of the expected type signature. Should be an empty string in
//! this case.
//! \return True if the string is empty (e.g. nothing to validate), false otherwise
static bool Validate(const StringType& baseSignature)
{
Signature sig(baseSignature);
return sig.m_signature.empty();
}
//! \brief Validates n-number of parameters type signatures
//! \param baseSignature The string representation of the expected JNI type signatures in \p parameters
//! \param parameters The input arguments to be validated
//! \return True if all arguments in \p parameters match the expected type signature in \p baseSignature, False otherwise
template<typename... Args>
static bool Validate(const StringType& baseSignature, Args&&... parameters)
{
Signature sig(baseSignature);
return (sig.m_signature.empty() ?
false :
sig.ValidateImpl(AZStd::forward<Args>(parameters)...));
}
private:
//! \brief Internal constructor of the signature util used for generation. Pre-allocates some memory for the internal
//! cache to try and prevent the hammering of reallocations in most cases.
Signature()
: m_signature()
, m_signatureLength(0)
, m_currentIndex(0)
{
m_signature.reserve(8);
}
//! \brief Internal constructor of the signature util used for validation.
//! \param baseSignature The signature to compare against
explicit Signature(const StringType& baseSignature)
: m_signature(baseSignature)
, m_signatureLength(0)
, m_currentIndex(0)
{
m_signatureLength = m_signature.length();
}
//! \brief Appends the desired type to the internal signature cache
//! \param value Throwaway variable only used to forward the type on to \ref AZ::Android::JNI::Internal::GetTypeSignature
template<typename Type>
void GenerateImpl(Type value)
{
m_signature.append(Internal::GetTypeSignature(value));
}
///!@{
//! \brief Explicit definitions for jobject and jobjectArray need to be defined in order to route their
//! calls to the correct version of \ref AZ::Android::JNI::Internal::GetTypeSignature which returns
//! a string instead of a c-string.
void GenerateImpl(jobject value)
{
m_signature.append(Internal::GetTypeSignature<StringType>(value));
}
void GenerateImpl(jobjectArray value)
{
m_signature.append(Internal::GetTypeSignature<StringType>(value));
}
//!@}
//! \brief Appends the desired type to the internal signature cache
//! \param first Variable only used to forward the type on to \ref AZ::Android::JNI::Signature::GenerateImpl
//! \param parameters Additional variables only used to forward their types into recursive calls
template<typename Type, typename... Args>
void GenerateImpl(Type first, Args&&... parameters)
{
GenerateImpl(first);
GenerateImpl(AZStd::forward<Args>(parameters)...);
}
//! \brief Validates a single (or final) type against the remaining signature(s) in the internal signature cache
//! \param param The type to be validated
//! \return True if the type of \p param matches the remaining signature(s) in the internal signature cache,
//! False otherwise
template<typename Type>
bool ValidateImpl(Type param)
{
int paramLength = m_signatureLength - m_currentIndex;
if (paramLength > 0)
{
StringType paramSignature = m_signature.substr(m_currentIndex, paramLength);
return Internal::CompareTypeSignature<StringType>(paramSignature, param);
}
return false;
}
//! \brief Validates n-number of parameters type signatures
//! \param first The first type to be validated
//! \param parameters The remaining types to be validated recursively
//! \return True if all the types in \p parameters match the expected type signature stored internally,
//! False otherwise
template<typename Type, typename... Args>
bool ValidateImpl(Type first, Args&&... parameters);
// ----
StringType m_signature; //!< Internal cache for signature generation/validation
int m_signatureLength; //!< Cache of the total length of the base signature for validation
int m_currentIndex; //!< Current index in walking the base signature for validation
};
//! \brief Default Signature template (AZStd::string), primarily used in \ref AZ::Android::JNI::GetSignature
//! and \ref AZ::Android::JNI::ValidateSignature
typedef Signature<AZStd::string> SignatureUtil;
//! \brief Generates a fully qualified Java signature from n-number of parameters. This is the preferred implementation
//! for generating JNI signatures
//! \param parameters Variables only used to forward their type info on to \ref AZ::Android::Signature::Generate
//! \return String containing a fully qualified Java signature
template<typename... Args>
AZ_INLINE AZStd::string GetSignature(Args&&... parameters)
{
return SignatureUtil::Generate(AZStd::forward<Args>(parameters)...);
}
//! \brief Validates a JNI signature with n-number of parameters. Will walk the signature validating
//! each parameter individually. The validation will exit once an argument fails validation.
//! \param baseSignature Base JNI signature to be comparing against
//! \param parameters The input arguments to be validated
//! \return True if all arguments match the signature, False otherwise
template<typename... Args>
AZ_INLINE bool ValidateSignature(const AZStd::string& baseSignature, Args&&... parameters)
{
return SignatureUtil::Validate(baseSignature, AZStd::forward<Args>(parameters)...);
};
} // namespace JNI
} // namespace Android
} // namespace AZ
#include <AzCore/Android/JNI/Internal/Signature_impl.h>
@@ -0,0 +1,136 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/std/typetraits/typetraits.h>
#include <AzCore/Android/JNI/JNI.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
//! A scoped_ref works in the same way a AZStd::scoped_ptr except it's specificially
//! designed for the opaque pointer JNI types (e.g. jobject, jarray, etc.). Guarantees
//! the java object is released from the JNI environment when the scoped_ref falls
//! out of scope.
template<typename JniType>
class scoped_ref
{
static_assert(AZStd::is_convertible<JniType, jobject>::value, "Specified type is not convertible to jobject.");
typedef scoped_ref<JniType> ThisType;
public:
typedef JniType ThisType::* UnspecifiedBoolType;
// ---
//! Only explicit scoped_refs are allowed to be constructed
//! \param javaObject Raw pointer to the java object. Currently only supports Local and
//! Global reference types. Weak Global reference are NOT supported.
explicit scoped_ref(JniType javaObject = nullptr)
: m_javaObject(javaObject)
{
#if defined(AZ_ENABLE_TRACING)
if (m_javaObject)
{
int refType = GetRefType(m_javaObject);
AZ_Error("JNI::scoped_ref",
refType == JNIGlobalRefType || refType == JNILocalRefType,
"Unsupported JNI reference type (%d) used in JNI::scoped_ref. "
"Supported reference types are JNIGlobalRefType and JNILocalRefType. "
"This may lead to unexpected behaviour.", refType);
}
#endif // defined(AZ_ENABLE_TRACING)
}
//! Automatically release the reference with the JNI environment when the object
//! goes out of scope
~scoped_ref()
{
DeleteRef(m_javaObject);
}
//! Compatibilty with the 'not' operator for validity checks. Only checkes for raw
//! pointer validity, NOT if it's pointing to a null reference (weak global ref).
bool operator !() const
{
return (m_javaObject == nullptr);
}
//! Operator for implicit bool conversions
//! \return 'True' if internal reference is valid, False otherwise
operator UnspecifiedBoolType() const
{
return (m_javaObject == nullptr ? nullptr : &ThisType::m_javaObject);
}
//! Explicit accessor of the raw pointer to the java reference.
//! \return The raw pointer to the java object reference
JniType get() const
{
return m_javaObject;
}
//! Swap the internal reference with another scoped_ref of the same type
//! \param lhs The scoped_ref (of same type) to be swaped with
void swap(scoped_ref& lhs)
{
AZStd::swap(m_javaObject, lhs.m_javaObject);
}
//! Reset the internal reference with a new pointer
//! \param javaObject Raw pointer to the java object. Must be of same type.
void reset(JniType javaObject = nullptr)
{
// Pointer level self reset. Triggering this assert will cause a crash when either this
// reference is used, or when the this scoped ref is cleaned up (double/invalid delete).
AZ_Assert(javaObject == nullptr || javaObject != m_javaObject, "JNI::scoped_ref pointer level self reset!");
// JNI reference level "self" reset. The references themselves are different so this is a
// valid reset, however the underlining java object the references are pointing to
// is the same in this case. As far as the JNI environment is concerned this is ok
// but we should still make note of these occurrences.
// NOTE: This warning will also trigger in the event the pointers are the same.
AZ_Warning("JNI::scoped_ref", GetEnv()->IsSameObject(m_javaObject, javaObject) == JNI_FALSE, "JNI::scoped_ref JNI reference level self reset.");
ThisType(javaObject).swap(*this);
}
private:
//! Disable copy/move
///@{
AZ_DISABLE_COPY_MOVE(scoped_ref);
///@}
//! Disable direct comparisons of other scoped_refs
///@{
void operator==(scoped_ref const&) const;
void operator!=(scoped_ref const&) const;
///@}
// ----
JniType m_javaObject; //!< Raw pointer of the java object reference (e.g. jobject, jarray, etc.)
};
}
}
}
@@ -0,0 +1,449 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Debug/Trace.h>
#include <AzCore/std/typetraits/is_convertible.h>
#include <AzCore/std/smart_ptr/shared_count.h>
#include <AzCore/Android/JNI/JNI.h>
namespace AZ
{
namespace Android
{
namespace JNI
{
namespace Internal
{
using sp_counted_base = AZStd::Internal::sp_counted_base;
using sp_typeinfo = AZStd::type_id;
//! Similar to the AZStd::Internal::sp_counted_impl_pa in that accepts the data type and
//! custom allocator type, however the data type is restricted to types that inherit from
//! jobject. See AzCore/std/smartptr/shared_count.h for more details
template<typename JniType, typename AllocatorType>
class sr_counted_impl
: public AZStd::Internal::sp_counted_base
{
static_assert(AZStd::is_convertible<JniType, jobject>::value, "Specified type is not convertible to jobject.");
public:
//! Only explicit contruction of the shared_count private impl for shared_refs
//! \param javaObject The raw JNI pointer
//! \param allocator Custom allocator, used in the deallocation of this particular object,
//! NOT the JNI pointer
sr_counted_impl(JniType javaObject, const AllocatorType& allocator)
: m_javaObject(javaObject)
, m_allocator(allocator)
{
}
//! Called when the use count drops to zero. DOES release the JNI referene.
void dispose() override
{
DeleteRef(m_javaObject);
}
//! Called when the weak count drops to zero. Does NOT release the JNI referene.
void destroy() override
{
this->~ThisType();
m_allocator.deallocate(this, sizeof(ThisType), AZStd::alignment_of<ThisType>::value);
}
//! Throwaway pure-virtual. Custom deleters are not supported for shared_refs since
//! they have to be released from the JNI environment
void* get_deleter(sp_typeinfo const&) override
{
return nullptr;
}
private:
typedef sr_counted_impl<JniType, AllocatorType> ThisType;
//! Disable copy/move
///@{
AZ_DISABLE_COPY_MOVE(sr_counted_impl);
///@}
// ----
JniType m_javaObject; //!< The raw JNI pointer from the JVM
AllocatorType m_allocator; //!< Custom alloctor used for the [de]allocation of this object
};
//! Similar to the AZStd::Internal::shared_count, however the data type is restricted to
//! types that inherit from jobject. See AzCore/std/smartptr/shared_count.h for more details
class shared_count
{
public:
//! Default contruction, no private impl will be created e.i. the count is not valid
shared_count()
: m_impl(nullptr)
{
}
//! Explicit construction of the shared_count requiring the raw JNI pointer to manage
//! and a custom allocator to handle the private impl count [de]allocations
//! \param javaObject Raw JNI pointer from the JVM
//! \param allocator Custom allocator used only for the private impl count [de]allocations
template<typename JniType, typename Allocator>
shared_count(JniType javaObject, const Allocator& allocator)
: m_impl(nullptr)
{
static_assert(AZStd::is_convertible<JniType, jobject>::value, "Specified type is not convertible to jobject.");
typedef sr_counted_impl<JniType, Allocator> impl_type;
Allocator a2(allocator);
m_impl = reinterpret_cast<sp_counted_base*>(a2.allocate(sizeof(impl_type), AZStd::alignment_of<impl_type>::value));
if (m_impl)
{
new(m_impl)impl_type(javaObject, allocator);
}
else
{
AZ_Assert(false, "Failed to allocate the shared count for JNI::shared_ref. Releasing reference from JVM.");
DeleteRef(javaObject);
}
}
//! Copy the shared count, increase the count if valid
shared_count(shared_count const& rhs)
: m_impl(rhs.m_impl)
{
if (m_impl)
{
m_impl->add_ref_copy();
}
}
//! Move the shared count, will invalidate the private impl of of the moved shared_count
shared_count(shared_count&& rhs)
: m_impl(rhs.m_impl)
{
rhs.m_impl = nullptr;
}
//! Decrease the shared count, if valid, on deletion
~shared_count()
{
if (m_impl)
{
m_impl->release();
}
}
//! Copy the shared count. Increases the new count, if valid; decreases the old count, if valid
shared_count& operator =(shared_count const& rhs)
{
sp_counted_base* tmp = rhs.m_impl;
if (tmp != m_impl)
{
if (tmp)
{
tmp->add_ref_copy();
}
if (m_impl)
{
m_impl->release();
}
m_impl = tmp;
}
return *this;
}
//! Check to see if two shared_counts are managing the same private impl pointer
bool operator ==(shared_count const& rhs)
{
return (m_impl == rhs.m_impl);
}
//! Swap the private impl pointers between two shared_counts
void swap(shared_count& rhs)
{
AZStd::swap(m_impl, rhs.m_impl);
}
//! Get the number of reference held by the shared count, if valid
long use_count() const
{
return (m_impl != nullptr ? m_impl->use_count() : 0);
}
//! Check to see if the shared_count is the only one holding on to the private
//! impl pointer
bool unique() const
{
return (use_count() == 1);
}
private:
//! The private impl of the shared_count. This object is the one responsible for
//! releasing the JNI reference with the JVM.
sp_counted_base* m_impl;
};
}
//! A shared_ref works in the same way a AZStd::shared_ptr except it's specificially
//! designed for the opaque pointer JNI types (e.g. jobject, jarray, etc.). Guarantees
//! the java object is released from the JNI environment once the last shared_ref pointing
//! to is released.
template<typename JniType>
class shared_ref
{
static_assert(AZStd::is_convertible<JniType, jobject>::value, "Specified type is not convertible to jobject.");
typedef shared_ref<JniType> ThisType;
public:
typedef JniType ThisType::* UnspecifiedBoolType;
// ---
//! Construct a default shared_ref with a null raw JNI pointer
shared_ref()
: m_javaObject(nullptr)
, m_count()
{
}
//! Explicit construction of shared_ref with a null raw JNI pointer
shared_ref(AZStd::nullptr_t)
: shared_ref()
{
}
//! Only allow explicit construction from the raw pointer to the java object reference.
//! Will use the AZ::SytemAllocator for the shared count allocations
//! \param javaObject Raw pointer to the java object. Currently only supports Local and
//! Global reference types. Weak Global reference are NOT supported.
explicit shared_ref(JniType javaObject)
: m_javaObject(javaObject)
, m_count(javaObject, AZStd::allocator())
{
#if defined(AZ_ENABLE_TRACING)
if (m_javaObject)
{
int refType = GetRefType(m_javaObject);
AZ_Error("JNI::shared_ref",
refType == JNIGlobalRefType || refType == JNILocalRefType,
"Unsupported JNI reference type (%d) used in JNI::shared_ref. "
"Supported reference types are JNIGlobalRefType and JNILocalRefType. "
"This may lead to unexpected behaviour.", refType);
}
#endif // defined(AZ_ENABLE_TRACING)
}
//! Create a shared_ref with a custom allocator.
//! NOTE: The custom allocator is only used for allocating the shared_count
//! \param javaObject Raw pointer to the java object. Currently only supports Local and
//! Global reference types. Weak Global reference are NOT supported.
//! \param allocator Custom allocator for usage within the shared count.
template<typename Allocator>
shared_ref(JniType javaObject, const Allocator& allocator)
: m_javaObject(javaObject)
, m_count(javaObject, allocator)
{
#if defined(AZ_ENABLE_TRACING)
if (m_javaObject)
{
int refType = GetRefType(m_javaObject);
AZ_Error("JNI::shared_ref",
refType == JNIGlobalRefType || refType == JNILocalRefType,
"Unsupported JNI reference type (%d) used in JNI::shared_ref. "
"Supported reference types are JNIGlobalRefType and JNILocalRefType. "
"This may lead to unexpected behaviour.", refType);
}
#endif // defined(AZ_ENABLE_TRACING)
}
//! Make a copy of the shared_ref, increase the reference count
//! \param rhs The shared_ref to copy
explicit shared_ref(const shared_ref& rhs)
: m_javaObject(rhs.m_javaObject)
, m_count(rhs.m_count)
{
}
//! Polymorphic copy of a shared_ref
//! \param rhs The shared_ref of a derived JNI pointer type to be copied
template<typename Y>
shared_ref(const shared_ref<Y>& rhs, typename AZStd::enable_if<AZStd::is_convertible<Y, JniType>::value, Y>::type = AZStd::nullptr_t())
: m_javaObject(rhs.m_javaObject)
, m_count(rhs.m_count)
{
}
//! Move the shared_ref from one shared_ref to another, Ctor
//! \param rhs The shared_ref to be moved
shared_ref(shared_ref&& rhs)
: m_javaObject(rhs.m_javaObject)
, m_count()
{
m_count.swap(rhs.m_count);
rhs.m_javaObject = nullptr;
}
//! Polymorphic move the shared_ref from one shared_ref to another, Ctor
//! \param rhs The shared_ref to be moved
template<typename Y>
shared_ref(shared_ref<Y>&& rhs)
: m_javaObject(rhs.m_javaObject)
, m_count()
{
m_count.swap(rhs.m_count);
rhs.m_javaObject = nullptr;
}
//! Make a copy of the shared_ref, increase the reference count
//! \param rhs The shared_ref to copy
shared_ref& operator=(const shared_ref& rhs) // never throws
{
ThisType(rhs).swap(*this);
return *this;
}
//! Make a polymorphic copy of the shared_ref, increase the reference count
//! \param rhs The shared_ref to copy
template<typename Y>
shared_ref& operator=(const shared_ref<Y>& rhs)
{
ThisType(rhs).swap(*this);
return *this;
}
//! Move the shared_ref from one shared_ref to another
//! \param rhs The shared_ref to be moved
shared_ref& operator=(shared_ref&& rhs)
{
ThisType(rhs).swap(*this);
return *this;
}
//! Polymorphic move of a shared_ref from one shared_ref to another
//! \param rhs The shared_ref to be moved
template<typename Y>
shared_ref& operator=(shared_ref<Y>&& rhs)
{
ThisType(rhs).swap(*this);
return *this;
}
//! Determine if two shared_refs are the same
//! \param rhs The shared_ref to compare against, may be of another JNI pointer type
//! \return True if the raw pointers are the same, False otherwise
template<typename Y>
bool operator==(const shared_ref<Y>& rhs) const
{
return this->get() == rhs.get();
}
//! Determine if two shared_refs are not the same
//! \param rhs The shared_ref to compare against, may be of another JNI pointer type
//! \return True if the raw pointers are the same, False otherwise
template<typename Y>
bool operator!=(const shared_ref<Y>& rhs) const
{
return this->get() != rhs.get();
}
//! Compatibilty with the 'not' operator for validity checks. Only checkes for raw
//! pointer validity, NOT if it's pointing to a null reference (weak global ref).
bool operator !() const
{
return (m_javaObject == nullptr);
}
//! Operator for implicit bool conversions
//! \return 'True' if internal reference is valid, False otherwise
operator UnspecifiedBoolType() const
{
return (m_javaObject == nullptr ? nullptr : &ThisType::m_javaObject);
}
//! Explicit accessor of the raw pointer to the java reference.
//! \return The raw pointer to the java object reference
JniType get() const
{
return m_javaObject;
}
//! Check to see if the shared_ref is the only one holding on to the raw JNI pointer
//! \return True if the only refernece, False othewise
bool unique() const
{
return m_count.unique();
}
//! Get the number of reference held on the raw JNI pointer
long use_count() const
{
return m_count.use_count();
}
//! Swap the internal reference with another shared_ref of the same type
//! \param lhs The shared_ref (of same type) to be swaped with
void swap(shared_ref& lhs)
{
AZStd::swap(m_javaObject, lhs.m_javaObject);
m_count.swap(lhs.m_count);
}
//! Default reset of the internal reference to nullptr
void reset()
{
ThisType().swap(*this);
}
//! Reset the internal reference with a new pointer
//! \param javaObject Raw pointer to the java object. Must be of same type.
void reset(JniType javaObject)
{
// Pointer level self reset. Triggering this assert will cause a crash when either this
// reference is used, or when the this scoped ref is cleaned up (double/invalid delete).
AZ_Assert(javaObject == nullptr || javaObject != m_javaObject, "JNI::shared_ref pointer level self reset!");
// JNI reference level "self" reset. The references themselves are different so this is a
// valid reset, however the underlining java object the references are pointing to
// is the same in this case. As far as the JNI environment is concerned this is ok
// but we should still make note of these occurrences.
// NOTE: This warning will also trigger in the event the pointers are the same.
AZ_Warning("JNI::shared_ref", GetEnv()->IsSameObject(m_javaObject, javaObject) == JNI_FALSE, "JNI::shared_ref JNI reference level self reset.");
ThisType(javaObject).swap(*this);
}
private:
template<class Y> friend class shared_ref;
// ----
JniType m_javaObject; //!< Raw pointer of the java object reference (e.g. jobject, jarray, etc.)
Internal::shared_count m_count; //!< Shared reference count, responsible for releaseing the JNI reference from the JVM
};
}
}
}
@@ -0,0 +1,471 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Android/Utils.h>
#include <AzCore/Android/JNI/JNI.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Android/JNI/Signature.h>
// Include Testing Framework Here
using namespace AZ::Android;
namespace UnitTest
{
struct SimpleJavaObject
{
SimpleJavaObject()
: m_classRef(nullptr)
, m_objectRef(nullptr)
{
m_classRef = JNI::LoadClass("com/amazon/test/SimpleObject");
JNIEnv* jniEnv = JNI::GetEnv();
jmethodID constructorMethodId = jniEnv->GetMethodID(m_classRef, "<init>", "()V");
jobject localObjectRef = jniEnv->NewObject(m_classRef, constructorMethodId);
m_objectRef = jniEnv->NewGlobalRef(localObjectRef);
jniEnv->DeleteLocalRef(localObjectRef);
}
~SimpleJavaObject()
{
JNI::DeleteRef(m_objectRef);
}
jclass m_classRef;
jobject m_objectRef;
};
// ----
TEST(Signature, Sanity)
{
EXPECT_EQ(1, 1);
}
// ----
// Generation Tests
// ----
TEST(Signature, Generate_NoArgs_IsEmptyString)
{
AZStd::string emptyStr = JNI::GetSignature();
ASSERT_TRUE(emptyStr.empty());
}
TEST(Signature, Generate_DefaultNativeBooleanTypes_IsZ)
{
AZStd::string nativeTrueType = JNI::GetSignature(true);
ASSERT_STREQ(nativeTrueType.c_str(), "Z");
AZStd::string nativeFalseType = JNI::GetSignature(false);
ASSERT_STREQ(nativeFalseType.c_str(), "Z");
AZStd::string boolType = JNI::GetSignature(bool());
ASSERT_STREQ(boolType.c_str(), "Z");
AZStd::string allBoolTypes = JNI::GetSignature(true, false, bool());
ASSERT_STREQ(allBoolTypes.c_str(), "ZZZ");
}
TEST(Signature, Generate_DefaultJBooleanTypes_IsZ)
{
AZStd::string jniTrueType = JNI::GetSignature(JNI_TRUE);
ASSERT_STREQ(jniTrueType.c_str(), "Z");
AZStd::string jniFalseType = JNI::GetSignature(JNI_FALSE);
ASSERT_STREQ(jniFalseType.c_str(), "Z");
AZStd::string jboolType = JNI::GetSignature(jboolean());
ASSERT_STREQ(jboolType.c_str(), "Z");
AZStd::string jniBoolArrayType = JNI::GetSignature(jbooleanArray());
ASSERT_STREQ(jniBoolArrayType.c_str(), "[Z");
AZStd::string allJBoolTypes = JNI::GetSignature(JNI_TRUE, JNI_FALSE, jboolean(), jbooleanArray());
ASSERT_STREQ(allJBoolTypes.c_str(), "ZZZ[Z");
}
TEST(Signature, Generate_AllDefaultBooleanTypes_IsZ)
{
AZStd::string allBoolTypes = JNI::GetSignature(true, false, bool(), JNI_TRUE, JNI_FALSE, jboolean(), jbooleanArray());
ASSERT_STREQ(allBoolTypes.c_str(), "ZZZZZZ[Z");
}
TEST(Signature, Generate_DefaultJByteTypes_IsB)
{
AZStd::string jbyteType = JNI::GetSignature(jbyte());
ASSERT_STREQ(jbyteType.c_str(), "B");
AZStd::string jbyteArrayType = JNI::GetSignature(jbyteArray());
ASSERT_STREQ(jbyteArrayType.c_str(), "[B");
AZStd::string allJByteTypes = JNI::GetSignature(jbyte(), jbyteArray());
ASSERT_STREQ(allJByteTypes.c_str(), "B[B");
}
TEST(Signature, Generate_DefaultJCharTypes_IsC)
{
AZStd::string jcharType = JNI::GetSignature(jchar());
ASSERT_STREQ(jcharType.c_str(), "C");
AZStd::string jcharArrayType = JNI::GetSignature(jcharArray());
ASSERT_STREQ(jcharArrayType.c_str(), "[C");
AZStd::string allJCharTypes = JNI::GetSignature(jchar(), jcharArray());
ASSERT_STREQ(allJCharTypes.c_str(), "C[C");
}
TEST(Signature, Generate_DefaultJShortTypes_IsS)
{
AZStd::string jshortType = JNI::GetSignature(jshort());
ASSERT_STREQ(jshortType.c_str(), "S");
AZStd::string jshortArrayType = JNI::GetSignature(jshortArray());
ASSERT_STREQ(jshortArrayType.c_str(), "[S");
AZStd::string allJShortTypes = JNI::GetSignature(jshort(), jshortArray());
ASSERT_STREQ(allJShortTypes.c_str(), "S[S");
}
TEST(Signature, Generate_DefaultJIntTypes_IsI)
{
AZStd::string jintType = JNI::GetSignature(jint());
ASSERT_STREQ(jintType.c_str(), "I");
AZStd::string jintArrayType = JNI::GetSignature(jintArray());
ASSERT_STREQ(jintArrayType.c_str(), "[I");
AZStd::string allJIntTypes = JNI::GetSignature(jint(), jintArray());
ASSERT_STREQ(allJIntTypes.c_str(), "I[I");
}
TEST(Signature, Generate_DefaultJLongTypes_IsJ)
{
AZStd::string jlongType = JNI::GetSignature(jlong());
ASSERT_STREQ(jlongType.c_str(), "J");
AZStd::string jlongArrayType = JNI::GetSignature(jlongArray());
ASSERT_STREQ(jlongArrayType.c_str(), "[J");
AZStd::string allJLongTypes = JNI::GetSignature(jlong(), jlongArray());
ASSERT_STREQ(allJLongTypes.c_str(), "J[J");
}
TEST(Signature, Generate_DefaultJFloatTypes_IsF)
{
AZStd::string jfloatType = JNI::GetSignature(jfloat());
ASSERT_STREQ(jfloatType.c_str(), "F");
AZStd::string jfloatArrayType = JNI::GetSignature(jfloatArray());
ASSERT_STREQ(jfloatArrayType.c_str(), "[F");
AZStd::string allJFloatTypes = JNI::GetSignature(jfloat(), jfloatArray());
ASSERT_STREQ(allJFloatTypes.c_str(), "F[F");
}
TEST(Signature, Generate_DefaultJDoubleTypes_IsD)
{
AZStd::string jdoubleType = JNI::GetSignature(jdouble());
ASSERT_STREQ(jdoubleType.c_str(), "D");
AZStd::string jdoubleArrayType = JNI::GetSignature(jdoubleArray());
ASSERT_STREQ(jdoubleArrayType.c_str(), "[D");
AZStd::string allJDoubleTypes = JNI::GetSignature(jdouble(), jdoubleArray());
ASSERT_STREQ(allJDoubleTypes.c_str(), "D[D");
}
TEST(Signature, Generate_DefaultJStringTypes_IsLjava_lang_String)
{
AZStd::string jstringType = JNI::GetSignature(jstring());
ASSERT_STREQ(jstringType.c_str(), "Ljava/lang/String;");
}
TEST(Signature, Generate_DefaultJClassTypes_IsLjava_lang_Class)
{
AZStd::string jclassType = JNI::GetSignature(jclass());
ASSERT_STREQ(jclassType.c_str(), "Ljava/lang/Class;");
}
TEST(Signature, Generate_DefaultJObjectType_IsEmptyString)
{
AZStd::string jobjectType = JNI::GetSignature(jobject());
ASSERT_TRUE(jobjectType.empty());
AZStd::string jobjectArrayType = JNI::GetSignature(jobjectArray());
ASSERT_TRUE(jobjectArrayType.empty());
}
TEST(Signature, Generate_SimpleJObjectType_IsLcom_amazon_test_SimpleObject)
{
SimpleJavaObject simpleObject;
AZStd::string simpleObjectType = JNI::GetSignature(simpleObject.m_objectRef);
ASSERT_STREQ(simpleObjectType.c_str(), "Lcom/amazon/test/SimpleObject;");
}
TEST(Signature, Generate_AllDefaultPrimtiveTypes_IsZZZBBCCSSIIJJFFDD)
{
AZStd::string allPrimitiveTypes = JNI::GetSignature(
bool(), jboolean(), jbooleanArray(),
jbyte(), jbyteArray(),
jchar(), jcharArray(),
jshort(), jshortArray(),
jint(), jintArray(),
jlong(), jlongArray(),
jfloat(), jfloatArray(),
jdouble(), jdoubleArray()
);
ASSERT_STREQ(allPrimitiveTypes.c_str(), "ZZ[ZB[BC[CS[SI[IJ[JF[FD[D");
}
TEST(Signature, Generate_DefaultJStringJClassTypes_IsLjava_lang_StringLjava_lang_Class)
{
AZStd::string jstringJClassTypes = JNI::GetSignature(jstring(), jclass());
ASSERT_STREQ(jstringJClassTypes.c_str(), "Ljava/lang/String;Ljava/lang/Class;");
}
TEST(Signature, Generate_AllTypes_IsZZZBBCCSSIIJJFFDDLjava_lang_StringLjava_lang_ClassLcom_amazon_test_SimpleObject)
{
SimpleJavaObject simpleObject;
AZStd::string allTypes = JNI::GetSignature(
bool(), jboolean(), jbooleanArray(),
jbyte(), jbyteArray(),
jchar(), jcharArray(),
jshort(), jshortArray(),
jint(), jintArray(),
jlong(), jlongArray(),
jfloat(), jfloatArray(),
jdouble(), jdoubleArray(),
jstring(), jclass(),
simpleObject.m_objectRef
);
ASSERT_STREQ(allTypes.c_str(), "ZZ[ZB[BC[CS[SI[IJ[JF[FD[DLjava/lang/String;Ljava/lang/Class;Lcom/amazon/test/SimpleObject;");
}
// ----
// Validation Tests
// ----
TEST(Signature, Validate_NoArgs_IsEmptyString)
{
ASSERT_TRUE(JNI::ValidateSignature(""));
}
TEST(Signature, Validate_DefaultNativeBooleanTypes_IsZ)
{
ASSERT_TRUE(JNI::ValidateSignature("Z", true));
ASSERT_TRUE(JNI::ValidateSignature("Z", false));
ASSERT_TRUE(JNI::ValidateSignature("Z", bool()));
}
TEST(Signature, Validate_DefaultJBooleanTypes_IsZ)
{
ASSERT_TRUE(JNI::ValidateSignature("Z", JNI_TRUE));
ASSERT_TRUE(JNI::ValidateSignature("Z", JNI_FALSE));
ASSERT_TRUE(JNI::ValidateSignature("Z", jboolean()));
ASSERT_TRUE(JNI::ValidateSignature("[Z", jbooleanArray()));
}
TEST(Signature, Validate_AllDefaultBooleanTypes_IsZ)
{
ASSERT_TRUE(JNI::ValidateSignature("ZZZ", true, false, bool()));
ASSERT_TRUE(JNI::ValidateSignature("ZZZ[Z", JNI_TRUE, JNI_FALSE, jboolean(), jbooleanArray()));
ASSERT_TRUE(JNI::ValidateSignature("ZZZZZZ[Z",
true, false, bool(),
JNI_TRUE, JNI_FALSE, jboolean(),
jbooleanArray()));
}
TEST(Signature, Validate_DefaultJByteTypes_IsB)
{
ASSERT_TRUE(JNI::ValidateSignature("B", jbyte()));
ASSERT_TRUE(JNI::ValidateSignature("[B", jbyteArray()));
}
TEST(Signature, Validate_AllDefaultJByteTypes_IsB)
{
ASSERT_TRUE(JNI::ValidateSignature("B[B", jbyte(), jbyteArray()));
}
TEST(Signature, Validate_DefaultJCharTypes_IsC)
{
ASSERT_TRUE(JNI::ValidateSignature("C", jchar()));
ASSERT_TRUE(JNI::ValidateSignature("[C", jcharArray()));
}
TEST(Signature, Validate_AllDefaultJCharTypes_IsC)
{
ASSERT_TRUE(JNI::ValidateSignature("C[C", jchar(), jcharArray()));
}
TEST(Signature, Validate_DefaultJShortTypes_IsS)
{
ASSERT_TRUE(JNI::ValidateSignature("S", jshort()));
ASSERT_TRUE(JNI::ValidateSignature("[S", jshortArray()));
}
TEST(Signature, Validate_AllDefaultJShortTypes_IsS)
{
ASSERT_TRUE(JNI::ValidateSignature("S[S", jshort(), jshortArray()));
}
TEST(Signature, Validate_DefaultJIntTypes_IsI)
{
ASSERT_TRUE(JNI::ValidateSignature("I", jint()));
ASSERT_TRUE(JNI::ValidateSignature("[I", jintArray()));
}
TEST(Signature, Validate_AllDefaultJIntTypes_IsI)
{
ASSERT_TRUE(JNI::ValidateSignature("I[I", jint(), jintArray()));
}
TEST(Signature, Validate_DefaultJLongTypes_IsJ)
{
ASSERT_TRUE(JNI::ValidateSignature("J", jlong()));
ASSERT_TRUE(JNI::ValidateSignature("[J", jlongArray()));
}
TEST(Signature, Validate_AllDefaultJLongTypes_IsJ)
{
ASSERT_TRUE(JNI::ValidateSignature("J[J", jlong(), jlongArray()));
}
TEST(Signature, Validate_DefaultJFloatTypes_IsF)
{
ASSERT_TRUE(JNI::ValidateSignature("F", jfloat()));
ASSERT_TRUE(JNI::ValidateSignature("[F", jfloatArray()));
}
TEST(Signature, Validate_AllDefaultJFloatTypes_IsF)
{
ASSERT_TRUE(JNI::ValidateSignature("F[F", jfloat(), jfloatArray()));
}
TEST(Signature, Validate_DefaultJDoubleTypes_IsD)
{
ASSERT_TRUE(JNI::ValidateSignature("D", jdouble()));
ASSERT_TRUE(JNI::ValidateSignature("[D", jdoubleArray()));
}
TEST(Signature, Validate_AllDefaultJDoubleTypes_IsD)
{
ASSERT_TRUE(JNI::ValidateSignature("D[D", jdouble(), jdoubleArray()));
}
TEST(Signature, Validate_AllDefaultPrimtiveTypes_IsZZZBBCCSSIIJJFFDD)
{
ASSERT_TRUE(JNI::ValidateSignature(
"ZZ[ZB[BC[CS[SI[IJ[JF[FD[D",
bool(), jboolean(), jbooleanArray(),
jbyte(), jbyteArray(),
jchar(), jcharArray(),
jshort(), jshortArray(),
jint(), jintArray(),
jlong(), jlongArray(),
jfloat(), jfloatArray(),
jdouble(), jdoubleArray()
));
}
TEST(Signature, Validate_JClass_IsL_java_lang_Class)
{
jclass signatureClass = JNI::LoadClass("com/amazon/test/SimpleObject");
ASSERT_TRUE(JNI::ValidateSignature("Ljava/lang/Class;", signatureClass));
}
TEST(Signature, Validate_JString_IsL_java_lang_String)
{
JNIEnv* jniEnv = JNI::GetEnv();
jstring javaString = jniEnv->NewStringUTF("Test");
EXPECT_TRUE(JNI::ValidateSignature("Ljava/lang/String;", javaString));
jniEnv->DeleteLocalRef(javaString);
}
TEST(Signature, Validate_SimpleJObjectType_IsLcom_amazon_test_SimpleObject)
{
SimpleJavaObject simpleObject;
ASSERT_TRUE(JNI::ValidateSignature("Lcom/amazon/test/SimpleObject;", simpleObject.m_objectRef));
}
TEST(Signature, Validate_PolymorphicActivityType_IsLandroid_app_Activity)
{
jobject activity = Utils::GetActivityRef();
ASSERT_TRUE(JNI::ValidateSignature("Landroid/app/Activity;", activity));
}
TEST(Signature, Validate_JStringJClass_IsLjava_lang_StringLjava_lang_Class)
{
JNIEnv* jniEnv = JNI::GetEnv();
jclass signatureClass = JNI::LoadClass("com/amazon/test/SimpleObject");
jstring javaString = jniEnv->NewStringUTF("Test");
EXPECT_TRUE(JNI::ValidateSignature("Ljava/lang/String;Ljava/lang/Class;", javaString, signatureClass));
jniEnv->DeleteLocalRef(javaString);
}
TEST(Signature, Validate_AllTypes_IsZZZBBCCSSIIJJFFDDL_java_lang_StringL_java_lang_ClassLcom_amazon_test_SimpleObjectLandroid_app_Activity)
{
JNIEnv* jniEnv = JNI::GetEnv();
jstring javaString = jniEnv->NewStringUTF("Test");
SimpleJavaObject simpleObject;
jobject activity = Utils::GetActivityRef();
ASSERT_TRUE(JNI::ValidateSignature(
"ZZ[ZB[BC[CS[SI[IJ[JF[FD[DLjava/lang/String;Ljava/lang/Class;Lcom/amazon/test/SimpleObject;Landroid/app/Activity;",
bool(), jboolean(), jbooleanArray(),
jbyte(), jbyteArray(),
jchar(), jcharArray(),
jshort(), jshortArray(),
jint(), jintArray(),
jlong(), jlongArray(),
jfloat(), jfloatArray(),
jdouble(), jdoubleArray(),
javaString,
simpleObject.m_classRef,
simpleObject.m_objectRef,
activity
));
jniEnv->DeleteLocalRef(javaString);
}
TEST(Signature, Validate_ExtraParams_IsFalse)
{
ASSERT_FALSE(JNI::ValidateSignature("Z", JNI_TRUE, JNI_TRUE));
}
TEST(Signature, Validate_MissingParams_IsFalse)
{
ASSERT_FALSE(JNI::ValidateSignature("ZZ", JNI_TRUE));
}
TEST(Signature, Validate_WrongParms_IsFalse)
{
ASSERT_FALSE(JNI::ValidateSignature("ZI", JNI_TRUE, jfloat()));
}
} // namespace UnitTest
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,206 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Android/Utils.h>
#include <AzCore/Android/AndroidEnv.h>
#include <AzCore/Android/APKFileHandler.h>
#include <AzCore/Android/JNI/Object.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/Memory/OSAllocator.h>
namespace AZ
{
namespace Android
{
namespace Utils
{
namespace
{
////////////////////////////////////////////////////////////////
const char* GetApkAssetsPrefix()
{
return "/APK/";
}
}
////////////////////////////////////////////////////////////////
jclass GetActivityClassRef()
{
return AndroidEnv::Get()->GetActivityClassRef();
}
////////////////////////////////////////////////////////////////
jobject GetActivityRef()
{
return AndroidEnv::Get()->GetActivityRef();
}
////////////////////////////////////////////////////////////////
AAssetManager* GetAssetManager()
{
return AndroidEnv::Get()->GetAssetManager();
}
////////////////////////////////////////////////////////////////
AConfiguration* GetConfiguration()
{
return AndroidEnv::Get()->GetConfiguration();
}
////////////////////////////////////////////////////////////////
void UpdateConfiguration()
{
return AndroidEnv::Get()->UpdateConfiguration();
}
////////////////////////////////////////////////////////////////
const char* GetAppPrivateStoragePath()
{
return AndroidEnv::Get()->GetAppPrivateStoragePath();
}
////////////////////////////////////////////////////////////////
const char* GetAppPublicStoragePath()
{
return AndroidEnv::Get()->GetAppPublicStoragePath();
}
////////////////////////////////////////////////////////////////
const char* GetObbStoragePath()
{
return AndroidEnv::Get()->GetObbStoragePath();
}
////////////////////////////////////////////////////////////////
const char* GetPackageName()
{
return AndroidEnv::Get()->GetPackageName();
}
////////////////////////////////////////////////////////////////
int GetAppVersionCode()
{
return AndroidEnv::Get()->GetAppVersionCode();
}
////////////////////////////////////////////////////////////////
const char* GetObbFileName(bool mainFile)
{
return AndroidEnv::Get()->GetObbFileName(mainFile);
}
////////////////////////////////////////////////////////////////
bool IsApkPath(const char* filePath)
{
return (strncmp(filePath, GetApkAssetsPrefix(), 4) == 0); // +3 for "APK", +1 for '/' starting slash
}
////////////////////////////////////////////////////////////////
const char* StripApkPrefix(const char* filePath)
{
const int prefixLength = 5; // +3 for "APK", +2 for '/' on either end
if (!IsApkPath(filePath))
{
return filePath;
}
return filePath + prefixLength;
}
////////////////////////////////////////////////////////////////
const char* FindAssetsDirectory()
{
#if defined(LY_NO_ASSETS)
// The TestRunner app which runs unit tests does not have any assets.
return GetAppPublicStoragePath();
#endif
#if !defined(_RELEASE)
// first check to see if they are in public storage (application specific)
const char* publicAppStorage = GetAppPublicStoragePath();
OSString path = OSString::format("%s/bootstrap.cfg", publicAppStorage);
AZ_TracePrintf("Android::Utils", "Searching for %s\n", path.c_str());
FILE* f = fopen(path.c_str(), "r");
if (f != nullptr)
{
fclose(f);
return publicAppStorage;
}
#endif // !defined(_RELEASE)
// if they aren't in public storage, they are in private storage (APK)
AAssetManager* mgr = GetAssetManager();
if (mgr)
{
AAsset* asset = AAssetManager_open(mgr, "bootstrap.cfg", AASSET_MODE_UNKNOWN);
if (asset)
{
AAsset_close(asset);
return GetApkAssetsPrefix();
}
}
AZ_Assert(false, "Failed to locate the bootstrap.cfg path");
return nullptr;
}
////////////////////////////////////////////////////////////////
void ShowSplashScreen()
{
JNI::Internal::Object<AZ::OSAllocator> activity(GetActivityClassRef(), GetActivityRef());
activity.RegisterMethod("ShowSplashScreen", "()V");
activity.InvokeVoidMethod("ShowSplashScreen");
}
////////////////////////////////////////////////////////////////
void DismissSplashScreen()
{
JNI::Internal::Object<AZ::OSAllocator> activity(GetActivityClassRef(), GetActivityRef());
activity.RegisterMethod("DismissSplashScreen", "()V");
activity.InvokeVoidMethod("DismissSplashScreen");
}
////////////////////////////////////////////////////////////////
ANativeWindow* GetWindow()
{
return AndroidEnv::Get()->GetWindow();
}
////////////////////////////////////////////////////////////////
bool GetWindowSize(int& widthPixels, int& heightPixels)
{
ANativeWindow* window = GetWindow();
if (window)
{
widthPixels = ANativeWindow_getWidth(window);
heightPixels = ANativeWindow_getHeight(window);
// should an error occur from the above functions a negative value will be returned
return (widthPixels > 0 && heightPixels > 0);
}
return false;
}
////////////////////////////////////////////////////////////////
void SetLoadFilesToMemory(const char* fileNames)
{
APKFileHandler::SetLoadFilesToMemory(fileNames);
}
}
}
}
@@ -0,0 +1,98 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/string/string.h>
#include <jni.h>
#include <android/asset_manager.h>
#include <android/configuration.h>
#include <android/native_window.h>
namespace AZ
{
namespace Android
{
namespace Utils
{
//! Request the global reference to the activity class
jclass GetActivityClassRef();
//! Request the global reference to the activity instance
jobject GetActivityRef();
//! Get the global pointer to the Android asset manager, which is used for APK file i/o.
AAssetManager* GetAssetManager();
//! Get the global pointer to the device/application configuration,
AConfiguration* GetConfiguration();
//! If the AndroidEnv owns the native configuration, it will be updated with the latest configuration
//! information, otherwise nothing will happen.
void UpdateConfiguration();
//! Get the hidden internal storage, typically this is where the application is installed
//! on the device.
//! e.g. /data/data/<package_name>/files
const char* GetAppPrivateStoragePath();
//! Get the application specific directory for public public storage.
//! e.g. <public_storage>/Android/data/<package_name>/files
const char* GetAppPublicStoragePath();
//! Get the application specific directory for obb files.
//! e.g. <public_storage>/Android/obb/<package_name>/files
const char* GetObbStoragePath();
//! Get the dot separated package name for the current application.
//! e.g. com.lumberyard.samples for SamplesProject
const char* GetPackageName();
//! Get the app version code (android:versionCode in the manifest).
int GetAppVersionCode();
//! Get the filename of the obb. This doesn't include the path to the obb folder.
const char* GetObbFileName(bool mainFile);
//! Check to see if the path is prefixed with "/APK"
bool IsApkPath(const char* filePath);
//! Will first check to verify the argument is an apk asset path and if so
//! will strip the prefix from the path.
//! \return The pointer position of the relative asset path
const char* StripApkPrefix(const char* filePath);
//! Searches application storage and the APK for bootstrap.cfg. Will return nullptr
//! if bootstrap.cfg is not found.
const char* FindAssetsDirectory();
//! Calls into Java to show the splash screen on the main UI (Java) thread
void ShowSplashScreen();
//! Calls into Java to dismiss the splash screen on the main UI (Java) thread
void DismissSplashScreen();
//! Get the native android window
ANativeWindow* GetWindow();
//! Query the pixel dimensions of the window
//! \param[out] widthPixels Returns the pixel width of the window
//! \param[out] heightPixels Returns the pixel height of the window
//! \return True if successful, False otherwise
bool GetWindowSize(int& widthPixels, int& heightPixels);
//! Set the filenames for files to be loaded to memory
void SetLoadFilesToMemory(const char* fileNames);
}
}
}
@@ -0,0 +1,470 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/std/string/conversions.h>
namespace AZ
{
namespace Data
{
AssetFilterInfo::AssetFilterInfo(const AssetId& id, const AssetType& assetType, AssetLoadBehavior loadBehavior)
: m_assetId(id)
, m_assetType(assetType)
, m_loadBehavior(loadBehavior)
{
}
AssetFilterInfo::AssetFilterInfo(const Asset<AssetData>& asset)
: m_assetId(asset.GetId())
, m_assetType(asset.GetType())
, m_loadBehavior(asset.GetAutoLoadBehavior())
{
}
AssetId AssetId::CreateString(AZStd::string_view input)
{
size_t separatorIdx = input.find(':');
if (separatorIdx == AZStd::string_view::npos)
{
return AssetId();
}
AssetId assetId;
assetId.m_guid = Uuid::CreateString(input.data(), separatorIdx);
if (assetId.m_guid.IsNull())
{
return AssetId();
}
assetId.m_subId = strtoul(&input[separatorIdx + 1], nullptr, 16);
return assetId;
}
void AssetId::Reflect(AZ::ReflectContext* context)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<Data::AssetId>()
->Version(1)
->Field("guid", &Data::AssetId::m_guid)
->Field("subId", &Data::AssetId::m_subId)
;
}
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->Class<Data::AssetId>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Constructor()
->Constructor<const Uuid&, u32>()
->Method("CreateString", &Data::AssetId::CreateString)
->Method("IsValid", &Data::AssetId::IsValid)
->Attribute(AZ::Script::Attributes::Alias, "is_valid")
->Method("ToString", [](const Data::AssetId* self) { return self->ToString<AZStd::string>(); })
->Attribute(AZ::Script::Attributes::Alias, "to_string")
->Method("IsEqual", [](const Data::AssetId& self, const Data::AssetId& other) { return self == other; })
->Attribute(AZ::Script::Attributes::Alias, "is_equal")
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)
;
behaviorContext->Class<Data::AssetInfo>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Property("assetId", BehaviorValueGetter(&Data::AssetInfo::m_assetId), nullptr)
->Property("assetType", BehaviorValueGetter(&Data::AssetInfo::m_assetType), nullptr)
->Property("sizeBytes", BehaviorValueGetter(&Data::AssetInfo::m_sizeBytes), nullptr)
->Property("relativePath", BehaviorValueGetter(&Data::AssetInfo::m_relativePath), nullptr)
;
}
}
namespace AssetInternal
{
Asset<AssetData> FindOrCreateAsset(const AssetId& id, const AssetType& type, AssetLoadBehavior assetReferenceLoadBehavior)
{
return AssetManager::Instance().FindOrCreateAsset(id, type, assetReferenceLoadBehavior);
}
Asset<AssetData> GetAsset(const AssetId& id, const AssetType& type, AssetLoadBehavior assetReferenceLoadBehavior,
const AssetLoadParameters& loadParams)
{
return AssetManager::Instance().GetAsset(id, type, assetReferenceLoadBehavior, loadParams);
}
AssetData::AssetStatus BlockUntilLoadComplete(const Asset<AssetData>& asset)
{
return AssetManager::Instance().BlockUntilLoadComplete(asset);
}
void UpdateAssetInfo(AssetId& id, AZStd::string& assetHint)
{
// it is possible that the assetID given is legacy / old and we have a new assetId we can use instead for it.
// in that case, upgrade the AssetID to the new one, so that future saves are in the new format.
// this function should only be invoked if the feature is turned on in the asset manager as it can be (slightly) expensive
if ((!AssetManager::IsReady()) || (!AssetManager::Instance().GetAssetInfoUpgradingEnabled()))
{
return;
}
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, id);
if (assetInfo.m_assetId.IsValid())
{
id = assetInfo.m_assetId;
if (!assetInfo.m_relativePath.empty())
{
assetHint = assetInfo.m_relativePath;
}
}
}
bool ReloadAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior)
{
AssetManager::Instance().ReloadAsset(assetData->GetId(), assetReferenceLoadBehavior);
return true;
}
bool SaveAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior)
{
AssetManager::Instance().SaveAsset({ assetData, assetReferenceLoadBehavior });
return true;
}
Asset<AssetData> GetAssetData(const AssetId& id, AssetLoadBehavior assetReferenceLoadBehavior)
{
if (AssetManager::IsReady())
{
AZStd::lock_guard<AZStd::recursive_mutex> assetLock(AssetManager::Instance().m_assetMutex);
auto it = AssetManager::Instance().m_assets.find(id);
if (it != AssetManager::Instance().m_assets.end())
{
return { it->second, assetReferenceLoadBehavior };
}
}
return {};
}
AssetId ResolveAssetId(const AssetId& id)
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, id);
if (assetInfo.m_assetId.IsValid())
{
return assetInfo.m_assetId;
}
else
{
return id;
}
}
}
AssetData::~AssetData()
{
UnregisterWithHandler();
}
void AssetData::Reflect(AZ::ReflectContext* context)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<AZ::Data::AssetData>()
->Version(1)
;
}
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->Class<AssetData>("AssetData")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Method("IsReady", &AssetData::IsReady)
->Attribute(AZ::Script::Attributes::Alias, "is_ready")
->Method("IsError", &AssetData::IsError)
->Attribute(AZ::Script::Attributes::Alias, "is_error")
->Method("IsLoading", &AssetData::IsLoading)
->Attribute(AZ::Script::Attributes::Alias, "is_loading")
->Method("GetId", &AssetData::GetId)
->Attribute(AZ::Script::Attributes::Alias, "get_id")
->Method("GetUseCount", &AssetData::GetUseCount)
->Attribute(AZ::Script::Attributes::Alias, "get_use_count")
;
}
}
void AssetData::Acquire()
{
AZ_Assert(m_useCount >= 0, "AssetData has been deleted")
AcquireWeak();
++m_useCount;
}
void AssetData::Release()
{
AZ_Assert(m_useCount > 0, "Usecount is already 0!");
if (m_useCount.fetch_sub(1) == 1)
{
if (AssetManager::IsReady())
{
AssetManager::Instance().OnAssetUnused(this);
}
else
{
AZ_Assert(false, "Attempting to release asset after AssetManager has been destroyed!");
}
}
ReleaseWeak();
}
void AssetData::AcquireWeak()
{
AZ_Assert(m_useCount >= 0, "AssetData has been deleted");
++m_weakUseCount;
}
void AssetData::ReleaseWeak()
{
AZ_Assert(m_weakUseCount > 0, "WeakUseCount is already 0");
AssetId assetId = m_assetId;
int creationToken = m_creationToken;
AssetType assetType = GetType();
bool removeFromHash = IsRegisterReadonlyAndShareable();
// default creation token implies that the asset was not created by the asset manager and therefore it cannot be in the asset map.
removeFromHash = creationToken == s_defaultCreationToken ? false : removeFromHash;
if (m_weakUseCount.fetch_sub(1) == 1)
{
if (AssetManager::IsReady())
{
AssetManager::Instance().ReleaseAsset(this, assetId, assetType, removeFromHash, creationToken);
}
else
{
AZ_Assert(false, "Attempting to release asset after AssetManager has been destroyed!");
}
}
}
bool AssetData::IsLoading(bool includeQueued) const
{
auto curStatus = GetStatus();
return(curStatus == AssetStatus::Loading || curStatus == AssetStatus::LoadedPreReady || curStatus==AssetStatus::StreamReady ||
(includeQueued && curStatus == AssetStatus::Queued));
}
void AssetData::RegisterWithHandler(AssetHandler* handler)
{
if (!handler)
{
AZ_Error("AssetData", false, "No handler to register with");
return;
}
m_registeredHandler = handler;
}
void AssetData::UnregisterWithHandler()
{
if (m_registeredHandler)
{
m_registeredHandler = nullptr;
}
}
bool AssetData::GetFlag(const AssetDataFlags& checkFlag) const
{
return m_flags[aznumeric_cast<AZStd::size_t>(checkFlag)];
}
void AssetData::SetFlag(const AssetDataFlags& checkFlag, bool setValue)
{
m_flags.set(aznumeric_cast<AZStd::size_t>(checkFlag), setValue);
}
bool AssetData::GetRequeue() const
{
return GetFlag(AssetDataFlags::Requeue);
}
void AssetData::SetRequeue(bool requeue)
{
SetFlag(AssetDataFlags::Requeue, requeue);
}
void AssetBusCallbacks::SetCallbacks(const AssetReadyCB& readyCB, const AssetMovedCB& movedCB, const AssetReloadedCB& reloadedCB,
const AssetSavedCB& savedCB, const AssetUnloadedCB& unloadedCB, const AssetErrorCB& errorCB, const AssetCanceledCB& cancelCB)
{
m_onAssetReadyCB = readyCB;
m_onAssetMovedCB = movedCB;
m_onAssetReloadedCB = reloadedCB;
m_onAssetSavedCB = savedCB;
m_onAssetUnloadedCB = unloadedCB;
m_onAssetErrorCB = errorCB;
m_onAssetCanceledCB = cancelCB;
}
void AssetBusCallbacks::ClearCallbacks()
{
SetCallbacks(AssetBusCallbacks::AssetReadyCB(),
AssetBusCallbacks::AssetMovedCB(),
AssetBusCallbacks::AssetReloadedCB(),
AssetBusCallbacks::AssetSavedCB(),
AssetBusCallbacks::AssetUnloadedCB(),
AssetBusCallbacks::AssetErrorCB(),
AssetBusCallbacks::AssetCanceledCB());
}
void AssetBusCallbacks::SetOnAssetReadyCallback(const AssetReadyCB& readyCB)
{
m_onAssetReadyCB = readyCB;
}
void AssetBusCallbacks::SetOnAssetMovedCallback(const AssetMovedCB& movedCB)
{
m_onAssetMovedCB = movedCB;
}
void AssetBusCallbacks::SetOnAssetReloadedCallback(const AssetReloadedCB& reloadedCB)
{
m_onAssetReloadedCB = reloadedCB;
}
void AssetBusCallbacks::SetOnAssetSavedCallback(const AssetSavedCB& savedCB)
{
m_onAssetSavedCB = savedCB;
}
void AssetBusCallbacks::SetOnAssetUnloadedCallback(const AssetUnloadedCB& unloadedCB)
{
m_onAssetUnloadedCB = unloadedCB;
}
void AssetBusCallbacks::SetOnAssetErrorCallback(const AssetErrorCB& errorCB)
{
m_onAssetErrorCB = errorCB;
}
void AssetBusCallbacks::SetOnAssetCanceledCallback(const AssetCanceledCB& cancelCB)
{
m_onAssetCanceledCB = cancelCB;
}
void AssetBusCallbacks::OnAssetReady(Asset<AssetData> asset)
{
if (m_onAssetReadyCB)
{
m_onAssetReadyCB(asset, *this);
}
}
void AssetBusCallbacks::OnAssetMoved(Asset<AssetData> asset, void* oldDataPointer)
{
if (m_onAssetMovedCB)
{
m_onAssetMovedCB(asset, oldDataPointer, *this);
}
}
void AssetBusCallbacks::OnAssetReloaded(Asset<AssetData> asset)
{
if (m_onAssetReloadedCB)
{
m_onAssetReloadedCB(asset, *this);
}
}
void AssetBusCallbacks::OnAssetSaved(Asset<AssetData> asset, bool isSuccessful)
{
if (m_onAssetSavedCB)
{
m_onAssetSavedCB(asset, isSuccessful, *this);
}
}
void AssetBusCallbacks::OnAssetUnloaded(const AssetId assetId, const AssetType assetType)
{
if (m_onAssetUnloadedCB)
{
m_onAssetUnloadedCB(assetId, assetType, *this);
}
}
void AssetBusCallbacks::OnAssetError(Asset<AssetData> asset)
{
if (m_onAssetErrorCB)
{
m_onAssetErrorCB(asset, *this);
}
}
void AssetBusCallbacks::OnAssetCanceled(const AssetId assetId)
{
if (m_onAssetCanceledCB)
{
m_onAssetCanceledCB(assetId, *this);
}
}
/*static*/ bool AssetFilterNoAssetLoading([[maybe_unused]] const AssetFilterInfo& filterInfo)
{
return false;
}
namespace ProductDependencyInfo
{
AZ::Data::AssetLoadBehavior LoadBehaviorFromFlags(const ProductDependencyFlags& dependencyFlags)
{
AZ::u8 loadBehaviorValue = 0;
for (AZ::u8 thisFlag = aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorLow);
thisFlag <= aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorHigh); ++thisFlag)
{
if (dependencyFlags[thisFlag])
{
loadBehaviorValue |= (1 << thisFlag);
}
}
return static_cast<AZ::Data::AssetLoadBehavior>(loadBehaviorValue);
}
ProductDependencyFlags CreateFlags(AZ::Data::AssetLoadBehavior autoLoadBehavior)
{
AZ::Data::ProductDependencyInfo::ProductDependencyFlags returnFlags;
AZ::u8 loadBehavior = aznumeric_caster(autoLoadBehavior);
for (AZ::u8 thisFlag = aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorLow);
thisFlag <= aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorHigh); ++thisFlag)
{
if (loadBehavior & (1 << thisFlag))
{
returnFlags[thisFlag] = 1;
}
}
return returnFlags;
}
}
} // namespace Data
} // namespace AZ
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,651 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Asset/AssetContainer.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Asset/AssetManager.h>
namespace AZ
{
namespace Data
{
AssetContainer::AssetContainer(Asset<AssetData> rootAsset, const AssetLoadParameters& loadParams)
{
m_rootAsset = AssetInternal::WeakAsset<AssetData>(rootAsset);
m_containerAssetId = m_rootAsset.GetId();
AddDependentAssets(rootAsset, loadParams);
}
AssetContainer::~AssetContainer()
{
// Validate that if the AssetManager is performing normal processing duties, the AssetContainer is only destroyed once all
// dependent asset loads have completed.
if (AssetManager::IsReady() && !AssetManager::Instance().ShouldCancelAllActiveJobs())
{
AZ_Assert(m_waitingCount == 0, "Container destroyed while dependent assets are still loading. The dependent assets may "
"end up in a perpetual loading state if there is no top-level container signalling the completion of the full load.");
}
AssetBus::MultiHandler::BusDisconnect();
AssetLoadBus::MultiHandler::BusDisconnect();
}
void AssetContainer::AddDependentAssets(Asset<AssetData> rootAsset, const AssetLoadParameters& loadParams)
{
AssetId rootAssetId = rootAsset.GetId();
AssetType rootAssetType = rootAsset.GetType();
// Every asset we're going to be waiting on a load for - the root and all valid dependencies
AZStd::vector<AssetId> waitingList;
waitingList.push_back(rootAssetId);
// Every asset dependency that we're aware of, whether or not it gets filtered out by the asset filter callback.
// This will be used at the point that asset references get serialized in to see whether or not we've received any
// unexpected assets that didn't appear in our asset catalog dependency list that need to be loaded anyways.
AZStd::vector<AssetId> handledAssetDependencyList;
// Cached AssetInfo to save another lookup inside Assetmanager
AZStd::vector<AssetInfo> dependencyInfoList;
Outcome<AZStd::vector<ProductDependency>, AZStd::string> getDependenciesResult = Failure(AZStd::string());
// Track preloads in an additional list - they're in our waiting/dependencyInfo lists as well, but preloads require us to
// suppress emitting "AssetReady" until everything we care about in this context is ready
PreloadAssetListType preloadDependencies;
if (loadParams.m_dependencyRules == AssetDependencyLoadRules::UseLoadBehavior)
{
AZStd::unordered_set<AssetId> noloadDependencies;
AssetCatalogRequestBus::BroadcastResult(getDependenciesResult, &AssetCatalogRequestBus::Events::GetLoadBehaviorProductDependencies,
rootAssetId, noloadDependencies, preloadDependencies);
if (!noloadDependencies.empty())
{
AZStd::lock_guard<AZStd::recursive_mutex> dependencyLock(m_dependencyMutex);
m_unloadedDependencies.insert(noloadDependencies.begin(), noloadDependencies.end());
}
}
else if (loadParams.m_dependencyRules == AssetDependencyLoadRules::LoadAll)
{
AssetCatalogRequestBus::BroadcastResult(getDependenciesResult, &AssetCatalogRequestBus::Events::GetAllProductDependencies, rootAssetId);
}
// Do as much validation of dependencies as we can before the AddWaitingAssets and GetAsset calls for dependencies below
if (getDependenciesResult.IsSuccess())
{
for (const auto& thisAsset : getDependenciesResult.GetValue())
{
AssetInfo assetInfo;
AssetCatalogRequestBus::BroadcastResult(assetInfo, &AssetCatalogRequestBus::Events::GetAssetInfoById, thisAsset.m_assetId);
// No matter whether or not the asset dependency is valid, loaded, or filtered out, mark it as successfully handled.
// When we encounter the asset reference during serialization, we will know that it should intentionally be skipped.
// Otherwise, it would be treated as a missing dependency and assert.
handledAssetDependencyList.emplace_back(thisAsset.m_assetId);
if (!assetInfo.m_assetId.IsValid())
{
// Handlers may just not currently be around for a given asset type so we only warn here
AZ_Warning("AssetContainer", false, "Asset %s (%s) references/depends on asset %s which does not exist in the catalog and cannot be loaded.",
rootAsset.GetHint().c_str(),
rootAssetId.ToString<AZStd::string>().c_str(),
thisAsset.m_assetId.ToString<AZStd::string>().c_str());
m_invalidDependencies++;
continue;
}
if (assetInfo.m_assetId == rootAssetId)
{
// Circular dependencies in our graph need to be raised as errors as they could cause problems elsewhere
AZ_Error("AssetContainer", false, "Circular dependency found under asset %s", rootAssetId.ToString<AZStd::string>().c_str());
m_invalidDependencies++;
continue;
}
if (!AssetManager::Instance().GetHandler(assetInfo.m_assetType))
{
// Handlers may just not currently be around for a given asset type so we only warn here
m_invalidDependencies++;
continue;
}
if (loadParams.m_assetLoadFilterCB)
{
if (!loadParams.m_assetLoadFilterCB({thisAsset.m_assetId, assetInfo.m_assetType,
AZ::Data::ProductDependencyInfo::LoadBehaviorFromFlags(thisAsset.m_flags) }))
{
continue;
}
}
dependencyInfoList.push_back(assetInfo);
}
}
for (auto& thisInfo : dependencyInfoList)
{
waitingList.push_back(thisInfo.m_assetId);
}
// Add waiting assets ahead of time to hear signals for any which may already be loading
AddWaitingAssets(waitingList);
SetupPreloadLists(move(preloadDependencies), rootAssetId);
auto loadParamsCopyWithNoLoadingFilter = loadParams;
// All asset dependencies below the root asset should be provided by the asset catalog, and therefore should *not*
// get triggered to load when the asset reference is serialized in. However, it's useful to detect, warn, and handle
// the case where the asset dependencies are NOT set up correctly.
loadParamsCopyWithNoLoadingFilter.m_assetLoadFilterCB = [handledAssetDependencyList](const AssetFilterInfo& filterInfo)
{
// NoLoad dependencies should always get filtered out and not loaded.
if (filterInfo.m_loadBehavior == AZ::Data::AssetLoadBehavior::NoLoad)
{
return false;
}
// In the normal case, the dependent asset appears in the handled asset list, and we should return false so that
// the asset isn't attempted to be loaded, since the asset will already be triggered to get loaded or was possibly
// already filtered out by the load filter callback.
// In the error case, the asset dependencies haven't been produced by the builder correctly, so assets
// have shown up that the asset container hasn't triggered to load and isn't listening for. Assert that this case
// has happened so that the builder for this asset type can be fixed.
// Ideally we would proceed forward and load them by returning "true", but the triggered load would use this lambda
// function as the asset load filter for that load as well, which isn't correct. If we ever want to support that
// behavior, we would need to rework the way filters work as well as the code in AssetSerializer.cpp to pass down
// the loadParams.m_assetLoadFilterCB that was passed into the AddDependentAssets() methods to use as the dependent
// asset filter instead of this lambda function.
AZ_Assert(AZStd::find(handledAssetDependencyList.begin(), handledAssetDependencyList.end(), filterInfo.m_assetId) !=
handledAssetDependencyList.end(),
"Dependent Asset ID (%s) is expected to load, but the Asset Catalog has no dependency recorded. "
"Examine the asset builder for the asset relying on this to ensure it is generating the correct dependencies.",
filterInfo.m_assetId.ToString<AZStd::string>().c_str());
// The dependent asset should have already been created and at least queued to load prior to reaching this point.
// The asset serializer needs to get a successful result from FindAsset(), or else our asset reference will fail
// to point to the asset data once it is loaded.
if (!Data::AssetManager::Instance().FindAsset(filterInfo.m_assetId, AZ::Data::AssetLoadBehavior::Default))
{
AZ_Assert(!Data::AssetManager::Instance().FindAsset(filterInfo.m_assetId, AZ::Data::AssetLoadBehavior::Default),
"Dependent Asset ID (%s) can't be found in the AssetManager, which means the asset referencing it has probably "
"started loading before the dependent asset has been queued to load. Verify that the asset dependencies have "
"been created correctly for the parent asset.",
filterInfo.m_assetId.ToString<AZStd::string>().c_str());
}
return false;
};
// This will contain the list of dependent assets that have been created (or found) and queued to load.
// We also keep a copy of the AssetInfo structure as a small optimization to avoid a redundant lookup in GetAssetInternal.
AZStd::vector<AZStd::pair<AssetInfo, Asset<AssetData>>> dependencyAssets;
// Make sure all the dependencies are created first before we try to load them.
// Since we've set the load filter to not load dependencies, we need to ensure all the assets are created beforehand
// so the dependencies can be hooked up as soon as each asset gets serialized in, even if they start getting serialized
// while we're still in the middle of triggering all of the asset loads below.
for (auto& thisInfo : dependencyInfoList)
{
auto dependentAsset = AssetManager::Instance().FindOrCreateAsset(
thisInfo.m_assetId, thisInfo.m_assetType, AZ::Data::AssetLoadBehavior::Default);
if (!dependentAsset || !dependentAsset.GetId().IsValid())
{
AZ_Warning("AssetContainer", false, "Dependency Asset %s (%s) was not found\n",
thisInfo.m_assetId.ToString<AZStd::string>().c_str(), thisInfo.m_relativePath.c_str());
RemoveWaitingAsset(thisInfo.m_assetId);
continue;
}
dependencyAssets.emplace_back(thisInfo, AZStd::move(dependentAsset));
}
// Queue the loading of all of the dependent assets before loading the root asset.
for (auto& [dependentAssetInfo, dependentAsset] : dependencyAssets)
{
// Queue each asset to load.
auto queuedDependentAsset = AssetManager::Instance().GetAssetInternal(
dependentAsset.GetId(), dependentAsset.GetType(),
AZ::Data::AssetLoadBehavior::Default, loadParamsCopyWithNoLoadingFilter,
dependentAssetInfo, HasPreloads(dependentAsset.GetId()));
// Verify that the returned asset reference matches the one that we found or created and queued to load.
AZ_Assert(dependentAsset == queuedDependentAsset, "GetAssetInternal returned an unexpected asset reference for Asset %s",
dependentAsset.GetId().ToString<AZStd::string>().c_str());
}
// Add all of the queued dependent assets as dependencies
{
AZStd::lock_guard<AZStd::recursive_mutex> dependencyLock(m_dependencyMutex);
for (auto& [dependentAssetInfo, dependentAsset] : dependencyAssets)
{
AddDependency(AZStd::move(dependentAsset));
}
}
// Finally, after creating and queueing the dependent assets, queue the root asset. This is saved until last to ensure that
// it doesn't have any chance of serializing in until after all the dependent assets have been queued for loading and have
// been added to the list of dependencies.
auto thisAsset = AssetManager::Instance().GetAssetInternal(rootAssetId, rootAssetType, rootAsset.GetAutoLoadBehavior(),
loadParamsCopyWithNoLoadingFilter, AssetInfo(), HasPreloads(rootAssetId));
if (!thisAsset)
{
AZ_Assert(false, "Root asset with id %s failed to load, asset container is invalid.",
rootAssetId.ToString<AZStd::string>().c_str());
ClearWaitingAssets();
// initComplete remains false, because we have failed to initialize successfully.
return;
}
CheckReady();
m_initComplete = true;
}
bool AssetContainer::IsReady() const
{
return (m_rootAsset && m_waitingCount == 0);
}
bool AssetContainer::IsLoading() const
{
return (m_rootAsset || m_waitingCount);
}
bool AssetContainer::IsValid() const
{
return (m_containerAssetId.IsValid() && m_initComplete);
}
void AssetContainer::CheckReady()
{
if (!m_dependencies.empty())
{
for (auto& [assetId, dependentAsset] : m_dependencies)
{
if (dependentAsset->IsReady())
{
HandleReadyAsset(dependentAsset);
}
}
}
if (auto asset = m_rootAsset.GetStrongReference(); asset.IsReady())
{
HandleReadyAsset(asset);
}
}
Asset<AssetData> AssetContainer::GetRootAsset()
{
return m_rootAsset.GetStrongReference();
}
AssetId AssetContainer::GetContainerAssetId()
{
return m_containerAssetId;
}
void AssetContainer::ClearRootAsset()
{
AssetId rootId = m_rootAsset.GetId();
{
AZStd::lock_guard<AZStd::recursive_mutex> preloadGuard(m_preloadMutex);
// Erase the entry in the preloadWaitList for the root asset if one exists.
m_preloadWaitList.erase(rootId);
// It's possible that the root asset has preload dependencies, so make sure to check the preload list and remove
// the entry for the root asset if it has one.
auto rootAssetPreloadIter = m_preloadList.find(rootId);
if (rootAssetPreloadIter != m_preloadList.end())
{
// Since the root asset has a preload list, that means the preload wait list will also have references to the
// root asset. (The preload wait list is a list of assets waiting on a preload asset to finish) Clear those
// out as well.
auto waitAssetSet = rootAssetPreloadIter->second;
for (auto& waitId : waitAssetSet)
{
auto waitAssetIter = m_preloadWaitList.find(waitId);
if (waitAssetIter != m_preloadWaitList.end())
{
waitAssetIter->second.erase(rootId);
}
}
m_preloadList.erase(rootAssetPreloadIter);
}
}
// Clear out the root asset before removing it from the waiting list to ensure that we trigger an "OnAssetContainerCanceled"
// event instead of "OnAssetContainerReady".
m_rootAsset = {};
RemoveWaitingAsset(rootId);
}
void AssetContainer::AddDependency(const Asset<AssetData>& newDependency)
{
m_dependencies[newDependency->GetId()] = newDependency;
}
void AssetContainer::AddDependency(Asset<AssetData>&& newDependency)
{
m_dependencies[newDependency->GetId()] = AZStd::move(newDependency);
}
void AssetContainer::OnAssetReady(Asset<AssetData> asset)
{
HandleReadyAsset(asset);
}
void AssetContainer::OnAssetError(Asset<AssetData> asset)
{
HandleReadyAsset(asset);
}
void AssetContainer::HandleReadyAsset(Asset<AssetData> asset)
{
RemoveFromAllWaitingPreloads(asset->GetId());
RemoveWaitingAsset(asset->GetId());
}
void AssetContainer::OnAssetDataLoaded(Asset<AssetData> asset)
{
// Remove only from this asset's waiting list. Anything else should
// listen for OnAssetReady as the true signal. This is essentially removing the
// "marker" we placed in SetupPreloads that we need to wait for our own data
RemoveFromWaitingPreloads(asset->GetId(), asset->GetId());
}
void AssetContainer::RemoveFromWaitingPreloads(const AssetId& waiterId, const AssetId& preloadID)
{
{
AZStd::lock_guard<AZStd::recursive_mutex> preloadGuard(m_preloadMutex);
auto remainingPreloadIter = m_preloadList.find(waiterId);
if (remainingPreloadIter == m_preloadList.end())
{
AZ_Warning("AssetContainer", !m_initComplete, "Couldn't find waiting list for %s", waiterId.ToString<AZStd::string>().c_str());
return;
}
if (!remainingPreloadIter->second.erase(preloadID))
{
AZ_Warning("AssetContainer", !m_initComplete, "Couldn't remove %s from waiting list of %s", preloadID.ToString<AZStd::string>().c_str(), waiterId.ToString<AZStd::string>().c_str());
return;
}
if (!remainingPreloadIter->second.empty())
{
return;
}
}
auto thisAsset = GetAssetData(waiterId);
AssetManager::Instance().ValidateAndPostLoad(thisAsset, true, false, nullptr);
}
void AssetContainer::RemoveFromAllWaitingPreloads(const AssetId& thisId)
{
AZStd::unordered_set<AssetId> checkList;
{
AZStd::lock_guard<AZStd::recursive_mutex> preloadGuard(m_preloadMutex);
auto waitingList = m_preloadWaitList.find(thisId);
if (waitingList != m_preloadWaitList.end())
{
checkList = move(waitingList->second);
m_preloadWaitList.erase(waitingList);
}
}
for (auto& thisDepId : checkList)
{
if (thisDepId != thisId)
{
RemoveFromWaitingPreloads(thisDepId, thisId);
}
}
}
void AssetContainer::ClearWaitingAssets()
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_readyMutex);
m_waitingCount = 0;
for (auto& thisAsset : m_waitingAssets)
{
AssetBus::MultiHandler::BusDisconnect(thisAsset);
}
m_waitingAssets.clear();
}
void AssetContainer::ListWaitingAssets() const
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_readyMutex);
AZ_TracePrintf("AssetContainer", "Waiting on assets:\n");
for (auto& thisAsset : m_waitingAssets)
{
AZ_TracePrintf("AssetContainer", " %s\n",thisAsset.ToString<AZStd::string>().c_str());
}
}
void AssetContainer::ListWaitingPreloads(const AssetId& assetId) const
{
AZStd::lock_guard<AZStd::recursive_mutex> preloadGuard(m_preloadMutex);
auto preloadEntry = m_preloadList.find(assetId);
if (preloadEntry != m_preloadList.end())
{
AZ_TracePrintf("AssetContainer", "%s waiting on preloads : \n",assetId.ToString<AZStd::string>().c_str());
for (auto& thisId : preloadEntry->second)
{
AZ_TracePrintf("AssetContainer", " %s\n",thisId.ToString<AZStd::string>().c_str());
}
}
else
{
AZ_TracePrintf("AssetContainer", "%s isn't waiting on any preloads:\n", assetId.ToString<AZStd::string>().c_str());
}
}
void AssetContainer::AddWaitingAssets(const AZStd::vector<AssetId>& assetList)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_readyMutex);
for (auto& thisAsset : assetList)
{
if (m_waitingAssets.insert(thisAsset).second)
{
++m_waitingCount;
AssetBus::MultiHandler::BusConnect(thisAsset);
AssetLoadBus::MultiHandler::BusConnect(thisAsset);
}
}
}
void AssetContainer::AddWaitingAsset(const AssetId& thisAsset)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_readyMutex);
if (m_waitingAssets.insert(thisAsset).second)
{
++m_waitingCount;
AssetBus::MultiHandler::BusConnect(thisAsset);
AssetLoadBus::MultiHandler::BusConnect(thisAsset);
}
}
void AssetContainer::RemoveWaitingAsset(const AssetId& thisAsset)
{
bool allReady{ false };
{
bool disconnectEbus = false;
{ // Intentionally limiting lock scope
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_readyMutex);
// If we're trying to remove something already removed, just ignore it
if (m_waitingAssets.erase(thisAsset))
{
m_waitingCount -= 1;
disconnectEbus = true;
if (m_waitingAssets.empty())
{
allReady = true;
}
}
}
if(disconnectEbus)
{
AssetBus::MultiHandler::BusDisconnect(thisAsset);
AssetLoadBus::MultiHandler::BusDisconnect(thisAsset);
}
}
if (allReady && m_initComplete)
{
if (m_rootAsset)
{
AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetContainerReady, this);
}
else
{
AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetContainerCanceled, this);
}
}
}
AssetContainer::operator bool() const
{
return m_rootAsset ? true : false;
}
const AssetContainer::DependencyList& AssetContainer::GetDependencies() const
{
return m_dependencies;
}
const AZStd::unordered_set<AssetId>& AssetContainer::GetUnloadedDependencies() const
{
return m_unloadedDependencies;
}
void AssetContainer::SetupPreloadLists(PreloadAssetListType&& preloadList, const AssetId& rootAssetId)
{
if (!preloadList.empty())
{
// This method can be entered as additional NoLoad dependency groups are loaded - the container could
// be in the middle of loading so we need to grab both mutexes.
AZStd::scoped_lock<AZStd::recursive_mutex, AZStd::recursive_mutex> lock(m_readyMutex, m_preloadMutex);
for (auto thisListPair = preloadList.begin(); thisListPair != preloadList.end();)
{
// We only should add ourselves if we have another valid preload we're waiting on
bool foundAsset{ false };
// It's possible this set of preload dependencies was culled out by lack of asset handler
// Or filtering rules. This is not an error, we should just remove it from the list of
// Preloads we're waiting on
if (!m_waitingAssets.count(thisListPair->first))
{
thisListPair = preloadList.erase(thisListPair);
continue;
}
for (auto thisAsset = thisListPair->second.begin(); thisAsset != thisListPair->second.end();)
{
// These are data errors. We'll emit the error but carry on. The container
// will load the assets but won't/can't create a circular preload dependency chain
if (*thisAsset == rootAssetId)
{
AZ_Error("AssetContainer", false, "Circular preload dependency found - %s has a preload"
"dependency back to root %s\n",
thisListPair->first.ToString<AZStd::string>().c_str(),
rootAssetId.ToString<AZStd::string>().c_str());
thisAsset = thisListPair->second.erase(thisAsset);
continue;
}
else if (*thisAsset == thisListPair->first)
{
AZ_Error("AssetContainer", false, "Circular preload dependency found - Root asset %s has a preload"
"dependency on %s which depends back back to itself\n",
rootAssetId.ToString<AZStd::string>().c_str(),
thisListPair->first.ToString<AZStd::string>().c_str());
thisAsset = thisListPair->second.erase(thisAsset);
continue;
}
else if (m_preloadWaitList.count(thisListPair->first) && m_preloadWaitList[thisListPair->first].count(*thisAsset))
{
AZ_Error("AssetContainer", false, "Circular dependency found - Root asset %s has a preload"
"dependency on %s which has a circular dependency with %s\n",
rootAssetId.ToString<AZStd::string>().c_str(),
thisListPair->first.ToString<AZStd::string>().c_str(),
thisAsset->ToString<AZStd::string>().c_str());
thisAsset = thisListPair->second.erase(thisAsset);
continue;
}
else if (m_waitingAssets.count(*thisAsset))
{
foundAsset = true;
m_preloadWaitList[*thisAsset].insert(thisListPair->first);
++thisAsset;
}
else
{
// This particular preload dependency of this asset was culled
// similar to the case above this can be due to no established asset handler
// or filtering rules. We'll just erase the entry because we're not loading this
thisAsset = thisListPair->second.erase(thisAsset);
continue;
}
}
if (foundAsset)
{
// We've established that this asset has at least one preload dependency it needs to wait on
// so we additionally add the waiting asset as its own preload so all of our "waiting assets"
// are managed in the same list. We can't consider this asset to be "ready" until all
// of its preloads are ready and it has been loaded. It will request an OnAssetDataLoaded
// notification from AssetManager rather than an OnAssetReady because of these additional dependencies.
thisListPair->second.insert(thisListPair->first);
m_preloadWaitList[thisListPair->first].insert(thisListPair->first);
}
++thisListPair;
}
for(auto& thisList : preloadList)
{
m_preloadList[thisList.first].insert(thisList.second.begin(), thisList.second.end());
}
}
}
bool AssetContainer::HasPreloads(const AssetId& assetId) const
{
AZStd::lock_guard<AZStd::recursive_mutex> preloadGuard(m_preloadMutex);
auto preloadEntry = m_preloadList.find(assetId);
if (preloadEntry != m_preloadList.end())
{
return !preloadEntry->second.empty();
}
return false;
}
Asset<AssetData> AssetContainer::GetAssetData(const AssetId& assetId) const
{
AZStd::lock_guard<AZStd::recursive_mutex> dependenciesGuard(m_dependencyMutex);
if (auto rootAsset = m_rootAsset.GetStrongReference(); rootAsset.GetId() == assetId)
{
return rootAsset;
}
auto dependencyIter = m_dependencies.find(assetId);
if (dependencyIter != m_dependencies.end())
{
return dependencyIter->second;
}
AZ_Warning("AssetContainer", false, "Asset %s not found in container", assetId.ToString<AZStd::string>().c_str());
return {};
}
int AssetContainer::GetNumWaitingDependencies() const
{
return m_waitingCount.load();
}
int AssetContainer::GetInvalidDependencies() const
{
return m_invalidDependencies.load();
}
} // namespace Data
} // namespace AZ
@@ -0,0 +1,158 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetManager_private.h>
#include <AzCore/Asset/AssetInternal/WeakAsset.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/set.h>
namespace AZ
{
namespace Data
{
struct AssetLoadParameters;
// AssetContainer loads an asset and all of its dependencies as a collection which is parallellized as much as possible.
// With the container, the data will all load in parallel. Dependent asset loads will still obey the expected rules
// where PreLoad assets will emit OnAssetReady before the parent does, and QueueLoad assets will emit OnAssetReady in
// no guaranteed order. However, the OnAssetContainerReady signals will not emit until all PreLoad and QueueLoad assets
// are ready. NoLoad dependencies are not loaded by default but can be loaded along with their dependencies using the
// same rules as above by using the LoadAll dependency rule.
class AssetContainer :
AZ::Data::AssetBus::MultiHandler,
AZ::Data::AssetLoadBus::MultiHandler
{
public:
AZ_CLASS_ALLOCATOR(AssetContainer, SystemAllocator, 0);
AssetContainer() = default;
AssetContainer(Asset<AssetData> asset, const AssetLoadParameters& loadParams);
~AssetContainer();
bool IsReady() const;
bool IsLoading() const;
bool IsValid() const;
/// Get a reference to the current root asset.
/// This will either be the asset the container was originally created for, or invalid if the asset load has been canceled.
Asset<AssetData> GetRootAsset();
/// Get a reference to the asset id for the asset the container was originally created for.
/// Even if the root asset has been cleared, this will still contain the originally-requested id.
AssetId GetContainerAssetId();
// Remove an asset from the container.
void ClearRootAsset();
operator bool() const;
using DependencyList = AZStd::unordered_map< AZ::Data::AssetId, AZ::Data::Asset<AssetData>>;
const DependencyList& GetDependencies() const;
int GetNumWaitingDependencies() const;
int GetInvalidDependencies() const;
void ListWaitingAssets() const;
void ListWaitingPreloads(const AZ::Data::AssetId& assetId) const;
// Default behavior is to store dependencies flagged as "NoLoad" AutoLoadBehavior
// These can be kicked off with a LoadDependency request
const AZStd::unordered_set<AZ::Data::AssetId>& GetUnloadedDependencies() const;
//////////////////////////////////////////////////////////////////////////
// AssetBus
void OnAssetReady(Asset<AssetData> asset) override;
void OnAssetError(Asset<AssetData> asset) override;
//////////////////////////////////////////////////////////////////////////
// AssetLoadBus
void OnAssetDataLoaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
protected:
// Waiting assets are those which have not yet signalled ready. In the case of PreLoad dependencies the data may have completed the load cycle but
// the Assets aren't considered "Ready" yet if there are PreLoad dependencies still loading and will still be in the list until the point that asset and
// All of its preload dependencies have been loaded, when it signals OnAssetReady
void AddWaitingAsset(const AZ::Data::AssetId& waitingAsset);
void AddWaitingAssets(const AZStd::vector<AZ::Data::AssetId>& waitingAssets);
void RemoveWaitingAsset(const AZ::Data::AssetId& waitingAsset);
void ClearWaitingAssets();
// Internal check to validate ready status at the end of initialization
void CheckReady();
// Add an individual asset to our list of known dependencies. Does not include the root asset which is in m_rootAset
void AddDependency(const Asset<AssetData>& newDependency);
void AddDependency(Asset<AssetData>&& addDependency);
// Add a "graph section" to our list of dependencies. This checks the catalog for all Pre and Queue load assets which are dependents of the requested asset and kicks off loads
// NoLoads which are encounted are placed in another list and can be loaded on demand with the LoadDependency call.
void AddDependentAssets(Asset<AssetData> rootAsset, const AssetLoadParameters& loadParams);
// If "PreLoad" assets are found in the graph these are cached and tracked with both OnAssetReady and OnAssetDataLoaded messages.
// OnAssetDataLoaded is used to suppress what would normally be an OnAssetReady call - we need to use the container to evaluate whether
// all of an asset's preload dependencies are ready before completing the load cycle where OnAssetReady will be signalled and the asset
// will be removed from the waiting list in the container
void SetupPreloadLists(PreloadAssetListType&& preloadList, const AZ::Data::AssetId& rootAssetId);
bool HasPreloads(const AZ::Data::AssetId& assetId) const;
// Remove a specific id from the list an asset is waiting for and complete the load if everything is ready
void RemoveFromWaitingPreloads(const AZ::Data::AssetId& waitingId, const AZ::Data::AssetId& preloadAssetId);
// Iterate over the list that was waiting for this asset and remove it from each
void RemoveFromAllWaitingPreloads(const AZ::Data::AssetId& assetId);
Asset<AssetData> GetAssetData(const AZ::Data::AssetId& assetId) const;
// Used for final CheckReady after setup as well as internal handling for OnAssetReady
// duringInit if we're coming from the checkReady method - containers that start ready don't need to signal
void HandleReadyAsset(AZ::Data::Asset<AZ::Data::AssetData> asset);
// Optimization to save the lookup in the dependencies map
AssetInternal::WeakAsset<AssetData> m_rootAsset;
// The root asset id is stored here semi-redundantly on initialization so that we can still refer to it even if the
// root asset reference gets cleared.
AssetId m_containerAssetId;
mutable AZStd::recursive_mutex m_dependencyMutex;
DependencyList m_dependencies;
mutable AZStd::recursive_mutex m_readyMutex;
AZStd::set<AssetId> m_waitingAssets;
AZStd::atomic_int m_waitingCount{0};
AZStd::atomic_int m_invalidDependencies{ 0 };
AZStd::unordered_set<AZ::Data::AssetId> m_unloadedDependencies;
AZStd::atomic_bool m_initComplete{ false };
mutable AZStd::recursive_mutex m_preloadMutex;
// AssetId -> List of assets it is still waiting on
PreloadAssetListType m_preloadList;
// AssetId -> List of assets waiting on it
PreloadAssetListType m_preloadWaitList;
private:
AssetContainer operator=(const AssetContainer& copyContainer) = delete;
AssetContainer operator=(const AssetContainer&& copyContainer) = delete;
AssetContainer(const AssetContainer& copyContainer) = delete;
AssetContainer(AssetContainer&& copyContainer) = delete;
};
} // namespace Data
} // namespace AZ
@@ -0,0 +1,273 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Asset/AssetDataStream.h>
namespace AZ::Data
{
AssetDataStream::AssetDataStream(AZ::IO::IStreamerTypes::RequestMemoryAllocator* bufferAllocator)
: m_bufferAllocator(bufferAllocator ? bufferAllocator : &m_defaultAllocator)
{
ClearInternalStateData();
}
AssetDataStream::~AssetDataStream()
{
if (m_isOpen)
{
Close();
}
}
void AssetDataStream::Open(const AZStd::vector<AZ::u8>& data)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore);
AZ_Assert(!m_isOpen, "Attempting to open the stream when it is already open.");
// Initialize the state variables and start tracking the overall load timings
OpenInternal(data.size(), "(mem buffer)");
// Create the asset buffer
auto result = m_bufferAllocator->Allocate(data.size(), data.size(), AZCORE_GLOBAL_NEW_ALIGNMENT);
m_buffer = result.m_address;
m_loadedSize = result.m_size;
// "Load" the asset buffer by copying the provided data buffer
memcpy(m_buffer, data.data(), m_loadedSize);
}
void AssetDataStream::Open(AZStd::vector<AZ::u8>&& data)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore);
AZ_Assert(!m_isOpen, "Attempting to open the stream when it is already open.");
// Initialize the state variables and start tracking the overall load timings
OpenInternal(data.size(), "(mem buffer)");
// Directly take ownership of the provided buffer
m_preloadedData = AZStd::move(data);
m_buffer = m_preloadedData.data();
m_loadedSize = m_preloadedData.size();
}
void AssetDataStream::Open(const AZStd::string& filePath, size_t fileOffset, size_t assetSize,
AZStd::chrono::milliseconds deadline, AZ::IO::IStreamerTypes::Priority priority,
OnCompleteCallback loadCallback)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore);
AZ_Assert(!m_isOpen, "Attempting to open the stream when it is already open.");
AZ_Assert(!m_curReadRequest, "Queueing an asset stream load while one is still in progress.");
AZ_Assert(!filePath.empty(), "AssetDataStream::Open called without a valid file name.");
// Initialize the state variables and start tracking the overall load timings
OpenInternal(assetSize, filePath.c_str());
m_filePath = filePath;
m_fileOffset = fileOffset;
// If the asset load is requesting more than 0 bytes of data, queue it up with the file streamer.
if (m_requestedAssetSize > 0)
{
// Set up the callback that will process the asset data once the raw file load is finished.
auto streamerCallback = [this, loadCallback](AZ::IO::FileRequestHandle fileHandle)
{
AZ_PROFILE_SCOPE_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "AZ::Data::LoadAssetDataStreamCallback %s",
m_filePath.c_str());
// Get the results
auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
AZ::u64 bytesRead = 0;
bool result = streamer->GetReadRequestResult(fileHandle, m_buffer, bytesRead,
AZ::IO::IStreamerTypes::ClaimMemory::Yes);
auto status = streamer->GetRequestStatus(fileHandle);
m_loadedSize = aznumeric_cast<size_t>(bytesRead);
// Validate that our read request generated expected results.
AZ_Assert(m_buffer, "Streamer provided a null buffer in the file read callback for %s.", m_filePath.c_str());
AZ_Error("AssetDataStream", m_loadedSize == m_requestedAssetSize,
"Buffer for %s was expected to be %zu bytes, but is %zu bytes.",
m_filePath.c_str(), m_requestedAssetSize, m_loadedSize);
{
AZStd::scoped_lock<AZStd::mutex> lock(m_readRequestMutex);
// The read request finished, so stop tracking it.
m_curReadRequest = nullptr;
}
// Call the load callback to start processing the loaded data.
if (loadCallback)
{
loadCallback(status);
}
else
{
AZ_Error("AssetDataStream", status == AZ::IO::IStreamerTypes::RequestStatus::Completed,
"AssetDataStream failed to load %s", m_filePath.c_str());
}
// Notify that the load is complete, in case anyone is using BlockUntilLoadComplete to block.
m_readRequestActive.notify_one();
};
// Queue the raw file load with the file streamer.
auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
m_curReadRequest = streamer->Read(
m_filePath,
*m_bufferAllocator,
m_requestedAssetSize,
deadline, priority, m_fileOffset);
m_curDeadline = deadline;
m_curPriority = priority;
streamer->SetRequestCompleteCallback(m_curReadRequest, streamerCallback);
streamer->QueueRequest(m_curReadRequest);
}
else
{
// If 0 bytes are requested, skip the file streamer entirely, and just directly call the load callback.
if (loadCallback)
{
loadCallback(AZ::IO::IStreamerTypes::RequestStatus::Completed);
}
m_readRequestActive.notify_one();
}
}
void AssetDataStream::Reschedule(AZStd::chrono::milliseconds newDeadline, AZ::IO::IStreamerTypes::Priority newPriority)
{
if (m_curReadRequest && (newDeadline < m_curDeadline || newPriority > m_curPriority))
{
auto deadline = AZStd::GetMin(m_curDeadline, newDeadline);
auto priority = AZStd::GetMax(m_curPriority, newPriority);
auto streamer = Interface<IO::IStreamer>::Get();
m_curReadRequest = streamer->RescheduleRequest(m_curReadRequest, deadline, priority);
m_curDeadline = deadline;
m_curPriority = priority;
}
}
void AssetDataStream::BlockUntilLoadComplete()
{
AZStd::unique_lock<AZStd::mutex> lock(m_readRequestMutex);
m_readRequestActive.wait(lock, [this] { return m_curReadRequest == nullptr; });
lock.unlock();
}
void AssetDataStream::ClearInternalStateData()
{
// Clear all our internal state data.
m_preloadedData.resize(0);
m_buffer = nullptr;
m_loadedSize = 0;
m_requestedAssetSize = 0;
m_curOffset = 0;
m_filePath.clear();
m_fileOffset = 0;
m_isOpen = false;
}
void AssetDataStream::OpenInternal(size_t assetSize, [[maybe_unused]] const char* streamName)
{
// Due to a bug, we need to create a superfluous profile interval here, because for some reason
// the real interval we want to record below won't show up unless this is here.
/**/
{
AZ_PROFILE_INTERVAL_START(AZ::Debug::ProfileCategory::AzCore, this + 1, "AssetDataStream: %s", streamName);
AZ_PROFILE_INTERVAL_END(AZ::Debug::ProfileCategory::AzCore, this + 1);
}
/**/
// Start a timespan marker to track the full load time for the requested asset.
AZ_PROFILE_INTERVAL_START(AZ::Debug::ProfileCategory::AzCore, this, "AssetLoad: %s", streamName);
// Lock the allocator to ensure it remains active from Open to Close.
m_bufferAllocator->LockAllocator();
// Init all the tracking variables.
ClearInternalStateData();
m_requestedAssetSize = assetSize;
m_isOpen = true;
}
void AssetDataStream::Close()
{
AZ_Assert(m_isOpen, "Attempting to close a stream that hasn't been opened.");
// Destroy the asset buffer and unlock the allocator, so the allocator itself knows that it is no longer needed.
if (m_buffer != m_preloadedData.data())
{
m_bufferAllocator->Release(m_buffer);
}
m_bufferAllocator->UnlockAllocator();
ClearInternalStateData();
// End the load time timespan marker for this asset.
AZ_PROFILE_INTERVAL_END(AZ::Debug::ProfileCategory::AzCore, this);
}
void AssetDataStream::Seek(AZ::IO::OffsetType bytes, AZ::IO::GenericStream::SeekMode mode)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore);
AZ::IO::OffsetType requestedOffset = 0;
switch (mode)
{
case ST_SEEK_BEGIN:
requestedOffset = bytes;
break;
case ST_SEEK_CUR:
requestedOffset = aznumeric_cast<AZ::IO::OffsetType>(m_curOffset) + bytes;
break;
case ST_SEEK_END:
requestedOffset = aznumeric_cast<AZ::IO::OffsetType>(m_loadedSize) + bytes;
break;
}
size_t calculatedOffset = aznumeric_cast<size_t>(AZ::GetMax(aznumeric_cast<AZ::IO::OffsetType>(0), requestedOffset));
if (calculatedOffset >= m_curOffset)
{
m_curOffset = calculatedOffset;
}
else
{
AZ_Assert(false, "Backwards seeking is not allowed in AssetDataStream, since previously-read data might be paged out "
"of memory. Current stream offset is %zu, requested offset is %zu.", m_curOffset, calculatedOffset);
}
}
AZ::IO::SizeType AssetDataStream::Read(AZ::IO::SizeType bytes, void* oBuffer)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore);
if (m_curOffset >= m_loadedSize)
{
return 0;
}
bytes = AZ::GetMin(bytes, aznumeric_cast<AZ::IO::SizeType>(m_loadedSize - m_curOffset));
if (bytes)
{
memcpy(oBuffer, reinterpret_cast<AZ::u8*>(m_buffer) + m_curOffset, aznumeric_cast<size_t>(bytes));
m_curOffset += aznumeric_cast<size_t>(bytes);
}
return bytes;
}
} // AZ::Data
@@ -0,0 +1,138 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/IStreamerTypes.h>
#include <AzCore/IO/IStreamer.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/parallel/condition_variable.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Debug/Profiler.h>
namespace AZ::Data
{
class AssetDataStream : public AZ::IO::GenericStream
{
public:
// The default Generic Stream APIs in this class will only allow for a single sequential pass
// through the data, no seeking. Reads will block when pages aren't available yet, and
// pages will be marked for recycling once reading has progressed beyond them.
//! Construct a new AssetDataStream
explicit AssetDataStream(AZ::IO::IStreamerTypes::RequestMemoryAllocator* bufferAllocator = nullptr);
~AssetDataStream() override;
// Open the AssetDataStream and make a copy of the provided memory buffer.
void Open(const AZStd::vector<AZ::u8>& data);
// Open the AssetDataStream and directly take ownership of a pre-populated memory buffer.
void Open(AZStd::vector<AZ::u8>&& data);
// Open the AssetDataStream and load it via file streaming
using OnCompleteCallback = AZStd::function<void(AZ::IO::IStreamerTypes::RequestStatus)>;
void Open(const AZStd::string& filePath, size_t fileOffset, size_t assetSize,
AZStd::chrono::milliseconds deadline = AZ::IO::IStreamerTypes::s_noDeadline,
AZ::IO::IStreamerTypes::Priority priority = AZ::IO::IStreamerTypes::s_priorityMedium,
OnCompleteCallback loadCallback = {});
// Reschedule the outstanding request. Will only update with shorter deadline values or higher priority values
void Reschedule(AZStd::chrono::milliseconds newDeadline, AZ::IO::IStreamerTypes::Priority newPriority);
// Optionally block until the Open and data load has completed.
void BlockUntilLoadComplete();
// GenericStream APIs
bool IsOpen() const override { return m_isOpen && IsFullyLoaded(); }
bool CanSeek() const override { return false; }
bool CanRead() const override { return true; }
bool CanWrite() const override { return false; }
void Seek(AZ::IO::OffsetType bytes, AZ::IO::GenericStream::SeekMode mode) override;
AZ::IO::SizeType Write([[maybe_unused]] AZ::IO::SizeType bytes, [[maybe_unused]] const void* iBuffer) override
{
AZ_Assert(false, "Writing is not supported in AssetDataStream.");
return 0;
}
AZ::IO::SizeType Read(AZ::IO::SizeType bytes, void* oBuffer) override;
AZ::IO::SizeType GetCurPos() const override { return m_curOffset; }
AZ::IO::SizeType GetLength() const override { return m_requestedAssetSize; }
void Close() override;
const char* GetFilename() const override { return m_filePath.c_str(); }
// AssetDataStream specific APIs
//! Whether or not all data has been loaded.
bool IsFullyLoaded() const { return m_isOpen && (m_loadedSize == m_requestedAssetSize); }
//! Gets the size of data loaded (so far).
size_t GetLoadedSize() const { return m_loadedSize; }
private:
//! Perform any operations needed by all variants of Open()
void OpenInternal(size_t assetSize, const char* streamName);
void ClearInternalStateData();
//! The allocator to use for allocating / deallocating asset buffers
AZ::IO::IStreamerTypes::RequestMemoryAllocator* m_bufferAllocator{ nullptr };
//! The default allocator to use if no specialized allocators are passed in.
AZ::IO::IStreamerTypes::DefaultRequestMemoryAllocator m_defaultAllocator;
//! The path and file name of the asset being loaded
AZStd::string m_filePath;
//! The offset into the file to start loading at.
size_t m_fileOffset{ 0 };
//! The amount of data that's expected to be loaded.
size_t m_requestedAssetSize{ 0 };
//! Optional data buffer that's been directly passed in through Open(), instead of reading data from a file.
AZStd::vector<AZ::u8> m_preloadedData;
//! The buffer that will hold the raw data after it's loaded from the file.
void* m_buffer{ nullptr };
//! The amount of data that's been loaded. This can differ from the requested size if for
//! instance a problem was encountered during loading.
size_t m_loadedSize{ 0 };
//! The current offset representing how far we've read into the buffer.
size_t m_curOffset{ 0 };
//! The current active streamer read request - tracked in case we need to cancel it prematurely
AZ::IO::FileRequestPtr m_curReadRequest{ nullptr };
//! The current request deadline. Used to avoid requesting a reschedule to the same (current) deadline.
AZStd::chrono::milliseconds m_curDeadline{ AZ::IO::IStreamerTypes::s_noDeadline };
//! The current request priority. Used to avoid requesting a reschedule to the same (current) priority.
AZ::IO::IStreamerTypes::Priority m_curPriority{ AZ::IO::IStreamerTypes::s_priorityMedium };
//! Synchronization for the read request, so that it's possible to block until completion.
AZStd::mutex m_readRequestMutex;
AZStd::condition_variable m_readRequestActive;
//! Track whether or not the stream is currently open
bool m_isOpen{ false };
};
} // AZ::Data
@@ -0,0 +1,178 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
namespace AZ {
namespace Data {
class AssetData;
}
}
namespace AZ::Data::AssetInternal
{
/// WeakAsset keeps a reference to AssetData but will not cause an asset to load
/// If an asset is only referenced by WeakAssets, any pending load will be canceled and the asset should be released shortly after
/// This class is only intended for use in AssetManager systems
template<class T>
class WeakAsset
{
public:
WeakAsset() = default;
WeakAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior);
explicit WeakAsset(const Asset<AssetData>& asset);
WeakAsset(const WeakAsset& rhs);
WeakAsset(WeakAsset&& rhs);
WeakAsset& operator=(const WeakAsset& rhs);
WeakAsset& operator=(WeakAsset&& rhs);
~WeakAsset();
void SetData(AssetData* assetData);
AssetId GetId() const;
/// Attempts to get a full reference to the AssetData as long as there is at least 1 existing Asset<T> reference
Asset<T> GetStrongReference() const;
explicit operator bool() const;
private:
AssetId m_assetId{};
AssetData* m_assetData{ nullptr };
AssetLoadBehavior m_assetLoadBehavior{ AssetLoadBehavior::Default };
};
template <class T>
WeakAsset<T>::WeakAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior)
: m_assetLoadBehavior(assetReferenceLoadBehavior)
{
SetData(assetData);
}
template <class T>
WeakAsset<T>::WeakAsset(const Asset<AssetData>& asset)
: m_assetLoadBehavior(asset.GetAutoLoadBehavior())
{
SetData(asset.GetData());
}
template <class T>
WeakAsset<T>::WeakAsset(const WeakAsset& rhs)
: m_assetLoadBehavior(rhs.m_assetLoadBehavior)
{
SetData(rhs.m_assetData);
}
template <class T>
WeakAsset<T>::WeakAsset(WeakAsset&& rhs)
: m_assetData(AZStd::move(rhs.m_assetData))
, m_assetLoadBehavior(rhs.m_assetLoadBehavior)
{
rhs.m_assetData = nullptr;
if (m_assetData)
{
m_assetId = AZStd::move(rhs.m_assetId);
}
}
template <class T>
WeakAsset<T>& WeakAsset<T>::operator=(const WeakAsset& rhs)
{
m_assetLoadBehavior = rhs.m_assetLoadBehavior;
SetData(rhs.m_assetData);
return *this;
}
template <class T>
WeakAsset<T>& WeakAsset<T>::operator=(WeakAsset&& rhs)
{
m_assetLoadBehavior = rhs.m_assetLoadBehavior;
// Make sure the assetData ptr getting replaced releases its weak reference. Otherwise this will "leak" a weak reference:
// - If the left side is different than the right, the left side will have one less reference when it gets overwritten
// - If the left and right sides are the same, clearing the right side's reference means one less reference will exist
if (m_assetData)
{
m_assetData->ReleaseWeak();
}
m_assetData = AZStd::move(rhs.m_assetData);
rhs.m_assetData = nullptr;
if (m_assetData)
{
m_assetId = AZStd::move(rhs.m_assetId);
}
else
{
m_assetId.SetInvalid();
}
return *this;
}
template <class T>
WeakAsset<T>::~WeakAsset()
{
SetData(nullptr);
}
template <class T>
void WeakAsset<T>::SetData(AssetData* assetData)
{
m_assetId.SetInvalid();
if (assetData)
{
assetData->AcquireWeak();
m_assetId = assetData->GetId();
}
if (m_assetData)
{
m_assetData->ReleaseWeak();
}
m_assetData = assetData;
}
template <class T>
AssetId WeakAsset<T>::GetId() const
{
return m_assetId;
}
template <class T>
Asset<T> WeakAsset<T>::GetStrongReference() const
{
if (!m_assetData || m_assetData->GetUseCount() <= 0)
{
return Asset<T>(m_assetId, AssetType::CreateNull());
}
return Asset<T>(m_assetData, m_assetLoadBehavior);
}
template <class T>
WeakAsset<T>::operator bool() const
{
return m_assetData != nullptr;
}
}
@@ -0,0 +1,159 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetJsonSerializer.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/StackedString.h>
#include <AzCore/Memory/SystemAllocator.h>
namespace AZ
{
namespace Data
{
AZ_CLASS_ALLOCATOR_IMPL(AssetJsonSerializer, SystemAllocator, 0);
JsonSerializationResult::Result AssetJsonSerializer::Load(void* outputValue, const Uuid& /*outputValueTypeId*/,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult;
switch (inputValue.GetType())
{
case rapidjson::kObjectType:
return LoadAsset(outputValue, inputValue, context);
case rapidjson::kArrayType: // fall through
case rapidjson::kNullType: // fall through
case rapidjson::kStringType: // fall through
case rapidjson::kFalseType: // fall through
case rapidjson::kTrueType: // fall through
case rapidjson::kNumberType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. Asset<T> can only be read from an object.");
default:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unknown, "Unknown json type encountered for Asset<T>.");
}
}
JsonSerializationResult::Result AssetJsonSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
const void* defaultValue, const Uuid& /*valueTypeId*/, JsonSerializerContext& context)
{
namespace JSR = JsonSerializationResult;
const Asset<AssetData>* instance = reinterpret_cast<const Asset<AssetData>*>(inputValue);
const Asset<AssetData>* defaultInstance = reinterpret_cast<const Asset<AssetData>*>(defaultValue);
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
ScopedContextPath subPathId(context, "m_assetId");
const auto* id = &instance->GetId();
const auto* defaultId = defaultInstance ? &defaultInstance->GetId() : nullptr;
rapidjson::Value assetIdValue;
result = ContinueStoring(assetIdValue, id, defaultId, azrtti_typeid<AssetId>(), context);
if (result.GetOutcome() == JSR::Outcomes::Success || result.GetOutcome() == JSR::Outcomes::PartialDefaults)
{
if (!outputValue.IsObject())
{
outputValue.SetObject();
}
outputValue.AddMember(rapidjson::StringRef("assetId"), AZStd::move(assetIdValue), context.GetJsonAllocator());
}
}
{
ScopedContextPath subPathHint(context, "m_assetHint");
const AZStd::string* hint = &instance->GetHint();
const AZStd::string defaultHint;
rapidjson::Value assetHintValue;
JSR::ResultCode resultHint = ContinueStoring(assetHintValue, hint, &defaultHint, azrtti_typeid<AZStd::string>(), context);
if (resultHint.GetOutcome() == JSR::Outcomes::Success || resultHint.GetOutcome() == JSR::Outcomes::PartialDefaults)
{
if (!outputValue.IsObject())
{
outputValue.SetObject();
}
outputValue.AddMember(rapidjson::StringRef("assetHint"), AZStd::move(assetHintValue), context.GetJsonAllocator());
}
result.Combine(resultHint);
}
return context.Report(result,
result.GetProcessing() == JSR::Processing::Completed ? "Successfully stored Asset<T>." : "Failed to store Asset<T>.");
}
JsonSerializationResult::Result AssetJsonSerializer::LoadAsset(void* outputValue, const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult;
Asset<AssetData>* instance = reinterpret_cast<Asset<AssetData>*>(outputValue);
AssetId id;
JSR::ResultCode result(JSR::Tasks::ReadField);
auto it = inputValue.FindMember("assetId");
if (it != inputValue.MemberEnd())
{
ScopedContextPath subPath(context, "assetId");
result = ContinueLoading(&id, azrtti_typeid<AssetId>(), it->value, context);
if (!id.m_guid.IsNull())
{
if (instance->Create(id))
{
result.Combine(context.Report(result, "Successfully created Asset<T>."));
}
else
{
result.Combine(context.Report(JSR::Tasks::Convert, JSR::Outcomes::Unknown,
"The asset id was successfully read, but creating an Asset<T> instance from it failed."));
}
}
else if (result.GetProcessing() == JSR::Processing::Completed)
{
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
"Null Asset<T> created."));
}
else
{
result.Combine(context.Report(result, "Failed to retrieve asset id for Asset<T>."));
}
}
else
{
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
"The asset id is missing, so there's not enough information to create an Asset<T>."));
}
it = inputValue.FindMember("assetHint");
if (it != inputValue.MemberEnd())
{
ScopedContextPath subPath(context, "assetHint");
AZStd::string hint;
result.Combine(ContinueLoading(&hint, azrtti_typeid<AZStd::string>(), it->value, context));
instance->SetHint(AZStd::move(hint));
}
else
{
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
"The asset hint is missing for Asset<T>, so it will be left empty."));
}
bool success = result.GetOutcome() <= JSR::Outcomes::PartialSkip;
bool defaulted = result.GetOutcome() == JSR::Outcomes::DefaultsUsed || result.GetOutcome() == JSR::Outcomes::PartialDefaults;
AZStd::string_view message =
success ? "Successfully loaded information and created instance of Asset<T>." :
defaulted ? "A default id was provided for Asset<T>, so no instance could be created." :
"Not enough information was available to create an instance of Asset<T> or data was corrupted.";
return context.Report(result, message);
}
} // namespace Data
} // namespace AZ
@@ -0,0 +1,41 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
namespace AZ
{
namespace Data
{
//! JSON serializer for Asset<T>.
class AssetJsonSerializer
: public BaseJsonSerializer
{
public:
AZ_RTTI(AssetJsonSerializer, "{9674F4F5-7989-44D7-9CAC-DBD494A0A922}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
//! Note that the information for the Asset<T> will be loaded, but the asset data won't be loaded. After deserialization has
//! completed it's up to the caller to queue the Asset<T> for loading with the AssetManager.
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
private:
JsonSerializationResult::Result LoadAsset(void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context);
};
} // namespace Data
} // namespace AZ
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,671 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetContainer.h>
#include <AzCore/Asset/AssetDataStream.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/IO/Streamer/FileRequest.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h> // used as allocator for most components
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/intrusive_list.h>
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzCore/std/smart_ptr/weak_ptr.h>
namespace AZ::Data
{
struct AssetContainerKey;
}
namespace AZStd
{
template<>
struct hash<AZ::Data::AssetContainerKey>
{
size_t operator()(const AZ::Data::AssetContainerKey& obj) const;
};
}
namespace AZ
{
namespace IO
{
class GenericStream;
enum class OpenMode : AZ::u32;
}
namespace IO::IStreamerTypes
{
class RequestMemoryAllocator;
}
namespace Data
{
class AssetHandler;
class AssetCatalog;
class AssetDatabaseJob;
class WaitForAsset;
struct IDebugAssetEvent
{
AZ_RTTI(IDebugAssetEvent, "{1FEF8289-C730-426D-B3B9-4BBA66339D66}");
IDebugAssetEvent() = default;
virtual ~IDebugAssetEvent() = default;
virtual void AssetStatusUpdate(AZ::Data::AssetId id, AZ::Data::AssetData::AssetStatus status) = 0;
virtual void ReleaseAsset(AZ::Data::AssetId id) = 0;
};
struct AssetContainerKey
{
AssetId m_assetId;
AssetLoadParameters m_loadParameters;
bool operator==(const AssetContainerKey& rhs) const
{
return m_assetId == rhs.m_assetId && m_loadParameters == rhs.m_loadParameters;
}
};
class AssetStreamInfo
{
public:
AssetStreamInfo()
: m_streamFlags(IO::OpenMode())
, m_dataLen(0)
, m_dataOffset(0)
{}
bool IsValid() const
{
return !m_streamName.empty();
}
AZStd::string m_streamName;
IO::OpenMode m_streamFlags;
u64 m_dataLen;
u64 m_dataOffset;
};
struct AssetDependencyEntry
{
AssetId m_assetId;
AssetType m_assetType;
};
typedef AZStd::vector<AssetDependencyEntry> AssetDependencyList;
/*
* This is the base class for Async AssetDatabase jobs
*/
class AssetDatabaseJob
: public AZStd::intrusive_list_node<AssetDatabaseJob>
{
friend class AssetManager;
protected:
AssetDatabaseJob(AssetManager* owner, const Asset<AssetData>& asset, AssetHandler* assetHandler);
virtual ~AssetDatabaseJob();
AssetManager* m_owner;
AssetInternal::WeakAsset<AssetData> m_asset;
AssetHandler* m_assetHandler;
};
/**
* AssetDatabase handles the creation, refcounting and automatic
* destruction of assets.
*
* In general for any events while loading/saving/etc. create an AssetEventHandler and pass
* it to AssetDatabase::GetAsset().
* You can also connect to AssetBus if you want to listen for
* events without holding an asset.
* If an asset is ready at the time you connect to AssetBus or GetAsset() is called,
* your handler will be notified immediately, otherwise all events are dispatched asynchronously.
*/
class AssetManager
: private AssetManagerBus::Handler
{
friend class AssetData;
friend class AssetDatabaseJob;
friend class ReloadAssetJob;
friend class LoadAssetJob;
friend Asset<AssetData> AssetInternal::GetAssetData(const AssetId& id, AssetLoadBehavior assetReferenceLoadBehavior);
friend class AssetContainer;
friend class WaitForAsset;
public:
struct Descriptor
{
Descriptor() = default;
};
typedef AZStd::unordered_map<AssetType, AssetHandler*> AssetHandlerMap;
typedef AZStd::unordered_map<AssetType, AssetCatalog*> AssetCatalogMap;
typedef AZStd::unordered_map<AssetId, AssetData*> AssetMap;
typedef AZStd::unordered_map<AssetContainerKey, AZStd::weak_ptr<AssetContainer>> WeakAssetContainerMap;
typedef AZStd::unordered_map<AssetContainer*, AZStd::shared_ptr<AssetContainer>> OwnedAssetContainerMap;
AZ_CLASS_ALLOCATOR(AssetManager, SystemAllocator, 0);
static bool Create(const Descriptor& desc);
static void Destroy();
static bool IsReady();
static AssetManager& Instance();
// Takes ownership
static bool SetInstance(AssetManager* assetManager);
// @{ Asset handler management
/// Register handler with the system for a particular asset type.
/// A handler should be registered for each asset type it handles.
/// Please note that all the handlers are registered just once during app startup from the main thread
/// and therefore this is not a thread safe method and should not be invoked from different threads.
void RegisterHandler(AssetHandler* handler, const AssetType& assetType);
/// Unregister handler from the asset system.
/// Please note that all the handlers are unregistered just once during app shutdown from the main thread
/// and therefore this is not a thread safe method and should not be invoked from different threads.
void UnregisterHandler(AssetHandler* handler);
// @}
// @{ Asset catalog management
/// Register a catalog with the system for a particular asset type.
/// A catalog should be registered for each asset type it is responsible for.
void RegisterCatalog(AssetCatalog* catalog, const AssetType& assetType);
/// Unregister catalog from the asset system.
void UnregisterCatalog(AssetCatalog* catalog);
// @}
void GetHandledAssetTypes(AssetCatalog* catalog, AZStd::vector<AZ::Data::AssetType>& assetTypes);
/// Prevents assets from being released when no longer referenced.
void SuspendAssetRelease();
/// Resumes releasing assets that are no longer referenced. Any currently un-referenced assets will be released upon calling this.
void ResumeAssetRelease();
/**
* Blocks the current thread until the specified asset has finished loading (whether successful or not)
* \param asset a valid asset which has already been requested to load. It is an error to block on an asset which has not been requested to load already
* This will return as soon as the asset has finished loading (i.e. the appropriate internal AssetJobBus notification has triggered)
* It does not wait for the AssetManager to notify external listeners via the AssetBus OnAsset* events.
* If the asset is loaded successfully, the return state may be ReadyPreNotify or Ready depending on thread timing
*/
AssetData::AssetStatus BlockUntilLoadComplete(const Asset<AssetData>& asset);
/**
* Gets an asset from the database, if not present it loads it from the catalog/stream. For events register a handler by calling RegisterEventHandler().
* \param assetId a valid id of the asset
* \param assetReferenceLoadBehavior the AssetLoadBehavior set on the returned Asset<T> object. Important (only) when the
* Asset<T> is saved to disk as this behavior will be preserved and used when loading the asset containing this reference
* This specifically does not have a default parameter to ensure callers intentionally choose the correct behavior
* For asset references intended to be saved to disk
* \param loadParams optional set of parameters to control loading
* Keep in mind that this is an async operation, the asset will not be loaded after the call to this function completes.
*/
template<class AssetClass>
Asset<AssetClass> GetAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior, const AssetLoadParameters& loadParams = AssetLoadParameters{});
/**
* Gets an asset from the database, if not present it loads it from the catalog/stream. For events register a handler by calling RegisterEventHandler().
* \param assetId a valid id of the asset
* \param assetType type id of the asset
* \param assetReferenceLoadBehavior the AssetLoadBehavior set on the returned Asset<T> object. Important (only) when the
* Asset<T> is saved to disk as this behavior will be preserved and used when loading the asset containing this reference
* This specifically does not have a default parameter to ensure callers intentionally choose the correct behavior
* For asset references intended to be saved to disk
* \param loadParams optional set of parameters to control loading
* Keep in mind that this async operation, asset will not be loaded after the call to this function completes.
**/
Asset<AssetData> GetAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior, const AssetLoadParameters& loadParams = AssetLoadParameters{});
/**
* Locates an existing in-memory asset, if the asset is unknown, a new in-memory asset will be created.
* The asset will not be queued for load.
* \param assetReferenceLoadBehavior the AssetLoadBehavior set on the returned Asset<T> object. Important (only) when the
* Asset<T> is saved to disk as this behavior will be preserved and used when loading the asset containing this reference
* This specifically does not have a default parameter to ensure callers intentionally choose the correct behavior
* For asset references intended to be saved to disk
*/
template<class AssetClass>
Asset<AssetClass> FindOrCreateAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior);
Asset<AssetData> FindOrCreateAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior);
/** Locates an existing in-memory asset. If the asset is unknown, a null asset pointer is returned.
* \param assetReferenceLoadBehavior the AssetLoadBehavior set on the returned Asset<T> object. Important (only) when the
* Asset<T> is saved to disk as this behavior will be preserved and used when loading the asset containing this reference
* This specifically does not have a default parameter to ensure callers intentionally choose the correct behavior
* For asset references intended to be saved to disk
*/
template<class AssetClass>
Asset<AssetClass> FindAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior);
Asset<AssetData> FindAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior);
/** Creates an in-memory asset and returns the pointer. If the asset already exists it will return NULL (then you should use GetAsset/FindAsset to obtain it).
* \param assetReferenceLoadBehavior the AssetLoadBehavior set on the returned Asset<T> object. Important (only) when the
* Asset<T> is saved to disk as this behavior will be preserved and used when loading the asset containing this reference
*/
template<class AssetClass>
Asset<AssetClass> CreateAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior = AssetLoadBehavior::Default);
Asset<AssetData> CreateAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior = AssetLoadBehavior::Default);
/**
* Triggers an asset save an asset if possible. In general most assets will NOT support save as they are generated from external tool.
* This is the interface for the rare cases we do save. If you want to know the state of the save (if completed and result)
* listen on the AssetBus.
*/
void SaveAsset(const Asset<AssetData>& asset);
/**
* Requests a reload of a given asset from storage.
*/
void ReloadAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior, bool isAutoReload = false);
/**
* Reloads an asset from provided in-memory data.
* Ownership of the provided asset data is transferred to the asset manager.
*/
void ReloadAssetFromData(const Asset<AssetData>& asset);
/**
* Assign new data for the specified asset Id. This is effectively reloading the asset
* with the provided data. Listeners will be notified to process the new data.
*/
void AssignAssetData(const Asset<AssetData>& asset);
/**
* Gets a pointer to an asset handler for a type.
* Returns nullptr if a handler for that type does not exist.
*/
AssetHandler* GetHandler(const AssetType& assetType);
AssetStreamInfo GetLoadStreamInfoForAsset(const AssetId& assetId, const AssetType& assetType);
AssetStreamInfo GetSaveStreamInfoForAsset(const AssetId& assetId, const AssetType& assetType);
void DispatchEvents();
/**
* Old 'legacy' assetIds and asset hints can be automatically replaced with new ones during deserialize / assignment.
* This operation can be somewhat costly, and its only useful if the program subsequently re-saves the files its loading so that
* the asset hints and assetIds actually persist. Thus, it can be disabled in situations where you know you are not going to be
* saving over or creating new source files (for example builders/background apps)
* By default, it is enabled.
*/
void SetAssetInfoUpgradingEnabled(bool enable);
bool GetAssetInfoUpgradingEnabled() const;
bool ShouldCancelAllActiveJobs() const;
/**
* Parallel dependent loading is enabled by default, but needs to be disabled by Asset Builders or other tools connecting
* directly with the Asset Processor because dependency information isn't guaranteed to be complete and usable for loading
* dependencies when querying during asset building. It only becomes usable after assets have finished building.
*
**/
void SetParallelDependentLoadingEnabled(bool enable);
bool GetParallelDependentLoadingEnabled() const;
/**
* This method must be invoked before you start unregistering handlers manually and shutting down the asset manager.
* This method ensures that all jobs in flight are either canceled or completed.
* This method is automatically called in the destructor but if you are unregistering handlers manually,
* you must invoke it yourself.
*/
void PrepareShutDown();
/**
* Returns whether or not any threaded asset requests are currently active.
*/
bool HasActiveJobsOrStreamerRequests();
protected:
AssetManager(const Descriptor& desc);
virtual ~AssetManager();
void WaitForActiveJobsAndStreamerRequestsToFinish();
void NotifyAssetReady(Asset<AssetData> asset);
void NotifyAssetPreReload(Asset<AssetData> asset);
void NotifyAssetReloaded(Asset<AssetData> asset);
void NotifyAssetReloadError(Asset<AssetData> asset);
void NotifyAssetError(Asset<AssetData> asset);
void NotifyAssetCanceled(AssetId assetId);
void NotifyAssetContainerReady(Asset<AssetData> asset);
void ReleaseAsset(AssetData* asset, AssetId assetId, AssetType assetType, bool removeAssetFromHash, int creationToken);
void OnAssetUnused(AssetData* asset);
void AddJob(AssetDatabaseJob* job);
void RemoveJob(AssetDatabaseJob* job);
void AddActiveStreamerRequest(AssetId assetId, AZStd::shared_ptr<AssetDataStream> readRequest);
void RescheduleStreamerRequest(AssetId assetId, AZStd::chrono::milliseconds newDeadline, AZ::IO::IStreamerTypes::Priority newPriority);
void RemoveActiveStreamerRequest(AssetId assetId);
void AddBlockingRequest(AssetId assetId, WaitForAsset* blockingRequest);
void RemoveBlockingRequest(AssetId assetId, WaitForAsset* blockingRequest);
void ValidateAndPostLoad(AZ::Data::Asset < AZ::Data::AssetData>& asset, bool loadSucceeded, bool isReload, AZ::Data::AssetHandler* assetHandler = nullptr);
void PostLoad(AZ::Data::Asset < AZ::Data::AssetData>& asset, bool loadSucceeded, bool isReload, AZ::Data::AssetHandler* assetHandler = nullptr);
Asset<AssetData> GetAssetInternal(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior, const AssetLoadParameters& loadParams = AssetLoadParameters{}, AssetInfo assetInfo = AssetInfo(), bool signalLoaded = false);
void UpdateDebugStatus(AZ::Data::Asset<AZ::Data::AssetData> asset);
/**
* Gets a root asset and dependencies as individual async loads if necessary.
* \param assetId a valid id of the asset
* \param loadFilter optional filter predicate for dependent asset loads.
* If the asset container is already loaded just hand back a new shared ptr
**/
AZStd::shared_ptr<AssetContainer> GetAssetContainer(Asset<AssetData> asset, const AssetLoadParameters& loadParams = AssetLoadParameters{});
/**
* Creates a new shared AssetContainer with an optional loadFilter
* **/
AZStd::shared_ptr<AssetContainer> CreateAssetContainer(Asset<AssetData> asset, const AssetLoadParameters& loadParams = AssetLoadParameters{}) const;
/**
* Releases all references to asset containers that are currently attempting to load this asset.
* If all "external" references to the asset are destroyed (i.e. nothing but loading code references the asset),
* this makes sure that the containers are cleaned up and the loading is canceled as a part of destroying the AssetData.
**/
void ReleaseAssetContainersForAsset(AssetData* asset);
/**
* Clears all references to the owned asset container.
**/
void ReleaseOwnedAssetContainer(AssetContainer* assetContainer);
//////////////////////////////////////////////////////////////////////////
// AssetManagerBus
void OnAssetReady(const Asset<AssetData>& asset) override;
void OnAssetReloaded(const Asset<AssetData>& asset) override;
void OnAssetReloadError(const Asset<AssetData>& asset) override;
void OnAssetError(const Asset<AssetData>& asset) override;
void OnAssetCanceled(AssetId asset) override;
void OnAssetContainerReady(AssetContainer* container) override;
void OnAssetContainerCanceled(AssetContainer* container) override;
//////////////////////////////////////////////////////////////////////////
//! Get the load stream info for an asset, including missing-asset substitution and custom AssetHandler overrides.
AssetStreamInfo GetModifiedLoadStreamInfoForAsset(const Asset<AssetData>& asset, AssetHandler* handler);
//! Queue an async file load with the AssetDataStream as the first step in an asset load
void QueueAsyncStreamLoad(Asset<AssetData> asset, AZStd::shared_ptr<AssetDataStream> dataStream,
const AZ::Data::AssetStreamInfo& streamInfo, bool isReload,
AssetHandler* handler, const AssetLoadParameters& loadParameters, bool signalLoaded);
AssetHandlerMap m_handlers;
AssetCatalogMap m_catalogs;
AZStd::recursive_mutex m_catalogMutex; // lock when accessing the catalog map
AssetMap m_assets;
AZStd::recursive_mutex m_assetMutex; // lock when accessing the asset map
WeakAssetContainerMap m_assetContainers;
OwnedAssetContainerMap m_ownedAssetContainers;
AZStd::unordered_multimap<AssetId, AssetContainer*> m_ownedAssetContainerLookup;
AZStd::recursive_mutex m_assetContainerMutex; // lock when accessing the assetContainers map
AZStd::thread::id m_mainThreadId;
IDebugAssetEvent* m_debugAssetEvents{ nullptr };
int m_creationTokenGenerator = 0; // this is used to generate unique identifiers for assets
typedef AZStd::unordered_map<AssetId, Asset<AssetData> > ReloadMap;
ReloadMap m_reloads; // book-keeping and reference-holding for asset reloads
typedef AZStd::intrusive_list<AssetDatabaseJob, AZStd::list_base_hook<AssetDatabaseJob> > ActiveJobList;
ActiveJobList m_activeJobs;
//! The AssetDataStream read requests that are pending or processing for a specific asset.
using AssetRequestMap = AZStd::unordered_map<AssetId, AZStd::shared_ptr<AssetDataStream>>;
AssetRequestMap m_activeAssetDataStreamRequests;
// Lock when accessing the list of active jobs or streamer requests
AZStd::recursive_mutex m_activeJobOrRequestMutex;
//! The set of all blocking requests that currently exist, grouped by AssetId.
//! The information is used internally to route LoadAssetJob processing to any thread that currently is blocked waiting
//! for that load to complete.
using BlockingRequestMap = AZStd::unordered_multimap<AssetId, WaitForAsset*>;
BlockingRequestMap m_activeBlockingRequests;
// Mutex lock when accessing the list of active blocking requests
AZStd::recursive_mutex m_activeBlockingRequestMutex;
//! Enable or disable parallel loading of dependent assets via the use of Asset Containers.
//! default = true, but Asset Builders and other tools using real-time in-progress dependency information need
//! to set it to false.
bool m_enableParallelDependentLoading = true;
bool m_assetInfoUpgradingEnabled = true;
static EnvironmentVariable<AssetManager*> s_assetDB;
// used internally by the cycle checking on the job system. Used for blocking loads.
void RegisterAssetLoading(const Asset<AssetData>& asset);
// Variant of RegisterAssetLoading used for jobs which have been queued and need to verify the status of the asset
// before loading in order to prevent cases where a load is queued, then a blocking load goes through, then the queued
// load is processed. This validation step leaves the loaded (And potentially modified) data as is in that case.
bool ValidateAndRegisterAssetLoading(const Asset<AssetData>& asset);
void UnregisterAssetLoading(const Asset<AssetData>& asset);
// Setting this to true will cause all loadAssets jobs that have not started yet to cancel as soon as they start.
bool m_cancelAllActiveJobs = false;
AZStd::atomic_int m_suspendAssetRelease{ 0 };
};
/**
* AssetHandlers are responsible for loading and destroying assets
* when the asset manager requests it.
*
* To create a handler for a specific asset type, derive from this class
* and register an instance of the handler with the asset manager.
*
* Asset handling functions may be called from multiple threads, so the
* handlers need to be thread-safe.
* It is ok for the handler to block the calling thread during the actual
* asset load.
*
* NOTE! Because it doesn't go without saying:
* It is NOT OK for an AssetHandler to queue work for another thread and block
* on that work being finished, in the case that that thread is the same one doing
* the blocking. That will result in a single thread deadlock.
*
* If you need to queue work, the logic needs to be similar to this:
*
AssetHandler::LoadResult MyAssetHandler::LoadAssetData(const Asset<AssetData>& asset, AZStd::shared_ptr<AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB)
{
.
.
.
if (AZStd::this_thread::get_id() == m_loadingThreadId)
{
// load asset immediately
}
else
{
// queue job to load asset in thread identified by m_loadingThreadId
auto* queuedJob = QueueLoadingOnOtherThread(...);
// block waiting for queued job to complete
queuedJob->BlockUntilComplete();
}
.
.
.
}
*/
class AssetHandler
{
friend class AssetManager;
friend class AssetData;
public:
AZ_RTTI(AssetHandler, "{58BD1FDF-E668-42E5-9091-16F46022F551}");
AssetHandler();
virtual ~AssetHandler();
// Called by the asset manager to create a new asset. No loading should occur during this call
virtual AssetPtr CreateAsset(const AssetId& id, const AssetType& type) = 0;
//! Result from LoadAssetData - it either finished loading, didn't finish and is waiting for more data, or had an error.
enum class LoadResult : u8
{
Error, // The provided data failed to load correctly
MoreDataRequired, // The provided data loaded correctly, but more data is required to finish the asset load
LoadComplete // The provided data loaded correctly, and the asset has been created
};
// Called by the asset manager to load in the asset data.
LoadResult LoadAssetDataFromStream(
const Asset<AssetData>& asset,
AZStd::shared_ptr<AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB);
// Called by the asset manager to perform actual asset save. Returns true if successful otherwise false (default - as we don't require support save).
virtual bool SaveAssetData(const Asset<AssetData>& asset, IO::GenericStream* stream) { (void)asset; (void)stream; return false; }
//! Called when an asset requested to load is actually missing from the catalog when we are trying to resolve it
//! from an ID to a file name and other streaming info.
//! Here, optionally, you can return a non-empty asset ID for it to try to use that as fallback data instead.
//! Providing it with a non-empty assetId will cause it to attach the handler to the file data for that asset instead,
//! but still retain the original assetId for the loaded asset. This allows you to perform simple 'placeholder'
//! substitution for assets that are missing, errored, or still being compiled. If you need your
//! system to do something more complicated than simple substitution, the place for that is in the component entity
//! class that requested the load in the first place. This API is just for basic substitution cases.
virtual AZ::Data::AssetId AssetMissingInCatalog(const Asset<AssetData>& /*asset*/) {return AZ::Data::AssetId(); }
// Called after the data loading stage and after all dependencies have been fulfilled.
// Override this if the asset needs post-load init. If overriden, the handler is responsible
// for notifying the asset manager when the asset is ready via AssetDatabaseBus::OnAssetReady.
virtual void InitAsset(const Asset<AssetData>& asset, bool loadStageSucceeded, bool isReload);
// Called by the asset manager when an asset should be deleted.
virtual void DestroyAsset(AssetPtr ptr) = 0;
// Called by asset manager on registration.
virtual void GetHandledAssetTypes(AZStd::vector<AssetType>& assetTypes) = 0;
// Verify that the provided asset is of a type handled by this handler
virtual bool CanHandleAsset(const AssetId& /*id*/) const { return true; }
//! Give asset handlers the ability to optionally modify the stream info (asset path, I/O flags, etc) prior to loading.
//! (Very few asset handlers should need this functionality)
virtual void GetCustomAssetStreamInfoForLoad([[maybe_unused]] AssetStreamInfo& streamInfo) {}
//! Asset Handlers have the ability to provide custom asset buffer allocators for any non-standard allocation needs.
virtual IO::IStreamerTypes::RequestMemoryAllocator* GetAssetBufferAllocator() { return nullptr; }
virtual void GetDefaultAssetLoadPriority([[maybe_unused]] AssetType type, AZStd::chrono::milliseconds& defaultDeadline,
AZ::IO::IStreamerTypes::Priority& defaultPriority) const
{
defaultDeadline = IO::IStreamerTypes::s_noDeadline;
defaultPriority = IO::IStreamerTypes::s_priorityMedium;
}
protected:
// Called by the asset manager to perform actual asset load.
virtual LoadResult LoadAssetData(
const Asset<AssetData>& asset,
AZStd::shared_ptr<AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) = 0;
private:
AZStd::atomic_int m_nHandledTypes; // how many asset types are currently being handled by this handler.
};
/**
* Base interface to find an asset in a catalog. By design this is not
* performance critical code (as we use it on load only), but it is important to make sure this catalog operates
* in a reasonably fast way. Cache the information (if needed) about assets location (if we will
* do often load/unload)
*
* Asset catalogs functions may be called from multiple threads, so make sure your code is thread safe.
*/
class AssetCatalog
{
public:
virtual ~AssetCatalog() {}
/**
* Find the stream the asset can be loaded from. Empty string if asset can't be found.
* \param id - asset id
*/
virtual AssetStreamInfo GetStreamInfoForLoad(const AssetId& assetId, const AssetType& assetType) = 0;
/**
* Same as \ref GetStreamInfoForLoad but for saving. It's not typical that assets will have 'save' support,
* as they are generated from external tools, etc. But when needed, the framework provides an interface.
*/
virtual AssetStreamInfo GetStreamInfoForSave(const AssetId& assetId, const AssetType& assetType)
{
(void)assetId;
(void)assetType;
AZ_Assert(false, "GetStreamInfoForSave() has not been implemented for assets of type 0x%x.", assetType);
return AssetStreamInfo();
}
};
//=========================================================================
// GetAsset
//=========================================================================
template <class AssetClass>
Asset<AssetClass> AssetManager::GetAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior, const AssetLoadParameters& loadParams)
{
Asset<AssetData> asset = GetAsset(assetId, AzTypeInfo<AssetClass>::Uuid(), assetReferenceLoadBehavior, loadParams);
return static_pointer_cast<AssetClass>(asset);
}
template <class AssetClass>
Asset<AssetClass> AssetManager::FindOrCreateAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior)
{
Asset<AssetData> asset = FindOrCreateAsset(assetId, AzTypeInfo<AssetClass>::Uuid(), assetReferenceLoadBehavior);
return static_pointer_cast<AssetClass>(asset);
}
//=========================================================================
// FindAsset
//=========================================================================
template<class AssetClass>
Asset<AssetClass> AssetManager::FindAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior)
{
Asset<AssetData> asset = FindAsset(assetId, assetReferenceLoadBehavior);
if (asset.GetAs<AssetClass>())
{
return static_pointer_cast<AssetClass>(asset);
}
return Asset<AssetData>();
}
//=========================================================================
// CreateAsset
//=========================================================================
template<class AssetClass>
Asset<AssetClass> AssetManager::CreateAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior)
{
Asset<AssetData> asset = CreateAsset(assetId, AzTypeInfo<AssetClass>::Uuid(), assetReferenceLoadBehavior);
return static_pointer_cast<AssetClass>(asset);
}
} // namespace Data
} // namespace AZ
@@ -0,0 +1,266 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_ASSET_DATABASE_BUS_H
#define AZCORE_ASSET_DATABASE_BUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/bitset.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/containers/unordered_map.h>
namespace AzFramework
{
class AssetRegistry;
}
namespace AZ
{
namespace Data
{
/** Asset Information (returned by bus queries to the catalog)
* Note that Multiple UUIDs may point at the same "asset information"
* so that legacy UUIDs (such as those generated using a different scheme) can still resolve to a valid asset
* however, only one such entry will have 'canonical' set to true, meaning its the latest scheme.
* UIs which enumerate assets should only use canonical assets.
*/
class AssetInfo
{
public:
AZ_TYPE_INFO(AssetInfo, "{E6D8372B-8419-4287-B478-1353709A972F}");
AZ::Data::AssetId m_assetId; // this is in case you look up by a legacy Id or other remapping and it resolves to a new ID.
AZ::Data::AssetType m_assetType = s_invalidAssetType;
AZ::u64 m_sizeBytes = 0;
AZStd::string m_relativePath; // (legacy asset name)
};
struct ProductDependency
{
AZ_TYPE_INFO(ProductDependency, "{5B9A8F1C-407A-4D2B-88F4-A79584684CC4}");
ProductDependency() = default;
ProductDependency(const AZ::Data::AssetId& assetId, AZStd::bitset<64> flags) : m_assetId(assetId), m_flags(flags) {}
AZ::Data::AssetId m_assetId;
AZStd::bitset<64> m_flags;
};
using PreloadAssetListType = AZStd::unordered_map<AZ::Data::AssetId, AZStd::unordered_set<AZ::Data::AssetId>>;
/**
* Request bus for asset catalogs. Presently we expect only one asset catalog, so this
* bus is limited to one handlers.
*/
class AssetCatalogRequests
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides - Application is a singleton
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
//////////////////////////////////////////////////////////////////////////
virtual ~AssetCatalogRequests() = default;
/// Enables the catalog.
virtual void EnableCatalogForAsset(const AZ::Data::AssetType& /*assetType*/) {}
/// Disables the catalog.
virtual void DisableCatalog() {}
/// Enable monitoring of asset changes.
virtual void StartMonitoringAssets() {};
/// Stop monitoring of asset changes.
virtual void StopMonitoringAssets() {};
/// Populates catalog data from specified file.
/// \param catalogRegistryFile cache-relative file path from which catalog should be pre-loaded.
/// \return true if catalog was successfuly loaded.
virtual bool LoadCatalog(const char* /*catalogRegistryFile*/) { return false; }
virtual void ClearCatalog() {}
/// Write out our existing catalog to the given file.
virtual bool SaveCatalog(const char* /*outputFile*/) { return false; }
/// Load a catalog file on top of our existing catalog data
virtual bool AddDeltaCatalog(AZStd::shared_ptr<AzFramework::AssetRegistry> /*deltaCatalog*/) { return true; }
/// Insert a new delta catalog at a particular index
virtual bool InsertDeltaCatalog(AZStd::shared_ptr<AzFramework::AssetRegistry> /*deltaCatalog*/, size_t /* slotNum */) { return true; }
/// Insert a new delta catalog before the given next unique catalog name
virtual bool InsertDeltaCatalogBefore(AZStd::shared_ptr<AzFramework::AssetRegistry> /*deltaCatalog*/, AZStd::shared_ptr<AzFramework::AssetRegistry> /*nextDeltaCatalog*/) { return true; }
/// Remove a catalog from our delta list and rebuild the catalog from remaining items
virtual bool RemoveDeltaCatalog(AZStd::shared_ptr<AzFramework::AssetRegistry> /*deltaCatalog*/) { return true; }
/// Creates a manifest with the given DeltaCatalog name
virtual bool CreateBundleManifest(const AZStd::string& /*deltaCatalogPath*/, const AZStd::vector<AZStd::string>& /*dependentBundleNames*/, const AZStd::string& /*fileDirectory*/, int /*bundleVersion*/, const AZStd::vector<AZStd::string>& /*levelDirs*/) { return false; }
/// Creates an instance of a registry containing info for just the specified files, and writes it out to a file at the specified path
virtual bool CreateDeltaCatalog(const AZStd::vector<AZStd::string>& /*files*/, const AZStd::string& /*filePath*/) { return false; }
/// Adds an extension to the catalog's handled list.
/// \param file extension to add to catalog's list of those handled. With and without prefix '.' are both accepted.
virtual void AddExtension(const char* /*extension*/) {}
/// Adds an asset type to the catalog's handled list.
/// \param asset type to add to the catalog's list of those handled.
virtual void AddAssetType(const AZ::Data::AssetType& /*assetType*/) {}
/// Fills a vector with all registered AssetTypes.
/// \param the list reference to fill with registered types.
virtual void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& /*assetTypes*/) {}
/// Get Asset Type Uuid from its Display Name
virtual AZ::Data::AssetType GetAssetTypeByDisplayName(const AZStd::string_view /*displayName*/) { return AZ::Data::AssetType(); }
/// Adds an asset to the catalog.
/// \param id - the id to assign the asset.
/// \param info - the information to assign to that ID
virtual void RegisterAsset(const AZ::Data::AssetId& /*id*/, AZ::Data::AssetInfo& /*info*/) {}
/// Removes an asset from the catalog (by ID)
virtual void UnregisterAsset(const AZ::Data::AssetId& /*id*/) {}
/// Retrieves an asset-root-relative path by Id.
/// \return asset relative path given an Id, if it's in the catalog, otherwise an empty string.
virtual AZStd::string GetAssetPathById(const AZ::Data::AssetId& /*id*/) { return AZStd::string(); }
/// Retrieves an asset Id given a full or asset-root-relative path.
/// \param path - asset full or asset-root relative path.
/// \param typeToRegister - if autoRegisterIfNotFound is set and the asset isn't already registered, it will be registered as this type.
/// \param autoRegisterIfNotFound - registers the asset if not already in the catalog.
/// \return valid AssetId if it's in the registry, otherwise an empty AssetId.
virtual AZ::Data::AssetId GetAssetIdByPath(const char* /*path*/, const AZ::Data::AssetType& /*typeToRegister*/, bool /*autoRegisterIfNotFound*/) { return AZ::Data::AssetId(); }
/// Retrieves file paths of all the registered assets
virtual AZStd::vector<AZStd::string> GetRegisteredAssetPaths() { return AZStd::vector<AZStd::string>(); }
/// Given an asset ID, retrieve general information about that asset.
virtual AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& /*id*/) { return AssetInfo(); }
/// Compute an asset Id from a path.
/// This is TEMPORARY functionality. Side-by-side metadata and/or will eventually contain Uuid information.
/// For now it's computed based on path.
/// \param path - asset full or asset-root relative path.
/// \return AssetId computed from path. Returned Id will be invalid if input path is full, but not under the asset root.
virtual AZ::Data::AssetId GenerateAssetIdTEMP(const char* /*path*/) { return AZ::Data::AssetId(); }
/// Retrieves a list of all products the given (product) asset directly depends on.
/// \param id - the id of the asset to look up the dependencies for
/// \return AZ::Success containing a list of dependencies, AZ::Failure if id is not found
virtual AZ::Outcome<AZStd::vector<ProductDependency>, AZStd::string> GetDirectProductDependencies(const AssetId& /*id*/) { return AZ::Failure<AZStd::string>("Not implemented"); }
/// Retrieves a list of all products the given (product) asset depends on (recursively).
/// \param id - the id of the asset to look up the dependencies for
/// \return AZ::Success containing a list of dependencies
virtual AZ::Outcome<AZStd::vector<ProductDependency>, AZStd::string> GetAllProductDependencies(const AssetId& /*id*/) { return AZ::Failure<AZStd::string>("Not implemented"); }
/// Retrieves a list of products the given (product) asset depends on (recursively) which are not flagged as NoLoad.
/// NoLoad dependencies will be returned in the noload set for the caller to load on demand if desired
/// \param id - the id of the asset to look up the dependencies for
/// \return AZ::Success containing a list of dependencies, noloadSet with the dependencies flagged as NoLoad, preloadLists contains the specific dependencies which are PreLoad for
/// each asset. These assets are all also found in the product dependency list which the noloadset are not. This is because the intent of the return value product dependency list
/// is the entire set of assets which need to load by default for the requested assetID, and the preload list is only to allow us to manage and communicate about subsets of those assets
/// which have additional reporting requirements. We don't want to report assets which have preload dependencies as "Ready" until all of their "PreLoad" dependencies are also ready
/// NoLoad assets however simply wait for the user to request an additional load - they or their dependencies don't begin loading by default
virtual AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> GetLoadBehaviorProductDependencies([[maybe_unused]] const AZ::Data::AssetId& id, [[maybe_unused]] AZStd::unordered_set<AZ::Data::AssetId>& noloadSet, [[maybe_unused]] PreloadAssetListType& preloadLists) { return AZ::Failure<AZStd::string>("Not implemented"); }
/// Retrieves a list of all products the given (product) asset depends on (recursively).
/// \param id - the id of the asset to look up the dependencies for
/// \param exclusionList - list of AssetIds to ignore (recursively). If a match is found, it and all its dependencies are skipped.
/// \param wildcardPatternExclusionList - if a dependency matches any of these wildcard patterns, it should be ignored (recursively). If a match is found, it and all its dependencies are skipped.
/// \return AZ::Success containing a list of dependencies
virtual AZ::Outcome<AZStd::vector<ProductDependency>, AZStd::string> GetAllProductDependenciesFilter([[maybe_unused]] const AssetId& id, [[maybe_unused]] const AZStd::unordered_set<AssetId>& exclusionList, [[maybe_unused]] const AZStd::vector<AZStd::string>& wildcardPatternExclusionList) { return AZ::Failure<AZStd::string>("Not implemented"); }
/// Checks the relative path of the asset associated with the assetId against the input wildcard pattern.
/// Does not verify the validity of the input wildcard pattern.
/// AssetIds that cannot be resolved to a relative path are treated as though they do not match the input pattern.
/// \return true if the relative path associated with the input assetId matches the input wildcard pattern
virtual bool DoesAssetIdMatchWildcardPattern(const AZ::Data::AssetId& /*assetId*/, const AZStd::string&/* wildcardPattern*/) { return false; }
using BeginAssetEnumerationCB = AZStd::function< void() >;
using AssetEnumerationCB = AZStd::function< void(const AZ::Data::AssetId /*id*/, const AZ::Data::AssetInfo& /*info*/) >;
using EndAssetEnumerationCB = AZStd::function< void() >;
/// Iterate through all assets and call the callback for each one.
/// These callbacks will run on the same thread as the caller.
/// \param beginCB - called before any assets are enumerated.
/// \param enumerateCB - called for each asset.
/// \param endCB - called after all assets are enumerated.
virtual void EnumerateAssets(BeginAssetEnumerationCB /*beginCB*/, AssetEnumerationCB /*enumerateCB*/, EndAssetEnumerationCB /*endCB*/) {}
};
using AssetCatalogRequestBus = AZ::EBus<AssetCatalogRequests>;
/*
* Events that AssetManager listens for
*/
class AssetManagerEvents
: public EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
//////////////////////////////////////////////////////////////////////////
virtual ~AssetManagerEvents() {}
/// Signal that an asset is ready for use
virtual void OnAssetReady(const Asset<AssetData>& asset) = 0;
/// Signal that an asset has been reloaded
virtual void OnAssetReloaded(const Asset<AssetData>& asset) = 0;
/// Signal that an asset failed to reload.
virtual void OnAssetReloadError(const Asset<AssetData>& asset) = 0;
/// Signal that an asset error has occurred
virtual void OnAssetError(const Asset<AssetData>& asset) = 0;
/// Signal that an asset load has been canceled
virtual void OnAssetCanceled(AssetId assetId) = 0;
/// Signal that an asset container load has finished.
virtual void OnAssetContainerReady(AssetContainer* container) = 0;
/// When an asset is loaded as part of a container this signal is sent if the root asset is canceled / destroyed.
/// The signal isn't sent until all the dependent assets in the container have finished loading, to help ensure that
/// dependent assets don't get stuck in a perpetual loading state.
virtual void OnAssetContainerCanceled(AssetContainer* container) = 0;
};
typedef EBus<AssetManagerEvents> AssetManagerBus;
/*
* Events that the AssetManager broadcasts.
*/
class AssetManagerNotifications
: public EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Multiple;
typedef AZStd::recursive_mutex MutexType;
//////////////////////////////////////////////////////////////////////////
/// Notify listeners that asset events are starting to dispatch
virtual void OnAssetEventsDispatchBegin() {}
/// Notify listeners that all asset events have finished dispatching
virtual void OnAssetEventsDispatchEnd() {}
};
typedef EBus<AssetManagerNotifications> AssetManagerNotificationBus;
} // namespace Data
} // namespace AZ
#endif // AZCORE_ASSET_DATABASE_BUS_H
#pragma once
@@ -0,0 +1,137 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Asset/AssetJsonSerializer.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Slice/SliceAssetHandler.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/Math/Crc.h>
namespace AZ
{
//=========================================================================
// AssetDatabaseComponent
// [6/25/2012]
//=========================================================================
AssetManagerComponent::AssetManagerComponent()
{
}
//=========================================================================
// Activate
// [6/25/2012]
//=========================================================================
void AssetManagerComponent::Activate()
{
Data::AssetManager::Descriptor desc;
Data::AssetManager::Create(desc);
SystemTickBus::Handler::BusConnect();
}
//=========================================================================
// Deactivate
// [6/25/2012]
//=========================================================================
void AssetManagerComponent::Deactivate()
{
Data::AssetManager::Instance().DispatchEvents(); // clear any waiting assets.
SystemTickBus::Handler::BusDisconnect();
Data::AssetManager::Destroy();
}
//=========================================================================
// OnTick
// [6/25/2012]
//=========================================================================
void AssetManagerComponent::OnSystemTick()
{
Data::AssetManager::Instance().DispatchEvents();
}
//=========================================================================
// GetProvidedServices
//=========================================================================
void AssetManagerComponent::GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("AssetDatabaseService"));
}
//=========================================================================
// GetIncompatibleServices
//=========================================================================
void AssetManagerComponent::GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("AssetDatabaseService"));
}
//=========================================================================
// GetRequiredServices
//=========================================================================
void AssetManagerComponent::GetRequiredServices(ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("DataStreamingService"));
required.push_back(AZ_CRC_CE("JobsService"));
}
//=========================================================================
// Reflect
//=========================================================================
void AssetManagerComponent::Reflect(ReflectContext* context)
{
Data::AssetId::Reflect(context);
Data::AssetData::Reflect(context);
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->RegisterGenericType<Data::Asset<Data::AssetData>>();
serializeContext->Class<AssetManagerComponent, AZ::Component>()
->Version(1)
;
if (EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<AssetManagerComponent>(
"Asset Database", "Asset database system functionality")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Engine")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
;
}
}
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->EBus<Data::AssetCatalogRequestBus>("AssetCatalogRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Event("GetAssetPathById", &Data::AssetCatalogRequests::GetAssetPathById)
->Event("GetAssetIdByPath", &Data::AssetCatalogRequests::GetAssetIdByPath)
->Event("GetAssetTypeByDisplayName", &Data::AssetCatalogRequests::GetAssetTypeByDisplayName)
;
}
if (JsonRegistrationContext* jsonContext = azrtti_cast<JsonRegistrationContext*>(context))
{
jsonContext->Serializer<AZ::Data::AssetJsonSerializer>()->HandlesType<AZ::Data::Asset>();
}
}
}
@@ -0,0 +1,57 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_ASSETDATABASE_COMPONENT_H
#define AZCORE_ASSETDATABASE_COMPONENT_H
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Math/Crc.h>
namespace AZ
{
/**
*
*/
class AssetManagerComponent
: public Component
, public SystemTickBus::Handler
{
public:
AZ_COMPONENT(AssetManagerComponent, "{D5A73BCC-0098-4d1e-8FE4-C86101E374AC}", Component)
AssetManagerComponent();
protected:
//////////////////////////////////////////////////////////////////////////
// Component base
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// SystemTickBus
void OnSystemTick() override;
//////////////////////////////////////////////////////////////////////////
/// \ref ComponentDescriptor::GetProvidedServices
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided);
/// \ref ComponentDescriptor::GetIncompatibleServices
static void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible);
/// \ref ComponentDescriptor::GetRequiredServices
static void GetRequiredServices(ComponentDescriptor::DependencyArrayType& required);
/// \ref ComponentDescriptor::Reflect
static void Reflect(ReflectContext* reflection);
};
}
#endif // AZCORE_ASSETDATABASE_COMPONENT_H
#pragma once
@@ -0,0 +1,47 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/parallel/mutex.h>
namespace AZ
{
namespace Data
{
// Private system events - external systems should not listen for these
class AssetLoadEvents
: public EBusTraits
{
public:
AZ_RTTI(AssetLoadEvents, "{7F8128CD-3951-46C0-A9CA-E6F1F6A5B6FB}");
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using MutexType = AZStd::recursive_mutex;
using BusIdType = AssetId;
virtual ~AssetLoadEvents() {}
/// Called when an asset's data is loaded into memory for assets which have dependencies
/// which have been set to load first (Preload dependencies)
virtual void OnAssetDataLoaded([[maybe_unused]] Asset<AssetData> rootAsset) {}
};
using AssetLoadBus = EBus<AssetLoadEvents>;
} // namespace Data
} // namespace AZ
@@ -0,0 +1,345 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/IO/SystemFile.h>
namespace AZ {
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
const Uuid& GetAssetClassId()
{
static Uuid s_typeId("{77A19D40-8731-4d3c-9041-1B43047366A4}");
return s_typeId;
}
//-------------------------------------------------------------------------
AssetSerializer AssetSerializer::s_serializer;
//-------------------------------------------------------------------------
size_t AssetSerializer::DataToText(IO::GenericStream& in, IO::GenericStream& out, bool isDataBigEndian /*= false*/)
{
(void)isDataBigEndian;
const size_t dataSize = sizeof(Data::AssetId) + sizeof(Data::AssetType);
AZ_Assert(in.GetLength() >= dataSize, "Invalid data in stream");
(void)dataSize;
Data::AssetId assetId;
Data::AssetType assetType;
Data::AssetLoadBehavior assetLoadBehavior;
size_t hintSize = 0;
AZStd::string assetHint;
in.Read(sizeof(Data::AssetId), reinterpret_cast<void*>(&assetId));
in.Read(sizeof(assetType), reinterpret_cast<void*>(&assetType));
in.Read(sizeof(size_t), reinterpret_cast<void*>(&hintSize));
AZ_SERIALIZE_SWAP_ENDIAN(assetId.m_subId, isDataBigEndian);
AZ_SERIALIZE_SWAP_ENDIAN(hintSize, isDataBigEndian);
assetHint.resize(hintSize);
in.Read(hintSize, reinterpret_cast<void*>(assetHint.data()));
in.Read(sizeof(assetLoadBehavior), reinterpret_cast<void*>(&assetLoadBehavior));
AZ_SERIALIZE_SWAP_ENDIAN(assetLoadBehavior, isDataBigEndian);
AZStd::string outText = AZStd::string::format("id=%s,type=%s,hint={%s},loadBehavior=%u",
assetId.ToString<AZStd::string>().c_str(), assetType.ToString<AZStd::string>().c_str(), assetHint.c_str(),
aznumeric_cast<u32>(assetLoadBehavior));
return static_cast<size_t>(out.Write(outText.size(), outText.c_str()));
}
//-------------------------------------------------------------------------
bool AssetSerializer::Load(void* classPtr, IO::GenericStream& stream, unsigned int version, bool isDataBigEndian)
{
AZ_Assert(classPtr, "AssetSerializer::Load received invalid data pointer.");
using namespace AZ::Data;
(void)isDataBigEndian;
// version 0 just has asset Id and type
size_t dataSize = sizeof(AssetId) + sizeof(AssetType);
// version 1 adds asset hint
if (version > 0)
{
dataSize += sizeof(IO::SizeType); // There must be at least enough room for the hint length
}
// version 2 adds asset auto load behavior
if (version > 1)
{
dataSize += sizeof(AZ::Data::AssetLoadBehavior);
}
if (stream.GetLength() < dataSize)
{
return false;
}
AssetId assetId = AssetId();
AssetType assetType = AssetType::CreateNull();
Data::AssetLoadBehavior assetLoadBehavior = Data::AssetLoadBehavior::Default;
AZStd::string assetHint;
stream.Seek(0, IO::GenericStream::ST_SEEK_BEGIN);
AZ::IO::SizeType bytesRead = 0;
bytesRead += stream.Read(sizeof(assetId), reinterpret_cast<void*>(&assetId));
AZ_SERIALIZE_SWAP_ENDIAN(assetId.m_subId, isDataBigEndian);
bytesRead += stream.Read(sizeof(assetType), reinterpret_cast<void*>(&assetType));
if (version > 0)
{
IO::SizeType hintSize = 0;
bytesRead += stream.Read(sizeof(hintSize), reinterpret_cast<void*>(&hintSize));
AZ_SERIALIZE_SWAP_ENDIAN(hintSize, isDataBigEndian);
AZ_Warning("Asset", hintSize < AZ_MAX_PATH_LEN, "Invalid asset hint, will be truncated");
hintSize = AZStd::min<size_t>(hintSize, AZ_MAX_PATH_LEN);
assetHint.resize(hintSize);
dataSize += hintSize;
bytesRead += stream.Read(hintSize, reinterpret_cast<void*>(assetHint.data()));
}
if (version > 1)
{
bytesRead += stream.Read(sizeof(assetLoadBehavior), reinterpret_cast<void*>(&assetLoadBehavior));
AZ_SERIALIZE_SWAP_ENDIAN(assetLoadBehavior, isDataBigEndian);
}
AZ_Assert(bytesRead == dataSize, "Invalid asset type/read");
(void)bytesRead;
Asset<AssetData>* asset = reinterpret_cast<Asset<AssetData>*>(classPtr);
asset->m_assetId = assetId;
asset->m_assetType = assetType;
asset->m_assetHint = assetHint;
// Only overwrite the AutoLoad Behavior if a saved value existed. This preserves the behavior of letting the
// asset class constructor set a default value at runtime if no written value exists.
if (version > 1)
{
asset->SetAutoLoadBehavior(assetLoadBehavior);
}
asset->UpgradeAssetInfo();
return true;
}
//-------------------------------------------------------------------------
bool AssetSerializer::LoadWithFilter(void* classPtr, IO::GenericStream& stream, unsigned int version, const Data::AssetFilterCB& assetFilterCallback, bool isDataBigEndian)
{
if (Load(classPtr, stream, version, isDataBigEndian))
{
Data::Asset<Data::AssetData>* asset = reinterpret_cast<Data::Asset<Data::AssetData>*>(classPtr);
return PostSerializeAssetReference(*asset, assetFilterCallback);
}
return false;
}
//-------------------------------------------------------------------------
void AssetSerializer::Clone(const void* sourcePtr, void* destPtr)
{
AZ_Assert(sourcePtr, "AssetSerializer::Clone received invalid source pointer.");
AZ_Assert(destPtr, "AssetSerializer::Clone received invalid destination pointer.");
const Data::Asset<Data::AssetData>* sourceAsset = reinterpret_cast<const Data::Asset<Data::AssetData>*>(sourcePtr);
Data::Asset<Data::AssetData>* destAsset = reinterpret_cast<Data::Asset<Data::AssetData>*>(destPtr);
*destAsset = *sourceAsset;
}
//-------------------------------------------------------------------------
size_t AssetSerializer::TextToData(const char* text, unsigned int textVersion, IO::GenericStream& stream, bool isDataBigEndian)
{
(void)isDataBigEndian;
// Parse the asset id and type
const char* idGuidStart = strchr(text, '{');
AZ_Assert(idGuidStart, "Invalid asset guid data! %s", text);
const char* idGuidEnd = strchr(idGuidStart, ':');
AZ_Assert(idGuidEnd, "Invalid asset guid data! %s", idGuidStart);
const char* idSubIdStart = idGuidEnd + 1;
const char* idSubIdEnd = strchr(idSubIdStart, ',');
AZ_Assert(idSubIdEnd, "Invalid asset subId data! %s", idSubIdStart);
const char* idTypeStart = strchr(idSubIdEnd, '{');
AZ_Assert(idTypeStart, "Invalid asset type data! %s", idSubIdEnd);
const char* idTypeEnd = strchr(idTypeStart, '}');
AZ_Assert(idTypeEnd, "Invalid asset type data! %s", idTypeStart);
idTypeEnd++;
AZStd::string assetHint;
Data::AssetLoadBehavior assetLoadBehavior = Data::AssetLoadBehavior::PreLoad;
// Read hint for version >= 1
if (textVersion > 0)
{
const char* hintStart = strchr(idTypeEnd, '{');
AZ_Assert(hintStart, "Invalid asset hint data! %s", idTypeEnd);
const char* hintEnd = strchr(hintStart, '}');
AZ_Assert(hintEnd, "Invalid asset hint data! %s", hintStart);
assetHint.assign(hintStart+1, hintEnd);
// Read loadBehavior for version >= 2
if (textVersion > 1)
{
const char* loadBehaviorStart = strchr(hintEnd, '=');
AZ_Assert(loadBehaviorStart, "Invalid asset load behavior data! %s", loadBehaviorStart);
assetLoadBehavior = static_cast<Data::AssetLoadBehavior>(strtoul(loadBehaviorStart+1, nullptr, 16));
}
}
Data::AssetId assetId;
assetId.m_guid = Uuid::CreateString(idGuidStart, idGuidEnd - idGuidStart);
assetId.m_subId = static_cast<u32>(strtoul(idSubIdStart, nullptr, 16));
Data::AssetType assetType = Uuid::CreateString(idTypeStart, idTypeEnd - idTypeStart);
Data::Asset<Data::AssetData> asset(assetId, assetType, assetHint);
// Only overwrite the AutoLoad Behavior if a saved value existed. This preserves the behavior of letting the
// asset class constructor set a default value at runtime if no written value exists.
if (textVersion > 1)
{
asset.SetAutoLoadBehavior(assetLoadBehavior);
}
return Save(&asset, stream, isDataBigEndian);
}
//-------------------------------------------------------------------------
size_t AssetSerializer::Save(const void* classPtr, IO::GenericStream& stream, bool isDataBigEndian)
{
(void)isDataBigEndian;
const Data::Asset<Data::AssetData>* asset = reinterpret_cast<const Data::Asset<Data::AssetData>*>(classPtr);
AZ_Assert(asset->Get() == nullptr || asset->GetType() != AzTypeInfo<Data::AssetData>::Uuid(),
"Asset contains data, but does not have a valid asset type.");
Data::AssetId assetId = asset->GetId();
Data::AssetType assetType = asset->GetType();
const AZStd::string& assetHint = asset->GetHint();
IO::SizeType assetHintSize = assetHint.size();
Data::AssetLoadBehavior assetLoadBehavior = asset->GetAutoLoadBehavior();
AZ_SERIALIZE_SWAP_ENDIAN(assetId.m_subId, isDataBigEndian);
AZ_SERIALIZE_SWAP_ENDIAN(assetHintSize, isDataBigEndian);
AZ_SERIALIZE_SWAP_ENDIAN(assetLoadBehavior, isDataBigEndian);
stream.Seek(0, IO::GenericStream::ST_SEEK_BEGIN);
size_t bytesWritten = static_cast<size_t>(stream.Write(sizeof(Data::AssetId), reinterpret_cast<void*>(&assetId)));
bytesWritten += static_cast<size_t>(stream.Write(sizeof(Data::AssetType), reinterpret_cast<void*>(&assetType)));
bytesWritten += static_cast<size_t>(stream.Write(sizeof(assetHintSize), reinterpret_cast<void*>(&assetHintSize)));
bytesWritten += static_cast<size_t>(stream.Write(assetHint.size(), assetHint.c_str()));
bytesWritten += static_cast<size_t>(stream.Write(sizeof(Data::AssetLoadBehavior), reinterpret_cast<void*>(&assetLoadBehavior)));
return bytesWritten;
}
//-------------------------------------------------------------------------
bool AssetSerializer::PostSerializeAssetReference(AZ::Data::Asset<AZ::Data::AssetData>& asset, const Data::AssetFilterCB& assetFilterCallback)
{
if (!asset.GetId().IsValid())
{
// The asset reference is null, so there's no additional processing required.
return true;
}
if (assetFilterCallback && !assetFilterCallback(AZ::Data::AssetFilterInfo(asset)))
{
// This asset reference is filtered out for further processing/loading.
// we are allowed to bind it to assets that are already loaded.
Data::AssetId assetId = asset.GetId();
if (assetId.IsValid() && asset.GetType() != Data::s_invalidAssetType)
{
// Valid populated asset pointer. If the asset has already been constructed and/or loaded, acquire a pointer.
if (Data::AssetManager::IsReady())
{
Data::Asset<Data::AssetData> existingAsset = Data::AssetManager::Instance().FindAsset(assetId, asset.GetAutoLoadBehavior());
if (existingAsset)
{
asset = existingAsset;
}
}
}
return true;
}
RemapLegacyIds(asset);
if (asset.Get())
{
// Asset reference is already fully populated.
return true;
}
const Data::AssetLoadBehavior loadBehavior = asset.GetAutoLoadBehavior();
if (loadBehavior == Data::AssetLoadBehavior::NoLoad)
{
// Asset reference is flagged to never load unless explicitly by user code.
return true;
}
// Save this in case GetAsset() fails
Data::AssetId assetId = asset.GetId();
Data::AssetType assetType = asset.GetType();
const bool blockingLoad = loadBehavior == Data::AssetLoadBehavior::PreLoad;
// Get the asset and start loading
asset = Data::AssetManager::Instance().GetAsset(assetId, assetType, loadBehavior, Data::AssetLoadParameters{ assetFilterCallback });
if (!asset.GetId().IsValid()) // This will happen if there is no asset handler registered
{
AZ_Error("Serialization", false, "Dependent asset (%s) could not be loaded.", assetId.ToString<AZStd::string>().c_str());
return false;
}
// If the asset is flagged to pre-load, kick off a blocking load.
if (blockingLoad)
{
asset.BlockUntilLoadComplete();
if (asset.IsError())
{
AZ_Error("Serialization", false, "Dependent asset (%s:%s) could not be loaded.",
asset.GetId().ToString<AZStd::string>().c_str(),
asset.GetHint().c_str());
return false;
}
}
return true;
}
//-------------------------------------------------------------------------
void AssetSerializer::RemapLegacyIds(AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, asset.GetId());
if (assetInfo.m_assetId.IsValid())
{
asset.m_assetId = assetInfo.m_assetId;
asset.m_assetHint = assetInfo.m_relativePath;
}
}
//-------------------------------------------------------------------------
bool AssetSerializer::CompareValueData(const void* lhs, const void* rhs)
{
return SerializeContext::EqualityCompareHelper<Data::Asset<Data::AssetData>>::CompareValues(lhs, rhs);
}
//-------------------------------------------------------------------------
} // namespace AZ
@@ -0,0 +1,193 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/RTTI/BehaviorContext.h>
namespace AZ {
struct Uuid;
namespace Data
{
template<typename T>
class Asset;
class AssetData;
using AssetFilterCB = AZStd::function<bool(const AssetFilterInfo& filterInfo)>;
} // namespace Data
/*
* Returns the serialization UUID for Asset class
*/
const Uuid& GetAssetClassId();
/// Generic IDataSerializer specialization for Asset<T>
/// This is used internally by the object stream because assets need
/// special handling during serialization
class AssetSerializer
: public SerializeContext::IDataSerializer
{
public:
// Store the class data into a stream.
size_t Save(const void* classPtr, IO::GenericStream& stream, bool isDataBigEndian = false) override;
// Convert binary data to text
size_t DataToText(IO::GenericStream& in, IO::GenericStream& out, bool isDataBigEndian /*= false*/) override;
// Convert text data to binary, to support loading old version formats. We must respect text version if the text->binary format has changed!
size_t TextToData(const char* text, unsigned int textVersion, IO::GenericStream& stream, bool isDataBigEndian /*= false*/) override;
// Load the class data from a stream.
bool Load(void* classPtr, IO::GenericStream& stream, unsigned int version, bool isDataBigEndian /*= false*/) override;
// Extended load function that enables asset filtering behavior.
bool LoadWithFilter(void* classPtr, IO::GenericStream& stream, unsigned int version, const Data::AssetFilterCB& assetFilterCallback, bool isDataBigEndian = false);
// Optimized clone operation for asset references that bypasses asset lookup if source is already populated.
void Clone(const void* sourcePtr, void* destPtr);
bool CompareValueData(const void* lhs, const void* rhs) override;
// Even though Asset<T> is a template class, we don't actually care about its underlying asset type
// during serialization, so all types will share the same instance of the serializer.
static AssetSerializer s_serializer;
private:
/// Called after we are done writing to the instance pointed by classPtr.
bool PostSerializeAssetReference(AZ::Data::Asset<AZ::Data::AssetData>& asset, const Data::AssetFilterCB& assetFilterCallback);
// Upgrade legacy Ids.
void RemapLegacyIds(AZ::Data::Asset<AZ::Data::AssetData>& asset);
};
/*
* Generic serialization descriptor for all Assets of all types.
*/
template<typename T>
struct SerializeGenericTypeInfo< Data::Asset<T> >
{
typedef typename Data::Asset<T> ThisType;
class Factory
: public SerializeContext::IObjectFactory
{
public:
void* Create(const char* name) override
{
(void)name;
AZ_Assert(false, "Asset<T> %s should be stored by value!", name);
return nullptr;
}
void Destroy(void*) override
{
// do nothing
}
};
class GenericClassGenericAsset
: public GenericClassInfo
{
public:
GenericClassGenericAsset()
: m_classData{ SerializeContext::ClassData::Create<ThisType>("Asset", GetAssetClassId(), &m_factory, &AssetSerializer::s_serializer) }
{
m_classData.m_version = 2;
}
SerializeContext::ClassData* GetClassData() override
{
return &m_classData;
}
size_t GetNumTemplatedArguments() override
{
return 1;
}
const Uuid& GetTemplatedTypeId(size_t element) override
{
(void)element;
return SerializeGenericTypeInfo<T>::GetClassTypeId();
}
const Uuid& GetSpecializedTypeId() const override
{
return GetAssetClassId();
}
const Uuid& GetGenericTypeId() const override
{
return GetAssetClassId();
}
void Reflect(SerializeContext* serializeContext) override
{
if (serializeContext)
{
serializeContext->RegisterGenericClassInfo(GetSpecializedTypeId(), this, &AZ::AnyTypeInfoConcept<Data::Asset<Data::AssetData>>::CreateAny);
serializeContext->RegisterGenericClassInfo(azrtti_typeid<ThisType>(), this, &AZ::AnyTypeInfoConcept<ThisType>::CreateAny);
}
}
Factory m_factory;
SerializeContext::ClassData m_classData;
};
using ClassInfoType = GenericClassGenericAsset;
static ClassInfoType* GetGenericInfo()
{
return GetCurrentSerializeContextModule().CreateGenericClassInfo<ThisType>();
}
static const Uuid& GetClassTypeId()
{
return GetGenericInfo()->m_classData.m_typeId;
}
};
//! OnDemandReflection for any generic Data::Asset<T>
template<typename T>
struct OnDemandReflection<Data::Asset<T>>
{
using DataAssetType = Data::Asset<T>;
static void Reflect(ReflectContext* context)
{
if (auto behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->Class<DataAssetType>()
->Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Automation)
->Attribute(Script::Attributes::Module, "asset")
->Method("IsReady", &DataAssetType::IsReady)
->Attribute(AZ::Script::Attributes::Alias, "is_ready")
->Method("IsError", &DataAssetType::IsError)
->Attribute(AZ::Script::Attributes::Alias, "is_error")
->Method("IsLoading", &DataAssetType::IsLoading)
->Attribute(AZ::Script::Attributes::Alias, "is_loading")
->Method("GetStatus", &DataAssetType::GetStatus)
->Attribute(AZ::Script::Attributes::Alias, "get_status")
->Method("GetId", &DataAssetType::GetId)
->Attribute(AZ::Script::Attributes::Alias, "get_id")
->Method("GetType", &DataAssetType::GetType)
->Attribute(AZ::Script::Attributes::Alias, "get_type")
->Method("GetHint", &DataAssetType::GetHint)
->Attribute(AZ::Script::Attributes::Alias, "get_hint")
->Method("GetData", &DataAssetType::GetData)
->Attribute(AZ::Script::Attributes::Alias, "get_data")
;
}
}
};
} // namespace AZ
@@ -0,0 +1,65 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
/**
* Bus for acquiring information about a given asset type, usually serviced by the relevant asset handler.
* Extensions, load parameters, custom stream settings, etc.
*/
class AssetTypeInfo
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::Data::AssetType BusIdType;
typedef AZStd::recursive_mutex MutexType;
//////////////////////////////////////////////////////////////////////////
//! this is the same type Id (uuid) as your AssetData-derived class's RTTI type.
virtual AZ::Data::AssetType GetAssetType() const = 0;
//! Retrieve the friendly name for the asset type.
virtual const char* GetAssetTypeDisplayName() const { return "Unknown"; }
//! This is the group or category that this kind of asset appears under for filtering and displaying in the browser.
virtual const char* GetGroup() const { return "Other"; }
//! You can implement this to apply a specific icon to all assets of your type instead of using built in heuristics
virtual const char* GetBrowserIcon() const { return ""; }
//! you can return the kind of component best suited to spawn on an entity if this kind of asset is dragged
//! to the viewport or to the component entity area.
virtual AZ::Uuid GetComponentTypeId() const { return AZ::Uuid::CreateNull(); }
//! Retrieve file extensions for the asset type.
virtual void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) { (void)extensions; }
//! Determines if a component can be created from the asset type
//! This will be called before attempting to create a component from an asset (drag&drop, etc)
//! You can use this to filter by subIds or do your own validation here if needed
virtual bool CanCreateComponent(const AZ::Data::AssetId& /*assetId*/) const { return true; }
};
using AssetTypeInfoBus = AZ::EBus<AssetTypeInfo>;
} // namespace AZ
@@ -0,0 +1,76 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/AzCoreModule.h>
// Component includes
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Debug/FrameProfilerComponent.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzCore/Jobs/JobManagerComponent.h>
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/NativeUI/NativeUISystemComponent.h>
#include <AzCore/Script/ScriptSystemComponent.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/Slice/SliceSystemComponent.h>
#include <AzCore/Slice/SliceMetadataInfoComponent.h>
#include <AzCore/Statistics/StatisticalProfilerProxySystemComponent.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Time/TimeSystemComponent.h>
#include <AzCore/Console/LoggerSystemComponent.h>
#include <AzCore/EBus/EventSchedulerSystemComponent.h>
namespace AZ
{
AzCoreModule::AzCoreModule()
: AZ::Module()
{
m_descriptors.insert(m_descriptors.end(), {
MemoryComponent::CreateDescriptor(),
StreamerComponent::CreateDescriptor(),
JobManagerComponent::CreateDescriptor(),
JsonSystemComponent::CreateDescriptor(),
AssetManagerComponent::CreateDescriptor(),
UserSettingsComponent::CreateDescriptor(),
Debug::FrameProfilerComponent::CreateDescriptor(),
NativeUI::NativeUISystemComponent::CreateDescriptor(),
SliceComponent::CreateDescriptor(),
SliceSystemComponent::CreateDescriptor(),
SliceMetadataInfoComponent::CreateDescriptor(),
TimeSystemComponent::CreateDescriptor(),
LoggerSystemComponent::CreateDescriptor(),
EventSchedulerSystemComponent::CreateDescriptor(),
#if !defined(_RELEASE)
Statistics::StatisticalProfilerProxySystemComponent::CreateDescriptor(),
#endif // #if !defined(_RELEASE)
#if !defined(AZCORE_EXCLUDE_LUA)
ScriptSystemComponent::CreateDescriptor(),
#endif // #if !defined(AZCORE_EXCLUDE_LUA)
});
}
AZ::ComponentTypeList AzCoreModule::GetRequiredSystemComponents() const
{
return AZ::ComponentTypeList
{
azrtti_typeid<TimeSystemComponent>(),
azrtti_typeid<LoggerSystemComponent>(),
azrtti_typeid<EventSchedulerSystemComponent>(),
#if !defined(_RELEASE)
azrtti_typeid<AZ::Statistics::StatisticalProfilerProxySystemComponent>(),
#endif // #if !defined(_RELEASE)
};
}
}
@@ -0,0 +1,30 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Module/Module.h>
namespace AZ
{
class AzCoreModule
: public AZ::Module
{
public:
AZ_RTTI(AzCoreModule, "{898CE9C5-B4CC-4331-811E-3B44B967A1C1}", AZ::Module);
AZ_CLASS_ALLOCATOR(AzCoreModule, AZ::OSAllocator, 0);
AzCoreModule();
~AzCoreModule() override = default;
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
};
}
+15
View File
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#define AZCORE_BUILD_NUMBER 368
#define AZCORE_BUILD_DATE "Thu 10/10/2013"
#define AZCORE_BUILD_TIME "19:42:16.96"
#define AZCORE_SOURCE_CHANGELIST 2992189
@@ -0,0 +1,60 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "AzCore/std/typetraits/conditional.h"
#include "AzCore/std/typetraits/is_arithmetic.h"
#include "AzCore/std/typetraits/is_enum.h"
/*
// Lossy casts are just a wrapper around static_cast, but indicate the *intent* that numeric data loss
// has been accounted for. This is only meant for lossy numeric casting, so expect compile errors if
// used with other types.
*/
template <typename ToType, typename FromType>
inline constexpr AZStd::enable_if_t<
(AZStd::is_arithmetic<FromType>::value || AZStd::is_enum<FromType>::value)
&& (AZStd::is_arithmetic<ToType>::value || AZStd::is_enum<ToType>::value)
, ToType > azlossy_cast(FromType value)
{
return static_cast<ToType>(value);
}
// This is a helper class that lets us induce the destination type of a lossy numeric cast.
// It should never be directly used by anything other than azlossy_caster.
namespace AZ
{
template <typename FromType>
class LossyCasted
{
public:
explicit constexpr LossyCasted(FromType value)
: m_value(value) { }
template <typename ToType>
constexpr operator ToType() const { return azlossy_cast<ToType>(m_value); }
private:
LossyCasted() = delete;
void operator=(LossyCasted const&) = delete;
FromType m_value;
};
}
// This is the primary function we should use when lossy casting, since it induces the type we need
// to cast to from the code rather than requiring an explicit coupling in the source.
template <typename FromType>
inline constexpr AZ::LossyCasted<FromType> azlossy_caster(FromType value)
{
return AZ::LossyCasted<FromType>(value);
}
@@ -0,0 +1,312 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/typetraits/is_arithmetic.h>
#include <AzCore/std/typetraits/is_class.h>
#include <AzCore/std/typetraits/is_enum.h>
#include <AzCore/std/typetraits/is_floating_point.h>
#include <AzCore/std/typetraits/is_integral.h>
#include <AzCore/std/typetraits/is_same.h>
#include <AzCore/std/typetraits/is_signed.h>
#include <AzCore/std/typetraits/is_unsigned.h>
#include <AzCore/std/typetraits/remove_cvref.h>
#include <AzCore/std/typetraits/underlying_type.h>
#include <AzCore/std/utils.h>
#include <limits>
/*
// Numeric casts add range checking when casting from one numeric type to another. It adds run-time validation (if enabled for the
// particular build configuration) to ensure that no actual data loss happens. Assigning long long(17) to an unsigned char is allowed,
// but assigning char(-1) to unsigned long long variable is not, and will result in an assert or error if the validation has been
// enabled.
//
// Because we can't do partial function specialization, I'm using enable_if to chop up the implementation into one of these
// implementations. If none of these fit, then we will get a compile error because it is an unknown conversionr.
//
//--------------------------------------------
// TYPE <- TYPE DigitLoss
// (A) Integer Unsigned N
// (A) Signed Signed N
// (B) Unsigned Signed N
// (C) Integer Unsigned Y
// (D) Integer Signed Y
//
// (E) Integer Enum -
// (F) Integer Floating -
//
// (G) Enum Integer -
//
// (H) Floating Integer -
//
// (I) Enum Enum -
//
// (J) Floating Floating N
// (K) Floating Floating Y
*/
// This is disabled by default because it puts in costly runtime checking of casted values.
// You can either change it here to enable it across the engine, or use push/pop_macro to enable per file/feature.
// Note that if using push/pop_macro, you may get some of the functions not inline and the definition coming from
// another compilation unit, in such case, you will have to push/pop_macro on that compilation unit as well.
// #define AZ_NUMERICCAST_ENABLED 1
#if AZ_NUMERICCAST_ENABLED
#define AZ_NUMERIC_ASSERT(expr, ...) AZ_Assert(expr, __VA_ARGS__)
#else
#define AZ_NUMERIC_ASSERT(expr, ...) void(0)
#endif
#pragma push_macro("max")
#undef max
namespace NumericCastInternal
{
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
!AZStd::is_integral<FromType>::value || !AZStd::is_floating_point<ToType>::value
, bool> ::type UnderflowsToType(const FromType& value)
{
return (value < static_cast<FromType>(std::numeric_limits<ToType>::lowest()));
}
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_integral<FromType>::value && AZStd::is_floating_point<ToType>::value
, bool> ::type UnderflowsToType(const FromType& value)
{
return (static_cast<ToType>(value) < std::numeric_limits<ToType>::lowest());
}
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
!AZStd::is_integral<FromType>::value || !AZStd::is_floating_point<ToType>::value
, bool> ::type OverflowsToType(const FromType& value)
{
return (value > static_cast<FromType>(std::numeric_limits<ToType>::max()));
}
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_integral<FromType>::value && AZStd::is_floating_point<ToType>::value
, bool> ::type OverflowsToType(const FromType& value)
{
return (static_cast<ToType>(value) > std::numeric_limits<ToType>::max());
}
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_integral<FromType>::value && AZStd::is_integral<ToType>::value
&& std::numeric_limits<FromType>::digits <= std::numeric_limits<ToType>::digits
&& AZStd::is_signed<FromType>::value && AZStd::is_unsigned<ToType>::value
, bool> ::type FitsInToType(const FromType& value)
{
return !NumericCastInternal::UnderflowsToType<ToType>(value);
}
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_integral<FromType>::value && AZStd::is_integral<ToType>::value
&& (std::numeric_limits<FromType>::digits > std::numeric_limits<ToType>::digits)
&& AZStd::is_unsigned<FromType>::value
, bool> ::type FitsInToType(const FromType& value)
{
return !NumericCastInternal::OverflowsToType<ToType>(value);
}
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
(!AZStd::is_integral<FromType>::value || !AZStd::is_integral<ToType>::value)
|| ((std::numeric_limits<FromType>::digits <= std::numeric_limits<ToType>::digits) && (AZStd::is_unsigned<FromType>::value || AZStd::is_signed<ToType>::value))
|| ((std::numeric_limits<FromType>::digits > std::numeric_limits<ToType>::digits) && AZStd::is_signed<FromType>::value)
, bool> ::type FitsInToType(const FromType& value)
{
return !NumericCastInternal::OverflowsToType<ToType>(value) && !NumericCastInternal::UnderflowsToType<ToType>(value);
}
} // namespace AZ
// INTEGER -> INTEGER
// (A) Not losing digits or risking sign loss
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_integral<FromType>::value&& AZStd::is_integral<ToType>::value
&& std::numeric_limits<FromType>::digits <= std::numeric_limits<ToType>::digits
&& (!std::numeric_limits<FromType>::is_signed || std::numeric_limits<ToType>::is_signed)
, ToType > ::type aznumeric_cast(FromType value)
{
return static_cast<ToType>(value);
}
// (B) Not losing digits, but we are losing sign, so make sure we aren't dealing with a negative number
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_integral<FromType>::value&& AZStd::is_integral<ToType>::value
&& std::numeric_limits<FromType>::digits <= std::numeric_limits<ToType>::digits
&& std::numeric_limits<FromType>::is_signed&& !std::numeric_limits<ToType>::is_signed
, ToType > ::type aznumeric_cast(FromType value)
{
AZ_NUMERIC_ASSERT(
NumericCastInternal::FitsInToType<ToType>(value),
"Attempted cast causes loss of signed value.");
return static_cast<ToType>(value);
}
// (C) Maybe losing digits from an unsigned type, so make sure we don't exceed the destination max value. No check against zero is necessary.
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_integral<FromType>::value&& AZStd::is_integral<ToType>::value
&& !(std::numeric_limits<FromType>::digits <= std::numeric_limits<ToType>::digits)
&& !std::numeric_limits<FromType>::is_signed
, ToType > ::type aznumeric_cast(FromType value)
{
AZ_NUMERIC_ASSERT(
NumericCastInternal::FitsInToType<ToType>(value),
"Attempted downcast of unsigned integer causes loss of high bits and type narrowing.");
return static_cast<ToType>(value);
}
// (D) Maybe losing digits within signed types, we need to check both the min and max values.
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_integral<FromType>::value&& AZStd::is_integral<ToType>::value
&& !(std::numeric_limits<FromType>::digits <= std::numeric_limits<ToType>::digits)
&& std::numeric_limits<FromType>::is_signed
, ToType > ::type aznumeric_cast(FromType value)
{
AZ_NUMERIC_ASSERT(
NumericCastInternal::FitsInToType<ToType>(value),
"Attempted downcast of signed integer causes loss of high bits and type narrowing.");
return static_cast<ToType>(value);
}
// ENUMS -> INTEGER
// (E) handled by changing the enum to its underlying type
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_enum<FromType>::value&& AZStd::is_integral<ToType>::value
, ToType > ::type aznumeric_cast(FromType value)
{
using UnderlyingFromType = typename AZStd::underlying_type<FromType>::type;
return aznumeric_cast<ToType>(static_cast<UnderlyingFromType>(value));
}
// FLOATING -> INTEGER
// (E) We'll accept precision loss as long as it stays in range
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_floating_point<FromType>::value&& AZStd::is_integral<ToType>::value
, ToType > ::type aznumeric_cast(FromType value)
{
AZ_NUMERIC_ASSERT(
NumericCastInternal::FitsInToType<ToType>(value),
"Attempted cast of floating point value does not fit in the supplied type.");
return static_cast<ToType>(value);
}
// INTEGER -> ENUM
// (G) We must cast to an enum so go through the backing type
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_integral<FromType>::value&& AZStd::is_enum<ToType>::value
, ToType > ::type aznumeric_cast(FromType value)
{
using UnderlyingToType = typename AZStd::underlying_type<ToType>::type;
return static_cast<ToType>(aznumeric_cast<UnderlyingToType>(value));
}
// INTEGER -> FLOATING POINT
// (H) Perhaps some faster code substitutions could be done here instead of the standard int->float calls
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_integral<FromType>::value&& AZStd::is_floating_point<ToType>::value
, ToType > ::type aznumeric_cast(FromType value)
{
return static_cast<ToType>(value);
}
// ENUM -> ENUM
// (I) crossing enums using the underlying type as the transport
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_enum<FromType>::value&& AZStd::is_enum<ToType>::value
, ToType > ::type aznumeric_cast(FromType value)
{
using UnderlyingFromType = typename AZStd::underlying_type<FromType>::type;
using UnderlyingToType = typename AZStd::underlying_type<ToType>::type;
return static_cast<ToType>(aznumeric_cast<UnderlyingToType>(static_cast<UnderlyingFromType>(value)));
}
// FLOATING POINT -> FLOATING POINT
// (J) crossing floats with no digit loss
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_floating_point<FromType>::value&& AZStd::is_floating_point<ToType>::value
&& std::numeric_limits<FromType>::digits <= std::numeric_limits<ToType>::digits
, ToType > ::type aznumeric_cast(FromType value)
{
return static_cast<ToType>(value);
}
// (K) crossing floats with digit loss
template <typename ToType, typename FromType>
inline constexpr typename AZStd::enable_if<
AZStd::is_floating_point<FromType>::value&& AZStd::is_floating_point<ToType>::value
&& !(std::numeric_limits<FromType>::digits <= std::numeric_limits<ToType>::digits)
, ToType > ::type aznumeric_cast(FromType value)
{
AZ_NUMERIC_ASSERT(
NumericCastInternal::FitsInToType<ToType>(value),
"Attempted cast of floating point value does not fit in the supplied type.");
return static_cast<ToType>(value);
}
// (L) Support for types that implement a specific conversion operator FromType()
// This is used to forward the numeric_cast from a class type to arithmetic type
template <typename ToType, typename FromType>
inline constexpr auto aznumeric_cast(FromType&& value) ->
AZStd::enable_if_t<AZStd::is_class_v<AZStd::remove_cvref_t<FromType>> && AZStd::is_arithmetic_v<ToType> && AZStd::is_convertible_v<AZStd::remove_cvref_t<FromType>, ToType>, ToType>
{
return static_cast<ToType>(value);
}
// This is a helper class that lets us induce the destination type of a numeric cast
// It should never be directly used by anything other than azlossy_caster.
namespace AZ
{
template <typename FromType>
class NumericCasted
{
public:
explicit constexpr NumericCasted(FromType value)
: m_value(value) { }
template <typename ToType>
constexpr operator ToType() const { return aznumeric_cast<ToType>(m_value); }
private:
NumericCasted() = delete;
void operator=(NumericCasted const&) = delete;
FromType m_value;
};
}
// This is the primary function we should use when doing numeric casting, since it induces the
// type we need to cast to from the code rather than requiring an explicit coupling in the source.
template <typename FromType>
inline constexpr AZ::NumericCasted<FromType> aznumeric_caster(FromType value)
{
return AZ::NumericCasted<FromType>(value);
}
#pragma pop_macro("max")
@@ -0,0 +1,179 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/Component.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Math/Sfmt.h>
#include <AzCore/Math/Crc.h>
namespace AZ
{
//=========================================================================
// Component
// [6/15/2012]
//=========================================================================
Component::Component()
: m_entity(nullptr)
, m_id(InvalidComponentId)
{
}
//=========================================================================
// ~Component
// [6/15/2012]
//=========================================================================
Component::~Component()
{
if (m_entity)
{
m_entity->RemoveComponent(this);
}
}
//=========================================================================
// GetEntityId
// [6/15/2012]
//=========================================================================
EntityId Component::GetEntityId() const
{
if (m_entity)
{
return m_entity->GetId();
}
AZ_Warning("System", false, "Can't get component %p entity ID as it is not attached to an entity yet!", this);
return EntityId();
}
NamedEntityId Component::GetNamedEntityId() const
{
if (m_entity)
{
return NamedEntityId(m_entity->GetId(), m_entity->GetName());
}
AZ_Warning("System", false, "Can't get component %p entity ID as it is not attached to an entity yet!", this);
return NamedEntityId();
}
//=========================================================================
// SetConfiguration
//=========================================================================
bool Component::SetConfiguration(const ComponentConfig& config)
{
// Components cannot be configured while activated.
if (!m_entity || (m_entity->GetState() <= Entity::State::Init))
{
if (ReadInConfig(&config))
{
return true;
}
AZ_Warning("System", false, "Configuration type '%s' %s incompatible with component type '%s' %s.",
config.RTTI_GetTypeName(), config.RTTI_GetType().ToString<AZStd::string>().c_str(),
RTTI_GetTypeName(), RTTI_GetType().ToString<AZStd::string>().c_str());
}
else
{
AZ_Warning("System", false, "Component cannot be configured while activated!");
}
return false;
}
//=========================================================================
// GetConfiguration
//=========================================================================
bool Component::GetConfiguration(ComponentConfig& outConfig) const
{
if (WriteOutConfig(&outConfig))
{
return true;
}
AZ_Warning("System", false, "Configuration type '%s' %s incompatible with component type '%s' %s.",
outConfig.RTTI_GetTypeName(), outConfig.RTTI_GetType().ToString<AZStd::string>().c_str(),
RTTI_GetTypeName(), RTTI_GetType().ToString<AZStd::string>().c_str());
return false;
}
//=========================================================================
// ReadInConfig
//=========================================================================
bool Component::ReadInConfig(const ComponentConfig*)
{
AZ_Warning("System", false, "ReadInConfig() is not implemented for component type '%s' %s",
RTTI_GetTypeName(), RTTI_GetType().ToString<AZStd::string>().c_str());
return false;
}
//=========================================================================
// WriteOutConfig
//=========================================================================
bool Component::WriteOutConfig(ComponentConfig*) const
{
AZ_Warning("System", false, "WriteOutConfig() is not implemented for component type '%s' %s",
RTTI_GetTypeName(), RTTI_GetType().ToString<AZStd::string>().c_str());
return false;
}
//=========================================================================
// Reflect
//=========================================================================
void Component::SetEntity(Entity* entity)
{
// This can called only from the entity, we assume the input is valid
if (m_entity != entity)
{
m_entity = entity;
if (entity)
{
// We don't have many components on an entity and we guarantee uniques only for this component
// Random should be find
if (m_id == InvalidComponentId)
{
// only if this component was removed the entity of it's a new component
m_id = Sfmt::GetInstance().Rand64();
}
}
else
{
m_id = InvalidComponentId;
}
}
}
//=========================================================================
// ReflectInternal
//=========================================================================
void Component::ReflectInternal(ReflectContext* reflection)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<Component>()->
PersistentId([](const void* instance) -> u64 { return reinterpret_cast<const Component*>(instance)->GetId(); })->
Field("Id", &Component::m_id);
}
}
//=========================================================================
// ~ReleaseDescriptor
//=========================================================================
void ComponentDescriptor::ReleaseDescriptor()
{
EBUS_EVENT(ComponentApplicationBus, UnregisterComponentDescriptor, this);
delete this;
}
} // namespace AZ
@@ -0,0 +1,622 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
/** @file
* Header file for the Component base class.
* In Lumberyard's component entity system, each component defines a discrete
* feature that can be attached to an entity.
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Component/NamedEntityId.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h> // Used as the allocator for most components.
#include <AzCore/Outcome/Outcome.h>
namespace AZ
{
class Entity;
class ComponentDescriptor;
typedef AZ::u32 ComponentServiceType; ///< ID of a user-defined component service. The system uses it to build a dependency tree.
using ImmutableEntityVector = AZStd::vector<AZ::Entity const *>;
using ComponentTypeList = AZStd::vector<Uuid>; ///< List of Component class type IDs.
using ComponentValidationResult = AZ::Outcome<void, AZStd::string>;
/**
* Base class for all components.
*/
class Component
{
friend class Entity;
public:
/**
* Adds run-time type information to the component.
*/
AZ_RTTI(AZ::Component, "{EDFCB2CF-F75D-43BE-B26B-F35821B29247}");
/**
* Initializes a component's internals.
* A component's constructor should initialize the component's variables only.
* Because the component is not active yet, it should not connect to message buses,
* send messages, and so on. Similarly, the component's constructor should not
* attempt to cache pointers or data from other components on the same entity
* because those components can be added or removed at any moment. To process
* and initialize all resources that make a component ready to operate, use Init().
*/
Component();
/**
* Destroys a component.
* The system always calls a component's Deactivate() function before destroying it.
*/
virtual ~Component();
/**
* Returns a pointer to the entity.
* If the component is not attached to any entity, this function returns a null pointer.
* In that case, the component is in the default state (not activated). However,
* except in the case of tools, you typically should not use this function. It is a best
* practice to access other components through EBuses instead of accessing them directly.
* For more information, see the
* <a href="http://docs.aws.amazon.com/lumberyard/latest/developerguide/component-entity-system-pg-intro.html">Programmer's Guide to Entities and Components</a>
* in the Lumberyard Developer Guide.
* @return A pointer to the entity. If the component is not attached to any entity,
* the return value is a null pointer.
*/
Entity* GetEntity() const { return m_entity; }
/**
* Returns the entity ID if the component is attached to an entity.
* If the component is not attached to any entity, this function asserts.
* As a safeguard, make sure that GetEntity()!=nullptr.
* @return The ID of the entity that contains the component.
*/
EntityId GetEntityId() const;
/**
* Returns the NamedEntityId if the component is attached to an entity.
* If the component is not attached to any entity, this function asserts.
* As a safeguard, make sure that GetEntity()!=nullptr.
* @return The ID of the entity that contains the component.
*/
NamedEntityId GetNamedEntityId() const;
/**
* Returns the component ID, which is valid only when the component is attached to an entity.
* If the component is not attached to any entity, the return value is 0.
* As a safeguard, make sure that GetEntity()!=nullptr.
* @return The ID of the component. If the component is attached to any entity,
* the return value is 0.
*/
ComponentId GetId() const { return m_id; }
/**
* Returns the type ID
* Can be overridden for components that wrap other components, to provide a punch through
* to the wrapped component's ID.
* @return The type ID of the component.
*/
virtual const TypeId& GetUnderlyingComponentType() const { return RTTI_GetType(); }
/**
* Sets the component ID.
* This function is for internal use only.
* @param id The ID to assign to the component.
*/
void SetId(const ComponentId& id) { m_id = id; }
/**
* Override to conduct per-component or per-slice validation logic during slice asset processing.
* @param sliceEntities All entities that belong to the slice that the entity with this component is on.
* @param platformTags List of platforms supplied during slice asset processing.
*/
virtual ComponentValidationResult ValidateComponentRequirements(const ImmutableEntityVector& /*sliceEntities*/,
const AZStd::unordered_set<AZ::Crc32>& /*platformTags*/) const { return AZ::Success(); }
/**
* Set the component's configuration.
* A component cannot be configured while it is activated.
* A component must implement the ReadInConfig() function for this to have an effect.
* @param config The component will set its properties based on this configuration.
* The configuration class must be of the appropriate type for this component.
* For example, use a TransformConfig with a TransformComponent.
*/
bool SetConfiguration(const AZ::ComponentConfig& config);
/**
* Get a component's configuration.
* A component must implement the WriteOutConfig() function for this to have an effect.
* @param outConfig[out] The component will copy its properties into this configuration class.
* The configuration class must be of the appropriate type for this component.
* For example, use a TransformConfig with a TransformComponent.
*/
bool GetConfiguration(AZ::ComponentConfig& outConfig) const;
protected:
/**
* Initializes a component's resources.
* (Optional) Override this function to initialize resources that the component needs.
* The system calls this function once for each entity that owns the component. Although the
* Init() function initializes the component, the component is not active until the system
* calls the component's Activate() function. We recommend that you minimize the component's
* CPU and memory overhead when the component is inactive.
*/
virtual void Init() {}
/**
* Puts the component into an active state.
* The system calls this function once during activation of each entity that owns the
* component. You must override this function. The system calls a component's Activate()
* function only if all services and components that the component depends on are present
* and active. Use GetProvidedServices and GetDependentServices to specify these dependencies.
*/
virtual void Activate() = 0;
/**
* Deactivates the component.
* The system calls this function when the owning entity is being deactivated. You must
* override this function. As a best practice, ensure that this function returns the component
* to a minimal footprint. The order of deactivation is the reverse of activation, so your
* component is deactivated before the components it depends on.
*
* The system always calls the component's Deactivate() function before destroying the component.
* However, deactivation is not always followed by the destruction of the component. An entity and
* its components can be deactivated and reactivated without being destroyed. Ensure that your
* Deactivate() implementation can handle this scenario.
*/
virtual void Deactivate() = 0;
/**
* Read properties from the configuration class into the component.
* Overriding this function allows your component to be configured at runtime.
* See AZ::ComponentConfig for more details.
* This function cannot be invoked while the component is activated.
*
* @code{.cpp}
* // sample implementation
* bool ReadInConfig(const ComponentConfig* baseConfig) override
* {
* if (auto config = azrtti_cast<const MyConfig*>(baseConfig))
* {
* m_propertyA = config->m_propertyA;
* m_propertyB = config->m_propertyB
* return true;
* }
* return false;
* }
* @endcode
*/
virtual bool ReadInConfig(const ComponentConfig* baseConfig);
/**
* Write properties from the component into the configuration class.
* Overriding this function allows your component's configuration to be queried at runtime.
* See AZ::ComponentConfig for more details.
*
* @code{.cpp}
* // sample implementation
* bool WriteOutConfig(ComponentConfig* outBaseConfig) const override
* {
* if (auto config = azrtti_cast<MyConfig*>(outBaseConfig))
* {
* config->m_propertyA = m_propertyA;
* config->m_propertyB = m_propertyB;
* return true;
* }
* return false;
* }
* @endcode
*/
virtual bool WriteOutConfig(ComponentConfig* outBaseConfig) const;
/**
* Sets the current entity.
* This function is called by the entity.
* @param entity The current entity.
*/
void SetEntity(Entity* entity);
/**
* Reflects the Component class.
* This function is called by the entity.
* @param reflection The reflection context.
*/
static void ReflectInternal(ReflectContext* reflection);
Entity* m_entity; ///< Reference to the entity that owns the component. The value is null if the component is not attached to an entity.
ComponentId m_id; ///< A component ID that is unique for an entity. This component ID is not unique across all entities.
};
/**
* Includes the core component code required to make a component work.
* This macro is typically included in other macros, such as AZ_COMPONENT, to
* create a component.
*/
#define AZ_COMPONENT_BASE(_ComponentClass, ...) \
AZ_CLASS_ALLOCATOR(_ComponentClass, AZ::SystemAllocator, 0) \
friend class AZ::HasComponentReflect<_ComponentClass>; \
friend class AZ::HasComponentProvidedServices<_ComponentClass>; \
friend class AZ::HasComponentDependentServices<_ComponentClass>; \
friend class AZ::HasComponentRequiredServices<_ComponentClass>; \
friend class AZ::HasComponentIncompatibleServices<_ComponentClass>; \
static AZ::ComponentDescriptor* CreateDescriptor() \
{ \
AZ::ComponentDescriptor* descriptor = nullptr; \
AZ::ComponentDescriptorBus::EventResult(descriptor, _ComponentClass::RTTI_Type(), &AZ::ComponentDescriptor::GetDescriptor); \
if (descriptor) \
{ \
/* Compare strings first, then pointers. If we compare pointers first, different strings will give the wrong error message */ \
if (strcmp(descriptor->GetName(), _ComponentClass::RTTI_TypeName()) != 0) \
{ \
AZ_Error("Component", false, "Two different components have the same UUID (%s), which is not allowed.\n" \
"Change the UUID on one of them.\nComponent A: %s\nComponent B: %s", \
_ComponentClass::RTTI_Type().ToString<AZStd::string>().c_str(), descriptor->GetName(), _ComponentClass::RTTI_TypeName()); \
return nullptr; \
} \
else if (descriptor->GetName() != _ComponentClass::RTTI_TypeName()) \
{ \
AZ_Error("Component", false, "The same component UUID (%s) / name (%s) was registered twice. This isn't allowed, " \
"it can cause lifetime management issues / crashes.\nThis situation can happen by declaring a component " \
"in a header and registering it from two different Gems.\n", \
_ComponentClass::RTTI_Type().ToString<AZStd::string>().c_str(), descriptor->GetName()); \
return nullptr; \
} \
return descriptor; \
} \
return aznew DescriptorType; \
}
/**
* Declares a descriptor class.
* Unless you are implementing very advanced internal functionality, we recommend using
* AZ_COMPONENT instead of this macro. This macro enables you to implement a static function
* in the Component class instead of writing a descriptor. It defines a CreateDescriptorFunction
* that you can call to register a descriptor. (Only one descriptor can exist per environment.)
* This macro fails silently if you implement the functions with the wrong signatures.
*/
#define AZ_COMPONENT_INTRUSIVE_DESCRIPTOR_TYPE(_ComponentClass) \
friend class AZ::ComponentDescriptorDefault<_ComponentClass>; \
typedef AZ::ComponentDescriptorDefault<_ComponentClass> DescriptorType;
/**
* Declares a component with the default settings.
* The component derives from AZ::Component, is not templated, uses AZ::SystemAllocator,
* and so on. AZ_COMPONENT(_ComponentClass, _ComponentId, OtherBaseClases... Component) is
* included automatically.
*
* The component that this macro creates has a static function called CreateDescriptor
* and a type called DescriptorType. Although you can delete the descriptor, keep in mind
* that you cannot use component instances without a descriptor. This is because descriptors
* are released when the component application closes or a module is unloaded. Descriptors
* must have access to AZ::ComponentDescriptor::Reflect, AZ::ComponentDescriptor::GetProvidedServices,
* and other descriptor services.
*
* You are not required to use the AZ_COMPONENT macro if you want to implement your own creation
* functions by calling AZ_CLASS_ALLOCATOR, AZ_RTTI, and so on.
*/
#define AZ_COMPONENT(_ComponentClass, ...) \
AZ_RTTI(_ComponentClass, __VA_ARGS__, AZ::Component) \
AZ_COMPONENT_INTRUSIVE_DESCRIPTOR_TYPE(_ComponentClass) \
AZ_COMPONENT_BASE(_ComponentClass, __VA_ARGS__)
/**
* Provides an interface through which the system can get the details of a component
* and reflect the component data to a variety of contexts.
* If you implement a component descriptor, inherit from ComponentDescriptorHelper
* to implement additional functionality.
*/
class ComponentDescriptor
{
public:
/**
* The type of array that components use to specify provided, required, dependent,
* and incompatible services.
*/
typedef AZStd::vector<ComponentServiceType> DependencyArrayType;
/**
* This type of array is used by the warning
*/
typedef AZStd::vector<AZStd::string> StringWarningArray;
/**
* Creates an instance of the component.
* @return Returns a pointer to the component.
*/
virtual Component* CreateComponent() = 0;
/**
* Gets the name of the component.
* @return Returns a pointer to the name of the component.
*/
virtual const char* GetName() const = 0;
/**
* Gets the ID of the component.
* @return Returns a pointer to the component ID.
*/
virtual const Uuid& GetUuid() const = 0;
/**
* Reflects component data into a variety of contexts (script, serialize, edit, and so on).
* @param reflection A pointer to the reflection context.
*/
virtual void Reflect(ReflectContext* reflection) const = 0;
/**
* Specifies the services that the component provides.
* The system uses this information to determine when to create the component.
* @param provided Array of provided services.
* @param instance Optional parameter with which you can refine services for each instance. This value is null if no instance exists.
*/
virtual void GetProvidedServices(DependencyArrayType& provided, const Component* instance) const { (void)provided; (void)instance; }
/**
* Specifies the services that the component depends on, but does not require.
* The system activates the dependent services before it activates this component.
* It also deactivates the dependent services after it deactivates this component.
* If a dependent service is missing before this component is activated, the system
* does not return an error and still activates this component.
* @param provided Array of dependent services.
* @param instance Optional parameter with which you can refine services for each instance. This value is null if no instance exists.
*/
virtual void GetDependentServices(DependencyArrayType& dependent, const Component* instance) const { (void)dependent; (void)instance; }
/**
* Specifies the services that the component requires.
* The system activates the required services before it activates this component.
* It also deactivates the required services after it deactivates this component.
* If a required service is missing before this component is activated, the system
* returns an error and does not activate this component.
* @param provided Array of required services.
* @param instance Optional parameter with which you can refine services for each instance. This value is null if no instance exists.
*/
virtual void GetRequiredServices(DependencyArrayType& required, const Component* instance) const { (void)required; (void)instance; }
/**
* Specifies the services that the component cannot operate with.
* For example, if two components provide a similar service and the system cannot use the services simultaneously,
* each of those components would specify the other component as an incompatible service.
* @param provided Array to fill with incompatible services.
* @param instance Optional parameter with which you can refine services for each instance. This value is null if no instance exists.
*/
virtual void GetIncompatibleServices(DependencyArrayType& incompatible, const Component* instance) const { (void)incompatible; (void)instance; }
/**
* Specifies warnings that you want in the component (will put a warning and a continue button).
* @param warnings provided array of strings that would be the actual warnings.
* @param instance Optional parameter with which you can refine services for each instance. This value is null if no instance exists.
*/
virtual void GetWarnings([[maybe_unused]] StringWarningArray& warnings, [[maybe_unused]] const Component* instance) const { }
/**
* Gets the current descriptor.
* @param instance The current descriptor.
*/
virtual ComponentDescriptor* GetDescriptor() { return this; }
/**
* Calls ComponentApplicationBus::UnregisterComponentDescriptor and deletes the descriptor.
*/
virtual void ReleaseDescriptor();
/**
* Destroys the descriptor, but you should call ReleaseDescriptor() instead of using this function.
*/
virtual ~ComponentDescriptor() = default;
};
/**
* Describes the properties of the component descriptor event bus.
* This bus uses AzTypeInfo::Uuid as the ID for the specific descriptor. Lumberyard allows only one
* descriptor for each component type. When you call functions on the bus for a specific component
* type, you can safely pass only one result variable because aggregating or overwriting results
* is impossible.
*/
struct ComponentDescriptorBusTraits
: public EBusTraits
{
// We have one bus for each entity bus ID.
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
// We can have only one descriptor per component type.
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
typedef Uuid BusIdType;
using MutexType = AZStd::recursive_mutex;
};
typedef AZ::EBus<ComponentDescriptor, ComponentDescriptorBusTraits> ComponentDescriptorBus;
/**
* Helps you create a custom implementation of a descriptor.
* For most cases we recommend using AZ_COMPONENT and ComponentDescriptorDefault instead.
*/
template<class ComponentClass>
class ComponentDescriptorHelper
: public ComponentDescriptorBus::Handler
{
public:
/**
* Connects to the component descriptor bus.
*/
ComponentDescriptorHelper()
{
BusConnect(AzTypeInfo<ComponentClass>::Uuid());
}
~ComponentDescriptorHelper()
{
BusDisconnect();
}
/**
* Creates an instance of the component.
* @return Returns a pointer to the component.
*/
Component* CreateComponent() override
{
return aznew ComponentClass;
}
/**
* Gets the name of the component.
* @return Returns a pointer to the name of the component.
*/
const char* GetName() const override
{
return AzTypeInfo<ComponentClass>::Name();
}
/**
* Gets the ID of the component.
* @return Returns a pointer to the component ID.
*/
const Uuid& GetUuid() const override
{
return AzTypeInfo<ComponentClass>::Uuid();
}
};
/// @cond EXCLUDE_DOCS
AZ_HAS_STATIC_MEMBER(ComponentReflect, Reflect, void, (ReflectContext*));
AZ_HAS_STATIC_MEMBER(ComponentProvidedServices, GetProvidedServices, void, (ComponentDescriptor::DependencyArrayType &));
AZ_HAS_STATIC_MEMBER(ComponentDependentServices, GetDependentServices, void, (ComponentDescriptor::DependencyArrayType &));
AZ_HAS_STATIC_MEMBER(ComponentRequiredServices, GetRequiredServices, void, (ComponentDescriptor::DependencyArrayType &));
AZ_HAS_STATIC_MEMBER(ComponentIncompatibleServices, GetIncompatibleServices, void, (ComponentDescriptor::DependencyArrayType &));
/// @endcond
/**
* Default descriptor implementation.
* This implementation forwards all descriptor calls to a static function inside the class.
*/
template<class ComponentClass>
class ComponentDescriptorDefault
: public ComponentDescriptorHelper<ComponentClass>
{
public:
/**
* Specifies that this class should use the AZ::SystemAllocator for memory
* management by default.
*/
AZ_CLASS_ALLOCATOR(ComponentDescriptorDefault<ComponentClass>, SystemAllocator, 0);
/**
* Calls the static function AZ::ComponentDescriptor::Reflect if the user provided it.
* @param A pointer to the reflection context.
*/
void Reflect(ReflectContext* reflection) const override
{
static_assert(HasComponentReflect<ComponentClass>::value, "All components using ComponentDescriptorDefault (AZ_COMPONENT macro) should implement 'static void Reflect(ReflectContext* reflection)' function!");
CallReflect(reflection, typename HasComponentReflect<ComponentClass>::type());
}
/**
* Calls the static function AZ::ComponentDescriptor::GetProvidedServices, if the user provided it.
* @param provided Array of provided services.
* @param instance Optional parameter with which you can refine services for each instance. This value is null if no instance exists.
*/
void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided, const Component* instance) const override
{
(void)instance; // Not used by default because most components have static (not instance-dependent) services.
CallProvidedServices(provided, typename HasComponentProvidedServices<ComponentClass>::type());
}
/**
* Calls the static function AZ::ComponentDescriptor::GetDependentServices, if the user provided it.
* @param provided Array of dependent services.
* @param instance Optional parameter with which you can refine services for each instance. This value is null if no instance exists.
*/
void GetDependentServices(ComponentDescriptor::DependencyArrayType& dependent, const Component* instance) const override
{
(void)instance; // Not used by default because most components have static (not instance-dependent) services.
CallDependentServices(dependent, typename HasComponentDependentServices<ComponentClass>::type());
}
/**
* Calls the static function AZ::ComponentDescriptor::GetRequiredServices, if the user provided it.
* @param provided Array of required services.
* @param instance Optional parameter with which you can refine services for each instance. This value is null if no instance exists.
*/
void GetRequiredServices(ComponentDescriptor::DependencyArrayType& required, const Component* instance) const override
{
(void)instance; // Not used by default because most components have static (not instance-dependent) services.
CallRequiredServices(required, typename HasComponentRequiredServices<ComponentClass>::type());
}
/**
* Calls the static function AZ::ComponentDescriptor::GetIncompatibleServices, if the user provided it.
* @param provided Array of incompatible services.
* @param instance Optional parameter with which you can refine services for each instance. This value is null if no instance exists.
*/
void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible, const Component* instance) const override
{
(void)instance; // Not used by default because most components have static (not instance-dependent) services.
CallIncompatibleServices(incompatible, typename HasComponentIncompatibleServices<ComponentClass>::type());
}
private:
void CallReflect(ReflectContext* reflection, const AZStd::true_type&) const
{
ComponentClass::Reflect(reflection);
}
void CallReflect(ReflectContext*, const AZStd::false_type&) const
{
}
void CallProvidedServices(ComponentDescriptor::DependencyArrayType& provided, const AZStd::true_type&) const
{
ComponentClass::GetProvidedServices(provided);
}
void CallProvidedServices(ComponentDescriptor::DependencyArrayType&, const AZStd::false_type&) const
{
}
void CallDependentServices(ComponentDescriptor::DependencyArrayType& dependent, const AZStd::true_type&) const
{
ComponentClass::GetDependentServices(dependent);
}
void CallDependentServices(ComponentDescriptor::DependencyArrayType&, const AZStd::false_type&) const
{
}
void CallRequiredServices(ComponentDescriptor::DependencyArrayType& required, const AZStd::true_type&) const
{
ComponentClass::GetRequiredServices(required);
}
void CallRequiredServices(ComponentDescriptor::DependencyArrayType&, const AZStd::false_type&) const
{
}
void CallIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible, const AZStd::true_type&) const
{
ComponentClass::GetIncompatibleServices(incompatible);
}
void CallIncompatibleServices(ComponentDescriptor::DependencyArrayType&, const AZStd::false_type&) const
{
}
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,429 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_COMPONENT_APPLICATION_H
#define AZCORE_COMPONENT_APPLICATION_H
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Debug/ProfileModuleInit.h>
#include <AzCore/Memory/AllocationRecords.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/Module/ModuleManager.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/RTTI/ReflectionManager.h>
#include <AzCore/Settings/CommandLine.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/Settings/SettingsRegistryConsoleUtils.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/string/osstring.h>
namespace AZ
{
class BehaviorContext;
class IConsole;
class Module;
class ModuleManager;
namespace Debug
{
class DrillerManager;
}
class ReflectionEnvironment
{
public:
ReflectionEnvironment()
{
m_reflectionManager = AZStd::make_unique<ReflectionManager>();
}
static void Init();
static void Reset();
static ReflectionManager* GetReflectionManager();
ReflectionManager* Get() { return m_reflectionManager.get(); }
private:
AZStd::unique_ptr<ReflectionManager> m_reflectionManager;
};
/**
* A main class that can be used directly or as a base to start a
* component based application. It will provide all the proper bootstrap
* and entity bookkeeping functionality.
*
* IMPORTANT: If you use this as a base class you must follow one rule. You can't add
* data that will allocate memory on construction. This is because the memory managers
* are NOT yet ready. They will be initialized during the Create call.
*/
class ComponentApplication
: public ComponentApplicationBus::Handler
, public TickRequestBus::Handler
{
// or try to use unordered set if we store the ID internally
typedef AZStd::unordered_map<EntityId, Entity*> EntitySetType;
public:
AZ_RTTI(ComponentApplication, "{1F3B070F-89F7-4C3D-B5A3-8832D5BC81D7}");
AZ_CLASS_ALLOCATOR(ComponentApplication, SystemAllocator, 0);
/**
* Configures the component application.
* \note This structure may be loaded from a file on disk. Values that
* must be set by a running application should go into StartupParameters.
* \note It's important this structure not contain members that allocate from the system allocator.
* Use the OSAllocator only.
*/
struct Descriptor
: public SerializeContext::IObjectFactory
{
AZ_TYPE_INFO(ComponentApplication::Descriptor, "{70277A3E-2AF5-4309-9BBF-6161AFBDE792}");
AZ_CLASS_ALLOCATOR(ComponentApplication::Descriptor, SystemAllocator, 0);
struct AllocatorRemapping
{
AZ_TYPE_INFO(ComponentApplication::Descriptor::AllocatorRemapping, "{4C865590-4506-4B76-BF14-6CCB1B83019A}");
AZ_CLASS_ALLOCATOR(ComponentApplication::Descriptor::AllocatorRemapping, OSAllocator, 0);
static void Reflect(ReflectContext* context, ComponentApplication* app);
OSString m_from;
OSString m_to;
};
typedef AZStd::vector<AllocatorRemapping, OSStdAllocator> AllocatorRemappings;
///////////////////////////////////////////////
// SerializeContext::IObjectFactory
void* Create(const char* name) override;
void Destroy(void* data) override;
///////////////////////////////////////////////
/// Reflect the descriptor data.
static void Reflect(ReflectContext* context, ComponentApplication* app);
Descriptor();
bool m_useExistingAllocator; //!< True if the user is creating the system allocation and setup tracking modes, if this is true all other parameters are IGNORED. (default: false)
bool m_grabAllMemory; //!< True if we want to grab all available memory minus reserved fields. (default: false)
bool m_allocationRecords; //!< True if we want to track memory allocations, otherwise false. (default: true)
bool m_allocationRecordsSaveNames; //!< True if we want to allocate space for saving the name/filename of each allocation so unloaded module memory leaks have valid names to read, otherwise false. (default: false, automatically true with recording mode FULL)
bool m_allocationRecordsAttemptDecodeImmediately; ///< True if we want to attempt decoding frames at time of allocation, otherwise false. Very expensive, used specifically for debugging allocations that fail to decode. (default: false)
bool m_autoIntegrityCheck; //!< True to check the heap integrity on each allocation/deallocation. (default: false)
bool m_markUnallocatedMemory; //!< True to mark all memory with 0xcd when it's freed. (default: true)
bool m_doNotUsePools; //!< True of we want to pipe all allocation to a generic allocator (not pools), this can help debugging a memory stomp. (default: false)
bool m_enableScriptReflection; //!< True if we want to enable reflection to the script context.
unsigned int m_pageSize; //!< Page allocation size must be 1024 bytes aligned. (default: SystemAllocator::Descriptor::Heap::m_defaultPageSize)
unsigned int m_poolPageSize; //!< Page size used to small memory allocations. Must be less or equal to m_pageSize and a multiple of it. (default: SystemAllocator::Descriptor::Heap::m_defaultPoolPageSize)
unsigned int m_memoryBlockAlignment; //!< Alignment of memory block. (default: SystemAllocator::Descriptor::Heap::m_memoryBlockAlignment)
AZ::u64 m_memoryBlocksByteSize; //!< Memory block size in bytes if. This parameter is ignored if m_grabAllMemory is set to true. (default: 0 - use memory on demand, no preallocation)
AZ::u64 m_reservedOS; //!< Reserved memory for the OS in bytes. Used only when m_grabAllMemory is set to true. (default: 0)
AZ::u64 m_reservedDebug; //!< Reserved memory for Debugging (allocation,etc.). Used only when m_grabAllMemory is set to true. (default: 0)
Debug::AllocationRecords::Mode m_recordingMode; //!< When to record stack traces (default: AZ::Debug::AllocationRecords::RECORD_STACK_IF_NO_FILE_LINE)
AZ::u64 m_stackRecordLevels; //!< If stack recording is enabled, how many stack levels to record. (default: 5)
bool m_enableDrilling; //!< True to enabled drilling support for the application. RegisterDrillers will be called. Ignored in release. (default: true)
bool m_useOverrunDetection; //!< True to use the overrun detection memory management scheme. Only available on some platforms; greatly increases memory consumption.
bool m_useMalloc; //!< True to use malloc instead of the internal memory manager. Intended for debugging purposes only.
AllocatorRemappings m_allocatorRemappings; //!< List of remappings of allocators to perform, so that they can alias each other.
ModuleDescriptorList m_modules; //!< Dynamic modules used by the application.
//!< These will be loaded on startup.
};
//! Application settings.
//! Unlike the Descriptor, these values must be set in code and cannot be loaded from a file.
struct StartupParameters
{
StartupParameters() {}
//! If set, this allocator is used to allocate the temporary bootstrap memory, as well as the main \ref SystemAllocator heap.
//! If it's left nullptr (default), the \ref OSAllocator will be used.
IAllocatorAllocate* m_allocator = nullptr;
//! Callback to create AZ::Modules for the static libraries linked by this application.
//! Leave null if the application uses no static AZ::Modules.
//! \note Dynamic AZ::Modules are specified in the ComponentApplication::Descriptor.
CreateStaticModulesCallback m_createStaticModulesCallback = nullptr;
//! If set, this is used as the app root folder instead of it being calculated.
const char* m_appRootOverride = nullptr;
//! The path to root of the asset cache folder. For instance: ./cache/<project>/pc
const char* m_cacheRootPath = nullptr;
//! The path to the project in the asset cache folder. For instance: ./cache/<project>/pc/<project>
const char* m_cacheProjectPath = nullptr;
//! Specifies which system components to create & activate. If no tags specified, all system components are used. Specify as comma separated list.
const char* m_systemComponentTags = nullptr;
//! Whether or not to load static modules associated with the application
bool m_loadStaticModules = true;
//! Whether or not to load dynamic modules described by \ref Descriptor::m_modules
bool m_loadDynamicModules = true;
//! Used by test fixtures to ensure reflection occurs to edit context.
bool m_createEditContext = false;
};
ComponentApplication();
ComponentApplication(int argC, char** argV);
virtual ~ComponentApplication();
/**
* Create function which accepts a variant which allows passing in either a ComponentApplication::Descriptor or a c-string path
* to an object stream descriptor file.
* The object stream descriptor path is deprecated and will removed when the gems are loaded from the settings registry
* If descriptor type = const char*: Loads the application configuration and systemEntity from 'applicationDescriptorFile' (path relative to AppRoot).
* It is expected that the first node in the file will be the descriptor, for memory manager creation.
* If descriptor type = Descriptor: Create system allocator and system entity. No components are added to the system node.
* You will need to setup all system components manually.
* \returns pointer to the system entity.
*/
virtual Entity* Create(const Descriptor& descriptor,
const StartupParameters& startupParameters = StartupParameters());
virtual void Destroy();
virtual void DestroyAllocator(); // Called at the end of Destroy(). Applications can override to do tear down work right before allocator is destroyed.
//////////////////////////////////////////////////////////////////////////
// ComponentApplicationRequests
void RegisterComponentDescriptor(const ComponentDescriptor* descriptor) override final;
void UnregisterComponentDescriptor(const ComponentDescriptor* descriptor) override final;
bool AddEntity(Entity* entity) override;
bool RemoveEntity(Entity* entity) override;
bool DeleteEntity(const EntityId& id) override;
Entity* FindEntity(const EntityId& id) override;
AZStd::string GetEntityName(const EntityId& id) override;
void EnumerateEntities(const ComponentApplicationRequests::EntityCallback& callback) override;
ComponentApplication* GetApplication() override { return this; }
/// Returns the serialize context that has been registered with the app, if there is one.
SerializeContext* GetSerializeContext() override;
/// Returns the behavior context that has been registered with the app, if there is one.
BehaviorContext* GetBehaviorContext() override;
/// Returns the json registration context that has been registered with the app, if there is one.
JsonRegistrationContext* GetJsonRegistrationContext() override;
/// Returns the working root folder that has been registered with the app, if there is one.
/// It's expected that derived applications will implement an application root.
const char* GetAppRoot() const override { return m_appRoot.c_str(); }
/// Returns the path to the folder the executable is in.
const char* GetExecutableFolder() const override { return m_exeDirectory.c_str(); }
/// Returns pointer to the driller manager if it's enabled, otherwise NULL.
Debug::DrillerManager* GetDrillerManager() override { return m_drillerManager; }
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/// TickRequestBus
float GetTickDeltaTime() override;
ScriptTimePoint GetTimeAtCurrentTick() override;
//////////////////////////////////////////////////////////////////////////
Descriptor& GetDescriptor() { return m_descriptor; }
/**
* Ticks all components using the \ref AZ::TickBus during simulation time. May not tick if the application is not active (i.e. not in focus)
*/
virtual void Tick(float deltaOverride = -1.f);
/**
* Ticks all using the \ref AZ::SystemTickBus at all times. Should always tick even if the application is not active.
*/
virtual void TickSystem();
/**
* Application-overridable way to state required system components.
* These components will be added to the system entity if they were
* not already provided by the application descriptor.
* \return the type-ids of required components.
*/
virtual ComponentTypeList GetRequiredSystemComponents() const { return {}; }
/**
* ResolveModulePath is called whenever LoadDynamicModule wants to resolve a module in order to actually load it.
* You can override this if you need to load modules from a different path or hijack module loading in some other way.
* If you do, ensure that you use platform-specific conventions to do so, as this is called by multiple platforms.
* The default implantation prepends the path to the executable to the module path, but you can override this behavior
* (Call the base class if you want this behavior to persist in overrides)
*/
void ResolveModulePath(AZ::OSString& modulePath) override;
void QueryApplicationType(AZ::ApplicationTypeQuery& appType) const override;
/**
* Returns Parsed CommandLine structure which supports query command line options and positional parameters
*/
AZ::CommandLine* GetAzCommandLine() override;
/**
* Retrieve the argc passed into the application class on startup, if any was passed in.
* Note that this could return nullptr if the application was not initialized with any such parameter.
* This is important to have because different operating systems have different level of access to the command line args
* and on some operating systems (MacOS) its fairly difficult to reliably retrieve them without resorting to NS libraries
* and making some assumptions. Instead, we allow you to pass your args in from the main(...) function.
* Another thing to notice here is that these are non-const pointers to the argc and argv values
* instead of int, char**, these are int*, char***.
* This is because some application layers (such as Qt) actually require that the ArgC and ArgV are modifiable,
* as they actually patch them to add/remove command line parameters during initialization.
* but also to highlight the fact that they are pointers to static memory that must remain relevant throughout the existence
* of the Application object.
* For best results, simply pass in &argc and &argv from your void main(argc, argv) in here - that memory is
* permanently tied to your process and is going to be available at all times during run.
*/
int* GetArgC();
/**
* Retrieve the argv parameter passed into the application class on startup. see the note on ArgC
* Note that this could return nullptr if the application was not initialized with any such parameter.
*/
char*** GetArgV();
//! Perform loading of modules by appending the modules in the Descriptor
//! to the list of modules in cmake_dependencies.*.setreg file for the active project
void LoadModules();
//! Loads only static modules which are populated via the CreateStaticModules member function
void LoadStaticModules();
//! Performs loading of dynamic modules made up of the list of modules in the cmake_dependencies.*.setreg
//! loaded into the AZ::SettingsRegistry plus the list of modules stored in the Descriptor::m_modules array
void LoadDynamicModules();
protected:
virtual void CreateReflectionManager();
void DestroyReflectionManager();
/// Perform any additional initialization needed before loading modules
virtual void PreModuleLoad() {};
virtual void CreateStaticModules(AZStd::vector<AZ::Module*>& outModules);
/// Common logic shared between the multiple Create(...) functions.
void CreateCommon();
/// Create the operating system allocator if not supplied in the StartupParameters
void CreateOSAllocator();
/// Create the system allocator using the data in the m_descriptor
void CreateSystemAllocator();
/// Create the drillers
void CreateDrillers();
virtual void MergeSettingsToRegistry(SettingsRegistryInterface& registry);
//! Sets the specializations that will be used when loading the Settings Registry. Extend this in derived
//! application classes to specialize settings for those applications.
virtual void SetSettingsRegistrySpecializations(SettingsRegistryInterface::Specializations& specializations);
/**
* This is the function that will be called instantly after the memory
* manager is created. This is where we should register all core component
* factories that will participate in the loading of the bootstrap file
* or all factories in general.
* When you create your own application this is where you should FIRST call
* ComponentApplication::RegisterCoreComponents and then register the application
* specific core components.
*/
virtual void RegisterCoreComponents() {};
/*
* Reflect classes from this framework to the appropriate context.
* Subclasses of AZ::Component should not be listed here, they are reflected through the ComponentDescriptorBus.
*/
virtual void Reflect(ReflectContext* context);
/// Check if a System Component should be created
bool ShouldAddSystemComponent(AZ::ComponentDescriptor* descriptor);
/// Adds system components requested by modules and the application to the system entity.
void AddRequiredSystemComponents(AZ::Entity* systemEntity);
/// Calculates the directory the application executable comes from.
void CalculateExecutablePath();
/// Calculates the directory where the bootstrap.cfg file resides.
void CalculateAppRoot(const char* appRootOverride = {});
/**
* Check/verify a given path for the engine marker (file) so that we can identify that
* a given path is the engine root. This is only valid for target platforms that are built
* for the host platform and not deployable (ie windows, mac).
* @param fullPath The full path to look for the engine marker
* @return true if the input path contains the engine marker file, false if not
*/
virtual bool CheckPathForEngineMarker(const char* fullPath) const;
template<typename Iterator>
static void NormalizePath(Iterator begin, Iterator end, bool doLowercase = true)
{
AZStd::replace(begin, end, '\\', '/');
if (doLowercase)
{
AZStd::to_lower(begin, end);
}
}
AZStd::chrono::system_clock::time_point m_currentTime{ AZStd::chrono::system_clock::time_point::max() };
float m_deltaTime{ 0.0f };
AZStd::unique_ptr<ModuleManager> m_moduleManager;
AZStd::unique_ptr<SettingsRegistryInterface> m_settingsRegistry;
AZ::IConsole* m_console{};
Descriptor m_descriptor;
bool m_isStarted{ false };
bool m_isSystemAllocatorOwner{ false };
bool m_isOSAllocatorOwner{ false };
bool m_ownsConsole{};
void* m_fixedMemoryBlock{ nullptr }; //!< Pointer to the memory block allocator, so we can free it OnDestroy.
IAllocatorAllocate* m_osAllocator{ nullptr };
EntitySetType m_entities;
AZ::StringFunc::Path::FixedString m_exeDirectory;
AZ::StringFunc::Path::FixedString m_appRoot;
AZ::SettingsRegistryInterface::NotifyEventHandler m_gameProjectChangedHandler;
// ConsoleFunctorHandle is responsible for unregistering the Settings Registry Console
// from the m_console member when it goes out of scope
AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle m_settingsRegistryConsoleFunctors;
// this is used when no argV/ArgC is supplied.
// in order to have the same memory semantics (writable, non-const)
// we create a buffer that can be written to (up to AZ_MAX_PATH_LEN) and then
// pack it with a single param.
char m_commandLineBuffer[AZ_MAX_PATH_LEN];
char* m_commandLineBufferAddress{ m_commandLineBuffer };
Debug::DrillerManager* m_drillerManager{ nullptr };
StartupParameters m_startupParameters;
char** m_argV{ nullptr };
int m_argC{ 0 };
AZ::CommandLine m_commandLine; // < Stores parsed command line supplied to the constructor
AZStd::unique_ptr<AZ::Entity> m_systemEntity; ///< Track the system entity to ensure we free it on shutdown.
};
}
#endif // AZCORE_COMPONENT_APPLICATION_H
#pragma once
@@ -0,0 +1,223 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/string/osstring.h>
namespace AZ
{
class CommandLine;
class ComponentApplication;
class ComponentDescriptor;
class Entity;
class EntityId;
class Module;
class DynamicModuleHandle;
class Component;
class SerializeContext;
class BehaviorContext;
class JsonRegistrationContext;
namespace Internal
{
class ComponentFactoryInterface;
}
namespace Debug
{
class DrillerManager;
}
struct ApplicationTypeQuery
{
bool IsEditor() const;
bool IsTool() const;
bool IsGame() const;
bool IsValid() const;
enum class Masks
{
Invalid = 0,
Editor = 1 << 0,
Tool = 1 << 1,
Game = 1 << 2,
};
Masks m_maskValue = Masks::Invalid;
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(ApplicationTypeQuery::Masks);
// use of the Masks operator&(Masks, Masks) needs to be after the definition above.
inline bool ApplicationTypeQuery::IsEditor() const { return (m_maskValue & Masks::Editor) == Masks::Editor; }
inline bool ApplicationTypeQuery::IsTool() const { return (m_maskValue & Masks::Tool) == Masks::Tool; }
inline bool ApplicationTypeQuery::IsGame() const { return (m_maskValue & Masks::Game) == Masks::Game; }
inline bool ApplicationTypeQuery::IsValid() const { return m_maskValue != Masks::Invalid; }
/**
* Event bus that components use to make requests of the main application.
* Only one application can exist at a time, which is why this bus
* supports only one listener.
*/
class ComponentApplicationRequests
: public AZ::EBusTraits
{
public:
/**
* Destroys the event bus that components use to make requests of the main application.
*/
virtual ~ComponentApplicationRequests() {}
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides - application is a singleton
/**
* Overrides the default AZ::EBusTraits handler policy to allow one
* listener only, because only one application can exist at a time.
*/
static const AZ::EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Single; // We sort components on m_initOrder.
/**
* Overrides the default AZ::EBusTraits mutex type to the AZStd implementation of
* a recursive mutex with exclusive ownership semantics. A mutex prevents multiple
* threads from accessing shared data simultaneously.
*/
typedef AZStd::recursive_mutex MutexType;
//////////////////////////////////////////////////////////////////////////
/**
* Registers a component descriptor with the application.
* @param descriptor A component descriptor.
*/
virtual void RegisterComponentDescriptor(const ComponentDescriptor* descriptor) = 0;
/**
* Unregisters a component descriptor with the application.
* @param descriptor A component descriptor.
*/
virtual void UnregisterComponentDescriptor(const ComponentDescriptor* descriptor) = 0;
/**
* Gets a pointer to the application.
* @return A pointer to the application.
*/
virtual ComponentApplication* GetApplication() = 0;
/**
* Adds an entity to the application's registry.
* Calling Init() on an entity automatically performs this operation.
* @param entity A pointer to the entity to add to the application's registry.
* @return True if the operation succeeded. False if the operation failed.
*/
virtual bool AddEntity(Entity* entity) = 0;
/**
* Removes the specified entity from the application's registry.
* Deleting an entity automatically performs this operation.
* @param entity A pointer to the entity that will be removed from the application's registry.
* @return True if the operation succeeded. False if the operation failed.
*/
virtual bool RemoveEntity(Entity* entity) = 0;
/**
* Unregisters and deletes the specified entity.
* @param entity A reference to the entity that will be unregistered and deleted.
* @return True if the operation succeeded. False if the operation failed.
*/
virtual bool DeleteEntity(const EntityId& id) = 0;
/**
* Returns the entity with the matching ID, if the entity is registered with the application.
* @param entity A reference to the entity that you are searching for.
* @return A pointer to the entity with the specified entity ID.
*/
virtual Entity* FindEntity(const EntityId& id) = 0;
/**
* Returns the name of the entity that has the specified entity ID.
* Entity names are not unique.
* This method exists to facilitate better debugging messages.
* @param entity A reference to the entity whose name you are seeking.
* @return The name of the entity with the specified entity ID.
* If no entity is found for the specified ID, it returns an empty string.
*/
virtual AZStd::string GetEntityName(const EntityId& id) { (void)id; return AZStd::string(); };
/**
* The type that AZ::ComponentApplicationRequests::EnumerateEntities uses to
* pass entity callbacks to the application for enumeration.
*/
using EntityCallback = AZStd::function<void(Entity*)>;
/**
* Enumerates all registered entities and invokes the specified callback for each entity.
* @param callback A reference to the callback that is invoked for each entity.
*/
virtual void EnumerateEntities(const EntityCallback& callback) = 0;
/**
* Returns the serialize context that was registered with the app.
* @return The serialize context, if there is one. SerializeContext is a class that contains reflection data
* for serialization and construction of objects.
*/
virtual class SerializeContext* GetSerializeContext() = 0;
/**
* Returns the behavior context that was registered with the app.
* @return The behavior context, if there is one. BehaviorContext is a class that reflects classes, methods,
* and EBuses for runtime interaction.
*/
virtual class BehaviorContext* GetBehaviorContext() = 0;
/**
* Returns the Json Registration context that was registered with the app.
* @return The Json Registration context, if there is one. JsonRegistrationContext is a class that contains
* the serializers used by the best-effort json serialization.
*/
virtual class JsonRegistrationContext* GetJsonRegistrationContext() = 0;
/**
* Gets the name of the working root folder that was registered with the app.
* @return A pointer to the name of the app's root folder, if a root folder was registered.
*/
virtual const char* GetAppRoot() const = 0;
/**
* Gets the path to the directory that contains the application's executable.
* @return A pointer to the name of the path that contains the application's executable.
*/
virtual const char* GetExecutableFolder() const = 0;
/**
* Returns a pointer to the driller manager, if driller is enabled.
* The driller manager manages all active driller sessions and driller factories.
* @return A pointer to the driller manager. If driller is not enabled,
* this function returns null.
*/
virtual Debug::DrillerManager* GetDrillerManager() = 0;
/**
* ResolveModulePath is called whenever LoadDynamicModule wants to resolve a module in order to actually load it.
* You can override this if you need to load modules from a different path or hijack module loading in some other way.
* If you do, ensure that you use platform-specific conventions to do so, as this is called by multiple platforms.
* The default implantation prepends the path to the executable to the module path, but you can override this behavior
* (Call the base class if you want this behavior to persist in overrides)
*/
virtual void ResolveModulePath(AZ::OSString& /*modulePath*/) { }
/**
* Returns AZ parsed command line structure.
* Command Line structure can be queried for switches (-<switch> /<switch>) or positional parameter (<value>)
*/
virtual AZ::CommandLine* GetAzCommandLine() { return{}; }
//! Returns all the flags that are true for the current application.
virtual void QueryApplicationType(ApplicationTypeQuery& appType) const = 0;
};
/**
* Used by components to make requests of the component application.
*/
typedef AZ::EBus<ComponentApplicationRequests> ComponentApplicationBus;
}
@@ -0,0 +1,31 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include "ComponentBus.h"
namespace AZ
{
/*static*/ void EntityComponentIdPair::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<EntityComponentIdPair>()
->Field("EntityId", &EntityComponentIdPair::m_entityId)
->Field("ComponentId", &EntityComponentIdPair::m_componentId)
->Version(0);
}
}
}
@@ -0,0 +1,245 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
/** @file
* Header file for the component bus class, which most AZ::Components use as the base
* class for their buses. Buses enable components to communicate with each other and
* with external systems.
*/
#ifndef AZCORE_COMPONENT_BUS_H
#define AZCORE_COMPONENT_BUS_H
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/EntityId.h>
namespace AZ
{
class ReflectContext;
typedef AZ::u64 ComponentId;
static const ComponentId InvalidComponentId = 0;
/**
* Base class for message buses.
* Most components that derive from AZ::Component use this class to implement
* their buses, and then override the default AZ::EBusTraits to suit their needs.
*/
class ComponentBus
: public AZ::EBusTraits
{
public:
/**
* Destroys a component bus.
*/
virtual ~ComponentBus() = default;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
/**
* Overrides the default AZ::EBusTraits address policy so that the bus
* has multiple addresses at which to receive messages. This bus is
* identified by EntityId. Messages addressed to an ID are received by
* handlers connected to that ID.
*/
static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById;
/**
* Overrides the default AZ::EBusTraits ID type so that entity IDs are
* used to access the addresses of the bus.
*/
typedef EntityId BusIdType;
//////////////////////////////////////////////////////////////////////////
};
/**
* Base class for component configurations.
* Components that accept a ComponentConfig can be configured in-game using code.
* To author a component that is runtime configurable:
* 1) Create a class which inherits from ComponentConfig.
* a) Write this class in a publicly available header file (ex: within an include/ folder).
* b) Add AZ_RTTI to this class.
* c) Put all properties that your component needs to be configured into this class.
* d) You might find it helpful to simply store an instance of the configuration class
* within your component, rather than having duplicate properties in each class.
* 2) Implement the ReadInConfig() function for your component.
* Set properties in your component, based on the properties in the configuration class.
* 3) Implement the WriteOutConfig() function for your component.
* Set properties in the config class, based on the properties in the component.
* 4) Reflect your configuration class to the appropriate contexts.
* BehaviorContext allows the configuration to be used from scripts.
* If your component stores an instance of the configuration within itself,
* reflect to the SerializeContext and EditContext to make the properties
* accessible in the Editor UI.
* @note Components are not required to support a configuration class.
* The EditContext can expose a component's properties to the editor's UI
* regardless of whether the component has a configuration class.
*/
class ComponentConfig
{
public:
AZ_CLASS_ALLOCATOR(ComponentConfig, SystemAllocator, 0);
AZ_RTTI(ComponentConfig, "{0A7929DF-2932-40EA-B2B3-79BC1C3490D0}");
virtual ~ComponentConfig() = default;
};
/**
* A pair of entity and component IDs that are used to access an address
* of an AZ::EntityComponentBus.
*/
class EntityComponentIdPair
{
public:
/**
* Specifies that this class should use AZ::SystemAllocator for memory
* management by default.
*/
AZ_CLASS_ALLOCATOR(EntityComponentIdPair, AZ::SystemAllocator, 0);
/**
* Adds run-time type information to this class.
*/
AZ_RTTI(EntityComponentIdPair, "{C845E5EC-5580-4E12-A9B2-9AE7E5B7826F}");
/**
* Creates an empty entity-component ID pair.
* Entity-component ID pairs are used to access addresses of an
* AZ::EntityComponentBus.
*/
EntityComponentIdPair() {}
/**
* Creates an empty entity-component ID pair with the specified entity and component ID.
* Entity-component ID pairs are used to access addresses of an AZ::EntityComponentBus.
* @param entityId ID of an entity.
* @param componentId ID of a component.
*/
EntityComponentIdPair(const AZ::EntityId& entityId, const AZ::ComponentId& componentId)
: m_entityId(entityId)
, m_componentId(componentId) {}
/**
* Destroys the entity-ID pair.
*/
virtual ~EntityComponentIdPair() = default;
/**
* Gets the ID of the entity so that it can be hashed and
* combined with the ID of the component to find which address
* to use on the message bus.
* @return The ID of the specified component.
*/
AZ::EntityId GetEntityId() const { return m_entityId; }
/**
* Gets the ID of the component so that it can be hashed and
* combined with the ID of the entity to find which address
* to use on the message bus.
* @return The ID of the specified component.
*/
AZ::ComponentId GetComponentId() const { return m_componentId; }
/**
* Overloads the == operator so that entity-component ID pairs can
* be checked for equality.
* @param other An entity-component ID pair whose equality you want to check against.
* @result Returns true if the entity-component ID pairs are equal.
*/
bool operator==(const EntityComponentIdPair& other) const
{
return m_entityId == other.m_entityId && m_componentId == other.m_componentId;
}
/**
* Overloads the != operator so that entity-component ID pairs can
* be checked for difference.
* @param other An entity-component ID pair whose equality you want to check against.
* @result Returns true if the entity-component ID pairs are not equal.
*/
bool operator!=(const EntityComponentIdPair& other) const
{
return m_entityId != other.m_entityId || m_componentId != other.m_componentId;
}
/**
* Reflects this class into a variety of contexts (script, serialize, edit, and so on).
* @param reflection A pointer to the reflection context.
*/
static void Reflect(AZ::ReflectContext* context);
private:
AZ::EntityId m_entityId;
AZ::ComponentId m_componentId;
};
/// @cond EXCLUDE_DOCS
// Base class for message buses that enable an entity to communicate
// with a specific instance of a component. This is similar to the
// AZ::ComponentBus base class. The difference is that this class requires
// messages to be addressed to a specific instance of a component
// rather than receiving messages for all components of the same type.
class EntityComponentBus
: public AZ::EBusTraits
{
public:
/**
* Destroys the bus that entities use to communicate with a component.
*/
virtual ~EntityComponentBus() = default;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
/**
* Overrides the default AZ::EBusTraits address policy so that the bus
* has multiple addresses at which to receive messages. This bus is
* identified by EntityId. Messages addressed to an ID are received by
* handlers connected to that ID.
*/
static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById;
/**
* Overrides the default AZ::EBusTraits ID type so that entity IDs are
* used to access the addresses of the bus.
*/
typedef EntityComponentIdPair BusIdType;
//////////////////////////////////////////////////////////////////////////
};
/// @endcond
}
namespace AZStd
{
/**
* Implements the hash for the entity-component ID pair, because buses are identified
* by a hash of the ID.
*/
template <>
struct hash < AZ::EntityComponentIdPair >
{
inline size_t operator()(const AZ::EntityComponentIdPair& entityComponentIdPair) const
{
AZStd::hash<AZ::EntityId> entityIdHasher;
size_t retVal = entityIdHasher(entityComponentIdPair.GetEntityId());
AZStd::hash_combine(retVal, entityComponentIdPair.GetComponentId());
return retVal;
}
};
}
#endif // AZCORE_COMPONENT_BUS_H
#pragma once
@@ -0,0 +1,60 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Crc.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/std/containers/unordered_set.h>
namespace AZ
{
class Component;
/**
* Descriptor used when converting editor components to runtime components (slice processing, play-in-editor, etc).
*/
struct ExportedComponent
{
AZ_TYPE_INFO(ExportedComponent, "{F8A00B8B-6981-4508-B939-731563849B97}");
ExportedComponent()
: m_component(nullptr)
, m_deleteAfterExport(false)
, m_componentExportHandled(true)
{}
ExportedComponent(AZ::Component* component, bool deleteAfterExport, bool componentExportHandled = true)
: m_component(component)
, m_deleteAfterExport(deleteAfterExport)
, m_componentExportHandled(componentExportHandled)
{}
AZ::Component* m_component; ///< Pointer to exported component. Null is valid, and conveys no component should be exported.
bool m_deleteAfterExport; ///< If true (false by default), the returned component will be cleaned up by the asset pipeline.
/**
* If true (true by default), the component export has been handled.
* This allows callbacks to announce whether they've handled or ignored the export. If this has been set to false, anything set
* in m_component or m_deleteAfterExport will be ignored. If it has been set to true, the m_component value will be used as the
* exported component. (A value of null in m_component means "don't export anything")
*/
bool m_componentExportHandled;
};
// List of platform tag Crcs for component exporting.
using PlatformTagSet = AZStd::unordered_set<AZ::Crc32>;
// Callback function delegate for customizing component export.
using CustomExportCallbackFunc = AZStd::function<ExportedComponent(AZ::Component* thisComponent, const PlatformTagSet& tags)>;
} // namespace AZ
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,435 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
/** @file
* Header file for the Entity class.
* In Lumberyard's component entity system, an entity is an addressable container for
* a group of components. The entity represents the functionality and properties of an
* object within your game.
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/EBus/Event.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
class Transform;
class TransformInterface;
//! An addressable container for a group of components.
//! An entity creates, initializes, activates, and deactivates its components.
//! An entity has an ID and, optionally, a name.
class Entity
{
public:
//! Specifies that this class should use AZ::SystemAllocator for memory management by default.
AZ_CLASS_ALLOCATOR(Entity, SystemAllocator, 0);
//! Adds run-time type information to this class.
AZ_RTTI(AZ::Entity, "{75651658-8663-478D-9090-2432DFCAFA44}");
//! The type of array that contains the entity's components.
//! Used when iterating over components.
typedef AZStd::vector<Component*> ComponentArrayType;
//! This type of array is used by the warning
typedef AZStd::vector<AZStd::string> StringWarningArray;
//! The state of the entity and its components.
//! @note An entity is only initialized once. It can be activated and deactivated multiple times.
enum class State : u8
{
Constructed, ///< The entity was constructed but is not initialized or active. This is the default state after an entity is created.
Initializing, ///< The entity is initializing itself and its components. This state is the transition between State::Constructed and State::Init.
Init, ///< The entity and its components are initialized. You can add and remove components from the entity when it is in this state.
Activating, ///< The entity is activating itself and its components. This state is the transition between State::Init and State::Active.
Active, ///< The entity and its components are active and fully operational. You cannot add or remove components from the entity unless you first deactivate the entity.
Deactivating, ///< The entity is deactivating itself and its components. This state is the transition between State::Active and State::Init.
Destroying, ///< The entity is in the process of being destroyed. This state is the transition between State::Init and State::Destroyed.
Destroyed ///< The entity has been fully destroyed.
};
//! An event that signals old state and new state during entity state changes.
using EntityStateEvent = Event<State, State>;
//! Represents whether an entity can be activated.
//! An entity cannot be activated unless all component dependency requirements are met, and
//! components are sorted so that each can be activated before the components that depend on it.
enum class DependencySortResult
{
Success = 0, ///< All component dependency requirements are met. The entity can be activated.
MissingRequiredService, ///< One or more components that provide required services are not in the list of components to activate.
HasCyclicDependency, ///< A cycle in component service dependencies was detected.
HasIncompatibleServices, ///< A component is incompatible with a service provided by another component.
DescriptorNotRegistered, ///< A component descriptor was not registered with the AZ::ComponentApplication.
MissingDescriptor, ///< Cannot find a component's ComponentDescriptor
// Deprecated values
DSR_OK = Success,
DSR_MISSING_REQUIRED = MissingRequiredService,
DSR_CYCLIC_DEPENDENCY = HasCyclicDependency,
};
//! Constructs an entity and automatically generates an entity ID.
//! @param name (Optional) A name for the entity. The entity ID is used for addressing and identification,
//! but a name is useful for debugging.
Entity(const char* name = nullptr);
//! Constructs an entity with the entity ID that you specify.
//! @param id An ID for the entity.
//! @param name (Optional) A name for the entity. The entity ID is used for addressing and identification,
//! but a name is useful for debugging.
Entity(const EntityId& id, const char* name = nullptr);
// Delete the copy constructor, because this contains vector of pointers and other pointers that
// are supposed to be unique, this would be a mistake. Its safer to cause code that tries to
// copy an Entity to fail on compile than it would be to allow it to transparently work via
// some sort of serializer-powered deep copy clone. (If you want to manually clone entities,
// use the serializer to do so explicitly).
Entity(const Entity& other) = delete;
Entity& operator=(const Entity& other) = delete;
// You are only allowed to move construct and assign:
Entity(Entity&& other) = default;
Entity& operator=(Entity&& other) = default;
//! Destroys an entity and its components.
//! Do not destroy an entity when it is in a transition state.
//! If the entity is in a transition state, this function asserts.
virtual ~Entity();
//! Gets the ID of the entity.
//! @return The ID of the entity.
EntityId GetId() const { return m_id; }
//! Gets the name of the entity.
//! @return The name of the entity.
const AZStd::string& GetName() const { return m_name; }
//! Sets the name of the entity.
//! @param name A name for the entity.
void SetName(AZStd::string name) { m_name = AZStd::move(name); OnNameChanged(); }
//! Gets the state of the entity.
//! @return The state of the entity. For example, the entity has been initialized, the entity is active, and so on.
State GetState() const { return m_state; }
//! Connects an entity state event handler to the entity.
//! All state changes will be signaled through this event.
//! @param handler reference to the EntityStateEvent handler to attach to the entities state event.
void AddStateEventHandler(EntityStateEvent::Handler& handler);
//! Sets the ID of the entity.
//! You can only change the ID of the entity when the entity has been constructed but is
//! not yet active or initialized.
//! @param id The ID of the entity.
void SetId(const EntityId& id);
//! Initializes the entity and its components.
//! This function is called only once in an entity's lifetime, whereas an entity
//! can be activated and deactivated multiple times.
//! This function calls each component's Init function and provides its entity ID
//! to each component.
virtual void Init();
//! Activates the entity and its components.
//! This function can be called multiple times throughout the lifetime of an
//! entity. Before activating the components, this function verifies that all
//! component dependency requirements are met, and that components are sorted
//! so that each can be activated before the components that depend on it.
//! If these requirements are met, this function calls the Activate function
//! of each component.
virtual void Activate();
//! Deactivates the entity and its components.
//! This function can be called multiple times throughout the lifetime of an
//! entity. This function calls the Deactivate function of each component.
virtual void Deactivate();
//! Creates a component and attaches the component to the entity.
//! You cannot add a component to an entity when the entity is
//! active or in a transition state. After the component is attached
//! to the entity, the entity owns the component. If you destroy the
//! entity, the component is destroyed along with the entity.
//! To release ownership without destroying the component, use RemoveComponent().
//! @return A pointer to the component. Returns a null pointer if
//! the component could not be created.
template<class ComponentType, typename... Args>
ComponentType* CreateComponent(Args&&... args);
//! Creates a component and attaches the component to the entity.
//! You cannot add a component to an entity when the entity is
//! active or in a transition state. After the component is attached
//! to the entity, the entity owns the component. If you destroy the
//! entity, the component is destroyed along with the entity.
//! To release ownership without destroying the component, use RemoveComponent().
//! @param componentTypeId The UUID of the component type.
//! @return A pointer to the component. Returns a null pointer if the component could not be created.
Component* CreateComponent(const Uuid& componentTypeId);
/// @cond EXCLUDE_DOCS
//! @deprecated In tools, use AzToolsFramework::EntityCompositionRequestBus
//! to ensure component requirements are met.
template<class ComponentType>
ComponentType* CreateComponentIfReady()
{
return static_cast<ComponentType*>(CreateComponentIfReady(AzTypeInfo<ComponentType>::Uuid()));
}
/// @endcond
/// @cond EXCLUDE_DOCS
//! @deprecated In tools, use AzToolsFramework::EntityCompositionRequestBus
//! to ensure component requirements are met.
Component* CreateComponentIfReady(const Uuid& componentTypeId);
/// @endcond
//! Attaches an existing component to the entity.
//! You cannot attach a component to an entity when the entity is active
//! or in a transition state. After the component is attached
//! to the entity, the entity owns the component. If you destroy the
//! entity, the component is destroyed along with the entity.
//! To release ownership without destroying the component, use RemoveComponent().
//! The component can be attached to only one entity at a time.
//! If the component is already attached to an entity, this code asserts.
//! @param component A pointer to the component to attach to the entity.
//! @return True if the component was successfully attached to the entity. Otherwise, false.
bool AddComponent(Component* component);
/// @cond EXCLUDE_DOCS
//! @deprecated In tools, use AzToolsFramework::EntityCompositionRequestBus
//! to ensure component requirements are met.
bool IsComponentReadyToAdd(const Component* component, ComponentDescriptor::DependencyArrayType* servicesNeededToBeAdded = nullptr, ComponentArrayType* incompatibleComponents = nullptr)
{
return IsComponentReadyToAdd(component->RTTI_GetType(), component, servicesNeededToBeAdded, incompatibleComponents);
}
/// @endcond
/// @cond EXCLUDE_DOCS
//! @deprecated In tools, use AzToolsFramework::EntityCompositionRequestBus
//! to ensure component requirements are met.
bool IsComponentReadyToAdd(const Uuid& componentTypeId, ComponentDescriptor::DependencyArrayType* servicesNeededToBeAdded = nullptr, ComponentArrayType* incompatibleComponents = nullptr)
{
return IsComponentReadyToAdd(componentTypeId, nullptr, servicesNeededToBeAdded, incompatibleComponents);
}
/// @endcond
//! Removes a component from the entity.
//! After the component is removed from the entity, you are responsible for destroying the component.
//! @param component A pointer to the component to remove from the entity.
//! @return True if the component was removed from the entity. False if the component could not be removed.
bool RemoveComponent(Component* component);
/// @cond EXCLUDE_DOCS
//! @deprecated In tools, use AzToolsFramework::EntityCompositionRequestBus
//! to ensure component requirements are met.
bool IsComponentReadyToRemove(Component* component, ComponentArrayType* componentsNeededToBeRemoved = nullptr);
/// @endcond
/// @cond EXCLUDE_DOCS
//! Replaces one of an entity's components with another component.
//! The entity takes ownership of the added component and relinquishes ownership of the removed component.
//! The added component is assigned the component ID of the removed component.
//! You can only swap the components of an entity when the entity is in the State::Constructed or State::Init state.
//! @param componentToRemove The component to remove from the entity.
//! @param componentToAdd The component to add to the entity.
//! @return True if the components were swapped. False if the components could not be swapped.
bool SwapComponents(Component* componentToRemove, Component* componentToAdd);
/// @endcond
//! Gets all components registered with the entity.
//! @return An array of all components registered with the entity.
const ComponentArrayType& GetComponents() const { return m_components; }
//! Finds a component by component ID.
//! @param id The ID of the component to find.
//! @return A pointer to the component with the specified component ID.
//! If a component with the specified ID cannot be found, the return value
//! is a null pointer.
Component* FindComponent(ComponentId id) const;
//! Finds the first component of the requested component type.
//! @param typeId The type of component to find.
//! @return A pointer to the first component of the requested type. Returns
//! a null pointer if a component of the requested type cannot be found.
Component* FindComponent(const Uuid& typeId) const;
//! Finds a component by component ID.
//! @param id The ID of the component to find.
//! @return A pointer to the component with the specified component ID.
//! If a component with the specified ID cannot be found or the component
//! type does not exist, the return value is a null pointer.
template<class ComponentType>
inline ComponentType* FindComponent(ComponentId id) const
{
return azrtti_cast<ComponentType*>(FindComponent(id));
}
//! Finds the first component of the requested component type.
//! @return A pointer to the first component of the requested type. Returns
//! a null pointer if a component of the requested type cannot be found.
template<class ComponentType>
inline ComponentType* FindComponent() const
{
return azrtti_cast<ComponentType*>(FindComponent(AzTypeInfo<ComponentType>::Uuid()));
}
//! Return a vector of all the components of the specified type in an entity.
//! @return a vector of all the components of the specified type.
ComponentArrayType FindComponents(const Uuid& typeId) const;
/// Return a vector of all the components of the specified type in an entity.
template<class ComponentType>
inline AZStd::vector<ComponentType*> FindComponents() const
{
ComponentArrayType componentArray = FindComponents(azrtti_typeid<ComponentType>());
AZStd::vector<ComponentType*> components(componentArray.size());
AZStd::transform(componentArray.begin(), componentArray.end(), components.begin(), [](Component* component) { return static_cast<ComponentType*>(component); });
return components;
}
//! Indicates to the entity that dependencies among its components need
//! to be evaluated.
//! Dependencies will be evaluated the next time the entity is activated.
void InvalidateDependencies();
//! Contains a failed DependencySortResult code and a detailed message that can be presented to users.
struct FailedSortDetails
{
DependencySortResult m_code;
AZStd::string m_message;
};
using DependencySortOutcome = AZ::Outcome<void, FailedSortDetails>;
//! Calls DependencySort() to sort an entity's components based on the dependencies
//! among components. If all dependencies are met, the required services can be
//! activated before the components that depend on them. An entity will not be
//! activated unless the sort succeeds.
//! @return A successful outcome is returned if the entity can
//! determine an order in which to activate its components.
//! Otherwise the failed outcome contains details on why the sort failed.
DependencySortOutcome EvaluateDependenciesGetDetails();
//! Same as EvaluateDependenciesGetDetails(), but if sort fails
//! only a code is returned, there is no detailed error message.
DependencySortResult EvaluateDependencies();
//! Mark the entity to be activated by default. This is observed automatically by EntityContext,
//! and should be observed by any other custom systems that create and manage entities.
//! @param activeByDefault whether the entity should be active by default after creation.
void SetRuntimeActiveByDefault(bool activeByDefault);
//! @return true if the entity is marked to activate by default upon creation.
bool IsRuntimeActiveByDefault() const;
//! Reflects the entity into a variety of contexts (script, serialize, edit, and so on).
//! @param reflection A pointer to the reflection context.
static void Reflect(ReflectContext* reflection);
//! Generates a unique entity ID.
//! @return An entity ID.
static EntityId MakeId();
//! Gets the Process Signature of the local machine.
//! @return The Process Signature of the local machine.
static AZ::u32 GetProcessSignature();
/// @cond EXCLUDE_DOCS
//! @deprecated Use the TransformBus to communicate with the TransformInterface.
inline TransformInterface* GetTransform() const { return m_transform; }
/// @endcond
//! Sorts an entity's components based on the dependencies between components.
//! If all dependencies are met, the required services can be activated
//! before the components that depend on them.
//! @param components An array of components attached to the entity.
//! @return A successful outcome is returned if the entity can
//! determine an order in which to activate its components.
//! Otherwise the outcome contains details on why the sort failed.
static DependencySortOutcome DependencySort(ComponentArrayType& components);
protected:
/// @cond EXCLUDE_DOCS
//! @deprecated In tools, use AzToolsFramework::EntityCompositionRequestBus
//! to ensure component requirements are met.
bool IsComponentReadyToAdd(const Uuid& componentTypeId, const Component* instance, ComponentDescriptor::DependencyArrayType* servicesNeededToBeAdded, ComponentArrayType* incompatibleComponents);
/// @endcond
//! Sets the entities internal state to the provided value.
//! @param state the new state for the entity.
void SetState(State state);
//! Signals to listeners that the entity's name has changed.
void OnNameChanged() const;
//! Finds whether the entity is in a state in which components can be added or removed.
//! Components can be added or removed when the entity is in the State::Constructed or State::Init state.
//! @return True if the entity is in a state in which that components can be added or removed, otherwise false.
bool CanAddRemoveComponents() const;
// Helpers for child classes
static void ActivateComponent(Component& component) { component.Activate(); }
static void DeactivateComponent(Component& component) { component.Deactivate(); }
//! The ID that the system uses to identify and address the entity.
//! The serializer determines whether this is an entity ID or an entity reference ID.
//! IMPORTANT: This must be the only EntityId member of the Entity class.
EntityId m_id;
//! An array of components attached to the entity.
ComponentArrayType m_components;
//! An event used to signal all entity state changes.
EntityStateEvent m_stateEvent;
//! A cached pointer to the transform interface.
//! We recommend using AZ::TransformBus and caching locally instead of accessing
//! the transform interface directly through this pointer.
TransformInterface* m_transform;
//! A user-friendly name for the entity. This makes error messages easier to read.
AZStd::string m_name;
//! The state of the entity.
State m_state;
//! Foundational entity properties/flags.
//! To keep AZ::Entity lightweight, one should resist the urge the add flags here unless they're extremely
//! common to AZ::Entity use cases, and inherently fundamental.
//! Furthermore, if more than 4 flags are needed, please consider using a more space-efficient container,
//! such as AZStd::bit_set<>. With just a couple flags, AZStd::bit_set's word-size of 32-bits will actually waste space.
bool m_isDependencyReady; ///< Indicates the component dependencies have been evaluated and sorting was completed successfully.
bool m_isRuntimeActiveByDefault; ///< Indicates the entity should be activated on initial creation.
};
template<class ComponentType, typename... Args>
inline ComponentType* Entity::CreateComponent(Args&&... args)
{
ComponentType* component = aznew ComponentType(AZStd::forward<Args>(args)...);
AZ_Assert(component, "Failed to create component: %s", AzTypeInfo<ComponentType>::Name());
if (component)
{
if (!AddComponent(component))
{
delete component;
component = nullptr;
}
}
return component;
}
} // namespace AZ
@@ -0,0 +1,195 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
/** @file
* Header file for buses that dispatch notification events concerning the AZ::Entity class.
* Buses enable entities and components to communicate with each other and with external
* systems.
*/
#ifndef AZCORE_ENTITY_BUS_H
#define AZCORE_ENTITY_BUS_H
#include <AzCore/std/string/string.h>
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
namespace AZ
{
/**
* Interface for the AZ::EntitySystemBus, which is the EBus that dispatches
* notification events about every entity in the system.
*/
class EntitySystemEvents
: public AZ::EBusTraits
{
public:
/**
* Destroys the instance of the class.
*/
virtual ~EntitySystemEvents() {}
/**
* Global entity initialization notification.
* @param id The ID of the initialized entity.
*/
virtual void OnEntityInitialized(const AZ::EntityId&) {}
/**
* Signals that an initialized entity is about to be deleted.
*/
virtual void OnEntityDestruction(const AZ::EntityId&) {}
/**
* Signals that an initialized entity has been deleted.
*/
virtual void OnEntityDestroyed(const AZ::EntityId&) {}
/**
* Signals that an entity was activated.
* This event is dispatched after the activation of the entity is complete.
* @param id The ID of the activated entity.
*/
virtual void OnEntityActivated(const AZ::EntityId&) {}
/**
* Signals that an entity is being deactivated.
* This event is dispatched immediately before the entity is deactivated.
* @param id The ID of the deactivated entity.
*/
virtual void OnEntityDeactivated(const AZ::EntityId&) {}
/**
* Signals that the name of an entity changed.
* @param id The ID of the entity.
* @param name The new name of the entity.
*/
virtual void OnEntityNameChanged(const AZ::EntityId&, const AZStd::string& /*name*/) {}
/**
* Signals that the start status of an entity changed.
* @param EntityId The ID of the entity that has had the status changed.
*/
virtual void OnEntityStartStatusChanged(const AZ::EntityId&) {}
};
/**
* The EBus for systemwide entity notification events.
* The events are defined in the AZ::EntitySystemEvents class.
*/
typedef AZ::EBus<EntitySystemEvents> EntitySystemBus;
/**
* Interface for the AZ::EntityBus, which is the EBus for notification
* events dispatched by a specific entity.
*/
class EntityEvents
: public ComponentBus
{
private:
template<class Bus>
struct EntityEventsConnectionPolicy
: public EBusConnectionPolicy<Bus>
{
static void Connect(typename Bus::BusPtr& busPtr, typename Bus::Context& context, typename Bus::HandlerNode& handler, typename Bus::Context::ConnectLockGuard& connectLock, const typename Bus::BusIdType& id = 0)
{
EBusConnectionPolicy<Bus>::Connect(busPtr, context, handler, connectLock, id);
Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, id);
if (entity)
{
const AZ::Entity::State entityState = entity->GetState();
if (entityState >= Entity::State::Init)
{
handler->OnEntityExists(id);
}
if (entityState == Entity::State::Active)
{
handler->OnEntityActivated(id);
}
}
}
};
public:
/**
* With this connection policy, AZ::EntityEvents::OnEntityExists and
* AZ::EntityEvents::OnEntityActivated events may be immediately
* dispatched when a handler connects to the bus.
*/
template<class Bus>
using ConnectionPolicy = EntityEventsConnectionPolicy<Bus>;
/**
* Destroys the instance of the class.
*/
virtual ~EntityEvents() {}
/**
* Signals that an entity has come into existence.
* This event is dispatched after initialization of the entity.
* It is also dispatched to handlers immediately upon connecting
* to the bus if the entity has already been initialized.
* Note that in this case the entity may or may not be activated.
* @param id The ID of the entity.
*/
virtual void OnEntityExists(const AZ::EntityId&) {}
/**
* Signals that an initialized entity is about to be deleted.
*/
virtual void OnEntityDestruction(const AZ::EntityId&) {}
/**
* Signals that an initialized entity has been deleted.
*/
virtual void OnEntityDestroyed(const AZ::EntityId&) {}
/**
* Signals that an entity was activated.
* This event is dispatched after the activation of the entity is complete.
* It is also dispatched immediately if the entity is already active
* when a handler connects to the bus.
* @param EntityId The ID of the entity that was activated.
*/
virtual void OnEntityActivated(const AZ::EntityId&) {}
/**
* Signals that an entity is being deactivated.
* This event is dispatched immediately before the entity is deactivated.
* @param EntityId The ID of the entity that is being deactivated.
*/
virtual void OnEntityDeactivated(const AZ::EntityId&) {}
/**
* Signals that the name of an entity changed.
* @param name The new name of the entity.
*/
virtual void OnEntityNameChanged(const AZStd::string& name) { (void)name; }
};
/**
* The EBus for notification events dispatched by a specific entity.
* The events are defined in the AZ::EntityEvents class.
*/
typedef AZ::EBus<EntityEvents> EntityBus;
}
#endif // AZCORE_ENTITY_BUS_H
#pragma once
@@ -0,0 +1,175 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_ENTITY_ID_H
#define AZCORE_ENTITY_ID_H
#include <AzCore/base.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/std/string/string.h>
/** @file
* Header file for the entity ID type.
* Entity IDs are used to uniquely identify entities.
*/
namespace AZ
{
/**
* Entity ID type.
* Entity IDs are used to uniquely identify entities. Each component that is
* attached to an entity is tagged with the entity's ID, and component buses
* are typically addressed by entity ID.
*/
class EntityId
{
friend class JsonEntityIdSerializer;
friend class Entity;
public:
/**
* Invalid entity ID with a machine ID of 0 and the maximum timestamp.
*/
static const u64 InvalidEntityId = 0x00000000FFFFFFFFull;
/**
* Enables this class to be identified across modules and serialized into
* different contexts.
*/
AZ_TYPE_INFO(EntityId, "{6383F1D3-BB27-4E6B-A49A-6409B2059EAA}");
/**
* Creates an entity ID instance.
* If you do not provide a value for the entity ID,
* the entity ID is set to an invalid value.
* @param id (Optional) An ID for the entity.
*/
explicit AZ_FORCE_INLINE EntityId(u64 id = InvalidEntityId)
: m_id(id)
{}
/**
* Casts the entity ID to u64.
* @return The entity ID.
*/
AZ_FORCE_INLINE explicit operator u64() const
{
return m_id;
}
/**
* Determines whether this entity ID is valid.
* An entity ID is invalid if you did not provide an argument
* to the entity ID constructor.
* @return Returns true if the entity ID is valid. Otherwise, false.
*/
AZ_FORCE_INLINE bool IsValid() const
{
return m_id != InvalidEntityId;
}
/**
* Sets the entity ID to an invalid value.
*/
AZ_FORCE_INLINE void SetInvalid()
{
m_id = InvalidEntityId;
}
/**
* Returns the entity ID as a string.
*/
AZStd::string ToString() const
{
return AZStd::string::format("[%llu]", m_id);
}
/**
* Compares two entity IDs for equality.
* @param rhs An entity ID whose value you want to compare to the
* given entity ID.
* @return True if the entity IDs are equal. Otherwise, false.
*/
AZ_FORCE_INLINE bool operator==(const EntityId& rhs) const
{
return m_id == rhs.m_id;
}
/**
* Compares two entity IDs.
* @param rhs An entity ID whose value you want to compare to the
* given entity ID.
* @return True if the entity IDs are different. Otherwise, false.
*/
AZ_FORCE_INLINE bool operator!=(const EntityId& rhs) const
{
return m_id != rhs.m_id;
}
/**
* Evaluates whether the entity ID is less than a given entity ID.
* @param rhs An entity ID whose size you want to compare to the given
* entity ID.
* @return True if the entity ID is less than the given entity ID.
* Otherwise, false.
*/
AZ_FORCE_INLINE bool operator<(const EntityId& rhs) const
{
return m_id < rhs.m_id;
}
/**
* Evaluates whether the entity ID is greater than a given entity ID.
* @param rhs An entity ID whose size you want to compare to the given
* entity ID.
* @return True if the entity ID is greater than the given entity ID.
* Otherwise, false.
*/
AZ_FORCE_INLINE bool operator>(const EntityId& rhs) const
{
return m_id > rhs.m_id;
}
protected:
/**
* Entity ID.
*/
u64 m_id;
};
/// @cond EXCLUDE_DOCS
static const EntityId SystemEntityId = EntityId(0);
/// @endcond
} // namespace AZ
namespace AZStd
{
/**
* Enables entity IDs to be keys in hashed data structures.
*/
template<>
struct hash<AZ::EntityId>
{
typedef AZ::EntityId argument_type;
typedef AZStd::size_t result_type;
AZ_FORCE_INLINE size_t operator()(const AZ::EntityId& id) const
{
AZStd::hash<AZ::u64> hasher;
return hasher(static_cast<AZ::u64>(id));
}
};
}
#endif // AZCORE_ENTITY_ID_H
#pragma once
@@ -0,0 +1,100 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/EntityId.h>
#include <AzCore/Component/EntityIdSerializer.h>
namespace AZ
{
AZ_CLASS_ALLOCATOR_IMPL(JsonEntityIdSerializer, SystemAllocator, 0);
JsonSerializationResult::Result JsonEntityIdSerializer::Load(void* outputValue, [[maybe_unused]] const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult;
AZ_Assert(azrtti_typeid<AZ::EntityId>() == outputValueTypeId,
"Unable to deserialize EntityId from json because the provided type is %s",
outputValueTypeId.ToString<AZStd::string>().c_str());
AZ::EntityId* entityIdInstance = reinterpret_cast<AZ::EntityId*>(outputValue);
AZ_Assert(entityIdInstance, "Output value for JsonEntityIdSerializer can't be null");
JSR::ResultCode result(JSR::Tasks::ReadField);
JsonEntityIdMapper** idMapper = context.GetMetadata().Find<JsonEntityIdMapper*>();
// Load the id via a mapper if provided
if (idMapper && *idMapper)
{
result.Combine((*idMapper)->MapJsonToId(*entityIdInstance, inputValue, context));
}
else if(inputValue.IsObject())
{
// Otherwise attempt to acquire the id member
auto idMember = inputValue.FindMember("id");
if (idMember != inputValue.MemberEnd())
{
AZ::ScopedContextPath subPathId(context, "id");
result.Combine(ContinueLoading(&entityIdInstance->m_id, azrtti_typeid<AZ::u64>(), idMember->value, context));
}
else
{
result.Combine(JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed));
}
}
else
{
// Default if neither mapper or id member are present
result.Combine(JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed));
}
return context.Report(result,
result.GetProcessing() == JSR::Processing::Completed ? "Succesfully loaded Entity Id information." :
"Failed to load Entity Id information.");
}
JsonSerializationResult::Result JsonEntityIdSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
[[maybe_unused]] const Uuid& valueTypeId, JsonSerializerContext& context)
{
namespace JSR = JsonSerializationResult;
AZ_Assert(azrtti_typeid<AZ::EntityId>() == valueTypeId, "Unable to Serialize Entity Id because the provided type is %s",
valueTypeId.ToString<AZStd::string>().c_str());
const EntityId* entityIdInstance = reinterpret_cast<const EntityId*>(inputValue);
AZ_Assert(entityIdInstance, "Input value for JsonEntityIdSerializer can't be null.");
const EntityId* defaultEntityIdInstance = reinterpret_cast<const EntityId*>(defaultValue);
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
JsonEntityIdMapper** idMapper = context.GetMetadata().Find<JsonEntityIdMapper*>();
// Store the id via a mapper if provided
if (idMapper && *idMapper)
{
result.Combine((*idMapper)->MapIdToJson(outputValue, *entityIdInstance, context));
}
else
{
const AZ::u64* id = &entityIdInstance->m_id;
const AZ::u64* defaultId = defaultEntityIdInstance ? &defaultEntityIdInstance->m_id : nullptr;
AZ::ScopedContextPath subPathId(context, "m_id");
result.Combine(ContinueStoringToJsonObjectField(outputValue, "id", id, defaultId, azrtti_typeid<AZ::u64>(), context));
}
}
return context.Report(result,
result.GetProcessing() == JSR::Processing::Completed ? "Succesfully stored Entity Id information." :
"Failed to store Entity Id information.");
}
}
@@ -0,0 +1,42 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
namespace AZ
{
class JsonEntityIdSerializer
: public BaseJsonSerializer
{
public:
AZ_RTTI(JsonEntityIdSerializer, "{AEA75997-087C-4E23-8E4F-465A4142EC77}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
class JsonEntityIdMapper
{
public:
AZ_RTTI(JsonEntityIdMapper, "{8E139C95-827F-45B1-BCF0-F54F2D02C594}");
virtual JsonSerializationResult::Result MapJsonToId(EntityId& outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context) = 0;
virtual JsonSerializationResult::Result MapIdToJson(rapidjson::Value& outputValue, const EntityId& inputValue, JsonSerializerContext& context) = 0;
};
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
}
@@ -0,0 +1,305 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/EntityUtils.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/std/containers/fixed_vector.h>
namespace AZ
{
namespace EntityUtils
{
//=========================================================================
// Reflect
//=========================================================================
void Reflect(ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<SerializableEntityContainer>()->
Version(1)->
Field("Entities", &SerializableEntityContainer::m_entities);
}
}
struct StackDataType
{
const SerializeContext::ClassData* m_classData;
const SerializeContext::ClassElement* m_elementData;
void* m_dataPtr;
bool m_isModifiedContainer;
};
//=========================================================================
// EnumerateEntityIds
//=========================================================================
void EnumerateEntityIds(const void* classPtr, const Uuid& classUuid, const EntityIdVisitor& visitor, SerializeContext* context)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore);
if (!context)
{
context = GetApplicationSerializeContext();
if (!context)
{
AZ_Error("Serialization", false, "No serialize context provided! Failed to get component application default serialize context! ComponentApp is not started or input serialize context should not be null!");
return;
}
}
AZStd::vector<const SerializeContext::ClassData*> parentStack;
parentStack.reserve(30);
auto beginCB = [ &](void* ptr, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* elementData) -> bool
{
(void)elementData;
if (classData->m_typeId == SerializeTypeInfo<EntityId>::GetUuid())
{
// determine if this is entity ref or just entityId (please refer to the function documentation for more info)
bool isEntityId = false;
if (!parentStack.empty() && parentStack.back()->m_typeId == SerializeTypeInfo<Entity>::GetUuid())
{
// our parent in the entity (currently entity has only one EntityId member, but we can check the offset for future proof
AZ_Assert(elementData && strcmp(elementData->m_name, "Id") == 0, "class Entity, should have only ONE EntityId member, the actual entity id!");
isEntityId = true;
}
EntityId* entityIdPtr = (elementData->m_flags & SerializeContext::ClassElement::FLG_POINTER) ?
*reinterpret_cast<EntityId**>(ptr) : reinterpret_cast<EntityId*>(ptr);
visitor(*entityIdPtr, isEntityId, elementData);
}
parentStack.push_back(classData);
return true;
};
auto endCB = [ &]() -> bool
{
parentStack.pop_back();
return true;
};
SerializeContext::EnumerateInstanceCallContext callContext(
beginCB,
endCB,
context,
SerializeContext::ENUM_ACCESS_FOR_READ,
nullptr
);
context->EnumerateInstanceConst(
&callContext,
classPtr,
classUuid,
nullptr,
nullptr
);
}
//=========================================================================
// GetApplicationSerializeContext
//=========================================================================
SerializeContext* GetApplicationSerializeContext()
{
SerializeContext* context = nullptr;
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
return context;
}
//=========================================================================
// FindFirstDerivedComponent
//=========================================================================
Component* FindFirstDerivedComponent(const Entity* entity, const Uuid& typeId)
{
for (AZ::Component* component : entity->GetComponents())
{
if (azrtti_istypeof(typeId, component))
{
return component;
}
}
return nullptr;
}
Component* FindFirstDerivedComponent(EntityId entityId, const Uuid& typeId)
{
Entity* entity{};
ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId);
return entity ? FindFirstDerivedComponent(entity, typeId) : nullptr;
}
//=========================================================================
// FindDerivedComponents
//=========================================================================
Entity::ComponentArrayType FindDerivedComponents(const Entity* entity, const Uuid& typeId)
{
Entity::ComponentArrayType result;
for (AZ::Component* component : entity->GetComponents())
{
if (azrtti_istypeof(typeId, component))
{
result.push_back(component);
}
}
return result;
}
Entity::ComponentArrayType FindDerivedComponents(EntityId entityId, const Uuid& typeId)
{
Entity* entity{};
ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId);
return entity ? FindDerivedComponents(entity, typeId) : Entity::ComponentArrayType();
}
bool EnumerateBaseRecursive(SerializeContext* context, const EnumerateBaseRecursiveVisitor& baseClassVisitor, const TypeId& typeToExamine)
{
AZ_Assert(context, "CheckDeclaresSerializeBaseClass called with no serialize context.");
if (!context)
{
return false;
}
AZStd::fixed_vector<TypeId, 64> knownBaseClasses = { typeToExamine }; // avoid allocating heap here if possible. 64 types are 64*sizeof(Uuid) which is only 1k.
bool foundBaseClass = false;
auto enumerateBaseVisitor = [&foundBaseClass, &baseClassVisitor, &knownBaseClasses](const AZ::SerializeContext::ClassData* classData, const TypeId& examineTypeId)
{
if (!classData)
{
return false;
}
if (AZStd::find(knownBaseClasses.begin(), knownBaseClasses.end(), classData->m_typeId) == knownBaseClasses.end())
{
if (knownBaseClasses.size() == 64)
{
// this should be pretty unlikely since a single class would have to have many other classes in its heirarchy
// and it'd all have to be basically in one layer, as we are popping as we explore.
AZ_WarningOnce("EntityUtils", false, "While trying to find a base class, all available slots were consumed. consider increasing the size of knownBaseClasses.\n");
// we cannot continue any further, assume we did not find it.
return false;
}
knownBaseClasses.push_back(classData->m_typeId);
}
return baseClassVisitor(classData, examineTypeId);
};
while (!knownBaseClasses.empty() && !foundBaseClass)
{
TypeId toExamine = knownBaseClasses.back();
knownBaseClasses.pop_back();
context->EnumerateBase(enumerateBaseVisitor, toExamine);
}
return foundBaseClass;
}
bool CheckIfClassIsDeprecated(SerializeContext* context, const TypeId& typeToExamine)
{
bool isDeprecated = false;
auto classVisitorFn = [&isDeprecated](const AZ::SerializeContext::ClassData* classData, const TypeId& /*rttiBase*/)
{
// Stop iterating once we stop receiving SerializeContext::ClassData*.
if (!classData)
{
return false;
}
// Stop iterating if we've found that the class is deprecated
if (classData->IsDeprecated())
{
isDeprecated = true;
return false;
}
return true; // keep iterating
};
// Check if the type is deprecated
const AZ::SerializeContext::ClassData* classData = context->FindClassData(typeToExamine);
if (classData->IsDeprecated())
{
return true;
}
// Check if any of its bases are deprecated
EnumerateBaseRecursive(context, classVisitorFn, typeToExamine);
return isDeprecated;
}
bool CheckDeclaresSerializeBaseClass(SerializeContext* context, const TypeId& typeToFind, const TypeId& typeToExamine)
{
AZ_Assert(context, "CheckDeclaresSerializeBaseClass called with no serialize context.");
if (!context)
{
return false;
}
bool foundBaseClass = false;
auto baseClassVisitorFn = [&typeToFind, &foundBaseClass](const AZ::SerializeContext::ClassData* reflectedBase, const TypeId& /*rttiBase*/)
{
if (!reflectedBase)
{
foundBaseClass = false;
return false; // stop iterating
}
foundBaseClass = (reflectedBase->m_typeId == typeToFind);
if (foundBaseClass)
{
return false; // we have a base, stop iterating
}
return true; // keep iterating
};
EnumerateBaseRecursive(context, baseClassVisitorFn, typeToExamine);
return foundBaseClass;
}
bool RemoveDuplicateServicesOfAndAfterIterator(
const ComponentDescriptor::DependencyArrayType::iterator& iterator,
ComponentDescriptor::DependencyArrayType& providedServiceArray,
const Entity* entity)
{
// Build types that strip out AZ_Warnings will complain that entity is unused without this.
(void)entity;
if (iterator == providedServiceArray.end())
{
return false;
}
bool duplicateFound = false;
for (ComponentDescriptor::DependencyArrayType::iterator duplicateCheckIter = AZStd::next(iterator);
duplicateCheckIter != providedServiceArray.end();)
{
if (*iterator == *duplicateCheckIter)
{
AZ_Warning("Entity", false, "Duplicate service %d found on entity %s [%s]",
*duplicateCheckIter,
entity ? entity->GetName().c_str() : "Entity not provided",
entity ? entity->GetId().ToString().c_str() : "");
duplicateCheckIter = providedServiceArray.erase(duplicateCheckIter);
duplicateFound = true;
}
else
{
++duplicateCheckIter;
}
}
return duplicateFound;
}
} // namespace EntityUtils
} // namespace AZ
@@ -0,0 +1,226 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_ENTITY_UTILS_H
#define AZCORE_ENTITY_UTILS_H
#include <AzCore/Component/Entity.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/functional.h>
#include <AzCore/Serialization/IdUtils.h>
namespace AZ
{
namespace EntityUtils
{
/// Return default application serialization context.
SerializeContext* GetApplicationSerializeContext();
/**
* Serializable container for entities, useful for data patching and serializer enumeration.
* Does not assume ownership of stored entities.
*/
struct SerializableEntityContainer
{
AZ_CLASS_ALLOCATOR(SerializableEntityContainer, SystemAllocator, 0);
AZ_TYPE_INFO(SerializableEntityContainer, "{E98CF1B5-6B72-46C5-AB87-3DB85FD1B48D}");
AZStd::vector<AZ::Entity*> m_entities;
};
/**
* Reflect entity utils data types.
*/
void Reflect(ReflectContext* context);
/**
* Given key, return the EntityId to map to
*/
typedef AZStd::function< EntityId(const EntityId& /*originalId*/, bool /*isEntityId*/) > EntityIdMapper;
typedef AZStd::function< void(const AZ::EntityId& /*id*/, bool /*isEntityId*/, const AZ::SerializeContext::ClassElement* /*elementData*/) > EntityIdVisitor;
/**
* Enumerates all entity references in the object's hierarchy and remaps them with the result returned by mapper.
* "entity reference" is considered any EntityId type variable, except Entity::m_id. Entity class had only one EntityId member (m_id)
* which represents an actual entity id, every other stored EntityId is considered a reference and will be remapped if when needed.
* What if I want to store and EntityId that should never be remapped. You should NOT do that, as we clone entities a lot and remap all
* references to maintain the same behavior. If you really HAVE TO store entity id which is not no be remapped, just use "u64" type variable.
*/
template<class T>
unsigned int ReplaceEntityRefs(T* classPtr, const EntityIdMapper& mapper, SerializeContext* context = nullptr)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore);
auto idMapper = [&mapper](const EntityId& originalId, bool isEntityId, const IdUtils::Remapper<EntityId>::IdGenerator&) -> EntityId
{
return mapper(originalId, isEntityId);
};
return IdUtils::Remapper<EntityId>::RemapIds(classPtr, SerializeTypeInfo<T>::GetUuid(classPtr), idMapper, context, false);
}
/**
* Enumerates all entity references in the object's hierarchy and invokes the specified visitor.
* \param classPtr - the object instance to enumerate.
* \param classUuid - the object instance's type Id.
* \param visitor - the visitor callback to be invoked for Entity::m_id (isEntityId==true) or reflected EntityId field, aka reference (isEntityId==false).
*/
void EnumerateEntityIds(const void* classPtr, const Uuid& classUuid, const EntityIdVisitor& visitor, SerializeContext* context = nullptr);
template<class T>
void EnumerateEntityIds(const T* classPtr, const EntityIdVisitor& visitor, SerializeContext* context = nullptr)
{
EnumerateEntityIds(classPtr, SerializeTypeInfo<T>::GetUuid(classPtr), visitor, context);
}
/**
* Replaces all entity ids in the object's hierarchy and remaps them with the result returned by the mapper.
* "entity id" is only Entity::m_id every other EntityId variable is considered a reference \ref ReplaceEntityRefs
*/
template<class T>
unsigned int ReplaceEntityIds(T* classPtr, const EntityIdMapper& mapper, SerializeContext* context = nullptr)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore);
auto idMapper = [&mapper](const EntityId& originalId, bool isEntityId, const IdUtils::Remapper<EntityId>::IdGenerator&) -> EntityId
{
return mapper(originalId, isEntityId);
};
return IdUtils::Remapper<EntityId>::RemapIds(classPtr, SerializeTypeInfo<T>::GetUuid(classPtr), idMapper, context, true);
}
/**
* Replaces all EntityId objects (entity ids and entity refs) in the object's hierarchy.
*/
template<class T>
unsigned int ReplaceEntityIdsAndEntityRefs(T* classPtr, const EntityIdMapper& mapper, SerializeContext* context = nullptr)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore);
auto idMapper = [&mapper](const EntityId& originalId, bool isEntityId, const IdUtils::Remapper<EntityId>::IdGenerator&) -> EntityId
{
return mapper(originalId, isEntityId);
};
return IdUtils::Remapper<EntityId>::ReplaceIdsAndIdRefs(classPtr, idMapper, context);
}
/**
* Generate new entity ids, and remap all reference
*/
template<class T, class MapType>
void GenerateNewIdsAndFixRefs(T* object, MapType& newIdMap, SerializeContext* context = nullptr)
{
IdUtils::Remapper<EntityId>::GenerateNewIdsAndFixRefs(object, newIdMap, context);
}
/**
* Clone the object T, generate new ids for all entities in the hierarchy, and fix all entityId.
*/
template<class T, class Map>
T* CloneObjectAndFixEntities(const T* object, Map& newIdMap, SerializeContext* context = nullptr)
{
return IdUtils::Remapper<EntityId>::CloneObjectAndGenerateNewIdsAndFixRefs(object, newIdMap, context);
}
/**
* Clones entities, generates new entityIds \ref ReplaceEntityIds and fixes all entity references \ref ReplaceEntityRefs
*/
template<class InputIterator, class OutputIterator, class TempAllocatorType >
void CloneAndFixEntities(InputIterator first, InputIterator last, OutputIterator result, const TempAllocatorType& allocator, SerializeContext* context = nullptr)
{
for (; first != last; ++first, ++result)
{
*result = CloneObjectAndFixEntities(*first, allocator, context);
}
}
template<class InputIterator, class OutputIterator>
void CloneAndFixEntities(InputIterator first, InputIterator last, OutputIterator result, SerializeContext* context = nullptr)
{
for (; first != last; ++first, ++result)
{
*result = CloneObjectAndFixEntities(*first, context);
}
}
/// Return the first component that is either of the specified type or derive from the specified type
Component* FindFirstDerivedComponent(const Entity* entity, const Uuid& typeId);
Component* FindFirstDerivedComponent(EntityId entityId, const Uuid& typeId);
/// Return the first component that is either of the specified type or derive from the specified type
template<class ComponentType>
inline ComponentType* FindFirstDerivedComponent(const Entity* entity)
{
return azrtti_cast<ComponentType*>(FindFirstDerivedComponent(entity, AzTypeInfo<ComponentType>::Uuid()));
}
template<class ComponentType>
inline ComponentType* FindFirstDerivedComponent(EntityId entityId)
{
Entity* entity{};
ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId);
return entity ? FindFirstDerivedComponent<ComponentType>(entity): nullptr;
}
/// Return a vector of all components that are either of the specified type or derive from the specified type
Entity::ComponentArrayType FindDerivedComponents(const Entity* entity, const Uuid& typeId);
Entity::ComponentArrayType FindDerivedComponents(EntityId entityId, const Uuid& typeId);
/// Return a vector of all components that are either of the specified type or derive from the specified type
template<class ComponentType>
inline AZStd::vector<ComponentType*> FindDerivedComponents(Entity* entity)
{
AZStd::vector<ComponentType*> result;
for (AZ::Component* component : entity->GetComponents())
{
auto derivedComponent = azrtti_cast<ComponentType*>(component);
if (derivedComponent)
{
result.push_back(derivedComponent);
}
}
return result;
}
template<class ComponentType>
inline AZStd::vector<ComponentType*> FindDerivedComponents(EntityId entityId)
{
Entity* entity{};
ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId);
return entity ? FindDerivedComponents<ComponentType>(entity) : AZStd::vector<ComponentType*>();
}
using EnumerateBaseRecursiveVisitor = AZStd::function< bool(const SerializeContext::ClassData*, const Uuid&)>;
bool EnumerateBaseRecursive(SerializeContext* context, const EnumerateBaseRecursiveVisitor& baseClassVisitor, const TypeId& typeToExamine);
//! Performs a recursive search of all classes declared in the serialize hierarchy of typeToExamine
//! and returns true it has been marked as deprecated, false otherwise.
bool CheckIfClassIsDeprecated(SerializeContext* context, const TypeId& typeToExamine);
//! performs a recursive search of all classes declared in the serialize hierarchy of typeToExamine
//! and returns true if it finds typeToFind, false otherwise.
bool CheckDeclaresSerializeBaseClass(SerializeContext* context, const TypeId& typeToFind, const TypeId& typeToExamine);
//! Checks if the provided service array has any duplicates of the iterator, after that iterator.
//! If a duplicate is found, a warning is given and the duplicate is removed from the providedServiceArray.
//! The caller is responsible for verifying the iterator is for the array provided.
//! \param iterator The iterator to start from for checking for duplicates.
//! \param providedServiceArray The container of services to scan for duplicates.
//! \param entity An optional associated entity, used in error reporting.
//! \return True if a duplicate service was found, false if not.
bool RemoveDuplicateServicesOfAndAfterIterator(
const ComponentDescriptor::DependencyArrayType::iterator& iterator,
ComponentDescriptor::DependencyArrayType& providedServiceArray,
const Entity* entity);
} // namespace EntityUtils
} // namespace AZ
#endif // AZCORE_ENTITY_UTILS_H
#pragma once
@@ -0,0 +1,104 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/NamedEntityId.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
//=========================================================================
// NamedEntityId
//=========================================================================
NamedEntityId::NamedEntityId()
: m_entityName("<Unknown>")
{
}
NamedEntityId::NamedEntityId(const AZ::EntityId& entityId, AZStd::string_view entityName)
: EntityId(entityId)
, m_entityName(entityName)
{
if (entityId.IsValid() && m_entityName.empty())
{
AZ::Entity* entity = nullptr;
ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId);
if (entity)
{
m_entityName = entity->GetName();
}
}
}
void NamedEntityId::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<NamedEntityId, EntityId>()
->Version(0)
->Field("name", &NamedEntityId::m_entityName)
;
}
}
AZStd::string NamedEntityId::ToString() const
{
return AZStd::string::format("%s [%llu]", m_entityName.c_str(), static_cast<AZ::u64>(static_cast<AZ::EntityId>(*this)));
}
AZStd::string_view NamedEntityId::GetName() const
{
return m_entityName;
}
bool NamedEntityId::operator==(const NamedEntityId& rhs) const
{
return EntityId::operator==(static_cast<const EntityId&>(rhs));
}
bool NamedEntityId::operator==(const EntityId& rhs) const
{
return EntityId::operator==(rhs);
}
bool NamedEntityId::operator!=(const NamedEntityId& rhs) const
{
return EntityId::operator!=(static_cast<const EntityId&>(rhs));
}
bool NamedEntityId::operator!=(const EntityId& rhs) const
{
return EntityId::operator!=(rhs);
}
bool NamedEntityId::operator<(const NamedEntityId& rhs) const
{
return EntityId::operator<(static_cast<const EntityId&>(rhs));
}
bool NamedEntityId::operator<(const EntityId& rhs) const
{
return EntityId::operator<(rhs);
}
bool NamedEntityId::operator>(const NamedEntityId& rhs) const
{
return EntityId::operator>(static_cast<const EntityId&>(rhs));
}
bool NamedEntityId::operator>(const EntityId& rhs) const
{
return EntityId::operator>(rhs);
}
} // namespace AZ
@@ -0,0 +1,75 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Memory/SystemAllocator.h>
namespace AZ
{
class ReflectContext;
class NamedEntityId
: public EntityId
{
public:
AZ_CLASS_ALLOCATOR(NamedEntityId, AZ::SystemAllocator, 0);
AZ_RTTI(NamedEntityId, "{27F37921-4B40-4BE6-B47B-7D3AB8682D58}", EntityId);
static void Reflect(AZ::ReflectContext* context);
NamedEntityId();
NamedEntityId(const AZ::EntityId& entityId, AZStd::string_view entityName = "");
virtual ~NamedEntityId() = default;
AZStd::string ToString() const;
AZStd::string_view GetName() const;
bool operator==(const NamedEntityId& rhs) const;
bool operator==(const EntityId& rhs) const;
bool operator!=(const NamedEntityId& rhs) const;
bool operator!=(const EntityId& rhs) const;
bool operator<(const NamedEntityId& rhs) const;
bool operator<(const EntityId& rhs) const;
bool operator>(const NamedEntityId& rhs) const;
bool operator>(const EntityId& rhs) const;
private:
AZStd::string m_entityName;
};
} // namespace AZ
namespace AZStd
{
/**
* Enables entity IDs to be keys in hashed data structures.
*/
template<>
struct hash<AZ::NamedEntityId>
{
typedef AZ::NamedEntityId argument_type;
typedef AZStd::size_t result_type;
AZ_FORCE_INLINE size_t operator()(const AZ::NamedEntityId& namedId) const
{
return AZStd::hash<AZ::EntityId>()(namedId);
}
};
} // namespace AZStd
@@ -0,0 +1,32 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/NonUniformScaleBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Math/Vector3.h>
namespace AZ
{
void NonUniformScaleRequests::Reflect(ReflectContext* context)
{
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->EBus<NonUniformScaleRequestBus>("NonUniformScaleRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(Script::Attributes::Category, "Entity")
->Attribute(Script::Attributes::Module, "entity")
->Event("GetScale", &NonUniformScaleRequestBus::Events::GetScale)
->Event("SetScale", &NonUniformScaleRequestBus::Events::SetScale)
;
}
}
} // namespace AZ
@@ -0,0 +1,43 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/EBus/Event.h>
namespace AZ
{
class Vector3;
using NonUniformScaleChangedEvent = AZ::Event<const AZ::Vector3&>;
//! Requests for working with non-uniform scale.
class NonUniformScaleRequests
: public AZ::ComponentBus
{
public:
static const EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Single;
static void Reflect(AZ::ReflectContext* context);
//! Gets the non-uniform scale.
virtual AZ::Vector3 GetScale() const = 0;
//! Sets the non-uniform scale.
virtual void SetScale(const Vector3& scale) = 0;
//! Registers a handler to be notified when the non-uniform scale is changed.
virtual void RegisterScaleChangedEvent(NonUniformScaleChangedEvent::Handler& handler) = 0;
};
using NonUniformScaleRequestBus = AZ::EBus<NonUniformScaleRequests>;
} // namespace AZ
@@ -0,0 +1,245 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
/** @file
* Header file for buses that dispatch tick notification events
* and receive tick-related requests.
* A tick is a unit of time generated by the application.
*/
#ifndef AZCORE_COMPONENT_TICK_BUS_H
#define AZCORE_COMPONENT_TICK_BUS_H
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Debug/AssetTracking.h>
#include <AzCore/std/chrono/chrono.h>
#include <AzCore/std/parallel/mutex.h> // For TickBus thread events.
#include <AzCore/Script/ScriptTimePoint.h>
namespace AZ
{
/**
* Values to help you set when a particular handler is notified of ticks.
*/
enum ComponentTickBus
{
TICK_FIRST = 0, ///< First position in the tick handler order.
TICK_PLACEMENT = 50, ///< Suggested tick handler position for components that need to be early in the tick order.
TICK_INPUT = 75, ///< Suggested tick handler position for input components.
TICK_GAME = 80, ///< Suggested tick handler for game-related components.
TICK_ANIMATION = 100, ///< Suggested tick handler position for animation components.
TICK_PHYSICS_SYSTEM = 200, ///< Suggested tick handler position for physics systems. Note: This should only be used for the Physics System.
TICK_PHYSICS = TICK_PHYSICS_SYSTEM + 1, ///< Suggested tick handler position for physics components
TICK_ATTACHMENT = 500, ///< Suggested tick handler position for attachment components.
TICK_PRE_RENDER = 750, ///< Suggested tick handler position to update render-related data.
TICK_DEFAULT = 1000, ///< Default tick handler position when the handler is constructed.
TICK_UI = 2000, ///< Suggested tick handler position for UI components.
TICK_LAST = 100000, ///< Last position in the tick handler order.
};
/**
* Interface for AZ::TickBus, which is the EBus that dispatches tick events.
* These tick events are executed on the main game thread. In games, AZ::TickBus
* dispatches ticks even if the application is not in focus. In tools, AZ::TickBus
* can become inactive when the tool loses focus.
* @note Do not add a mutex to TickEvents. It is unnecessary and typically degrades performance.
*/
class TickEvents
: public AZ::EBusTraits
{
public:
AZ_RTTI(TickEvents, "{DF79B555-D9E9-489A-8A00-AD39C564E258}");
/**
* Creates an instance of the class and sets the tick order of the
* handler to the default value.
*/
TickEvents()
: m_tickOrder(TICK_DEFAULT) {}
/**
* Destroys the instance of the class.
*/
virtual ~TickEvents() {}
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides - application is a singleton
/**
* Overrides the default AZ::EBusTraits handler policy so that multiple
* handlers can connect to the bus. This bus has one address because it
* uses the default EBusTraits address policy. At the address, handlers
* receive events based on the order in which the components are initialized,
* unless a handler explicitly sets its TickEvents::m_tickOrder.
*/
static const AZ::EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::MultipleAndOrdered;
/**
* Enables the event queue, which you can use to execute actions just before the OnTick event.
*/
static const bool EnableEventQueue = true;
/**
* Specifies the mutex that is used when adding and removing events from the event queue.
* This mutex is for the event queue, not TickEvents. Do not add a mutex to TickEvents.
*/
typedef AZStd::recursive_mutex EventQueueMutexType;
/**
* Determines the order in which handlers receive tick events.
* Handlers receive events based on the order in which the components are initialized,
* unless a handler explicitly sets its position.
*/
struct BusHandlerOrderCompare
{
AZ_FORCE_INLINE bool operator()(TickEvents* left, TickEvents* right) const { return left->GetTickOrder() < right->GetTickOrder(); }
};
/**
* Enable tick bus to work with the AssetTracking
*/
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
//////////////////////////////////////////////////////////////////////////
/**
* Signals that the application has issued a tick.
* @param deltaTime The delta (in seconds) from the previous tick and the current time.
* @param time The current time.
*/
virtual void OnTick(float deltaTime, ScriptTimePoint time) = 0;
/**
* Specifies The order in which a handler receives tick events relative to other handlers.
* This value should not be changed while the handler is connected.
* See the ComponentTickBus enum for recommended values.
* @return a value specifying this handler's relative order.
*/
virtual int GetTickOrder()
{
// m_tickOrder is deprecated, respect it for the time being but warn if it's used.
AZ_Warning("TickBus", m_tickOrder == TICK_DEFAULT, "TickBus::Handler::m_tickOrder has been deprecated, implement GetTickOrder() instead.");
return m_tickOrder;
}
protected:
// Only the component application is allowed to issue ticks.
friend class ComponentApplication;
/**
* Deprecated.
* @deprecated Override GetTickOrder() to specify the order in which the handler receives tick events.
*/
int m_tickOrder;
};
/**
* The EBus for tick notification events.
* The events are defined in the AZ::TickEvents class.
*/
typedef AZ::EBus<TickEvents> TickBus;
/**
* Interface for AZ::TickRequestBus, which components use to make tick-related
* requests.
* Available requests are to get the time between ticks or the current time in seconds.
*/
class TickRequests
: public AZ::EBusTraits
{
public:
/**
* Gets the latest time between ticks.
*/
virtual float GetTickDeltaTime() = 0;
/**
* Gets the time in seconds since the epoch.
*/
virtual ScriptTimePoint GetTimeAtCurrentTick() = 0;
};
/**
* The EBus for tick-related requests.
* The events are defined in the AZ::TickRequests class.
*/
typedef AZ::EBus<TickRequests> TickRequestBus;
/**
* Interface for AZ::SystemTickBus, which is the EBus that dispatches system tick events.
* System tick events are dispatched at some interval of a small number of milliseconds,
* even when the host application does not have focus. It can be be used for anything that needs to be serviced
* regularly, such as network or asset processor polling.
* Note that it does not necessarily occur at a consistent interval. In some tools, such as the Editor,
* OnSystemTick() can be called more often than the regular interval.
* If timing matters, use TickEvents::OnTick() instead.
* @note Do not add a mutex to SystemTickEvents. It is unnecessary and typically degrades performance.
*/
class SystemTickEvents : public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides - application is a singleton
/**
* Overrides the default AZ::EBusTraits handler policy so that multiple
* handlers can connect to the bus. This bus has one address because it
* uses the default EBusTraits address policy.
*/
static const AZ::EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Multiple;
/**
* Enables the event queue, which you can use to execute actions just before the OnSystemTick event.
*/
static const bool EnableEventQueue = true;
/**
* Specifies the mutex that is used when adding and removing events from the event queue.
* This mutex is for the event queue, not SystemTickEvents. Do not add a mutex to SystemTickEvents.
*/
typedef AZStd::mutex EventQueueMutexType;
/**
* Enable tick bus to work with the AssetTracking
*/
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
//////////////////////////////////////////////////////////////////////////
/**
* Signals that the application has issued a system tick.
*/
virtual void OnSystemTick() = 0;
};
/**
* The EBus for system tick notification events.
* The events are defined in the AZ::SystemTickEvents class.
*/
using SystemTickBus = AZ::EBus<SystemTickEvents>;
}
#endif // AZCORE_COMPONENT_TICK_BUS_H
#pragma once
@@ -0,0 +1,826 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
/** @file
* Header file for buses that dispatch and receive events related to positioning,
* rotating, scaling, and parenting an entity.
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Math/InterpolationSample.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/EBus/Event.h>
namespace AZ
{
class Transform;
/**
* Interface for AZ::TransformBus, which is an EBus that receives requests
* to translate (position), rotate, and scale an entity in 3D space. It
* also receives requests to get and set the parent of an entity and get
* the descendants of an entity.
*
* An entity's local transform is the entity's position relative to its
* parent entity. An entity's world transform is the entity's position
* within the entire game space.
*/
class TransformInterface
: public ComponentBus
{
public:
AZ_RTTI(TransformInterface, "{8DD8A4E2-7F61-4A36-9169-A31F03E25FEB}");
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides - application is a singleton.
/**
* Overrides the default AZ::EBusTraits handler policy to allow one
* listener only.
*/
static const EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Single;
//////////////////////////////////////////////////////////////////////////
/**
* Destroys the instance of the class.
*/
virtual ~TransformInterface() {}
//////////////////////////////////////////////////////////////////////////
// Transform modifiers
/**
* Returns the entity's local transform, not including the parent transform.
* @return A reference to a transform that represents the entity's position
* relative to its parent entity.
*/
virtual const Transform& GetLocalTM() = 0;
/**
* Sets the entity's local transform and notifies all listeners.
* @param tm A reference to a transform for positioning the entity
* relative to its parent entity.
*/
virtual void SetLocalTM(const Transform& /*tm*/) {}
/**
* Returns the entity's world transform, including the parent transform.
* @return A reference to a transform that represents the entity's position
* within the world.
*/
virtual const Transform& GetWorldTM() = 0;
/**
* Sets the world transform and notifies all listeners.
* @param tm A reference to a transform for positioning the entity
* within the world.
*/
virtual void SetWorldTM(const Transform& /*tm*/) {}
/**
* Retrieves the entity's local and world transforms.
* @param[out] localTM A reference to a transform that represents the entity's
* position relative to its parent entity.
* @param[out] worldTM A reference to a transform that represents the entity's
* position within the world.
*/
virtual void GetLocalAndWorld(Transform& /*localTM*/, Transform& /*worldTM*/) {}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Translation modifiers
/**
* Sets the entity's world space translation, which represents how
* to move the entity to a new position within the world.
* @param newPosition A three-dimensional translation vector.
*/
virtual void SetWorldTranslation(const AZ::Vector3& /*newPosition*/) {}
/**
* Sets the entity's local space translation, which represents how to move the
* entity to a new position relative to its parent.
* @param newPosition A three-dimensional translation vector.
*/
virtual void SetLocalTranslation(const AZ::Vector3& /*newPosition*/) {}
/**
* Gets the entity's world space translation.
* @return A three-dimensional translation vector.
*/
virtual AZ::Vector3 GetWorldTranslation() { return AZ::Vector3(FLT_MAX); }
/**
* Gets the entity's local space translation.
* @return A three-dimensional translation vector.
*/
virtual AZ::Vector3 GetLocalTranslation() { return AZ::Vector3(FLT_MAX); }
/**
* Moves the entity within world space.
* @param offset A three-dimensional vector that contains the offset
* to apply to the entity.
*/
virtual void MoveEntity(const AZ::Vector3& /*offset*/) {}
/**
* Sets the entity's X coordinate in world space.
* @param x A new value for the entity's X coordinate in world space.
*/
virtual void SetWorldX(float /*x*/) {}
/**
* Sets the entity's Y coordinate in world space.
* @param y A new value for the entity's Y coordinate in world space.
*/
virtual void SetWorldY(float /*y*/) {}
/**
* Sets the entity's Z coordinate in world space.
* @param z A new value for the entity's Z coordinate in world space.
*/
virtual void SetWorldZ(float /*z*/) {}
/**
* Gets the entity's X coordinate in world space.
* @return The entity's X coordinate in world space.
*/
virtual float GetWorldX() { return FLT_MAX; }
/**
* Gets the entity's Y coordinate in world space.
* @return The entity's Y coordinate in world space.
*/
virtual float GetWorldY() { return FLT_MAX; }
/**
* Gets the entity's Z coordinate in world space.
* @return The entity's Z coordinate in world space.
*/
virtual float GetWorldZ() { return FLT_MAX; }
/**
* Sets the entity's X coordinate in local space.
* @param x A new value for the entity's X coordinate in local space.
*/
virtual void SetLocalX(float /*x*/) {}
/**
* Sets the entity's Y coordinate in local space.
* @param y A new value for the entity's Y coordinate in local space.
*/
virtual void SetLocalY(float /*y*/) {}
/**
* Sets the entity's Z coordinate in local space.
* @param z A new value for the entity's Z coordinate in local space.
*/
virtual void SetLocalZ(float /*z*/) {}
/**
* Gets the entity's X coordinate in local space.
* @return The entity's X coordinate in local space.
*/
virtual float GetLocalX() { return FLT_MAX; }
/**
* Gets the entity's Y coordinate in local space.
* @return The entity's Y coordinate in local space.
*/
virtual float GetLocalY() { return FLT_MAX; }
/**
* Gets the entity's Z coordinate in local space.
* @return The entity's Z coordinate in local space.
*/
virtual float GetLocalZ() { return FLT_MAX; }
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Rotation modifiers
/**
* @deprecated Use SetLocalRotation()
* Sets the entity's rotation in the world. The origin of the axes
* is the entity's position in world space.
* @param eulerAnglesRadians A three-dimensional vector, containing Euler
* angles in radians, to rotate the entity by.
*/
virtual void SetRotation(const AZ::Vector3& /*eulerAnglesRadians*/) {}
/**
* @deprecated Use SetLocalRotation()
* Sets the entity's rotation around the world's X axis.
* The origin of the axis is the entity's position in world space.
* @param eulerAngleRadians The X coordinate Euler angle in radians to use
* for the entity's rotation.
*/
virtual void SetRotationX(float /*eulerAngleRadian*/) {}
/**
* @deprecated Use SetLocalRotation()
* Sets the entity's rotation around the world's Y axis.
* The origin of the axis is the entity's position in world space.
* @param eulerAngleRadians The Y coordinate Euler angle in radians to use
* for the entity's rotation.
*/
virtual void SetRotationY(float /*eulerAngleRadian*/) {}
/**
* @deprecated Use SetLocalRotation()
* Sets the entity's rotation around the world's Z axis.
* The origin of the axis is the entity's position in world space.
* @param eulerAngleRadians The Z coordinate Euler angle in radians to use
* for the entity's rotation.
*/
virtual void SetRotationZ(float /*eulerAngleRadian*/) {}
/**
* @deprecated Use SetLocalRotationQuaternion()
* Sets the entity's rotation in the world in quaternion notation.
* The origin of the axes is the entity's position in world space.
* @param quaternion A quaternion that represents the rotation to
* use for the entity.
*/
virtual void SetRotationQuaternion(const AZ::Quaternion& /*quaternion*/) {}
/**
* @deprecated Use RotateAroundLocalX()
* Rotates the entity around the world's X axis.
* The origin of the axis is the entity's position in world space.
* @param eulerAngleRadians The Euler angle in radians by which to rotate
* the entity around the X axis.
*/
virtual void RotateByX(float /*eulerAngleRadian*/) {}
/**
* @deprecated Use RotateAroundLocalY()
* Rotates the entity around the world's Y axis.
* The origin of the axis is the entity's position in world space.
* @param eulerAngleRadians The Euler angle in radians by which to rotate
* the entity around the Y axis.
*/
virtual void RotateByY(float /*eulerAngleRadian*/) {}
/**
* @deprecated Use RotateAroundLocalZ()
* Rotates the entity around the world's Z axis.
* The origin of the axis is the entity's position in world space.
* @param eulerAngleRadians The Euler angle in radians by which to rotate
* the entity around the Z axis.
*/
virtual void RotateByZ(float /*eulerAngleRadian*/) {}
/**
* @deprecated Use GetLocalRotation()
* Gets the entity's rotation in the world in Euler angles
* notation in radians.
* @return A three-dimensional vector, containing Euler
* angles in radians, that represents the entity's rotation.
*/
virtual AZ::Vector3 GetRotationEulerRadians() { return AZ::Vector3(FLT_MAX); }
/**
* @deprecated Use GetLocalRotationQuaternion()
* Gets the entity's rotation in the world in quaternion format.
* @return A quaternion that represents the entity's rotation in world space.
*/
virtual AZ::Quaternion GetRotationQuaternion() { return AZ::Quaternion::CreateZero(); }
/**
* @deprecated Use GetLocalRotation()
* Gets the entity's rotation around the world's X axis.
* @return The Euler angle in radians by which the the entity is rotated
* around the X axis in world space.
*/
virtual float GetRotationX() { return FLT_MAX; }
/**
* @deprecated Use GetLocalRotation()
* Gets the entity's rotation around the world's Y axis.
* @return The Euler angle in radians by which the the entity is rotated
* around the Y axis in world space.
*/
virtual float GetRotationY() { return FLT_MAX; }
/**
* @deprecated Use GetLocalRotation()
* Gets the entity's rotation around the world's Z axis.
* @return The Euler angle in radians by which the the entity is rotated
* around the Z axis in world space.
*/
virtual float GetRotationZ() { return FLT_MAX; }
/**
* Get angles in radian for each principle axis around which the world transform is
* rotated in the order of z-axis and y-axis and then x-axis.
* @return The Euler angles in radian indicating how much is rotated around each principle axis.
*/
virtual AZ::Vector3 GetWorldRotation() { return AZ::Vector3(FLT_MAX); }
/**
* Get the quaternion representing the world rotation.
* @return The Rotation quaternion in world space.
*/
virtual AZ::Quaternion GetWorldRotationQuaternion() { return AZ::Quaternion::CreateZero(); }
/**
* Set the local rotation matrix using the composition of rotations around
* the principle axes in the order of z-axis first and y-axis and then x-axis.
* @param eulerRadianAngles A Vector3 denoting radian angles of the rotations around each principle axis.
*/
virtual void SetLocalRotation(const AZ::Vector3& /*eulerRadianAngles*/) {}
/**
* Set the local rotation matrix using a quaternion.
* @param quaternion A quaternion representing the rotation to set.
*/
virtual void SetLocalRotationQuaternion(const AZ::Quaternion& /*quaternion*/) {}
/**
* Rotate around the local x-axis for a radian angle.
* @param eulerRadianAngle The angle to rotate around the local x-axis.
*/
virtual void RotateAroundLocalX(float /*eulerAngleRadian*/) {}
/**
* Rotate around the local y-axis for a radian angle.
* @param eulerRadianAngle The angle to rotate around the local y-axis.
*/
virtual void RotateAroundLocalY(float /*eulerAngleRadian*/) {}
/**
* Rotate around the local z-axis for a radian angle.
* @param eulerRadianAngle The angle to rotate around the local z-axis.
*/
virtual void RotateAroundLocalZ(float /*eulerAngleRadian*/) {}
/**
* Get angles in radian for each principle axis around which the local transform is
* rotated in the order of x-axis and y-axis and then z-axis.
* @return A value of type Vector3 indicating how much in radian is rotated around each principle axis.
*/
virtual AZ::Vector3 GetLocalRotation() { return AZ::Vector3(FLT_MAX); }
/**
* Get the quaternion representing the local rotation.
* @return The rotation quaternion in local space.
*/
virtual AZ::Quaternion GetLocalRotationQuaternion() { return AZ::Quaternion::CreateZero(); }
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Scale Modifiers
/**
* @deprecated Use SetLocalScale()
* Scales the entity along the world's axes. The origin of the axes
* is the entity's position in the world.
* @param scale A three-dimensional vector that represents the
* multipliers with which to scale the entity in world space.
*/
virtual void SetScale(const AZ::Vector3& /*scale*/) {}
/**
* @deprecated Use SetLocalScaleX()
* Scales the entity along the world's X axis. The origin of the axis
* is the entity's position in the world.
* @param scaleX The multiplier by which to scale the entity
* along the X axis in world space.
*/
virtual void SetScaleX(float /*scaleX*/) {}
/**
* @deprecated Use SetLocalScaleY()
* Scales the entity along the world's Y axis. The origin of the axis
* is the entity's position in the world.
* @param scaleY The multiplier by which to scale the entity
* along the Y axis in world space.
*/
virtual void SetScaleY(float /*scaleY*/) {}
/**
* @deprecated Use SetLocalScaleZ()
* Scales the entity along the world's Z axis. The origin of the axis
* is the entity's position in the world.
* @param scaleZ The multiplier by which to scale the entity
* along the Z axis in world space.
*/
virtual void SetScaleZ(float /*scaleZ*/) {}
/**
* @deprecated Use GetLocalScale()
* Gets the scale of the entity in world space.
* @return A three-dimensional vector that represents the
* scale of the entity in world space.
*/
virtual AZ::Vector3 GetScale() { return AZ::Vector3(FLT_MAX); }
/**
* @deprecated Use GetLocalScale()
* Gets the amount by which an entity is scaled along the
* world's X axis.
* @return The amount by which an entity is scaled along the
* X axis in world space.
*/
virtual float GetScaleX() { return FLT_MAX; }
/**
* @deprecated Use GetLocalScale()
* Gets the amount by which an entity is scaled along the
* world's Y axis.
* @return The amount by which an entity is scaled along the
* Y axis in world space.
*/
virtual float GetScaleY() { return FLT_MAX; }
/**
* @deprecated Use GetLocalScale()
* Gets the amount by which an entity is scaled along the
* world's Z axis.
* @return The amount by which an entity is scaled along the
* Z axis in world space.
*/
virtual float GetScaleZ() { return FLT_MAX; }
/**
* Set local scale of the transform.
* @param scale The new scale to set along three local axes.
*/
virtual void SetLocalScale(const AZ::Vector3& /*scale*/) {}
/**
* Set local scale of the transform on x-axis.
* @param scaleX The new x-axis scale to set.
*/
virtual void SetLocalScaleX(float /*scaleX*/) {}
/**
* Set local scale of the transform on y-axis.
* @param scaleY The new y-axis scale to set.
*/
virtual void SetLocalScaleY(float /*scaleY*/) {}
/**
* Set local scale of the transform on z-axis.
* @param scaleZ The new z-axis scale to set.
*/
virtual void SetLocalScaleZ(float /*scaleZ*/) {}
/**
* Get the scale value on each axis in local space
* @return The scale value of type Vector3 along each axis in local space.
*/
virtual AZ::Vector3 GetLocalScale() { return AZ::Vector3(FLT_MAX); }
/**
* Get the scale value on each axis in world space.
* Note the transform will be skewed when it is rotated and has a parent transform scaled, in which
* case the returned world-scale from this function will be inaccurate.
* @return The scale value of type Vector3 along each axis in world space.
*/
virtual AZ::Vector3 GetWorldScale() { return AZ::Vector3(FLT_MAX); }
//////////////////////////////////////////////////////////////////////////
/**
* Returns the entity ID of the entity's parent.
* @return The entity ID of the parent. The entity ID is invalid if the
* entity does not have a parent with a valid entity ID.
*/
virtual EntityId GetParentId() { return EntityId(); }
/**
* Returns the transform interface of the parent entity.
* @return A pointer to the transform interface of the parent.
* Returns a null pointer if no parent is set or the parent
* entity is not currently activated.
*/
virtual TransformInterface* GetParent() { return nullptr; }
/**
* Sets the entity's parent entity and notifies all listeners.
* The entity's local transform is moved into the parent entity's space
* to preserve the entity's world transform.
* @param id The ID of the entity to set as the parent.
*/
virtual void SetParent(EntityId /*id*/) {}
/**
* Sets the entity's parent entity, moves the transform relative to the
* parent entity, and notifies all listeners.
* This function uses the world transform as a local transform and moves the
* transform relative to the parent entity.
* @param id The ID of the entity to set as the parent.
*/
virtual void SetParentRelative(EntityId /*id*/) {}
/**
* Returns the entity IDs of the entity's immediate children.
* @return A vector that contains the entity IDs of the entity's immediate children.
*/
virtual AZStd::vector<AZ::EntityId> GetChildren() { return AZStd::vector<AZ::EntityId>(); };
/**
* Returns the entity IDs of all descendants of the entity. The descendants
* are the entity's children, the children's children, and so on.
* The entity IDs are ordered breadth-first.
* @return A vector that contains the entity IDs of the descendants.
*/
virtual AZStd::vector<AZ::EntityId> GetAllDescendants() { return AZStd::vector<AZ::EntityId>(); };
/**
* Returns the entity ID of the entity and all its descendants. The descendants
* are the entity's children, the children's children, and so on. The entity IDs
* are ordered breadth-first and this entity's ID is the first in the list.
* @return A vector that contains the entity ID of the entity followed by the entity
* IDs of its descendants.
*/
virtual AZStd::vector<AZ::EntityId> GetEntityAndAllDescendants() { return AZStd::vector<AZ::EntityId>(); }
/**
* Returns whether the transform is static.
* A static transform is unmovable and does not respond to requests that would move it.
* @return True if the transform is static, false if the transform is movable.
*/
virtual bool IsStaticTransform() = 0;
/**
* Set the transform to isStatic. This is needed to set a layer as static.
* A static transform is unmovable and does not respond to requests that would move it.
*/
virtual void SetIsStaticTransform(bool /* isStatic */) {}
/**
* Returns whether position of transform is interpolated via network sync.
* @return True if position of transform is interpolated via network sync.
*/
virtual bool IsPositionInterpolated() = 0;
/**
* Returns whether rotation of transform is interpolated via network sync.
* @return True if rotation of transform is interpolated via network sync.
*/
virtual bool IsRotationInterpolated() = 0;
};
/**
* The EBus for requests to position and parent an entity.
* The events are defined in the AZ::TransformInterface class.
*/
typedef AZ::EBus<TransformInterface> TransformBus;
/**
* Interface for AZ::TransformNotificationBus, which is the EBus that
* dispatches transform changes to listeners.
*/
class TransformNotification
: public ComponentBus
{
public:
/**
* Destroys the instance of the class.
*/
virtual ~TransformNotification() {}
/**
* Signals that the local or world transform of the entity changed.
* @param local A reference to the new local transform of the entity.
* @param world A reference to the new world transform of the entity.
*/
virtual void OnTransformChanged(const Transform& /*local*/, const Transform& /*world*/) { }
/**
* Signals that the static flag on the transform has changed. This should only be needed during editing.
* @param isStatic A boolean that indicates whether the transform is static or not.
*/
virtual void OnStaticChanged( bool /*isStatic*/) { }
/**
* Called right before a parent change, to allow listeners to prevent the entity's parent from changing.
* @param parentCanChange A reference used to track if the parent can change. A result parameter is used
* instead of a return value because this is a multi-handler.
* @param oldParent The entity ID of the old parent. The entity ID is invalid if there was no old parent.
* @param newParent The entity ID of the new parent. The entity ID is invalid if there is no new parent.
*/
virtual void CanParentChange(bool &parentCanChange, EntityId oldParent, EntityId newParent) { (void)parentCanChange; (void)oldParent; (void)newParent; }
/**
* Signals that the parent of the entity changed.
* To find if an entity ID is valid, use AZ::EntityId::IsValid().
* @param oldParent The entity ID of the old parent. The entity ID is invalid if there was no old parent.
* @param newParent The entity ID of the new parent. The entity ID is invalid if there is no new parent.
*/
virtual void OnParentChanged(EntityId oldParent, EntityId newParent) { (void)oldParent; (void)newParent; }
/**
* Signals that the transform of the parent of the entity is about to change. Some components will need adjusting before this happens.
* To find if an entity ID is valid, use AZ::EntityId::IsValid().
* @param oldTransform The transform of the old parent.
* @param newTransform The transform of the new parent.
*/
virtual void OnParentTransformWillChange(AZ::Transform oldTransform, AZ::Transform newTransform) { (void)oldTransform; (void)newTransform; }
/**
* Signals that a child was added to the entity.
* @param child The entity ID of the added child.
*/
virtual void OnChildAdded(EntityId child) { (void)child; }
/**
* Signals that a child was removed from the entity.
* @param child The entity ID of the removed child.
*/
virtual void OnChildRemoved(EntityId child) { (void)child; }
};
/**
* The EBus for transform notification events.
* The events are defined in the AZ::TransformNotification class.
*/
typedef AZ::EBus<TransformNotification> TransformNotificationBus;
/**
* The type ID of game component AzFramework::TransformComponent.
*/
static const TypeId TransformComponentTypeId = "{22B10178-39B6-4C12-BB37-77DB45FDD3B6}";
/**
* The type ID of editor component AzToolsFramework::Components::TransformComponent.
*/
static const TypeId EditorTransformComponentTypeId = "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0}";
/**
* Component configuration for the transform component.
*/
class TransformConfig
: public ComponentConfig
{
public:
AZ_RTTI(TransformConfig, "{B3AAB26D-D075-4E2B-9653-9527EE363DF8}", ComponentConfig);
AZ_CLASS_ALLOCATOR(TransformConfig, SystemAllocator, 0);
/**
* Behavior when a parent entity activates.
* A parent may activate before or after its children have activated.
*/
enum class ParentActivationTransformMode : u32
{
MaintainOriginalRelativeTransform, ///< Child will snap to originally-configured parent-relative transform when parent is activated.
MaintainCurrentWorldTransform, ///< Child will still follow parent, but will maintain its current world transform when parent is activated.
};
/**
* Constructor with all default values.
* Transform is positioned at (0,0,0) with no rotation and scale of 1.
*/
TransformConfig() = default;
/**
* Constructor which sets a 3D transform.
* Sets both the local and world transform to the same value.
* @param transform The entity's position, rotation, and scale in 3D.
*/
explicit TransformConfig(const Transform& transform)
: m_localTransform(transform)
, m_worldTransform(transform)
{}
/**
* World 3D transform.
* This property is used if no parent is assigned,
* or if the assigned parent entity cannot be found.
* This property is ignored if the assigned parent is present.
*/
Transform m_worldTransform = Transform::Identity();
/**
* Local 3D transform, as an offset from the parent entity.
* This property is used to offset the entity from its parent's world transform.
* Local transform is ignored if no parent is assigned.
*/
Transform m_localTransform = Transform::Identity();
/**
* ID of parent entity.
* When the parent entity moves, this transform will follow.
*/
EntityId m_parentId;
/**
* Behavior when the parent entity activates.
* A parent entity is not guaranteed to activate before its children.
* If a parent entity activates after its child, this property
* determines whether the entity maintains its current world transform
* or snaps to maintain the local transform as an offset from the parent.
*/
ParentActivationTransformMode m_parentActivationTransformMode = ParentActivationTransformMode::MaintainOriginalRelativeTransform;
/**
* Whether the transform can be synced over the network.
*/
bool m_netSyncEnabled = true;
/**
* Behavior for smoothing of position between network updates.
*/
InterpolationMode m_interpolatePosition = InterpolationMode::NoInterpolation;
/**
* Behavior for smoothing of rotation between network updates.
*/
InterpolationMode m_interpolateRotation = InterpolationMode::NoInterpolation;
/**
* Whether the transform is static.
* A static transform will never move.
*/
bool m_isStatic = false;
/// @cond EXCLUDE_DOCS
/// @deprecated Deprecated, access properties directly.
void SetTransform(const Transform& transform)
{
m_localTransform = transform;
m_worldTransform = transform;
}
/// @deprecated Deprecated, access properties directly.
void SetLocalAndWorldTransform(const Transform& localTransform, const Transform& worldTransform)
{
m_localTransform = localTransform;
m_worldTransform = worldTransform;
}
/// @deprecated Deprecated, access property directly.
const Transform& GetLocalTransform() const { return m_localTransform; }
/// @deprecated Deprecated, access property directly.
const Transform& GetWorldTransform() const { return m_worldTransform; }
/// @endcond
};
AZ_TYPE_INFO_SPECIALIZE(TransformConfig::ParentActivationTransformMode, "{03FD8A24-CE8F-4651-A3CC-09F40D36BC2C}");
/// @cond EXCLUDE_DOCS
/**
* Interface for AZ::TransformHierarchyInformationBus, which the transform components
* of parent entities use to get their children's entity IDs.
* Only children of a particular entity connect to this bus because they use the
* parent's entity ID to connect to the bus.
*/
class TransformHierarchyInformation
: public AZ::EBusTraits
{
public:
/**
* Destroys the instance of the class.
*/
virtual ~TransformHierarchyInformation() = default;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
/**
* Overrides the default AZ::EBusTraits address policy so that the bus
* has multiple addresses at which to receive messages. This bus is
* identified by EntityId. Messages addressed to an ID are received by
* handlers connected to that ID.
*/
static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById;
/**
* Overrides the default AZ::EBusTraits ID type so that entity IDs are
* used to access the addresses of the bus.
*/
typedef EntityId BusIdType;
//////////////////////////////////////////////////////////////////////////
/**
* Gets the entity IDs of the parent entity's children.
* @param children A vector that contains the entity IDs of the children.
*/
virtual void GatherChildren(AZStd::vector<AZ::EntityId>& /*children*/) {};
};
typedef AZ::EBus<TransformHierarchyInformation> TransformHierarchyInformationBus;
/// @endcond
}
@@ -0,0 +1,102 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_COMPRESSOR_H
#define AZCORE_COMPRESSOR_H
#include <AzCore/base.h>
struct z_stream_s;
namespace AZ
{
class IAllocator;
class IAllocatorAllocate;
/**
* The most well known and used compression algorithm. It gives the best compression ratios even on level 1,
* the speed and memory usage can be an issue. If you want detailed control over the compressed stream, include
* "AzCore/compression/zlib/zlib.h" and do it yourself!
*/
class ZLib
{
public:
ZLib(IAllocator* workMemAllocator = 0);
~ZLib();
enum FlushType
{
// Mapped to the Z_LIB flush values, please reference the ZLib documentation.
FT_NO_FLUSH = 0,
FT_PARTIAL_FLUSH,
FT_SYNC_FLUSH,
FT_FULL_FLUSH,
FT_FINISH,
FT_BLOCK,
FT_TREES,
};
typedef AZ::u16 Header; ///< Typedef for the 2 byte zlib header.
/// Must be called before we can compress. Compression level can vary from [0 - no compression to 9 - best compression]. Default is 9.
/// Compression level results from a test input stream of ~26MB comprised of a mix of string and binary data:
/// Level Compressed Size Time(ms)
/// 1 ~1.9MB 55ms
/// 2 ~1.8MB 53ms
/// 3 ~1.7MB 45ms
/// 4 ~1.7MB 230ms
/// 5 ~1.6MB 225ms
/// 6 ~1.5MB 325ms
/// 7 ~1.5MB 387ms
/// 8 ~1.4MB 827ms
/// 9 ~1.4MB 858ms
void StartCompressor(unsigned int compressionLevel = 9);
bool IsCompressorStarted() const { return m_strDeflate != 0; }
void StopCompressor();
void ResetCompressor();
/// Must be called before we can decompress. Hdr is optional hdr structure that is stored at the begin of the stream and should be passed to the ResetDecompresor.
void StartDecompressor(Header* hdr = NULL);
bool IsDecompressorStarted() const { return m_strInflate != 0; }
void StopDecompressor();
/// If you will use seek/sync points we require that you pass the header since the reset will reset all states and you can't really continue (unless from the start).
void ResetDecompressor(Header* header = NULL);
//////////////////////////////////////////////////////////////////////////
// Compressor
/// Return compressed buffer minimal size for the given source size. compressedDataSize in compress must be at least that size, otherwise compression will fail.
unsigned int GetMinCompressedBufferSize(unsigned int sourceDataSize);
/**
* Compressed data from the data buffer into compressedData buffer.
* If compressedData is NOT big enough the left over size will be returned in "dataSize". If dataSize is 0 all data has been compressed.
* \returns number of bytes written in compressedData.
*/
unsigned int Compress(const void* data, unsigned int& dataSize, void* compressedData, unsigned int compressedDataSize, FlushType flushType = FT_NO_FLUSH);
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Decompressor
unsigned int Decompress(const void* compressedData, unsigned int compressedDataSize, void* data, unsigned int& dataSize, FlushType flushType = FT_NO_FLUSH);
//////////////////////////////////////////////////////////////////////////
private:
static void* AllocateMem(void* userData, unsigned int items, unsigned int size);
static void FreeMem(void* userData, void* address);
void SetupDecompressHeader(Header header);
z_stream_s* m_strDeflate;
z_stream_s* m_strInflate;
IAllocatorAllocate* m_workMemoryAllocator;
};
}
#endif // AZCORE_COMPRESSOR_H
#pragma once
@@ -0,0 +1,295 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#if !defined(AZCORE_EXCLUDE_ZLIB)
#include <AzCore/Compression/Compression.h>
#include <AzCore/Memory/SystemAllocator.h>
using namespace AZ;
//////////////////////////////////////////////////////////////////////////
// ZLib
#define NO_GZIP
#include <zlib.h>
//=========================================================================
// Compress
// [3/21/2011]
//=========================================================================
ZLib::ZLib(IAllocator* workMemAllocator)
: m_strDeflate(NULL)
, m_strInflate(NULL)
{
m_workMemoryAllocator = workMemAllocator ? workMemAllocator->GetAllocationSource() : nullptr;
if (!m_workMemoryAllocator)
{
m_workMemoryAllocator = &AllocatorInstance<SystemAllocator>::Get();
}
}
//=========================================================================
// ~ZLib
// [3/21/2011]
//=========================================================================
ZLib::~ZLib()
{
if (m_strDeflate)
{
StopCompressor();
}
if (m_strInflate)
{
StopDecompressor();
}
}
//=========================================================================
// AllocateMem
// [3/21/2011]
//=========================================================================
void* ZLib::AllocateMem(void* userData, unsigned int items, unsigned int size)
{
IAllocatorAllocate* allocator = reinterpret_cast<IAllocatorAllocate*>(userData);
return allocator->Allocate(items * size, 4, 0, "ZLib", __FILE__, __LINE__);
}
//=========================================================================
// FreeMem
// [3/21/2011]
//=========================================================================
void ZLib::FreeMem(void* userData, void* address)
{
IAllocatorAllocate* allocator = reinterpret_cast<IAllocatorAllocate*>(userData);
allocator->DeAllocate(address);
}
//=========================================================================
// StartCompressor
// [3/21/2011]
//=========================================================================
void ZLib::StartCompressor(unsigned int compressionLevel)
{
AZ_Assert(m_strDeflate == NULL, "Compressor already started!");
m_strDeflate = reinterpret_cast< z_stream* >(AllocateMem(m_workMemoryAllocator, 1, sizeof(z_stream)));
m_strDeflate->zalloc = &ZLib::AllocateMem;
m_strDeflate->zfree = &ZLib::FreeMem;
m_strDeflate->opaque = m_workMemoryAllocator;
int r = deflateInit(m_strDeflate, compressionLevel);
(void)r;
AZ_Assert(r == Z_OK, "ZLib internal error - deflateInit() failed !!!\n");
}
//=========================================================================
// StopCompressor
// [3/21/2011]
//=========================================================================
void ZLib::StopCompressor()
{
AZ_Assert(m_strDeflate != NULL, "Compressor not started!");
deflateEnd(m_strDeflate);
FreeMem(m_workMemoryAllocator, m_strDeflate);
m_strDeflate = NULL;
}
//=========================================================================
// ResetCompressor
// [12/17/2012]
//=========================================================================
void ZLib::ResetCompressor()
{
AZ_Assert(m_strDeflate != NULL, "Compressor not started!");
int r = deflateReset(m_strDeflate);
(void)r;
AZ_Assert(r == Z_OK, "ZLib inconsistent state - deflateReset() failed !!!\n");
}
//=========================================================================
// Compress
// [3/21/2011]
//=========================================================================
unsigned int ZLib::Compress(const void* data, unsigned int& dataSize, void* compressedData, unsigned int compressedDataSize, FlushType flushType)
{
AZ_Assert(m_strDeflate != NULL, "Compressor not started!");
m_strDeflate->avail_in = dataSize;
m_strDeflate->next_in = (unsigned char*)data;
m_strDeflate->avail_out = compressedDataSize;
m_strDeflate->next_out = reinterpret_cast<unsigned char*>(compressedData);
int flush;
switch (flushType)
{
case FT_PARTIAL_FLUSH:
flush = Z_PARTIAL_FLUSH;
break;
case FT_SYNC_FLUSH:
flush = Z_SYNC_FLUSH;
break;
case FT_FULL_FLUSH:
flush = Z_FULL_FLUSH;
break;
case FT_FINISH:
flush = Z_FINISH;
break;
case FT_BLOCK:
flush = Z_BLOCK;
break;
case FT_TREES:
flush = Z_TREES;
break;
case FT_NO_FLUSH:
default:
flush = Z_NO_FLUSH;
}
int r = deflate(m_strDeflate, flush);
(void)r;
AZ_Assert(r >= Z_OK || r == Z_BUF_ERROR, "ZLib compress internal error %d", r);
dataSize = m_strDeflate->avail_in;
return compressedDataSize - m_strDeflate->avail_out;
}
//=========================================================================
// GetMinCompressedBufferSize
// [3/21/2011]
//=========================================================================
unsigned int ZLib::GetMinCompressedBufferSize(unsigned int sourceDataSize)
{
AZ_Assert(m_strDeflate != NULL, "Compressor not started!");
return static_cast<unsigned int>(deflateBound(m_strDeflate, sourceDataSize));
}
//=========================================================================
// StartDecompressor
// [3/21/2011]
//=========================================================================
void ZLib::StartDecompressor(Header* header)
{
AZ_Assert(m_strInflate == NULL, "Decompressor already started!");
m_strInflate = reinterpret_cast< z_stream* >(AllocateMem(m_workMemoryAllocator, 1, sizeof(z_stream)));
m_strInflate->zalloc = &ZLib::AllocateMem;
m_strInflate->zfree = &ZLib::FreeMem;
m_strInflate->opaque = m_workMemoryAllocator;
int r = inflateInit(m_strInflate);
(void)r;
AZ_Assert(r == Z_OK, "ZLib internal error - inflateInit() failed !!!\n");
if (header)
{
SetupDecompressHeader(*header);
}
}
//=========================================================================
// StopDecompressor
// [3/21/2011]
//=========================================================================
void ZLib::StopDecompressor()
{
AZ_Assert(m_strInflate != NULL, "Decompressor not started!");
inflateEnd(m_strInflate);
FreeMem(m_workMemoryAllocator, m_strInflate);
m_strInflate = NULL;
}
//=========================================================================
// ResetDecompressor
// [12/17/2012]
//=========================================================================
void ZLib::ResetDecompressor(Header* header)
{
AZ_Assert(m_strInflate != NULL, "Decompressor not started!");
int r = inflateReset(m_strInflate);
(void)r;
AZ_Assert(r == Z_OK, "ZLib inconsistent state - inflateReset() failed !!!\n");
if (header)
{
SetupDecompressHeader(*header);
}
}
//=========================================================================
// SetupHeader
// [12/17/2012]
//=========================================================================
void ZLib::SetupDecompressHeader(Header header)
{
unsigned int fakeBuffer;
unsigned int fakeBufferSize = sizeof(fakeBuffer);
unsigned int numDecompressed = Decompress(&header, sizeof(header), &fakeBuffer, fakeBufferSize);
(void)numDecompressed;
AZ_Assert(numDecompressed == sizeof(header), "If you provided a valid header it should have been processed!");
}
//=========================================================================
// Compress
// [3/21/2011]
//=========================================================================
unsigned int ZLib::Decompress(const void* compressedData, unsigned int compressedDataSize, void* data, unsigned int& dataSize, FlushType flushType)
{
AZ_Assert(m_strInflate != NULL, "Decompressor not started!");
m_strInflate->avail_in = compressedDataSize;
m_strInflate->next_in = (unsigned char*)compressedData;
m_strInflate->avail_out = dataSize;
m_strInflate->next_out = reinterpret_cast<unsigned char*>(data);
int flush;
switch (flushType)
{
case FT_PARTIAL_FLUSH:
flush = Z_PARTIAL_FLUSH;
break;
case FT_SYNC_FLUSH:
flush = Z_SYNC_FLUSH;
break;
case FT_FULL_FLUSH:
flush = Z_FULL_FLUSH;
break;
case FT_FINISH:
flush = Z_FINISH;
break;
case FT_BLOCK:
flush = Z_BLOCK;
break;
case FT_TREES:
flush = Z_TREES;
break;
case FT_NO_FLUSH:
default:
flush = Z_NO_FLUSH;
}
int r = inflate(m_strInflate, flush);
/*
Because of the way we allow random access to our compressed streams by way of adding seek points into the end of the stream, a Z_DATA_ERROR
will occur if the compressed stream is not decompressed sequentially. This is due to the adler32 checksum of all uncompressed data up to the Z_FINISH
being stored in the last 4 bytes of the compressed zlib data.
When zlib decompresses a compressed stream it calculates a running adler32 checksum of the data and when it reaches the end of the stream it compares this running checksum
against the checksum stored at the end of the compressed stream. When seek points are used to decompress specific offsets in the compressed stream, only a portion of the stream is decompressed.
When the last block of a deflate stream is decompressed it compares the running adler32 against the stored adler32 checksum.
This will be different since only a portion of the stream was decompressed and therefore the running adler32 checksum does not incorporate the entire amount of uncompressed data.
*/
//If we have parsed all the input data and received a Z_DATA_ERROR, then assume that mismatch of the adler32 checksum occurred.
// This may mask legitimate Z_DATA_ERROR's though
if (r == Z_DATA_ERROR && m_strInflate->avail_in == 0)
{
AZ_Warning("IO", r != Z_DATA_ERROR, "ZLib inflate returned a data error, this is OK if the compressed data is being retrieved by using seek points");
}
else
{
AZ_Assert(r >= Z_OK || r == Z_BUF_ERROR, "ZLib decompress internal error %d", r);
}
dataSize = m_strInflate->avail_out;
unsigned int processedCompressedData = compressedDataSize - m_strInflate->avail_in;
//if( isLastChunk )
// inflateEnd(m_strm);
return processedCompressedData;
}
//////////////////////////////////////////////////////////////////////////
#endif // #if !defined(AZCORE_EXCLUDE_ZLIB)
@@ -0,0 +1,183 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#if !defined(AZCORE_EXCLUDE_ZSTANDARD)
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Casting/lossy_cast.h>
#include <limits>
#include <AzCore/Compression/zstd_compression.h>
using namespace AZ;
ZStd::ZStd(IAllocatorAllocate* workMemAllocator)
{
m_workMemoryAllocator = workMemAllocator;
if (!m_workMemoryAllocator)
{
m_workMemoryAllocator = &AllocatorInstance<SystemAllocator>::Get();
}
m_streamCompression = nullptr;
m_streamDecompression = nullptr;
}
ZStd::~ZStd()
{
if (m_streamCompression)
{
StopCompressor();
}
if (m_streamDecompression)
{
StopDecompressor();
}
}
void* ZStd::AllocateMem(void* userData, size_t size)
{
IAllocatorAllocate* allocator = reinterpret_cast<IAllocatorAllocate*>(userData);
return allocator->Allocate(size, 4, 0, "ZStandard", __FILE__, __LINE__);
}
void ZStd::FreeMem(void* userData, void* address)
{
IAllocatorAllocate* allocator = reinterpret_cast<IAllocatorAllocate*>(userData);
allocator->DeAllocate(address);
}
void ZStd::StartCompressor(unsigned int compressionLevel)
{
AZ_Assert(!m_streamCompression, "Compressor already started!");
ZSTD_customMem customAlloc;
customAlloc.customAlloc = reinterpret_cast<ZSTD_allocFunction>(&AllocateMem);
customAlloc.customFree = &FreeMem;
AZ_UNUSED(compressionLevel);
m_streamCompression = (ZSTD_createCStream_advanced(customAlloc));
AZ_Assert( m_streamCompression , "ZStandard internal error - failed to create compression stream\n");
}
void ZStd::StopCompressor()
{
AZ_Assert(m_streamCompression, "Compressor not started!");
ZSTD_endStream(m_streamCompression, nullptr);
FreeMem(m_workMemoryAllocator, m_streamCompression);
m_streamCompression = nullptr;
}
void ZStd::ResetCompressor()
{
AZ_Assert(m_streamCompression, "Compressor not started!");
size_t r = ZSTD_resetCStream(m_streamCompression,0);
AZ_UNUSED(r);
AZ_Assert(!ZSTD_isError(r), "Can't reset compressor");
}
unsigned int ZStd::Compress(const void* data, unsigned int& dataSize, void* compressedData, unsigned int compressedDataSize, FlushType flushType)
{
AZ_UNUSED(data);
AZ_UNUSED(dataSize);
AZ_UNUSED(compressedData);
AZ_UNUSED(compressedDataSize);
AZ_UNUSED(flushType);
return 0;
}
unsigned int ZStd::GetMinCompressedBufferSize(unsigned int sourceDataSize)
{
return azlossy_cast<unsigned int>(ZSTD_compressBound(sourceDataSize));
}
void ZStd::StartDecompressor()
{
AZ_Assert(!m_streamDecompression, "Decompressor already started!");
ZSTD_customMem customAlloc;
customAlloc.customAlloc = reinterpret_cast<ZSTD_allocFunction>(&ZStd::AllocateMem);
customAlloc.customFree = &ZStd::FreeMem;
customAlloc.opaque = m_workMemoryAllocator;
m_streamDecompression = ZSTD_createDStream_advanced(customAlloc);
m_nextBlockSize = ZSTD_initDStream(m_streamDecompression);
AZ_Assert(!ZSTD_isError(m_nextBlockSize), "ZStandard internal error: %s", ZSTD_getErrorName(m_nextBlockSize));
//pointers will be set later....
m_inBuffer.pos = 0;
m_inBuffer.size = 0;
m_outBuffer.size = 0;
m_outBuffer.pos = 0;
m_compressedBufferIndex = 0;
}
void ZStd::StopDecompressor()
{
AZ_Assert(m_streamDecompression, "Decompressor not started!");
size_t result = ZSTD_freeDStream(m_streamDecompression);
AZ_Verify(!ZSTD_isError(result), "ZStandard internal error: %s", ZSTD_getErrorName(result));
m_streamDecompression = nullptr;
}
void ZStd::ResetDecompressor(Header* header)
{
AZ_UNUSED(header);
}
void ZStd::SetupDecompressHeader(Header header)
{
AZ_UNUSED(header);
}
unsigned int ZStd::Decompress(const void* compressedData, unsigned int compressedDataSize, void* outputData, unsigned int outputDataSize, size_t* sizeOfNextBlock)
{
AZ_Assert(m_streamDecompression, "Decompressor not started!");
AZ_UNUSED(compressedDataSize);
const char* data = reinterpret_cast<const char*>(compressedData);
m_inBuffer.src = reinterpret_cast<const void*>(&data[m_compressedBufferIndex]);
m_inBuffer.size = m_nextBlockSize;
m_inBuffer.pos = 0;
m_outBuffer.dst = outputData;
m_outBuffer.size = outputDataSize;
m_outBuffer.pos = 0;
m_nextBlockSize = ZSTD_decompressStream(m_streamDecompression, &m_outBuffer, &m_inBuffer);
if (ZSTD_isError(m_nextBlockSize))
{
AZ_Assert(false, "ZStd streaming decompression error: %s", ZSTD_getErrorName(m_nextBlockSize));
return std::numeric_limits<unsigned int>::max();
}
if (m_nextBlockSize == 0)
{
m_nextBlockSize = ZSTD_resetDStream(m_streamDecompression);
}
m_compressedBufferIndex += azlossy_cast<unsigned int>(m_inBuffer.pos);
*sizeOfNextBlock = m_nextBlockSize;
return azlossy_cast<unsigned int>(m_outBuffer.pos); //return number of bytes decompressed
}
bool ZStd::IsCompressorStarted() const
{
return m_streamCompression != nullptr;
}
bool ZStd::IsDecompressorStarted() const
{
return m_streamDecompression != nullptr;
}
//////////////////////////////////////////////////////////////////////////
#endif // #if !defined(AZCORE_EXCLUDE_ZSTANDARD)
@@ -0,0 +1,90 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#define ZSTD_STATIC_LINKING_ONLY
#include <zstd.h>
namespace AZ
{
class IAllocator;
class IAllocatorAllocate;
class ZStd
{
public:
ZStd(IAllocatorAllocate* workMemAllocator = 0);
~ZStd();
enum FlushType
{
// Mapped to the Z_LIB flush values, please reference the ZLib documentation.
FT_NO_FLUSH = 0,
FT_PARTIAL_FLUSH,
FT_SYNC_FLUSH,
FT_FULL_FLUSH,
FT_FINISH,
FT_BLOCK,
FT_TREES,
};
/*
Description of the zstd data format can be found here:
https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md
Additional information here:
https://tools.ietf.org/id/draft-kucherawy-dispatch-zstd-00.html
*/
using Header = AZ::u32; ///< Typedef for the byte zstd header.
void StartCompressor(unsigned int compressionLevel = 1);
bool IsCompressorStarted() const;
void StopCompressor();
void ResetCompressor();
void StartDecompressor();
bool IsDecompressorStarted() const;
void StopDecompressor();
/// If you will use seek/sync points we require that you pass the header since the reset will reset all states and you can't really continue (unless from the start).
void ResetDecompressor(Header* header = nullptr);
//////////////////////////////////////////////////////////////////////////
// Compressor
unsigned int GetMinCompressedBufferSize(unsigned int sourceDataSize);
unsigned int Compress(const void* data, unsigned int& dataSize, void* compressedData, unsigned int compressedDataSize, FlushType flushType = FT_NO_FLUSH);
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Decompressor
unsigned int Decompress(const void* compressedData, unsigned int compressedDataSize, void* outputData, unsigned int outputDataSize, size_t* sizeOfNextBlock);
//////////////////////////////////////////////////////////////////////////
private:
static void* AllocateMem(void* userData, size_t size);
static void FreeMem(void* userData, void* address);
void SetupDecompressHeader(Header header);
ZSTD_CStream* m_streamCompression;
ZSTD_DStream* m_streamDecompression;
IAllocatorAllocate* m_workMemoryAllocator;
ZSTD_inBuffer m_inBuffer;
ZSTD_outBuffer m_outBuffer;
size_t m_nextBlockSize;
unsigned int m_compressedBufferIndex;
};
};
@@ -0,0 +1,438 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Console/Console.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Settings/CommandLine.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <cctype>
namespace AZ
{
uint32_t CountMatchingPrefixes(const AZStd::string_view& string, const ConsoleCommandContainer& stringSet)
{
uint32_t count = 0;
for (AZStd::string_view iter : stringSet)
{
if (StringFunc::StartsWith(iter, string, false))
{
++count;
}
}
return count;
}
Console::Console()
: m_head(nullptr)
{
}
Console::~Console()
{
// on console destruction relink the console functors back to the deferred head
MoveFunctorsToDeferredHead(AZ::ConsoleFunctorBase::GetDeferredHead());
}
bool Console::PerformCommand
(
const char* command,
ConsoleSilentMode silentMode,
ConsoleInvokedFrom invokedFrom,
ConsoleFunctorFlags requiredSet,
ConsoleFunctorFlags requiredClear
)
{
AZStd::string_view commandView;
ConsoleCommandContainer commandArgsView;
bool firstIteration = true;
auto ConvertCommandStringToArray = [&firstIteration, &commandView, &commandArgsView](AZStd::string_view token)
{
if (firstIteration)
{
commandView = token;
firstIteration = false;
}
else
{
commandArgsView.emplace_back(token);
};
};
constexpr AZStd::string_view commandSeparators = " \t\n\r";
StringFunc::TokenizeVisitor(command, ConvertCommandStringToArray, commandSeparators);
return PerformCommand(commandView, commandArgsView, silentMode, invokedFrom, requiredSet, requiredClear);
}
bool Console::PerformCommand
(
const ConsoleCommandContainer& commandAndArgs,
ConsoleSilentMode silentMode,
ConsoleInvokedFrom invokedFrom,
ConsoleFunctorFlags requiredSet,
ConsoleFunctorFlags requiredClear
)
{
if (commandAndArgs.empty())
{
return false;
}
return PerformCommand(commandAndArgs.front(), ConsoleCommandContainer(commandAndArgs.begin() + 1, commandAndArgs.end()), silentMode, invokedFrom, requiredSet, requiredClear);
}
bool Console::PerformCommand
(
AZStd::string_view command,
const ConsoleCommandContainer& commandArgs,
ConsoleSilentMode silentMode,
ConsoleInvokedFrom invokedFrom,
ConsoleFunctorFlags requiredSet,
ConsoleFunctorFlags requiredClear
)
{
return DispatchCommand(command, commandArgs, silentMode, invokedFrom, requiredSet, requiredClear);
}
void Console::ExecuteConfigFile(AZStd::string_view configFileName)
{
IO::FixedMaxPath filePathFixed = configFileName;
if (AZ::IO::FileIOBase* fileIOBase = AZ::IO::FileIOBase::GetInstance())
{
fileIOBase->ResolvePath(filePathFixed, configFileName);
}
IO::SystemFile file;
if (!file.Open(filePathFixed.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY))
{
AZLOG_ERROR("Failed to load '%s'. File could not be opened.", filePathFixed.c_str());
return;
}
const IO::SizeType length = file.Length();
if (length == 0)
{
AZLOG_ERROR("Failed to load '%s'. File is empty.", filePathFixed.c_str());
return;
}
file.Seek(0, IO::SystemFile::SF_SEEK_BEGIN);
AZStd::string fileBuffer;
fileBuffer.resize(length);
IO::SizeType bytesRead = file.Read(length, fileBuffer.data());
file.Close();
// Resize again just in case bytesRead is less than length for some reason
fileBuffer.resize(bytesRead);
AZLOG_INFO("Loading config file %s", filePathFixed.c_str());
AZStd::vector<AZStd::string_view> separatedCommands;
auto BreakCommandsByLine = [&separatedCommands](AZStd::string_view token)
{
separatedCommands.emplace_back(token);
};
StringFunc::TokenizeVisitor(fileBuffer, BreakCommandsByLine, "\n\r");
for (const auto& commandView : separatedCommands)
{
ConsoleCommandContainer commandArgsView;
auto ConvertCommandStringToArray = [&commandArgsView](AZStd::string_view token)
{
commandArgsView.emplace_back(token);
};
constexpr AZStd::string_view commandSeparators = " =";
StringFunc::TokenizeVisitor(commandView, ConvertCommandStringToArray, commandSeparators);
PerformCommand(commandArgsView, ConsoleSilentMode::NotSilent, ConsoleInvokedFrom::AzConsole, ConsoleFunctorFlags::Null, ConsoleFunctorFlags::Null);
}
}
void Console::ExecuteCommandLine(const AZ::CommandLine& commandLine)
{
for (const auto& [switchKey, switchValues] : commandLine.GetSwitchList())
{
ConsoleCommandContainer commandArgs(switchValues.begin(), switchValues.end());
PerformCommand(switchKey, commandArgs, ConsoleSilentMode::NotSilent, ConsoleInvokedFrom::AzConsole, ConsoleFunctorFlags::Null, ConsoleFunctorFlags::Null);
}
}
bool Console::HasCommand(const char* command)
{
return FindCommand(command) != nullptr;
}
ConsoleFunctorBase* Console::FindCommand(const char* command)
{
CVarFixedString lowerName(command);
AZStd::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](char value) { return std::tolower(value); });
CommandMap::iterator iter = m_commands.find(lowerName);
if (iter != m_commands.end())
{
for (ConsoleFunctorBase* curr : iter->second)
{
if ((curr->GetFlags() & ConsoleFunctorFlags::IsInvisible) == ConsoleFunctorFlags::IsInvisible)
{
// Filter functors marked as invisible
continue;
}
return curr;
}
}
return nullptr;
}
AZStd::string Console::AutoCompleteCommand(const char* command)
{
const size_t commandLength = strlen(command);
if (commandLength <= 0)
{
return command;
}
ConsoleCommandContainer commandSubset;
for (ConsoleFunctorBase* curr = m_head; curr != nullptr; curr = curr->m_next)
{
if ((curr->GetFlags() & ConsoleFunctorFlags::IsInvisible) == ConsoleFunctorFlags::IsInvisible)
{
// Filter functors marked as invisible
continue;
}
if (StringFunc::Equal(curr->m_name, command, false, commandLength))
{
AZLOG_INFO("- %s : %s\n", curr->m_name, curr->m_desc);
commandSubset.push_back(curr->m_name);
}
}
AZStd::string largestSubstring = command;
if ((!largestSubstring.empty()) && (!commandSubset.empty()))
{
const uint32_t totalCount = CountMatchingPrefixes(command, commandSubset);
for (size_t i = largestSubstring.length(); i < commandSubset.front().length(); ++i)
{
const AZStd::string nextSubstring = largestSubstring + commandSubset.front()[i];
const uint32_t count = CountMatchingPrefixes(nextSubstring, commandSubset);
if (count < totalCount)
{
break;
}
largestSubstring = nextSubstring;
}
}
return largestSubstring;
}
void Console::VisitRegisteredFunctors(const FunctorVisitor& visitor)
{
for (auto& curr : m_commands)
{
visitor(curr.second.front());
}
}
void Console::RegisterFunctor(ConsoleFunctorBase* functor)
{
if (functor->m_console && (functor->m_console != this))
{
AZ_Assert(false, "Functor is already registered to a console");
return;
}
CVarFixedString lowerName = functor->GetName();
AZStd::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](char value) { return std::tolower(value); });
CommandMap::iterator iter = m_commands.find(lowerName);
if (iter != m_commands.end())
{
// Validate we haven't already added this cvar
AZStd::vector<ConsoleFunctorBase*>::iterator iter2 = AZStd::find(iter->second.begin(), iter->second.end(), functor);
if (iter2 != iter->second.end())
{
AZ_Assert(false, "Duplicate functor registered to the console");
return;
}
// If multiple cvars are registered with the same name, validate that the types and flags match
if (!iter->second.empty())
{
ConsoleFunctorBase* front = iter->second.front();
if (front->GetFlags() != functor->GetFlags() || front->GetTypeId() != functor->GetTypeId())
{
AZ_Assert(false, "Mismatched console functor types registered under the same name");
return;
}
}
}
m_commands[lowerName].emplace_back(functor);
functor->Link(m_head);
functor->m_console = this;
}
void Console::UnregisterFunctor(ConsoleFunctorBase* functor)
{
if (functor->m_console != this)
{
AZ_Assert(false, "Unregistering a functor bound to a different console");
return;
}
CVarFixedString lowerName = functor->GetName();
AZStd::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](char value) { return std::tolower(value); });
CommandMap::iterator iter = m_commands.find(lowerName);
if (iter != m_commands.end())
{
AZStd::vector<ConsoleFunctorBase*>::iterator iter2 = AZStd::find(iter->second.begin(), iter->second.end(), functor);
if (iter2 != iter->second.end())
{
iter->second.erase(iter2);
}
}
functor->Unlink(m_head);
functor->m_console = nullptr;
}
void Console::LinkDeferredFunctors(ConsoleFunctorBase*& deferredHead)
{
ConsoleFunctorBase* curr = deferredHead;
while (curr != nullptr)
{
ConsoleFunctorBase* next = curr->m_next;
curr->Unlink(deferredHead);
RegisterFunctor(curr);
curr->m_isDeferred = false;
curr = next;
}
deferredHead = nullptr;
}
void Console::MoveFunctorsToDeferredHead(ConsoleFunctorBase*& deferredHead)
{
m_commands.clear();
// Re-initialize all of the current functors to a deferred state
for (ConsoleFunctorBase* curr = m_head; curr != nullptr; curr = curr->m_next)
{
curr->m_console = nullptr;
curr->m_isDeferred = true;
}
// If the deferred head contains unregistered functors
// move this AZ::Console functors list to the end of the deferred functors
if (deferredHead)
{
ConsoleFunctorBase* oldDeferred = deferredHead;
while (oldDeferred->m_next != nullptr)
{
oldDeferred = oldDeferred->m_next;
}
oldDeferred->Link(m_head);
}
else
{
deferredHead = m_head;
}
ConsoleFunctorBase::s_deferredHeadInvoked = false;
m_head = nullptr;
}
bool Console::DispatchCommand
(
AZStd::string_view command,
const ConsoleCommandContainer& inputs,
ConsoleSilentMode silentMode,
ConsoleInvokedFrom invokedFrom,
ConsoleFunctorFlags requiredSet,
ConsoleFunctorFlags requiredClear
)
{
bool result = false;
ConsoleFunctorFlags flags = ConsoleFunctorFlags::Null;
CVarFixedString lowerName(command);
AZStd::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](char value) { return std::tolower(value); });
CommandMap::iterator iter = m_commands.find(lowerName);
if (iter != m_commands.end())
{
for (ConsoleFunctorBase* curr : iter->second)
{
if ((curr->GetFlags() & requiredSet) != requiredSet)
{
AZLOG_WARN("%s failed required set flag check\n", curr->m_name);
continue;
}
if ((curr->GetFlags() & requiredClear) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("%s failed required clear flag check\n", curr->m_name);
continue;
}
if ((curr->GetFlags() & ConsoleFunctorFlags::IsCheat) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("%s is marked as a cheat\n", curr->m_name);
}
if ((curr->GetFlags() & ConsoleFunctorFlags::IsDeprecated) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("%s is marked as deprecated\n", curr->m_name);
}
if ((curr->GetFlags() & ConsoleFunctorFlags::NeedsReload) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("Changes to %s will only take effect after level reload\n", curr->m_name);
}
// Letting this intentionally fall-through, since in editor we can register common variables multiple times
(*curr)(inputs);
if (!result)
{
result = true;
if ((silentMode == ConsoleSilentMode::NotSilent) && (curr->GetFlags() & ConsoleFunctorFlags::IsInvisible) != ConsoleFunctorFlags::IsInvisible)
{
CVarFixedString value;
curr->GetValue(value);
AZLOG_INFO("> %s : %s\n", curr->GetName(), value.empty() ? "<empty>" : value.c_str());
}
flags = curr->GetFlags();
}
}
}
if (result)
{
m_consoleCommandInvokedEvent.Signal(command, inputs, flags, invokedFrom);
}
else
{
m_dispatchCommandNotFoundEvent.Signal(command, inputs, invokedFrom);
}
return result;
}
}
@@ -0,0 +1,102 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Console/IConsole.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/containers/unordered_map.h>
namespace AZ
{
//! @class Console
//! A simple console class for providing text based variable and process interaction.
class Console final
: public IConsole
{
public:
AZ_RTTI(Console, "{CF6DCDE7-1A66-442C-BA87-01A432C13E7D}", IConsole);
AZ_CLASS_ALLOCATOR(Console, AZ::OSAllocator, 0);
Console();
~Console() override;
//! IConsole interface
//! @{
bool PerformCommand
(
const char* command,
ConsoleSilentMode silentMode = ConsoleSilentMode::NotSilent,
ConsoleInvokedFrom invokedFrom = ConsoleInvokedFrom::AzConsole,
ConsoleFunctorFlags requiredSet = ConsoleFunctorFlags::Null,
ConsoleFunctorFlags requiredClear = ConsoleFunctorFlags::ReadOnly
) override;
bool PerformCommand
(
const ConsoleCommandContainer& commandAndArgs,
ConsoleSilentMode silentMode = ConsoleSilentMode::NotSilent,
ConsoleInvokedFrom invokedFrom = ConsoleInvokedFrom::AzConsole,
ConsoleFunctorFlags requiredSet = ConsoleFunctorFlags::Null,
ConsoleFunctorFlags requiredClear = ConsoleFunctorFlags::ReadOnly
) override;
bool PerformCommand
(
AZStd::string_view command,
const ConsoleCommandContainer& commandArgs,
ConsoleSilentMode silentMode = ConsoleSilentMode::NotSilent,
ConsoleInvokedFrom invokedFrom = ConsoleInvokedFrom::AzConsole,
ConsoleFunctorFlags requiredSet = ConsoleFunctorFlags::Null,
ConsoleFunctorFlags requiredClear = ConsoleFunctorFlags::ReadOnly
) override;
void ExecuteConfigFile(AZStd::string_view configFileName) override;
void ExecuteCommandLine(const AZ::CommandLine& commandLine) override;
bool HasCommand(const char* command) override;
ConsoleFunctorBase* FindCommand(const char* command) override;
AZStd::string AutoCompleteCommand(const char* command) override;
void VisitRegisteredFunctors(const FunctorVisitor& visitor) override;
void RegisterFunctor(ConsoleFunctorBase* functor) override;
void UnregisterFunctor(ConsoleFunctorBase* functor) override;
void LinkDeferredFunctors(ConsoleFunctorBase*& deferredHead) override;
//! @}
private:
void MoveFunctorsToDeferredHead(ConsoleFunctorBase*& deferredHead);
//! Invokes a single console command, optionally returning the command output.
//! @param command the function to invoke
//! @param inputs the set of inputs to provide the function
//! @param silentMode if true, logs will be suppressed during command execution
//! @param invokedFrom the source point that initiated console invocation
//! @param requiredSet a set of flags that must be set on the functor for it to execute
//! @param requiredClear a set of flags that must *NOT* be set on the functor for it to execute
//! @return boolean true on success, false otherwise
bool DispatchCommand
(
AZStd::string_view command,
const ConsoleCommandContainer& inputs,
ConsoleSilentMode silentMode,
ConsoleInvokedFrom invokedFrom,
ConsoleFunctorFlags requiredSet,
ConsoleFunctorFlags requiredClear
);
AZ_DISABLE_COPY_MOVE(Console);
ConsoleFunctorBase* m_head;
using CommandMap = AZStd::unordered_map<CVarFixedString, AZStd::vector<ConsoleFunctorBase*>>;
CommandMap m_commands;
friend class ConsoleFunctorBase;
};
}
@@ -0,0 +1,135 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <atomic>
#include <AzCore/Console/ConsoleFunctor.h>
#include <AzCore/Threading/ThreadSafeObject.h>
namespace AZ
{
enum class ThreadSafety
{
RequiresLock
, UseStdAtomic
};
//! @class ConsoleDataContainer
//! Base data wrapper class.
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
class ConsoleDataContainer {};
template <typename BASE_TYPE>
class ConsoleDataContainer<BASE_TYPE, ThreadSafety::RequiresLock>
{
protected:
ThreadSafeObject<BASE_TYPE> m_value;
};
template <typename BASE_TYPE>
class ConsoleDataContainer<BASE_TYPE, ThreadSafety::UseStdAtomic>
{
protected:
std::atomic<BASE_TYPE> m_value;
};
//! @class ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>
//! Data wrapper class for console variables.
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
class ConsoleDataWrapper
: public ConsoleDataContainer<BASE_TYPE, THREAD_SAFETY>
{
public:
using BaseType = BASE_TYPE;
using SelfType = ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>;
using CallbackFunc = void(*)(const BaseType& value);
//! Constructor.
//! @param value the initial value to initialize the wrapped data to
//! @param callback an optional callback that will be invoked upon any change in value to this variable
//! @param name the string name of the variable
//! @param desc a string help description of the variable
//! @param flags a set of optional flags that mutate the behaviour of the variable
ConsoleDataWrapper(const BASE_TYPE& value, CallbackFunc callback, const char* name, const char* desc, ConsoleFunctorFlags flags);
//! Assignment from underlying base type.
//! @param rhs base type value to assign from
void operator =(const BASE_TYPE& rhs);
//! Const base type operator
//! @return value in const base type form.
operator BASE_TYPE() const;
//! Equality operator, provided for convenience.
//! The contained value could have changed after the comparison is made and before the result is returned.
//! @param rhs base type value to compare against
//! @return boolean true if this == rhs
bool operator ==(const BASE_TYPE& rhs) const;
//! Inequality operator, provided for convenience.
//! The contained value could have changed after the comparison is made and before the result is returned
//! @param rhs base type value to compare against
//! @return boolean true if this != rhs
bool operator !=(const BASE_TYPE& rhs) const;
//! Strictly less than operator, provided for convenience.
//! The contained value could have changed after the comparison is made and before the result is returned
//! @param rhs base type value to compare against
//! @return boolean true if this < rhs
bool operator <(const BASE_TYPE& rhs) const;
//! Less than equal to operator, provided for convenience.
//! The contained value could have changed after the comparison is made and before the result is returned
//! @param rhs base type value to compare against
//! @return boolean true if this <= rhs
bool operator <=(const BASE_TYPE& rhs) const;
//! Strictly greater than operator, provided for convenience.
//! The contained value could have changed after the comparison is made and before the result is returned
//! @param rhs base type value to compare against
//! @return boolean true if this > rhs
bool operator >(const BASE_TYPE& rhs) const;
//! Greater than equal to operator, provided for convenience.
//! The contained value could have changed after the comparison is made and before the result is returned
//! @param rhs base type value to compare against
//! @return boolean true if this >= rhs
bool operator >=(const BASE_TYPE& rhs) const;
//! Reads data contained in arguments to set the console variable value.
//! @param arguments StringSet instance to read new values from
//! @return boolean true on success, false if an error occurred
bool StringToValue(const ConsoleCommandContainer& arguments);
//! Stringifies the contained variables value and write the result to outString.
//! @param outString output string instance to write the stringified value to
void ValueToString(CVarFixedString& outString) const;
//! Invokes bound callback on the wrapped BaseType value.
void InvokeCallback() const;
//! Cvar functor, reads data contained in arguments to set the console variable value.
//! @param arguments StringSet instance to read new values from
void CvarFunctor(const ConsoleCommandContainer& arguments);
private:
ConsoleDataWrapper& operator =(const ConsoleDataWrapper&) = delete;
CallbackFunc m_callback;
ConsoleFunctor<SelfType, true> m_functor;
};
}
#include <AzCore/Console/ConsoleDataWrapper.inl>
@@ -0,0 +1,133 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <sstream>
#include <AzCore/Console/ConsoleTypeHelpers.h>
namespace AZ
{
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::ConsoleDataWrapper
(
const BASE_TYPE& value,
CallbackFunc callback,
const char* name,
const char* desc,
ConsoleFunctorFlags flags
)
: m_callback(callback)
, m_functor(name, desc, flags, AzTypeInfo<BASE_TYPE>::Uuid(), *this, &ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::CvarFunctor)
{
this->m_value = value;
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline void ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator =(const BASE_TYPE& rhs)
{
this->m_value = rhs;
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator BASE_TYPE() const
{
const BASE_TYPE currentValue = this->m_value;
return currentValue;
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline bool ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator ==(const BASE_TYPE& rhs) const
{
const BASE_TYPE currentValue = this->m_value;
return currentValue == rhs;
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline bool ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator !=(const BASE_TYPE& rhs) const
{
const BASE_TYPE currentValue = this->m_value;
return currentValue != rhs;
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline bool ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator <(const BASE_TYPE& rhs) const
{
const BASE_TYPE currentValue = this->m_value;
return currentValue < rhs;
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline bool ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator <=(const BASE_TYPE& rhs) const
{
const BASE_TYPE currentValue = this->m_value;
return currentValue <= rhs;
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline bool ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator >(const BASE_TYPE& rhs) const
{
const BASE_TYPE currentValue = this->m_value;
return currentValue > rhs;
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline bool ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator >=(const BASE_TYPE& rhs) const
{
const BASE_TYPE currentValue = this->m_value;
return currentValue >= rhs;
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
bool ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::StringToValue(const ConsoleCommandContainer& arguments)
{
const BASE_TYPE currentValue = this->m_value;
BASE_TYPE newValue = currentValue;
if (ConsoleTypeHelpers::StringSetToValue(newValue, arguments))
{
if (newValue != currentValue)
{
this->m_value = newValue;
InvokeCallback();
}
return true;
}
return false;
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
void ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::ValueToString(CVarFixedString& outString) const
{
const BASE_TYPE currentValue = this->m_value;
outString = ConsoleTypeHelpers::ValueToString(currentValue);
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline void ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::InvokeCallback() const
{
if (m_callback)
{
const BASE_TYPE currentValue = this->m_value;
m_callback(currentValue);
}
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline void ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::CvarFunctor(const ConsoleCommandContainer& arguments)
{
StringToValue(arguments);
}
}
@@ -0,0 +1,179 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Console/ConsoleFunctor.h>
#include <AzCore/Console/Console.h>
#include <AzCore/Interface/Interface.h>
namespace AZ
{
ConsoleFunctorBase* ConsoleFunctorBase::s_deferredHead = nullptr;
// Needed to guard against calling Interface<T>::Get(), it's not safe to call Interface<T>::Get() prior to Az Environment attach
// Otherwise this could trigger environment construction, which can assert on Gems
bool ConsoleFunctorBase::s_deferredHeadInvoked = false;
const char* GetEnumString(GetValueResult value)
{
switch (value)
{
case GetValueResult::Success:
return "GetValueResult::Success";
case GetValueResult::NotImplemented:
return "GetValueResult::NotImplemented : ConsoleFunctor object does not implement the GetAs function";
case GetValueResult::TypeNotConvertible:
return "GetValueResult::TypeNotConvertible : ConsoleFunctor contained type is not convertible to a ConsoleVar type";
case GetValueResult::ConsoleVarNotFound:
return "GetValueResult::ConsoleVarNotFound : ConsoleFunctor command could not be found";
default:
break;
}
return "";
}
ConsoleFunctorBase::ConsoleFunctorBase(const char* name, const char* desc, ConsoleFunctorFlags flags, const TypeId& typeId)
: m_name(name)
, m_desc(desc)
, m_flags(flags)
, m_typeId(typeId)
, m_console(nullptr)
, m_prev(nullptr)
, m_next(nullptr)
, m_isDeferred(true)
{
if (s_deferredHeadInvoked)
{
m_console = AZ::Interface<IConsole>::Get();
if (m_console)
{
ConsoleFunctorBase* functorPtr = this;
m_console->LinkDeferredFunctors(functorPtr);
m_console->GetConsoleCommandRegisteredEvent().Signal(this);
}
}
else
{
Link(s_deferredHead);
}
}
ConsoleFunctorBase::ConsoleFunctorBase(AZ::IConsole& console, const char* name, const char* desc, ConsoleFunctorFlags flags, const TypeId& typeId)
: m_name(name)
, m_desc(desc)
, m_flags(flags)
, m_typeId(typeId)
, m_console(&console)
, m_prev(nullptr)
, m_next(nullptr)
, m_isDeferred(false)
{
m_console->RegisterFunctor(this);
}
ConsoleFunctorBase::ConsoleFunctorBase(ConsoleFunctorBase&& other)
: m_name(other.m_name)
, m_desc(other.m_desc)
, m_flags(other.m_flags)
, m_console(other.m_console)
, m_isDeferred(other.m_isDeferred)
{
if (m_console)
{
m_console->UnregisterFunctor(&other);
m_console->RegisterFunctor(this);
}
}
ConsoleFunctorBase& ConsoleFunctorBase::operator=(ConsoleFunctorBase&& other)
{
if (m_console)
{
// Unlink assigned to instance from any registered consoles
m_console->UnregisterFunctor(this);
}
m_name = other.m_name;
m_desc = other.m_desc;
m_flags = other.m_flags;
m_console = other.m_console;
m_isDeferred = other.m_isDeferred;
if (m_console)
{
// Unlink the other instance from the its console and link
// the assigned to instance in its place
m_console->UnregisterFunctor(&other);
m_console->RegisterFunctor(this);
}
return *this;
}
ConsoleFunctorBase::~ConsoleFunctorBase()
{
if (m_console != nullptr)
{
m_console->UnregisterFunctor(this);
}
else if (m_isDeferred)
{
Unlink(s_deferredHead);
}
}
void ConsoleFunctorBase::Link(ConsoleFunctorBase*& head)
{
m_next = head;
if (head != nullptr)
{
head->m_prev = this;
}
head = this;
}
void ConsoleFunctorBase::Unlink(ConsoleFunctorBase*& head)
{
if (head == nullptr)
{
// The head has already been destroyed, no need to unlink
return;
}
if (head == this)
{
head = m_next;
}
ConsoleFunctorBase* prev = m_prev;
ConsoleFunctorBase* next = m_next;
if (m_prev != nullptr)
{
m_prev->m_next = next;
m_prev = nullptr;
}
if (m_next != nullptr)
{
m_next->m_prev = prev;
m_next = nullptr;
}
}
GetValueResult ConsoleFunctorBase::GetValueAsString(CVarFixedString&) const
{
return GetValueResult::NotImplemented;
}
}
@@ -0,0 +1,208 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <stdint.h>
#include <AzCore/base.h>
#include <AzCore/Console/IConsoleTypes.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/variant.h>
namespace AZ
{
class IConsole;
class Console;
enum class GetValueResult
{
Success,
NotImplemented,
TypeNotConvertible,
ConsoleVarNotFound,
};
const char* GetEnumString(GetValueResult value);
//! @class ConsoleFunctorBase
//! Base class for console functors.
class ConsoleFunctorBase
{
public:
//! Constructor.
//! @param name the string name of the functor, used to identify and invoke the functor through the console interface
//! @param desc a string help description of the functor
//! @param flags a set of flags that mutate the behaviour of the functor
//! @param typeId the TypeId associated with this console functor
ConsoleFunctorBase(const char* name, const char* desc, ConsoleFunctorFlags flags, const TypeId& typeId);
//! Console registration constructor.
//! @param console reference to valid console interface that the console functor will register with
//! @param name the string name of the functor, used to identify and invoke the functor through the console interface
//! @param desc a string help description of the functor
//! @param flags a set of flags that mutate the behaviour of the functor
//! @param typeId the TypeId associated with this console functor
ConsoleFunctorBase(AZ::IConsole& console, const char* name, const char* desc, ConsoleFunctorFlags flags, const TypeId& typeId);
//! Move Constructor
//! Unlinks the old console functor base address of the console interface and links the new
//! console base instance to it
ConsoleFunctorBase(ConsoleFunctorBase&& other);
ConsoleFunctorBase& operator=(ConsoleFunctorBase&& other);
//! Destructor.
virtual ~ConsoleFunctorBase();
//! Returns the string name of this functor.
//! @return the string name of this functor
const char* GetName() const;
//! Returns the help descriptor of this functor.
//! @return the help descriptor of this functor
const char* GetDesc() const;
//! Returns the flags set on this functor instance.
//! @return the flags set on this functor instance
ConsoleFunctorFlags GetFlags() const;
//! Returns the TypeId of the bound type if one exists.
//! @return the TypeId of the bound type if one exists
const TypeId& GetTypeId() const;
//! Execute operator, calling this executes the functor.
//! @param arguments set of string inputs to the functor
virtual void operator()(const ConsoleCommandContainer& arguments) = 0;
//! For functors that can be replicated (cvars).
//! This will generate a replication string suitable for remote execution
//! @param outString the output string which can be remotely executed
//! @return boolean true if a proper replicated value was returned, false otherwise
virtual bool GetReplicationString(CVarFixedString& outString) const = 0;
//! Attempts to retrieve the functor object instance as the provided type.
//! @param outResult the value to store the result in
//! @return GetConsoleValueResult::Success or an appropriate error code
template <typename RETURN_TYPE>
GetValueResult GetValue(RETURN_TYPE& outResult) const;
//! Used internally to link cvars and functors from various modules to the console as they are loaded.
static ConsoleFunctorBase*& GetDeferredHead();
protected:
virtual GetValueResult GetValueAsString(CVarFixedString& outString) const;
void Link(ConsoleFunctorBase*& head);
void Unlink(ConsoleFunctorBase*& head);
const char* m_name = "";
const char* m_desc = "";
ConsoleFunctorFlags m_flags = ConsoleFunctorFlags::Null;
TypeId m_typeId;
IConsole* m_console = nullptr;
ConsoleFunctorBase* m_prev = nullptr;
ConsoleFunctorBase* m_next = nullptr;
bool m_isDeferred = true;
static ConsoleFunctorBase* s_deferredHead;
static bool s_deferredHeadInvoked;
friend class Console;
};
//! @class ConsoleFunctor
//! @brief Console functor which wraps a function call into an object instance.
template <typename _TYPE, bool _REPLICATES_VALUE>
class ConsoleFunctor final
: public ConsoleFunctorBase
{
public:
using MemberFunctorSignature = void(_TYPE::*)(const ConsoleCommandContainer&);
using RawFunctorSignature = void(*)(_TYPE&, const ConsoleCommandContainer&);
using FunctorUnion = AZStd::variant<RawFunctorSignature, MemberFunctorSignature>;
//! Constructors.
//! @param name the string name of the functor, used to identify and invoke the functor through the console interface
//! @param desc a string help description of the functor
//! @param flags a set of flags that mutate the behaviour of the functor
//! @param typeId the TypeId associated with this console functor
//! @param object reference to the data storage object
ConsoleFunctor(const char* name, const char* desc, ConsoleFunctorFlags flags, const TypeId& typeId, _TYPE& object, FunctorUnion function);
ConsoleFunctor(IConsole& console, const char* name, const char* desc, ConsoleFunctorFlags flags, const TypeId& typeId, _TYPE& object, FunctorUnion function);
// Console Functors are movable
ConsoleFunctor(ConsoleFunctor&&) = default;
ConsoleFunctor& operator=(ConsoleFunctor&&) = default;
~ConsoleFunctor() override = default;
//! ConsoleFunctorBase overrides
//! @{
void operator()(const ConsoleCommandContainer& arguments) override;
bool GetReplicationString(CVarFixedString& outString) const override;
//! @}
//! Returns reference typed stored type wrapped stored by ConsoleFunctor.
//! @return reference to underlying type
_TYPE& GetValue();
private:
GetValueResult GetValueAsString(CVarFixedString& outString) const override;
// Console Functors are not copyable
ConsoleFunctor& operator=(const ConsoleFunctor&) = delete;
ConsoleFunctor(const ConsoleFunctor&) = delete;
_TYPE* m_object{};
FunctorUnion m_function;
};
//! @class ConsoleFunctor
//! @brief Console functor specialization for non-member console functions (no instance to invoke on).
template <bool _REPLICATES_VALUE>
class ConsoleFunctor<void, _REPLICATES_VALUE> final
: public ConsoleFunctorBase
{
public:
using FunctorSignature = void(*)(const ConsoleCommandContainer&);
ConsoleFunctor(const char* name, const char* desc, ConsoleFunctorFlags flags, const TypeId& typeId, FunctorSignature function);
ConsoleFunctor(AZ::IConsole& console, const char* name, const char* desc, ConsoleFunctorFlags flags, const TypeId& typeId, FunctorSignature function);
// Console Functors are movable
ConsoleFunctor(ConsoleFunctor&&) = default;
ConsoleFunctor& operator=(ConsoleFunctor&&) = default;
~ConsoleFunctor() override = default;
//! ConsoleFunctorBase overrides
//! @{
void operator()(const ConsoleCommandContainer& arguments) override;
bool GetReplicationString(CVarFixedString& outString) const override;
//! @}
private:
GetValueResult GetValueAsString([[maybe_unused]] CVarFixedString& outString) const override;
FunctorSignature m_function;
};
}
#include <AzCore/Console/ConsoleFunctor.inl>
@@ -0,0 +1,221 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Console/ConsoleTypeHelpers.h>
namespace AZ
{
inline const char* ConsoleFunctorBase::GetName() const
{
return m_name;
}
inline const char* ConsoleFunctorBase::GetDesc() const
{
return m_desc;
}
inline ConsoleFunctorFlags ConsoleFunctorBase::GetFlags() const
{
return m_flags;
}
inline const TypeId& ConsoleFunctorBase::GetTypeId() const
{
return m_typeId;
}
template <typename RETURN_TYPE>
inline GetValueResult ConsoleFunctorBase::GetValue(RETURN_TYPE& outResult) const
{
CVarFixedString buffer;
const GetValueResult resultCode = GetValueAsString(buffer);
if (resultCode != GetValueResult::Success)
{
return resultCode;
}
return ConsoleTypeHelpers::StringToValue(outResult, buffer)
? GetValueResult::Success
: GetValueResult::TypeNotConvertible;
}
// This is forcibly inlined because we must guarantee this is compiled into and invoked from the calling module rather than the .exe that links AzCore
AZ_FORCE_INLINE ConsoleFunctorBase*& ConsoleFunctorBase::GetDeferredHead()
{
s_deferredHeadInvoked = true;
return s_deferredHead;
}
template <typename _TYPE, bool _REPLICATES_VALUE>
inline ConsoleFunctor<_TYPE, _REPLICATES_VALUE>::ConsoleFunctor
(
const char* name,
const char* desc,
ConsoleFunctorFlags flags,
const TypeId& typeId,
_TYPE& object,
FunctorUnion function
)
: ConsoleFunctorBase(name, desc, flags, typeId)
, m_object(&object)
, m_function(AZStd::move(function))
{
;
}
template <typename _TYPE, bool _REPLICATES_VALUE>
inline ConsoleFunctor<_TYPE, _REPLICATES_VALUE>::ConsoleFunctor
(
AZ::IConsole& console,
const char* name,
const char* desc,
ConsoleFunctorFlags flags,
const TypeId& typeId,
_TYPE& object,
FunctorUnion function
)
: ConsoleFunctorBase(console, name, desc, flags, typeId)
, m_object(&object)
, m_function(AZStd::move(function))
{
;
}
template <typename _TYPE, bool _REPLICATES_VALUE>
inline void ConsoleFunctor<_TYPE, _REPLICATES_VALUE>::operator()(const ConsoleCommandContainer& arguments)
{
auto functorVisitor = [this, arguments](auto&& functor)
{
AZStd::invoke(functor, *m_object, arguments);
};
AZStd::visit(functorVisitor, m_function);
}
template <typename _TYPE, bool _REPLICATES_VALUE>
struct ConsoleReplicateHelper;
template <typename _TYPE>
struct ConsoleReplicateHelper<_TYPE, false>
{
static bool GetReplicationString(_TYPE&, const char* name, CVarFixedString& outString)
{
outString = name;
return false;
}
static bool StringToValue(_TYPE&, const ConsoleCommandContainer&)
{
return false;
}
static bool ValueToString(_TYPE&, CVarFixedString&)
{
return false;
}
};
template <typename _TYPE>
struct ConsoleReplicateHelper<_TYPE, true>
{
static bool GetReplicationString(_TYPE& instance, const char* name, CVarFixedString& outString)
{
CVarFixedString valueString;
ValueToString(instance, valueString);
outString = CVarFixedString(name) + " " + valueString;
return true;
}
static bool StringToValue(_TYPE& instance, const ConsoleCommandContainer& arguments)
{
return instance.StringToValue(arguments);
}
static void ValueToString(_TYPE& instance, CVarFixedString& outString)
{
instance.ValueToString(outString);
}
};
template <typename _TYPE, bool _REPLICATES_VALUE>
inline bool ConsoleFunctor<_TYPE, _REPLICATES_VALUE>::GetReplicationString(CVarFixedString& outString) const
{
return ConsoleReplicateHelper<_TYPE, _REPLICATES_VALUE>::GetReplicationString(*m_object, GetName(), outString);
}
template <typename _TYPE, bool _REPLICATES_VALUE>
inline _TYPE& ConsoleFunctor<_TYPE, _REPLICATES_VALUE>::GetValue()
{
return *m_object;
}
template <typename _TYPE, bool _REPLICATES_VALUE>
inline GetValueResult ConsoleFunctor<_TYPE, _REPLICATES_VALUE>::GetValueAsString(CVarFixedString& outString) const
{
ConsoleReplicateHelper<_TYPE, _REPLICATES_VALUE>::ValueToString(*m_object, outString);
return GetValueResult::Success;
}
// ConsoleFunctor Specialization for running non-member console functions with no instance
template <bool _REPLICATES_VALUE>
inline ConsoleFunctor<void, _REPLICATES_VALUE>::ConsoleFunctor
(
const char* name,
const char* desc,
ConsoleFunctorFlags flags,
const TypeId& typeId,
FunctorSignature function
)
: ConsoleFunctorBase(name, desc, flags, typeId)
, m_function(function)
{
;
}
template <bool _REPLICATES_VALUE>
inline ConsoleFunctor<void, _REPLICATES_VALUE>::ConsoleFunctor
(
AZ::IConsole& console,
const char* name,
const char* desc,
ConsoleFunctorFlags flags,
const TypeId& typeId,
FunctorSignature function
)
: ConsoleFunctorBase(console, name, desc, flags, typeId)
, m_function(function)
{
;
}
template <bool _REPLICATES_VALUE>
inline void ConsoleFunctor<void, _REPLICATES_VALUE>::operator()(const ConsoleCommandContainer& arguments)
{
(*m_function)(arguments);
}
template <bool _REPLICATES_VALUE>
inline bool ConsoleFunctor<void, _REPLICATES_VALUE>::GetReplicationString(CVarFixedString& outString) const
{
outString = GetName();
return false;
}
template <bool _REPLICATES_VALUE>
inline GetValueResult ConsoleFunctor<void, _REPLICATES_VALUE>::GetValueAsString([[maybe_unused]] CVarFixedString& outString) const
{
return GetValueResult::NotImplemented;
}
}
@@ -0,0 +1,45 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/Console/IConsoleTypes.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
namespace ConsoleTypeHelpers
{
//! Helper function for converting a typed value to a string representation.
//! @param value the value instance to convert to a string
//! @return the string representation of the value
template <typename TYPE>
CVarFixedString ValueToString(const TYPE& value);
//! Helper function for converting a set of strings to a value.
//! @param outValue the value instance to write to
//! @param arguments the value instance to convert to a string
//! @return boolean true on success, false if there was a conversion error
template <typename TYPE>
bool StringSetToValue(TYPE& outValue, const AZ::ConsoleCommandContainer& arguments);
//! Helper function for converting a typed value to a string representation.
//! @param outValue the value instance to write to
//! @param string the string to
//! @return boolean true on success, false if there was a conversion error
template <typename _TYPE>
bool StringToValue(_TYPE& outValue, AZStd::string_view string);
}
}
#include <AzCore/Console/ConsoleTypeHelpers.inl>
@@ -0,0 +1,363 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/containers/fixed_vector.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Math/Color.h>
namespace AZ
{
namespace ConsoleTypeHelpers
{
inline AZStd::string ConvertString(const CVarFixedString& value)
{
return AZStd::string(value.c_str(), value.size());
}
inline AZ::CVarFixedString ConvertString(const AZStd::string& value)
{
return AZ::CVarFixedString(value.c_str(), value.size());
}
template <typename TYPE>
inline CVarFixedString ValueToString(const TYPE& value)
{
return ConvertString(AZStd::to_string(value));
}
template <>
inline CVarFixedString ValueToString(const bool& value)
{
return value ? "true" : "false";
}
template <>
inline CVarFixedString ValueToString(const char& value)
{
return CVarFixedString(1, value);
}
template <>
inline CVarFixedString ValueToString<AZ::CVarFixedString>(const AZ::CVarFixedString& value)
{
return value;
}
template <>
inline CVarFixedString ValueToString<AZStd::string>(const AZStd::string& value)
{
return ConvertString(value);
}
template <>
inline CVarFixedString ValueToString<AZ::Vector2>(const AZ::Vector2& value)
{
return CVarFixedString::format("%0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()));
}
template <>
inline CVarFixedString ValueToString<AZ::Vector3>(const AZ::Vector3& value)
{
return CVarFixedString::format("%0.2f %0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()), static_cast<float>(value.GetZ()));
}
template <>
inline CVarFixedString ValueToString<AZ::Vector4>(const AZ::Vector4& value)
{
return CVarFixedString::format("%0.2f %0.2f %0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()), static_cast<float>(value.GetZ()), static_cast<float>(value.GetW()));
}
template <>
inline CVarFixedString ValueToString<AZ::Quaternion>(const AZ::Quaternion& value)
{
return CVarFixedString::format("%0.2f %0.2f %0.2f %0.2f", static_cast<float>(value.GetX()), static_cast<float>(value.GetY()), static_cast<float>(value.GetZ()), static_cast<float>(value.GetW()));
}
template <>
inline CVarFixedString ValueToString<AZ::Color>(const AZ::Color& value)
{
return CVarFixedString::format("%0.2f %0.2f %0.2f %0.2f", static_cast<float>(value.GetR()), static_cast<float>(value.GetG()), static_cast<float>(value.GetB()), static_cast<float>(value.GetA()));
}
template <>
inline bool StringSetToValue<bool>(bool& outValue, const AZ::ConsoleCommandContainer& arguments)
{
if (!arguments.empty())
{
AZ::CVarFixedString lower{ arguments.front() };
AZStd::to_lower(lower.begin(), lower.end());
if ((lower == "false") || (lower == "0"))
{
outValue = false;
return true;
}
else if ((lower == "true") || (lower == "1"))
{
outValue = true;
return true;
}
else
{
AZ_Warning("Az Console", false, "Invalid input for boolean variable");
}
}
return false;
}
template <>
inline bool StringSetToValue<char>(char& outValue, const AZ::ConsoleCommandContainer& arguments)
{
if (!arguments.empty())
{
AZStd::string_view frontArg = arguments.front();
outValue = frontArg[0];
}
else
{
// An empty string is actually a null character '\0'
outValue = '\0';
}
return true;
}
// Note that string-stream doesn't behave nicely with char and uint8_t..
// So instead we string-stream to a very large type that does work, and downcast if the result is within the limits of the target type
template <typename TYPE, typename MAX_TYPE>
inline bool StringSetToIntegralValue(TYPE& outValue, const AZ::ConsoleCommandContainer& arguments, [[maybe_unused]] const char* typeName, [[maybe_unused]] const char* errorString)
{
if (!arguments.empty())
{
AZ::CVarFixedString convertCandidate{ arguments.front() };
char* endPtr = nullptr;
MAX_TYPE value = static_cast<MAX_TYPE>(strtoll(convertCandidate.c_str(), &endPtr, 0));
if (endPtr == convertCandidate.c_str())
{
AZ_Warning("Az Console", false, "stringstream failed to convert value %s to %s type\n", convertCandidate.c_str(), typeName);
return false;
}
//Note: std::numeric_limits<>::min and max have extra parentheses here to min/max being evaluated as macros, to avoid colliding with min/max in Windows.h
if ((value >= (std::numeric_limits<TYPE>::min)()) && (value <= (std::numeric_limits<TYPE>::max)()))
{
outValue = static_cast<TYPE>(value);
return true;
}
else
{
AZ_Warning("Az Console", false, errorString, static_cast<MAX_TYPE>(outValue));
}
}
return false;
}
#define INTEGRAL_NUMERIC_HANDLER(TYPE, MAX_TYPE, FMT_STRING) \
template <> \
inline bool StringSetToValue<TYPE>(TYPE& outValue, const AZ::ConsoleCommandContainer& arguments) \
{ \
return StringSetToIntegralValue<TYPE, MAX_TYPE>(outValue, arguments, #TYPE, "attempted to assign out of range value %" #FMT_STRING "\n"); \
}
INTEGRAL_NUMERIC_HANDLER(int8_t, long long, lld);
INTEGRAL_NUMERIC_HANDLER(int16_t, long long, lld);
INTEGRAL_NUMERIC_HANDLER(int32_t, long long, lld);
INTEGRAL_NUMERIC_HANDLER(long, long long, lld);
INTEGRAL_NUMERIC_HANDLER(long long, long long, lld);
INTEGRAL_NUMERIC_HANDLER(uint8_t, unsigned long long, llu);
INTEGRAL_NUMERIC_HANDLER(uint16_t, unsigned long long, llu);
INTEGRAL_NUMERIC_HANDLER(uint32_t, unsigned long long, llu);
INTEGRAL_NUMERIC_HANDLER(unsigned long, unsigned long long, llu);
INTEGRAL_NUMERIC_HANDLER(unsigned long long, unsigned long long, llu);
#undef INTEGRAL_NUMERIC_HANDLER
template <>
inline bool StringSetToValue<float>(float& outValue, const AZ::ConsoleCommandContainer& arguments)
{
if (!arguments.empty())
{
AZ::CVarFixedString convertCandidate{ arguments.front() };
char* endPtr = nullptr;
const float converted = static_cast<float>(strtod(convertCandidate.c_str(), &endPtr));
if (endPtr == convertCandidate.c_str())
{
AZ_Warning("Az Console", false, "Invalid input for float variable");
return false;
}
outValue = converted;
return true;
}
return false;
}
template <>
inline bool StringSetToValue<double>(double& outValue, const AZ::ConsoleCommandContainer& arguments)
{
if (!arguments.empty())
{
AZ::CVarFixedString convertCandidate{ arguments.front() };
char* endPtr = nullptr;
const double converted = strtod(convertCandidate.c_str(), &endPtr);
if (endPtr == convertCandidate.c_str())
{
AZ_Warning("Az Console", false, "Invalid input for double variable");
return false;
}
outValue = converted;
return true;
}
return false;
}
template <>
inline bool StringSetToValue<AZ::CVarFixedString>(AZ::CVarFixedString& outValue, const AZ::ConsoleCommandContainer& arguments)
{
if (!arguments.empty())
{
outValue.clear();
bool addSpace = false;
for (AZStd::string_view argument : arguments)
{
if (addSpace)
{
outValue.push_back(' ');
}
outValue += argument;
addSpace = true;
}
return true;
}
return false;
}
template <>
inline bool StringSetToValue<AZStd::string>(AZStd::string& outValue, const AZ::ConsoleCommandContainer& arguments)
{
if (!arguments.empty())
{
outValue.clear();
StringFunc::Join(outValue, arguments.begin(), arguments.end(), " ");
return true;
}
return false;
}
template <typename TYPE, uint32_t ELEMENT_COUNT>
inline bool StringSetToVectorValue(TYPE& outValue, const AZ::ConsoleCommandContainer& arguments, [[maybe_unused]] const char* typeName)
{
if (arguments.size() < ELEMENT_COUNT)
{
AZ_Warning("Az Console", false, "Not enough arguments provided to %s StringSetToValue()", typeName);
return false;
}
AZ::CVarFixedString convertCandidate;
for (uint32_t i = 0; i < ELEMENT_COUNT; ++i)
{
convertCandidate = arguments[i];
outValue.SetElement(i, static_cast<float>(strtod(convertCandidate.c_str(), nullptr)));
}
return true;
}
inline bool StringSetToRgbaValue(AZ::Color& outColor, const AZ::ConsoleCommandContainer& arguments)
{
const uint32_t ArgumentCount = 4;
if (arguments.size() < ArgumentCount)
{
AZ_Warning("Az Console", false, "Not enough arguments provided to AZ::Color StringSetToValue()");
return false;
}
using ColorSetter = void (AZ::Color::*)(AZ::u8);
ColorSetter rgbaSetters[] = { &AZ::Color::SetR8, &AZ::Color::SetG8, &AZ::Color::SetB8, &AZ::Color::SetA8 };
AZ::CVarFixedString convertCandidate;
for (uint32_t i = 0; i < ArgumentCount; ++i)
{
convertCandidate = arguments[i];
AZStd::invoke(rgbaSetters[i], outColor, static_cast<AZ::u8>(strtoll(convertCandidate.c_str(), nullptr, 0)));
}
return true;
}
template <>
inline bool StringSetToValue<AZ::Vector2>(AZ::Vector2& outValue, const AZ::ConsoleCommandContainer& arguments)
{
return StringSetToVectorValue<AZ::Vector2, 2>(outValue, arguments, "AZ::Vector2");
}
template <>
inline bool StringSetToValue<AZ::Vector3>(AZ::Vector3& outValue, const AZ::ConsoleCommandContainer& arguments)
{
return StringSetToVectorValue<AZ::Vector3, 3>(outValue, arguments, "AZ::Vector3");
}
template <>
inline bool StringSetToValue<AZ::Vector4>(AZ::Vector4& outValue, const AZ::ConsoleCommandContainer& arguments)
{
return StringSetToVectorValue<AZ::Vector4, 4>(outValue, arguments, "AZ::Vector4");
}
template <>
inline bool StringSetToValue<AZ::Quaternion>(AZ::Quaternion& outValue, const AZ::ConsoleCommandContainer& arguments)
{
return StringSetToVectorValue<AZ::Quaternion, 4>(outValue, arguments, "AZ::Quaternion");
}
template <>
inline bool StringSetToValue<AZ::Color>(AZ::Color& outValue, const AZ::ConsoleCommandContainer& arguments)
{
const bool decimal = AZStd::any_of(
AZStd::cbegin(arguments), AZStd::cend(arguments), [](const AZStd::string argument)
{
return argument.find(".") != AZStd::string::npos;
});
if (decimal)
{
return StringSetToVectorValue<AZ::Color, 4>(outValue, arguments, "AZ::Color");
}
else
{
return StringSetToRgbaValue(outValue, arguments);
}
}
template <typename _TYPE>
inline bool StringToValue(_TYPE& outValue, AZStd::string_view string)
{
AZ::ConsoleCommandContainer arguments;
auto splitToVector = [&arguments](AZStd::string_view token)
{
arguments.emplace_back(token);
};
StringFunc::TokenizeVisitor(string, splitToVector, " ");
return StringSetToValue(outValue, arguments);
}
}
}
@@ -0,0 +1,248 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Console/ConsoleFunctor.h>
#include <AzCore/Console/ConsoleDataWrapper.h>
#include <AzCore/Console/IConsoleTypes.h>
#include <AzCore/EBus/Event.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/functional.h>
namespace AZ
{
class CommandLine;
//! @class IConsole
//! A simple console class for providing text based variable and process interaction.
class IConsole
{
public:
AZ_RTTI(IConsole, "{20001930-119D-4A80-BD67-825B7E4AEB3D}");
using FunctorVisitor = AZStd::function<void(ConsoleFunctorBase*)>;
IConsole() = default;
virtual ~IConsole() = default;
//! Invokes a single console command, optionally returning the command output.
//! @param command the command string to parse and execute on
//! @param silentMode if true, logs will be suppressed during command execution
//! @param invokedFrom the source point that initiated console invocation
//! @param requiredSet a set of flags that must be set on the functor for it to execute
//! @param requiredClear a set of flags that must *NOT* be set on the functor for it to execute
//! @return boolean true on success, false otherwise
virtual bool PerformCommand
(
const char* command,
ConsoleSilentMode silentMode = ConsoleSilentMode::NotSilent,
ConsoleInvokedFrom invokedFrom = ConsoleInvokedFrom::AzConsole,
ConsoleFunctorFlags requiredSet = ConsoleFunctorFlags::Null,
ConsoleFunctorFlags requiredClear = ConsoleFunctorFlags::ReadOnly
) = 0;
//! Invokes a single console command, optionally returning the command output.
//! @param commandAndArgs list of command and command arguments to execute. The first argument is the command itself
//! @param silentMode if true, logs will be suppressed during command execution
//! @param invokedFrom the source point that initiated console invocation
//! @param requiredSet a set of flags that must be set on the functor for it to execute
//! @param requiredClear a set of flags that must *NOT* be set on the functor for it to execute
//! @return boolean true on success, false otherwise
virtual bool PerformCommand
(
const ConsoleCommandContainer& commandAndArgs,
ConsoleSilentMode silentMode = ConsoleSilentMode::NotSilent,
ConsoleInvokedFrom invokedFrom = ConsoleInvokedFrom::AzConsole,
ConsoleFunctorFlags requiredSet = ConsoleFunctorFlags::Null,
ConsoleFunctorFlags requiredClear = ConsoleFunctorFlags::ReadOnly
) = 0;
//! Invokes a single console command, optionally returning the command output.
//! @param command the command to execute
//! @param commandArgs the arguments to the command to execute
//! @param silentMode if true, logs will be suppressed during command execution
//! @param invokedFrom the source point that initiated console invocation
//! @param requiredSet a set of flags that must be set on the functor for it to execute
//! @param requiredClear a set of flags that must *NOT* be set on the functor for it to execute
//! @return boolean true on success, false otherwise
virtual bool PerformCommand
(
AZStd::string_view command,
const ConsoleCommandContainer& commandArgs,
ConsoleSilentMode silentMode = ConsoleSilentMode::NotSilent,
ConsoleInvokedFrom invokedFrom = ConsoleInvokedFrom::AzConsole,
ConsoleFunctorFlags requiredSet = ConsoleFunctorFlags::Null,
ConsoleFunctorFlags requiredClear = ConsoleFunctorFlags::ReadOnly
) = 0;
//! Loads and executes the specified config file.
//! @param configFileName the filename of the config file to load and execute
virtual void ExecuteConfigFile(AZStd::string_view configFileName) = 0;
//! Invokes all of the commands as contained in a concatenated command-line string.
//! @param commandLine the concatenated command-line string to execute
virtual void ExecuteCommandLine(const AZ::CommandLine& commandLine) = 0;
//! HasCommand is used to determine if the console knows about a command.
//! @param command the command we are checking for
//! @return boolean true on if the command is registered, false otherwise
virtual bool HasCommand(const char* command) = 0;
//! FindCommand finds the console command with the specified console string.
//! @param command the command that is being searched for
//! @return non-null pointer to the console command if found
virtual ConsoleFunctorBase* FindCommand(const char* command) = 0;
//! Prints all commands of which the input is a prefix.
//! @param command the prefix string to dump all matching commands for
//! @return boolean true on success, false otherwise
virtual AZStd::string AutoCompleteCommand(const char* command) = 0;
//! Retrieves the value of the requested cvar.
//! @param command the name of the cvar to find and retrieve the current value of
//! @param outValue reference to the instance to write the current cvar value to
//! @return GetValueResult::Success if the operation succeeded, or an error result if the operation failed
template<typename RETURN_TYPE>
GetValueResult GetCvarValue(const char* command, RETURN_TYPE& outValue);
//! Visits all registered console functors.
//! @param visitor the instance to visit all functors with
virtual void VisitRegisteredFunctors(const FunctorVisitor& visitor) = 0;
//! Registers a ConsoleFunctor with the console instance.
//! @param functor pointer to the ConsoleFunctor to register
virtual void RegisterFunctor(ConsoleFunctorBase* functor) = 0;
//! Unregisters a ConsoleFunctor with the console instance.
//! @param functor pointer to the ConsoleFunctor to unregister
virtual void UnregisterFunctor(ConsoleFunctorBase* functor) = 0;
//! Should be invoked for every module that gets loaded.
//! @param pointer to the modules set of ConsoleFunctors to register
virtual void LinkDeferredFunctors(ConsoleFunctorBase*& deferredHead) = 0;
//! Returns the AZ::Event<> invoked whenever a console command is registered.
using ConsoleCommandRegisteredEvent = AZ::Event<ConsoleFunctorBase*>;
ConsoleCommandRegisteredEvent& GetConsoleCommandRegisteredEvent();
//! Returns the AZ::Event<> invoked whenever a console command is executed.
ConsoleCommandInvokedEvent& GetConsoleCommandInvokedEvent();
//! Returns the AZ::Event<> invoked whenever a console command could not be found.
DispatchCommandNotFoundEvent& GetDispatchCommandNotFoundEvent();
AZ_DISABLE_COPY_MOVE(IConsole);
protected:
ConsoleCommandRegisteredEvent m_consoleCommandRegisteredEvent;
ConsoleCommandInvokedEvent m_consoleCommandInvokedEvent;
DispatchCommandNotFoundEvent m_dispatchCommandNotFoundEvent;
};
inline auto IConsole::GetConsoleCommandRegisteredEvent() -> ConsoleCommandRegisteredEvent&
{
return m_consoleCommandRegisteredEvent;
}
inline auto IConsole::GetConsoleCommandInvokedEvent() -> ConsoleCommandInvokedEvent&
{
return m_consoleCommandInvokedEvent;
}
inline auto IConsole::GetDispatchCommandNotFoundEvent() -> DispatchCommandNotFoundEvent&
{
return m_dispatchCommandNotFoundEvent;
}
template<typename RETURN_TYPE>
inline GetValueResult IConsole::GetCvarValue(const char* command, RETURN_TYPE& outValue)
{
ConsoleFunctorBase* cvarFunctor = FindCommand(command);
if (cvarFunctor == nullptr)
{
return GetValueResult::ConsoleVarNotFound;
}
return cvarFunctor->GetValue(outValue);
}
}
template <typename _TYPE, typename = void>
static constexpr AZ::ThreadSafety ConsoleThreadSafety = AZ::ThreadSafety::RequiresLock;
template <typename _TYPE>
static constexpr AZ::ThreadSafety ConsoleThreadSafety<_TYPE, std::enable_if_t<std::is_arithmetic_v<_TYPE>>> = AZ::ThreadSafety::UseStdAtomic;
//! Standard cvar macro.
//! @param _TYPE the data type of the cvar
//! @param _NAME the name of the cvar
//! @param _INIT the initial value to assign to the cvar
//! @param _CALLBACK this is an optional callback function to get invoked when a cvar changes value
//! You have no guarantees as to what thread will invoke the callback
//! It is the responsibility of the implementor of the callback to ensure thread safety
//! @param _FLAGS a set of AzFramework::ConsoleFunctorFlags used to mutate behaviour
//! @param _DESC a description of the cvar
#define AZ_CVAR(_TYPE, _NAME, _INIT, _CALLBACK, _FLAGS, _DESC) \
using CVarDataWrapperType##_NAME = AZ::ConsoleDataWrapper<_TYPE, ConsoleThreadSafety<_TYPE>>; \
inline CVarDataWrapperType##_NAME _NAME(_INIT, _CALLBACK, #_NAME, _DESC, _FLAGS)
//! Block-scoped cvar macro.
//! This declaration should only be used within a block-scope or function body
//! @param _TYPE the data type of the cvar
//! @param _NAME the name of the cvar
//! @param _INIT the initial value to assign to the cvar
//! @param _CALLBACK this is an optional callback function to get invoked when a cvar changes value
//! @param _FLAGS a set of AzFramework::ConsoleFunctorFlags used to mutate behaviour
//! You have no guarantees as to what thread will invoke the callback
//! It is the responsibility of the implementor of the callback to ensure thread safety
//! @param _DESC a description of the cvar
#define AZ_CVAR_SCOPED(_TYPE, _NAME, _INIT, _CALLBACK, _FLAGS, _DESC) \
using CVarDataWrapperType##_NAME = AZ::ConsoleDataWrapper<_TYPE, ConsoleThreadSafety<_TYPE>>; \
CVarDataWrapperType##_NAME _NAME(_INIT, _CALLBACK, #_NAME, _DESC, _FLAGS)
//! Cvar macro that externs a console variable.
//! @param _TYPE the data type of the cvar to extern
//! @param _NAME the name of the cvar to extern
#define AZ_CVAR_EXTERNED(_TYPE, _NAME) \
using CVarDataWrapperType##_NAME = AZ::ConsoleDataWrapper<_TYPE, ConsoleThreadSafety<_TYPE>>; \
extern CVarDataWrapperType##_NAME _NAME;
//! Implements a console functor for a class member function.
//! @param _CLASS the that the function gets invoked on
//! @param _FUNCTION the method to invoke
//! You have no guarantees as to what thread will invoke the function
//! It is the responsibility of the implementor of the console function to ensure thread safety
//! @param _FLAGS a set of AzFramework::ConsoleFunctorFlags used to mutate behaviour
//! @param _DESC a description of the cvar
#define AZ_CONSOLEFUNC(_CLASS, _FUNCTION, _FLAGS, _DESC) \
AZ::ConsoleFunctor<_CLASS, false> m_functor##_FUNCTION{#_CLASS "." #_FUNCTION, _DESC, _FLAGS, AZ::TypeId::CreateNull(), *this, &_CLASS::_FUNCTION}
//! Implements a console functor for a non-member function.
//! @param _FUNCTION the method to invoke
//! ** YOU HAVE NO GUARANTEES AS TO WHAT THREAD WILL INVOKE YOUR FUNCTION ** It is the responsibility of the implementor of the console function to ensure thread safety
//! @param _FLAGS a set of AzFramework::ConsoleFunctorFlags used to mutate behaviour
//! @param _DESC a description of the cvar
#define AZ_CONSOLEFREEFUNC_3(_FUNCTION, _FLAGS, _DESC) \
inline AZ::ConsoleFunctor<void, false> Functor##_FUNCTION(#_FUNCTION, _DESC, _FLAGS, AZ::TypeId::CreateNull(), &_FUNCTION)
//! Implements a console functor for a non-member function.
//!
//! @param _FUNCTION the method to invoke
//! ** YOU HAVE NO GUARANTEES AS TO WHAT THREAD WILL INVOKE YOUR FUNCTION ** It is the responsibility of the implementor of the console function to ensure thread safety
//! @param _NAME the name the of the function in the console
//! @param _FLAGS a set of AzFramework::ConsoleFunctorFlags used to mutate behaviour
//! @param _DESC a description of the cvar
#define AZ_CONSOLEFREEFUNC_4(_NAME, _FUNCTION, _FLAGS, _DESC) \
inline AZ::ConsoleFunctor<void, false> Functor##_FUNCTION(#_FUNCTION, _DESC, _FLAGS, AZ::TypeId::CreateNull(), &_FUNCTION)
#define AZ_CONSOLEFREEFUNC(...) AZ_MACRO_SPECIALIZE(AZ_CONSOLEFREEFUNC_, AZ_VA_NUM_ARGS(__VA_ARGS__), (__VA_ARGS__))
@@ -0,0 +1,86 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/std/typetraits/underlying_type.h>
// AZStd forwards
namespace AZStd
{
template <class Element>
struct char_traits;
template <class Element, class Traits>
class basic_string_view;
template <class Element, size_t MaxElementCount, class Traits>
class basic_fixed_string;
template <class T, size_t Capacity>
class fixed_vector;
}
namespace AZ
{
template <class... Params>
class Event;
// Provides compile time constants and type aliases for accessing AZ Console
// Without the requirement to have a complete console type
inline constexpr size_t MaxConsoleCommandPlusArgsLength = 64;
using ConsoleCommandContainer = AZStd::fixed_vector<AZStd::basic_string_view<char, AZStd::char_traits<char>>, MaxConsoleCommandPlusArgsLength>;
inline constexpr size_t MaxCVarStringLength = 256;
using CVarFixedString = AZStd::basic_fixed_string<char, MaxCVarStringLength, AZStd::char_traits<char>>;
enum class ConsoleFunctorFlags
{
Null = 0 // Empty flags
, DontReplicate = (1 << 0) // Should not be replicated
, ServerOnly = (1 << 1) // Should never replicate to clients
, ReadOnly = (1 << 2) // Should not be invoked at runtime
, IsCheat = (1 << 3) // Command is a cheat, may require escalated privileges to modify
, IsInvisible = (1 << 4) // Should not be shown in the console for autocomplete
, IsDeprecated = (1 << 5) // Command is deprecated, show a warning when invoked
, NeedsReload = (1 << 6) // Level should be reloaded after executing this command
, AllowClientSet = (1 << 7) // Allow clients to modify this cvar even in release (this alters the cvar for all connected servers and clients, be VERY careful enabling this flag)
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(ConsoleFunctorFlags);
enum class ConsoleSilentMode
{
Silent,
NotSilent
};
enum class ConsoleInvokedFrom
{
AzConsole,
AzNetworking,
CryBinding
};
//! Called on invocation of any console command.
//! @param command the full command being executed on the console
//! @param commandArgs the array of arguments that was supplied to PeformCommand
//! @param flags the set of flags associated with the command being executed
//! @param invokedFrom the source point that initiated console invocation
using ConsoleCommandInvokedEvent = AZ::Event<AZStd::basic_string_view<char, AZStd::char_traits<char>>, const ConsoleCommandContainer&, ConsoleFunctorFlags, ConsoleInvokedFrom>;
//! Called when a command to dispatch has not been found within the AZ Console.
//! @param command the full command that was not found
//! @param commandArgs the array of arguments that was supplied to PeformCommand
//! @param invokedFrom the source point that initiated console invocation
using DispatchCommandNotFoundEvent = AZ::Event<AZStd::basic_string_view<char, AZStd::char_traits<char>>, const ConsoleCommandContainer&, ConsoleInvokedFrom>;
}
@@ -0,0 +1,177 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/Utils/TypeHash.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/EBus/Event.h>
#include <AzCore/RTTI/RTTI.h>
#include <stdarg.h>
namespace AZ
{
//! This essentially maps to standard syslog logging priorities to allow the logger to easily plug into standard logging services
enum class LogLevel : int8_t { Trace = 1, Debug = 2, Info = 3, Notice = 4, Warn = 5, Error = 6, Fatal = 7 };
//! @class ILogger
//! @brief This is an AZ::Interface<> for logging.
//! Usage:
//! #include <AzCore/Console/ILogger.h>
//! AZLOG_INFO("Your message here");
//! AZLOG_WARN("Your warn message here");
class ILogger
{
public:
AZ_RTTI(ILogger, "{69950316-3626-4C9D-9DCA-2E7ABF84C0A9}");
// LogLevel, message, file, function, line
using LogEvent = AZ::Event<LogLevel, const char*, const char*, const char*, int32_t>;
ILogger() = default;
virtual ~ILogger() = default;
//! Sets the the name of the log file.
//! @param a_LogName the new logfile name to use
virtual void SetLogName(const char* logName) = 0;
//! Gets the the name of the log file.
//! @return the current logfile name
virtual const char* GetLogName() const = 0;
//! Sets the log level for the logger instance.
//! @param logLevel the minimum log level to filter out log messages at
virtual void SetLogLevel(LogLevel logLevel) = 0;
//! Gets the log level for the logger instance.
//! @return the current minimum log level to filter out log messages at
virtual LogLevel GetLogLevel() const = 0;
//! Binds a log event handler.
//! @param handler the handler to bind to logging events
virtual void BindLogHandler(LogEvent::Handler& hander) = 0;
//! Queries whether the provided logging tag is enabled.
//! @param hashValue the hash value for the provided logging tag
//! @return boolean true if enabled
virtual bool IsTagEnabled(AZ::HashValue32 hashValue) = 0;
//! Immediately Flushes any pending messages without waiting for next thread update.
//! Should be invoked whenever unloading any shared library or module to avoid crashing on dangling string pointers
virtual void Flush() = 0;
//! Don't use this directly, use the logger macros defined below (AZLOG_INFO, AZLOG_WARN, AZLOG_ERROR, etc..)
virtual void LogInternalV(LogLevel level, const char* format, const char* file, const char* function, int32_t line, va_list args) = 0;
//! Don't use this directly, use the logger macros defined below (AZLOG_INFO, AZLOG_WARN, AZLOG_ERROR, etc..)
inline void LogInternal(LogLevel level, const char* format, const char* file, const char* function, int32_t line, ...) AZ_FORMAT_ATTRIBUTE(3, 7)
{
va_list args;
va_start(args, line);
LogInternalV(level, format, file, function, line, args);
va_end(args);
}
};
// EBus wrapper for ScriptCanvas
class ILoggerRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using ILoggerRequestBus = AZ::EBus<ILogger, ILoggerRequests>;
}
#define AZLOG_TRACE(MESSAGE, ...) \
{ \
AZ::ILogger* logger = AZ::Interface<AZ::ILogger>::Get(); \
if (logger != nullptr && AZ::LogLevel::Trace >= logger->GetLogLevel()) \
{ \
logger->LogInternal(AZ::LogLevel::Trace, MESSAGE, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} \
}
#define AZLOG_DEBUG(MESSAGE, ...) \
{ \
AZ::ILogger* logger = AZ::Interface<AZ::ILogger>::Get(); \
if (logger != nullptr && AZ::LogLevel::Debug >= logger->GetLogLevel()) \
{ \
logger->LogInternal(AZ::LogLevel::Debug, MESSAGE, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} \
}
#define AZLOG_INFO(MESSAGE, ...) \
{ \
AZ::ILogger* logger = AZ::Interface<AZ::ILogger>::Get(); \
if (logger != nullptr && AZ::LogLevel::Info >= logger->GetLogLevel()) \
{ \
logger->LogInternal(AZ::LogLevel::Info, MESSAGE, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} \
}
#define AZLOG_NOTICE(MESSAGE, ...) \
{ \
AZ::ILogger* logger = AZ::Interface<AZ::ILogger>::Get(); \
if (logger != nullptr && AZ::LogLevel::Notice >= logger->GetLogLevel()) \
{ \
logger->LogInternal(AZ::LogLevel::Notice, MESSAGE, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} \
}
#define AZLOG_WARN(MESSAGE, ...) \
{ \
AZ::ILogger* logger = AZ::Interface<AZ::ILogger>::Get(); \
if (logger != nullptr && AZ::LogLevel::Warn >= logger->GetLogLevel()) \
{ \
logger->LogInternal(AZ::LogLevel::Warn, MESSAGE, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} \
}
#define AZLOG_ERROR(MESSAGE, ...) \
{ \
AZ::ILogger* logger = AZ::Interface<AZ::ILogger>::Get(); \
if (logger != nullptr && AZ::LogLevel::Error >= logger->GetLogLevel()) \
{ \
logger->LogInternal(AZ::LogLevel::Error, MESSAGE, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} \
}
#define AZLOG_FATAL(MESSAGE, ...) \
{ \
AZ::ILogger* logger = AZ::Interface<AZ::ILogger>::Get(); \
if (logger != nullptr && AZ::LogLevel::Fatal >= logger->GetLogLevel()) \
{ \
logger->LogInternal(AZ::LogLevel::Fatal, MESSAGE, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} \
}
#define AZLOG(TAG, MESSAGE, ...) \
{ \
static const AZ::HashValue32 hashValue = AZ::TypeHash32(#TAG); \
AZ::ILogger* logger = AZ::Interface<AZ::ILogger>::Get(); \
if (logger != nullptr && logger->IsTagEnabled(hashValue)) \
{ \
logger->LogInternal(AZ::LogLevel::Notice, MESSAGE, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} \
}
#define AZLOG_FLUSH() \
{ \
AZ::ILogger* logger = AZ::Interface<AZ::ILogger>::Get(); \
if (logger != nullptr) \
{ \
logger->Flush(); \
} \
}
@@ -0,0 +1,238 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Console/LoggerSystemComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
const char* GetEnumString(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel::Trace:
return "Trace";
case LogLevel::Debug:
return "Debug";
case LogLevel::Info:
return "Info";
case LogLevel::Notice:
return "Notice";
case LogLevel::Warn:
return "Warn";
case LogLevel::Error:
return "Error";
case LogLevel::Fatal:
return "Fatal";
default:
break;
}
return "UNKNOWN";
}
void LoggerSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<LoggerSystemComponent, AZ::Component>()
->Version(1);
}
}
void LoggerSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("LoggerService"));
}
void LoggerSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("LoggerService"));
}
LoggerSystemComponent::LoggerSystemComponent()
{
AZ::Interface<ILogger>::Register(this);
ILoggerRequestBus::Handler::BusConnect();
}
LoggerSystemComponent::~LoggerSystemComponent()
{
ILoggerRequestBus::Handler::BusDisconnect();
AZ::Interface<ILogger>::Unregister(this);
}
void LoggerSystemComponent::Activate()
{
;
}
void LoggerSystemComponent::Deactivate()
{
;
}
void LoggerSystemComponent::SetLogName(const char* logName)
{
m_logName = logName;
}
const char* LoggerSystemComponent::GetLogName() const
{
return m_logName.c_str();
}
void LoggerSystemComponent::SetLogLevel(LogLevel logLevel)
{
m_logLevel = logLevel;
}
LogLevel LoggerSystemComponent::GetLogLevel() const
{
return m_logLevel;
}
void LoggerSystemComponent::BindLogHandler(LogEvent::Handler& handler)
{
handler.Connect(m_logEvent);
}
bool LoggerSystemComponent::IsTagEnabled(AZ::HashValue32 hashValue)
{
if (!m_quickHash.test(static_cast<AZStd::size_t>(hashValue) & (BitsetSize - 1)))
{
return false;
}
return IsTagEnabledHelper(hashValue);
}
void LoggerSystemComponent::Flush()
{
;
}
void LoggerSystemComponent::LogInternalV(LogLevel level, const char* format, const char* file, const char* function, int32_t line, va_list args)
{
constexpr AZStd::size_t MaxLogBufferSize = 1000;
char buffer[MaxLogBufferSize];
const AZStd::size_t length = azvsnprintf(buffer, MaxLogBufferSize, format, args);
buffer[AZStd::min<AZStd::size_t>(length, MaxLogBufferSize - 2)] = '\n';
buffer[AZStd::min<AZStd::size_t>(length + 1, MaxLogBufferSize - 1)] = '\0';
switch (level)
{
case LogLevel::Warn:
AZ_Warning("Logger", true, buffer);
break;
case LogLevel::Error:
AZ_Error("Logger", true, buffer);
break;
default:
// Catch all else with trace
AZ::Debug::Trace::Output("Logger", buffer);
break;
}
m_logEvent.Signal(level, buffer, file, function, line);
}
void LoggerSystemComponent::SetLevel(const AZ::ConsoleCommandContainer& arguments)
{
if (arguments.empty())
{
return;
}
AZ::CVarFixedString argument{ arguments.front() };
char* endPtr = nullptr;
int64_t value = static_cast<int64_t>(strtoll(argument.c_str(), &endPtr, 0));
if ((value < static_cast<int32_t>(LogLevel::Trace)) || (value > static_cast<int32_t>(LogLevel::Fatal)))
{
AZLOG_ERROR("Invalid log level: %d", static_cast<int32_t>(value));
return;
}
m_logLevel = static_cast<LogLevel>(value);
}
void LoggerSystemComponent::EnableLog(const AZ::ConsoleCommandContainer& arguments)
{
for (uint32_t i = 0; i < arguments.size(); ++i)
{
AZ::CVarFixedString argument{ arguments[i] };
const AZ::HashValue32 tagHash = AZ::TypeHash32(argument.c_str());
if (!IsTagEnabledHelper(tagHash))
{
EnableLogHelper(tagHash);
}
}
}
void LoggerSystemComponent::DisableLog(const AZ::ConsoleCommandContainer& arguments)
{
for (uint32_t i = 0; i < arguments.size(); ++i)
{
AZ::CVarFixedString argument{ arguments[i] };
const AZ::HashValue32 tagHash = AZ::TypeHash32(argument.c_str());
if (IsTagEnabledHelper(tagHash))
{
DisableLogHelper(tagHash);
}
}
}
void LoggerSystemComponent::ToggleLog(const AZ::ConsoleCommandContainer& arguments)
{
for (uint32_t i = 0; i < arguments.size(); ++i)
{
AZ::CVarFixedString argument{ arguments[i] };
const AZ::HashValue32 tagHash = AZ::TypeHash32(argument.c_str());
if (IsTagEnabledHelper(tagHash))
{
DisableLogHelper(tagHash);
}
else
{
EnableLogHelper(tagHash);
}
}
}
void LoggerSystemComponent::EnableLogHelper(AZ::HashValue32 hashValue)
{
m_quickHash.set(static_cast<AZStd::size_t>(hashValue) & (BitsetSize - 1), true);
AZStd::scoped_lock lock(m_enabledTagsMutex);
m_enabledTags.push_back(hashValue);
}
void LoggerSystemComponent::DisableLogHelper(AZ::HashValue32 hashValue)
{
AZStd::scoped_lock lock(m_enabledTagsMutex);
m_enabledTags.erase(AZStd::find(m_enabledTags.begin(), m_enabledTags.end(), hashValue));
m_quickHash.reset();
for (auto enabledTag : m_enabledTags)
{
m_quickHash.set(static_cast<AZStd::size_t>(enabledTag) & (BitsetSize - 1), true);
}
}
bool LoggerSystemComponent::IsTagEnabledHelper(AZ::HashValue32 hashValue)
{
AZStd::scoped_lock lock(m_enabledTagsMutex);
return AZStd::find(m_enabledTags.begin(), m_enabledTags.end(), hashValue) != m_enabledTags.end();
}
}
@@ -0,0 +1,87 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Console/ILogger.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Component/Component.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/bitset.h>
namespace AZ
{
//! Implementation of the ILogger system interface.
class LoggerSystemComponent
: public AZ::Component
, public ILoggerRequestBus::Handler
{
public:
AZ_COMPONENT(LoggerSystemComponent, "{56746640-9258-4D41-B255-663737493811}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
LoggerSystemComponent();
virtual ~LoggerSystemComponent();
//! AZ::Component overrides.
//! @{
void Activate() override;
void Deactivate() override;
//! @}
//! ILogger overrides.
//! @{
void SetLogName(const char* logName) override;
const char* GetLogName() const override;
void SetLogLevel(LogLevel logLevel) override;
LogLevel GetLogLevel() const override;
void BindLogHandler(LogEvent::Handler& hander) override;
bool IsTagEnabled(AZ::HashValue32 hashValue) override;
void Flush() override;
void LogInternalV(LogLevel level, const char* format, const char* file, const char* function, int32_t line, va_list args) override;
//! @}
//! Console commands.
//! @{
void SetLevel(const AZ::ConsoleCommandContainer& arguments);
void EnableLog(const AZ::ConsoleCommandContainer& arguments);
void DisableLog(const AZ::ConsoleCommandContainer& arguments);
void ToggleLog(const AZ::ConsoleCommandContainer& arguments);
//! @}
private:
void EnableLogHelper(AZ::HashValue32 a_HashValue);
void DisableLogHelper(AZ::HashValue32 a_HashValue);
bool IsTagEnabledHelper(AZ::HashValue32 a_HashValue);
AZ_CONSOLEFUNC(LoggerSystemComponent, SetLevel, AZ::ConsoleFunctorFlags::Null, "Sets the Logger log level");
AZ_CONSOLEFUNC(LoggerSystemComponent, EnableLog, AZ::ConsoleFunctorFlags::Null, "Enables conditional logs with the provided tag");
AZ_CONSOLEFUNC(LoggerSystemComponent, DisableLog, AZ::ConsoleFunctorFlags::Null, "Disables conditional logs with the provided tag");
AZ_CONSOLEFUNC(LoggerSystemComponent, ToggleLog, AZ::ConsoleFunctorFlags::Null, "Toggles conditional logs with the provided tag");
// Store a trivial bloom filter using the lower 10 bits. This filter can be safely checked outside of lock to reduce contention.
static constexpr uint32_t BitsetSize = 1024;
static_assert(IsPowerOfTwo(BitsetSize), "Bloom filter bitset size must be a power of two");
AZStd::string m_logName;
LogLevel m_logLevel = LogLevel::Info;
LogEvent m_logEvent;
AZStd::bitset<BitsetSize> m_quickHash;
AZStd::mutex m_enabledTagsMutex;
AZStd::vector<AZ::HashValue32> m_enabledTags;
};
}
@@ -0,0 +1,340 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetTracking.h"
#include <AzCore/Debug/AssetTrackingTypes.h>
#include <AzCore/Memory/AllocatorManager.h>
#include <AzCore/Memory/HphaSchema.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/smart_ptr/make_shared.h>
namespace AZ
{
namespace Debug
{
namespace
{
struct AssetTreeNode;
// Per-thread data that needs to be stored.
struct ThreadData
{
AZStd::vector<AssetTreeNodeBase*, AZStdAssetTrackingAllocator> m_currentAssetStack;
};
// Access thread data through a virtual function to ensure that the same thread-local data is being shared across DLLs.
// Otherwise, the thread_local variables are replicated across DLLs that link the AzCore library, and you'll get a
// different version in each module.
class ThreadDataProvider
{
public:
virtual ThreadData& GetThreadData() = 0;
};
}
class AssetTrackingImpl final :
public ThreadDataProvider
{
public:
AZ_TYPE_INFO(AssetTrackingImpl, "{01E2A099-3523-40BE-80E0-E0ADD861BEE1}");
AZ_CLASS_ALLOCATOR(AssetTrackingImpl, OSAllocator, 0);
AssetTrackingImpl(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable);
~AssetTrackingImpl();
void AssetBegin(const char* id, const char* file, int line);
void AssetAttach(void* otherAllocation, const char* file, int line);
void AssetEnd();
ThreadData& GetThreadData() override;
private:
static EnvironmentVariable<AssetTrackingImpl*>& GetEnvironmentVariable();
static AssetTrackingImpl* GetSharedInstance();
static ThreadData& GetSharedThreadData();
using MasterAssets = AZStd::unordered_map<AssetTrackingId, AssetMasterInfo, AZStd::hash<AssetTrackingId>, AZStd::equal_to<AssetTrackingId>, AZStdAssetTrackingAllocator>;
using ThreadData = ThreadData;
using mutex_type = AZStd::mutex;
using lock_type = AZStd::lock_guard<mutex_type>;
mutex_type m_mutex;
MasterAssets m_masterAssets;
AssetTreeNodeBase* m_assetRoot = nullptr;
AssetAllocationTableBase* m_allocationTable = nullptr;
bool m_performingAnalysis = false;
friend class AssetTracking;
friend class AssetTracking::Scope;
};
}
}
///////////////////////////////////////////////////////////////////////////////
// AssetTrackingImpl methods
///////////////////////////////////////////////////////////////////////////////
namespace AZ
{
namespace Debug
{
AssetTrackingImpl::AssetTrackingImpl(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable) :
m_assetRoot(&assetTree->GetRoot()),
m_allocationTable(allocationTable)
{
AZ_Assert(!GetSharedInstance(), "Only one AssetTrackingImpl can exist!");
GetEnvironmentVariable().Set(this);
AllocatorManager::Instance().EnterProfilingMode();
}
AssetTrackingImpl::~AssetTrackingImpl()
{
AllocatorManager::Instance().ExitProfilingMode();
GetEnvironmentVariable().Reset();
}
void AssetTrackingImpl::AssetBegin(const char* id, const char* file, int line)
{
// In the future it may be desirable to organize assets based on where in code the asset was entered into.
// For now these are ignored.
AZ_UNUSED(file);
AZ_UNUSED(line);
using namespace Internal;
AssetTrackingId assetId(id);
auto& threadData = GetSharedThreadData();
AssetTreeNodeBase* parentAsset = threadData.m_currentAssetStack.empty() ? nullptr : threadData.m_currentAssetStack.back();
AssetTreeNodeBase* childAsset;
AssetMasterInfo* assetMasterInfo;
if (!parentAsset)
{
parentAsset = m_assetRoot;
}
{
lock_type lock(m_mutex);
// Locate or create the master record for this asset
auto masterItr = m_masterAssets.find(assetId);
if (masterItr != m_masterAssets.end())
{
assetMasterInfo = &masterItr->second;
}
else
{
auto insertResult = m_masterAssets.emplace(assetId, AssetMasterInfo());
assetMasterInfo = &insertResult.first->second;
assetMasterInfo->m_id = &insertResult.first->first;
}
// Add this asset to the stack for this thread's context
childAsset = parentAsset->FindOrAddChild(assetId, assetMasterInfo);
}
threadData.m_currentAssetStack.push_back(childAsset);
}
void AssetTrackingImpl::AssetAttach(void* otherAllocation, const char* file, int line)
{
AZ_UNUSED(file);
AZ_UNUSED(line);
using namespace Internal;
AssetTreeNodeBase* assetInfo = m_allocationTable->FindAllocation(otherAllocation);
// We will push back a nullptr if there is no asset, this is necessary to balance the call to AssetEnd()
GetSharedThreadData().m_currentAssetStack.push_back(assetInfo);
}
void AssetTrackingImpl::AssetEnd()
{
AZ_Assert(!GetSharedThreadData().m_currentAssetStack.empty(), "AssetEnd() called without matching AssetBegin() or AssetAttach. Use the AZ_ASSET_NAMED_SCOPE and AZ_ASSET_ATTACH_TO_SCOPE macros to avoid this!");
GetSharedThreadData().m_currentAssetStack.pop_back();
}
AssetTrackingImpl* AssetTrackingImpl::GetSharedInstance()
{
auto environmentVariable = GetEnvironmentVariable();
if(environmentVariable)
{
return *environmentVariable;
}
return nullptr;
}
ThreadData& AssetTrackingImpl::GetSharedThreadData()
{
// Cast to the base type so our virtual call doesn't get optimized away. We require GetThreadData() to be executed in the same DLL every time.
return static_cast<ThreadDataProvider*>(GetSharedInstance())->GetThreadData();
}
AssetTrackingImpl::ThreadData& AssetTrackingImpl::GetThreadData()
{
static thread_local ThreadData* data = nullptr;
static thread_local typename AZStd::aligned_storage_t<sizeof(ThreadData), alignof(ThreadData)> storage;
if (!data)
{
data = new (&storage) ThreadData;
}
return *data;
}
EnvironmentVariable<AssetTrackingImpl*>& AssetTrackingImpl::GetEnvironmentVariable()
{
static EnvironmentVariable<AssetTrackingImpl*> assetTrackingImpl = Environment::CreateVariable<AssetTrackingImpl*>(AzTypeInfo<AssetTrackingImpl*>::Name());
return assetTrackingImpl;
}
///////////////////////////////////////////////////////////////////////////////
// AssetTracking::Scope functions
///////////////////////////////////////////////////////////////////////////////
AssetTracking::Scope AssetTracking::Scope::ScopeFromAssetId(const char* file, int line, const char* fmt, ...)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
static const int BUFFER_SIZE = 1024;
char buffer[BUFFER_SIZE];
va_list args;
va_start(args, fmt);
azvsnprintf(buffer, BUFFER_SIZE, fmt, args);
va_end(args);
impl->AssetBegin(buffer, file, line);
}
return Scope();
}
AssetTracking::Scope AssetTracking::Scope::ScopeFromAttachment(void* attachTo, const char* file, int line)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetAttach(attachTo, file, line);
}
return Scope();
}
AssetTracking::Scope::~Scope()
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetEnd();
}
}
AssetTracking::Scope::Scope()
{
}
///////////////////////////////////////////////////////////////////////////////
// AssetTracking functions
///////////////////////////////////////////////////////////////////////////////
void AssetTracking::EnterScopeByAssetId(const char* file, int line, const char* fmt, ...)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
static const int BUFFER_SIZE = 1024;
char buffer[BUFFER_SIZE];
va_list args;
va_start(args, fmt);
azvsnprintf(buffer, BUFFER_SIZE, fmt, args);
va_end(args);
impl->AssetBegin(buffer, file, line);
}
}
void AssetTracking::EnterScopeByAttachment(void* attachTo, const char* file, int line)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetAttach(attachTo, file, line);
}
}
void AssetTracking::ExitScope()
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetEnd();
}
}
const char* AssetTracking::GetDebugScope()
{
// Output debug information about the current asset scope in the current thread.
// Do not use in production code.
#ifndef RELEASE
static const int BUFFER_SIZE = 1024;
static char buffer[BUFFER_SIZE];
const auto& assetStack = AssetTrackingImpl::GetSharedInstance()->GetThreadData().m_currentAssetStack;
if (assetStack.empty())
{
azsnprintf(buffer, BUFFER_SIZE, "<none>");
}
else
{
char* pos = buffer;
for (auto itr = assetStack.rbegin(); itr != assetStack.rend(); ++itr)
{
pos += azsnprintf(pos, BUFFER_SIZE - (pos - buffer), "%s\n", (*itr)->GetAssetMasterInfo()->m_id->m_id.c_str());
if (pos >= buffer + BUFFER_SIZE)
{
break;
}
}
}
return buffer;
#else
return "";
#endif
}
AssetTracking::AssetTracking(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable)
{
m_impl.reset(aznew AssetTrackingImpl(assetTree, allocationTable));
}
AssetTracking::~AssetTracking()
{
}
AssetTreeNodeBase* AssetTracking::GetCurrentThreadAsset() const
{
const auto& assetStack = m_impl->GetThreadData().m_currentAssetStack;
AssetTreeNodeBase* result = assetStack.empty() ? nullptr : assetStack.back();
return result;
}
}
} // namespace AzFramework
@@ -0,0 +1,134 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/EBus/Policies.h>
#ifndef AZ_TRACK_ASSET_SCOPES
// You may manually uncomment this to enable asset tracking.
//# define AZ_TRACK_ASSET_SCOPES
#endif
#if !defined(AZ_TRACK_ASSET_SCOPES)
// Default to enabling asset tracking when memory tracking is enabled
# define AZ_TRACK_ASSET_SCOPES
#endif
#ifdef AZ_TRACK_ASSET_SCOPES
#define AZ_ASSET_SCOPE_VARIABLE_NAME(line) AZ_JOIN(_az_assettracking_scope_, line)
///////////////////////////////////////////////////////////////////////////////
// Preferred macros to use at the top of a scope you want to to track asset memory for.
///////////////////////////////////////////////////////////////////////////////
// Creates a new scope with a name, usually the name of an asset being loaded. (This may be a format-string, e.g. "Foo: %s", bar.c_str())
# define AZ_ASSET_NAMED_SCOPE(...) AZ::Debug::AssetTracking::Scope AZ_ASSET_SCOPE_VARIABLE_NAME(__LINE__) (AZ::Debug::AssetTracking::Scope::ScopeFromAssetId(__FILE__, __LINE__, __VA_ARGS__))
// Attempts to enter an existing scope that already owns some other allocation.
# define AZ_ASSET_ATTACH_TO_SCOPE(other) AZ::Debug::AssetTracking::Scope AZ_ASSET_SCOPE_VARIABLE_NAME(__LINE__) (AZ::Debug::AssetTracking::Scope::ScopeFromAttachment((other), __FILE__, __LINE__))
///////////////////////////////////////////////////////////////////////////////
// Optional macros to manually enter and exit a scope.
// It is the responsibility of the user to make sure every call to AZ_ASSET_ENTER_SCOPE_* is matched by a corresponding call to AZ_ASSET_EXIT_SCOPE.
///////////////////////////////////////////////////////////////////////////////
# define AZ_ASSET_ENTER_SCOPE_BY_ASSET_ID(...) AZ::Debug::AssetTracking::EnterScopeByAssetId(__FILE__, __LINE__, __VA_ARGS__)
# define AZ_ASSET_ENTER_SCOPE_BY_ATTACHMENT(other) AZ::Debug::AssetTracking::EnterScopeByAttachment((other), __FILE__, __LINE__)
# define AZ_ASSET_EXIT_SCOPE AZ::Debug::AssetTracking::ExitScope()
#else
# define AZ_ASSET_NAMED_SCOPE(...) (void)0
# define AZ_ASSET_ATTACH_TO_SCOPE(other) (void)0
# define AZ_ASSET_ENTER_SCOPE_BY_ASSET_ID(...) (void)0
# define AZ_ASSET_ENTER_SCOPE_BY_ATTACHMENT(other) (void)0
# define AZ_ASSET_EXIT_SCOPE (void)0
#endif
namespace AZ
{
class ReflectContext;
namespace Debug
{
class AssetTrackingImpl;
class AssetTreeBase;
class AssetTreeNodeBase;
class AssetAllocationTableBase;
class AssetTracking
{
public:
AZ_TYPE_INFO(AssetTracking, "{D4335180-09A2-415A-8B50-9B734E7CE1E6}");
AZ_CLASS_ALLOCATOR(AssetTracking, OSAllocator, 0);
// Provide RAII method for entering and exiting scopes.
// Generally you will want to use the macros at the top of this file rather than instantiating this object directly.
class Scope
{
public:
static Scope ScopeFromAssetId(const char* file, int line, const char* fmt, ...);
static Scope ScopeFromAttachment(void* attachTo, const char* file, int line);
Scope(Scope&&) = default;
~Scope();
private:
Scope();
};
// Generally you will want to use the macros at the top of this file rather than calling these functions directly.
static void EnterScopeByAssetId(const char* file, int line, const char* fmt, ...);
static void EnterScopeByAttachment(void* attachTo, const char* file, int line);
static void ExitScope();
static const char* GetDebugScope();
AssetTracking(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable);
~AssetTracking();
AssetTreeNodeBase* GetCurrentThreadAsset() const;
private:
AZStd::unique_ptr<AssetTrackingImpl> m_impl;
};
// An EBus processing policy that attempts to attach to an existing scope before calling a handler.
//
// Use this on EBuses where you want the callees to track asset memory during their event handlers.
// This will work so long as the callees were themselves allocated inside an existing asset scope.
//
// May be added to an existing EBus with the following code:
// using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy;
//
template<typename Parent = EBusEventProcessingPolicy>
struct AssetTrackingEventProcessingPolicy
{
template<class Results, class Function, class Interface, class... InputArgs>
static void CallResult(Results& results, Function&& func, Interface&& iface, InputArgs&&... args)
{
AZ_ASSET_ATTACH_TO_SCOPE(iface);
Parent::CallResult(results, AZStd::forward<Function>(func), AZStd::forward<Interface>(iface), AZStd::forward<InputArgs>(args)...);
}
template<class Function, class Interface, class... InputArgs>
static void Call(Function&& func, Interface&& iface, InputArgs&&... args)
{
AZ_ASSET_ATTACH_TO_SCOPE(iface);
Parent::Call(AZStd::forward<Function>(func), AZStd::forward<Interface>(iface), AZStd::forward<InputArgs>(args)...);
}
};
}
} // namespace AzFramework
@@ -0,0 +1,121 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/HphaSchema.h>
#include <AzCore/Memory/SimpleSchemaAllocator.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
namespace Debug
{
struct AssetTrackingId;
}
}
namespace AZStd
{
// Declare hash specializations for types that need them; implementations will have to come after the classes are fully defined
template<>
struct hash<AZ::Debug::AssetTrackingId>
{
size_t operator()(const AZ::Debug::AssetTrackingId& id) const;
};
}
namespace AZ
{
namespace Debug
{
class AssetTrackingImpl;
// Custom allocator for the Analyzer that doesn't go through profiling tools and cannot be overridden
class AssetTrackingAllocator : public AZ::SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false>
{
public:
AZ_TYPE_INFO(AssetTrackingAllocator, "{F6C08E92-559C-4153-9620-6A8491F78F10}");
using Base = AZ::SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false>;
using Descriptor = Base::Descriptor;
AssetTrackingAllocator()
: Base("AssetTrackingAllocator", "Allocator for the AssetTracking")
{
DisableOverriding();
}
};
using AZStdAssetTrackingAllocator = AZ::AZStdAlloc<AssetTrackingAllocator>;
using AssetTrackingString = AZStd::basic_string<char, AZStd::char_traits<char>, AZStdAssetTrackingAllocator>;
template<typename Key, typename MappedType>
using AssetTrackingMap = AZStd::unordered_map<Key, MappedType, AZStd::hash<Key>, AZStd::equal_to<Key>, AZStdAssetTrackingAllocator>;
// ID for an asset that is hashable.
// Currently only contains one string identifier, but we may want to store a more sophisticated ID in the future.
struct AssetTrackingId
{
AssetTrackingId(const char* id) : m_id(id)
{
}
bool operator==(const AssetTrackingId& other) const
{
return m_id == other.m_id;
}
AssetTrackingString m_id;
};
// Master information about an asset.
// Currently just contains the ID of the asset, but in the future may carry additional information about that asset (such as where in code it was initialized).
struct AssetMasterInfo
{
const AssetTrackingId* m_id;
};
// Base class for a node in the asset tree. Implemented by the template AssetTreeNode<>.
class AssetTreeNodeBase
{
public:
virtual const AssetMasterInfo* GetAssetMasterInfo() const = 0;
virtual AssetTreeNodeBase* FindOrAddChild(const AssetTrackingId& id, const AssetMasterInfo* info) = 0;
};
// Base class for an asset tree. Implemented by the template AssetTree<>.
class AssetTreeBase
{
public:
virtual AssetTreeNodeBase& GetRoot() = 0;
};
// Base class for an asset allocation table. Implemented by the template AssetAllocationTable<>.
class AssetAllocationTableBase
{
public:
virtual AssetTreeNodeBase* FindAllocation(void* ptr) const = 0;
};
}
}
///////////////////////////////////////////////////////////////////////////////
// Hash functions for map support
///////////////////////////////////////////////////////////////////////////////
inline size_t AZStd::hash<AZ::Debug::AssetTrackingId>::operator()(const AZ::Debug::AssetTrackingId& info) const
{
return AZStd::hash<AZ::Debug::AssetTrackingString>()(info.m_id);
}
@@ -0,0 +1,173 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Debug/AssetTrackingTypes.h>
#include <AzCore/std/containers/map.h>
namespace AZ
{
namespace Debug
{
// A node in the current asset state tree.
// Each thread maintains a stack of currently in-scope assets. As this stack changes the asset tree forms.
// The same asset may appear in multiple places in the tree, e.g. if asset A is a common asset loaded by both asset B and asset C, the tree may look like:
// Root -> B -> A
// \--> C -> A
template<typename AssetDataT>
class AssetTreeNode : public AssetTreeNodeBase
{
public:
AssetTreeNode(const AssetMasterInfo* masterInfo = nullptr, AssetTreeNode* parent = nullptr) :
m_masterInfo(masterInfo),
m_parent(parent)
{
}
const AssetMasterInfo* GetAssetMasterInfo() const override
{
return m_masterInfo;
}
AssetTreeNodeBase* FindOrAddChild(const AssetTrackingId& id, const AssetMasterInfo* info) override
{
AssetTreeNodeBase* result = nullptr;
auto childItr = m_children.find(id);
if (childItr != m_children.end())
{
result = &childItr->second;
}
else
{
auto childResult = m_children.emplace(id, AssetTreeNode(info, this));
result = &childResult.first->second;
}
return result;
}
using AssetMap = AssetTrackingMap<AssetTrackingId, AssetTreeNode>;
const AssetMasterInfo* m_masterInfo;
AssetTreeNode* m_parent;
AssetMap m_children;
AssetDataT m_data;
};
template<typename AssetDataT>
class AssetTree : public AssetTreeBase
{
public:
AssetTreeNodeBase& GetRoot() override
{
return m_rootAssets;
}
using NodeType = AssetTreeNode<AssetDataT>;
NodeType m_rootAssets;
};
template<typename AllocationDataT>
struct AllocationRecord
{
AssetTreeNodeBase* m_asset;
uint32_t m_size;
AllocationDataT m_data;
};
template<typename AllocationDataT>
class AllocationTable : public AssetAllocationTableBase
{
public:
using RecordType = AllocationRecord<AllocationDataT>;
using AllocationReverseMap = AZStd::map<void*, RecordType, AZStd::greater<void*>, AZStdAssetTrackingAllocator>;
using mutex_type = AZStd::mutex;
using lock_type = AZStd::lock_guard<mutex_type>;
AllocationTable(mutex_type& mutex) : m_mutex(mutex)
{
}
AssetTreeNodeBase* FindAllocation(void* ptr) const override
{
// Note that ptr is not guaranteed to have an exact entry in the map. For instance, ptr may point to a member of the original object that was allocated, or
// ptr may be a different "this" pointer in the case of multiple inheritance.
//
// To solve this, we use lower_bound() and check to see if ptr falls in the range of the nearest allocation. Our map uses AZStd::greater instead of
// AZStd::less as its sorting function, and thus sorts largest-to-smallest instead of smallest-to-largest. This causes lower_bound() to return the first
// iterator that is not greater than otherAllocation, i.e. less than or equal to ptr.
lock_type lock(m_mutex);
auto itr = m_allocationTable.lower_bound(ptr);
AssetTreeNodeBase* result = nullptr;
if (itr != m_allocationTable.end())
{
// Check if otherAllocation is within the size range of the allocation we found
if (reinterpret_cast<uintptr_t>(ptr) <= reinterpret_cast<uintptr_t>(itr->first) + itr->second.m_size)
{
result = itr->second.m_asset;
}
}
return result;
}
void ReallocateAllocation(void* prevAddress, void* newAddress, size_t newByteSize)
{
lock_type lock(m_mutex);
auto itr = m_allocationTable.find(prevAddress);
if (itr != m_allocationTable.end())
{
RecordType newAllocation = itr->second;
newAllocation.m_size = (uint32_t)newByteSize;
m_allocationTable.erase(itr);
m_allocationTable.emplace(newAddress, AZStd::move(newAllocation));
}
}
void ResizeAllocation(void* address, size_t newSize)
{
// Resize an existing allocation if we can find it
lock_type lock(m_mutex);
auto itr = m_allocationTable.find(address);
if (itr != m_allocationTable.end())
{
itr->second.m_size = (uint32_t)newSize;
}
}
AllocationReverseMap& Get()
{
return m_allocationTable;
}
const AllocationReverseMap& Get() const
{
return m_allocationTable;
}
private:
AllocationReverseMap m_allocationTable;
mutex_type& m_mutex;
};
}
}
@@ -0,0 +1,33 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Debug/EventTraceDrillerBus.h>
#include <AzCore/std/time.h>
#include <AzCore/std/parallel/thread.h>
namespace AZ
{
namespace Debug
{
EventTrace::ScopedSlice::ScopedSlice(const char* name, const char* category)
: m_Name(name)
, m_Category(category)
, m_Time(AZStd::GetTimeNowMicroSecond())
{}
EventTrace::ScopedSlice::~ScopedSlice()
{
EventTraceDrillerBus::TryQueueBroadcast(&EventTraceDrillerInterface::RecordSlice, m_Name, m_Category, AZStd::this_thread::get_id(), m_Time, (uint32_t)(AZStd::GetTimeNowMicroSecond() - m_Time));
}
}
}
@@ -0,0 +1,58 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/PlatformDef.h>
#include <AzCore/Debug/Profiler.h>
namespace AZStd
{
struct thread_id;
}
namespace AZ
{
namespace Debug
{
namespace EventTrace
{
class ScopedSlice
{
public:
ScopedSlice(const char* name, const char* category);
~ScopedSlice();
private:
const char* m_Name;
const char* m_Category;
u64 m_Time;
};
}
}
}
#define AZ_TRACE_METHOD_NAME_CATEGORY(name, category) AZ::Debug::EventTrace::ScopedSlice AZ_JOIN(ScopedSlice__, __LINE__)(name, category);
#ifdef AZ_PROFILE_TELEMETRY
# define AZ_TRACE_METHOD_NAME(name) \
AZ_TRACE_METHOD_NAME_CATEGORY(name, "") \
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzTrace, name)
# define AZ_TRACE_METHOD() \
AZ_TRACE_METHOD_NAME_CATEGORY(AZ_FUNCTION_SIGNATURE, "") \
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzTrace)
#else
# define AZ_TRACE_METHOD_NAME(name) AZ_TRACE_METHOD_NAME_CATEGORY(name, "")
# define AZ_TRACE_METHOD() AZ_TRACE_METHOD_NAME(AZ_FUNCTION_SIGNATURE)
#endif
@@ -0,0 +1,166 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/EventTraceDriller.h>
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/std/containers/array.h>
#include <algorithm>
namespace AZ
{
namespace Debug
{
namespace Crc
{
const u32 EventTraceDriller = AZ_CRC("EventTraceDriller", 0xf7aeae55);
const u32 Slice = AZ_CRC("Slice", 0x3dae78a5);
const u32 ThreadInfo = AZ_CRC("ThreadInfo", 0x89bf78be);
const u32 Name = AZ_CRC("Name", 0x5e237e06);
const u32 Category = AZ_CRC("Category", 0x064c19c1);
const u32 ThreadId = AZ_CRC("ThreadId", 0xd0fd9043);
const u32 Timestamp = AZ_CRC("Timestamp", 0xa5d6e63e);
const u32 Duration = AZ_CRC("Duration", 0x865f80c0);
const u32 Instant = AZ_CRC("Instant", 0x0e9047ad);
const u32 InstantScope = AZ_CRC("InstantScope", 0xed4bfb0e);
}
EventTraceDriller::EventTraceDriller()
{
EventTraceDrillerSetupBus::Handler::BusConnect();
AZStd::ThreadDrillerEventBus::Handler::BusConnect();
}
EventTraceDriller::~EventTraceDriller()
{
AZStd::ThreadDrillerEventBus::Handler::BusDisconnect();
EventTraceDrillerSetupBus::Handler::BusDisconnect();
}
void EventTraceDriller::Start(const Param* params, int numParams)
{
(void)params;
(void)numParams;
EventTraceDrillerBus::Handler::BusConnect();
TickBus::Handler::BusConnect();
EventTraceDrillerBus::AllowFunctionQueuing(true);
}
void EventTraceDriller::Stop()
{
EventTraceDrillerBus::AllowFunctionQueuing(false);
EventTraceDrillerBus::ClearQueuedEvents();
EventTraceDrillerBus::Handler::BusDisconnect();
TickBus::Handler::BusDisconnect();
}
void EventTraceDriller::OnTick(float deltaTime, ScriptTimePoint time)
{
(void)deltaTime;
(void)time;
AZ_TRACE_METHOD();
RecordThreads();
EventTraceDrillerBus::ExecuteQueuedEvents();
}
void EventTraceDriller::SetThreadName(const AZStd::thread_id& id, const char* name)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_ThreadMutex);
m_Threads[(size_t)id.m_id] = ThreadData{ name };
}
void EventTraceDriller::OnThreadEnter(const AZStd::thread::id& id, const AZStd::thread_desc* desc)
{
if (desc && desc->m_name)
{
SetThreadName(id, desc->m_name);
}
}
void EventTraceDriller::OnThreadExit(const AZStd::thread::id& id)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_ThreadMutex);
m_Threads.erase((size_t)id.m_id);
}
void EventTraceDriller::RecordThreads()
{
if (m_output && m_Threads.size())
{
// Main bus mutex guards m_output.
auto& context = EventTraceDrillerBus::GetOrCreateContext();
AZStd::scoped_lock<decltype(context.m_contextMutex), decltype(m_ThreadMutex)> lock(context.m_contextMutex, m_ThreadMutex);
for (const auto& keyValue : m_Threads)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::ThreadInfo);
m_output->Write(Crc::ThreadId, keyValue.first);
m_output->Write(Crc::Name, keyValue.second.name);
m_output->EndTag(Crc::ThreadInfo);
m_output->EndTag(Crc::EventTraceDriller);
}
}
}
void EventTraceDriller::RecordSlice(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp,
AZ::u32 duration)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::Slice);
m_output->Write(Crc::Name, name);
m_output->Write(Crc::Category, category);
m_output->Write(Crc::ThreadId, (size_t)threadId.m_id);
m_output->Write(Crc::Timestamp, timestamp);
m_output->Write(Crc::Duration, std::max(duration, 1u));
m_output->EndTag(Crc::Slice);
m_output->EndTag(Crc::EventTraceDriller);
}
void EventTraceDriller::RecordInstantGlobal(
const char* name,
const char* category,
AZ::u64 timestamp)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::Instant);
m_output->Write(Crc::Name, name);
m_output->Write(Crc::Category, category);
m_output->Write(Crc::Timestamp, timestamp);
m_output->EndTag(Crc::Instant);
m_output->EndTag(Crc::EventTraceDriller);
}
void EventTraceDriller::RecordInstantThread(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::Instant);
m_output->Write(Crc::Name, name);
m_output->Write(Crc::Category, category);
m_output->Write(Crc::ThreadId, (size_t)threadId.m_id);
m_output->Write(Crc::Timestamp, timestamp);
m_output->EndTag(Crc::Instant);
m_output->EndTag(Crc::EventTraceDriller);
}
}
}
@@ -0,0 +1,90 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <AzCore/Driller/Driller.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/parallel/threadbus.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/Debug/EventTraceDrillerBus.h>
namespace AZ
{
namespace Debug
{
class EventTraceDriller
: public Driller
, public EventTraceDrillerBus::Handler
, public EventTraceDrillerSetupBus::Handler
, public AZStd::ThreadDrillerEventBus::Handler
, public AZ::TickBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(EventTraceDriller, OSAllocator, 0)
EventTraceDriller();
virtual ~EventTraceDriller();
private:
// Driller
//////////////////////////////////////////////////////////////////////////
const char* GroupName() const override { return "SystemDrillers"; }
const char* GetName() const override { return "EventTraceDriller"; }
const char* GetDescription() const override { return "Handles timed events for a Chrome Tracing."; }
void Start(const Param* params = NULL, int numParams = 0) override;
void Stop() override;
// ThreadBus
//////////////////////////////////////////////////////////////////////////
void OnThreadEnter(const AZStd::thread::id& id, const AZStd::thread_desc* desc) override;
void OnThreadExit(const AZStd::thread::id& id) override;
// TickBus
//////////////////////////////////////////////////////////////////////////
void OnTick(float deltaTime, ScriptTimePoint time) override;
// EventTraceDrillerSetupBus
//////////////////////////////////////////////////////////////////////////
void SetThreadName(const AZStd::thread_id& threadId, const char* name) override;
// EventTraceDrillerBus
//////////////////////////////////////////////////////////////////////////
void RecordSlice(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp,
AZ::u32 duration) override;
void RecordInstantGlobal(
const char* name,
const char* category,
AZ::u64 timestamp) override;
void RecordInstantThread(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp) override;
void RecordThreads();
struct ThreadData
{
AZStd::string name;
};
AZStd::recursive_mutex m_ThreadMutex;
AZStd::unordered_map<size_t, ThreadData, AZStd::hash<size_t>, AZStd::equal_to<size_t>, OSStdAllocator> m_Threads;
};
}
} // namespace AZ
@@ -0,0 +1,85 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Driller/DrillerBus.h>
#include <AzCore/std/time.h>
#include <AzCore/std/string/string.h>
namespace AZStd
{
struct thread_id;
}
namespace AZ
{
namespace Debug
{
class EventTraceDrillerInterface
: public DrillerEBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const bool EnableEventQueue = true;
static const bool EventQueueingActiveByDefault = false;
//////////////////////////////////////////////////////////////////////////
virtual ~EventTraceDrillerInterface() {}
virtual void RecordSlice(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp,
AZ::u32 duration) = 0;
virtual void RecordInstantThread(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp) = 0;
virtual void RecordInstantGlobal(
const char* name,
const char* category,
AZ::u64 timestamp) = 0;
};
typedef AZ::EBus<EventTraceDrillerInterface> EventTraceDrillerBus;
class EventTraceDrillerSetupInterface
: public DrillerEBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//////////////////////////////////////////////////////////////////////////
virtual ~EventTraceDrillerSetupInterface() {}
virtual void SetThreadName(const AZStd::thread_id& threadId, const char* name) = 0;
};
typedef AZ::EBus<EventTraceDrillerSetupInterface> EventTraceDrillerSetupBus;
}
}
#define AZ_TRACE_INSTANT_GLOBAL_CATEGORY(name, category) \
EBUS_QUEUE_EVENT(AZ::Debug::EventTraceDrillerBus, RecordInstantGlobal, name, category, AZStd::GetTimeNowMicroSecond())
#define AZ_TRACE_INSTANT_GLOBAL(name) AZ_TRACE_INSTANT_GLOBAL_CATEGORY(name, "")
#define AZ_TRACE_INSTANT_THREAD_CATEGORY(name, category) \
EBUS_QUEUE_EVENT(AZ::Debug::EventTraceDrillerBus, RecordInstantThread, name, category, AZStd::this_thread::get_id(), AZStd::GetTimeNowMicroSecond())
#define AZ_TRACE_INSTANT_THREAD(name) AZ_TRACE_INSTANT_THREAD_CATEGORY(name, "")
@@ -0,0 +1,67 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_FRAME_PROFILER_H
#define AZCORE_FRAME_PROFILER_H
#include <AzCore/Driller/DrillerBus.h>
#include <AzCore/std/containers/ring_buffer.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/std/parallel/config.h>
#include <AzCore/std/containers/unordered_map.h>
namespace AZ
{
namespace Debug
{
namespace FrameProfiler
{
/**
* This structure is used for frame data history, make sure it's memory efficient.
*/
struct FrameData
{
unsigned int m_frameId; ///< Id of the frame this data belongs to.
union
{
ProfilerRegister::TimeData m_timeData;
ProfilerRegister::ValuesData m_userValues;
};
};
struct RegisterData
{
//////////////////////////////////////////////////////////////////////////
// Profile register snapshot
/// data that doesn't change
const char* m_name; ///< Name of the profiler register.
const char* m_function; ///< Function name in the code.
int m_line; ///< Line number if the code.
AZ::u32 m_systemId; ///< Register system id.
ProfilerRegister::Type m_type;
RegisterData* m_lastParent; ///< Pointer to the last parent register data.
AZStd::ring_buffer<FrameData> m_frames; ///< History of all frame deltas (basically the data you want to display)
};
struct ThreadData
{
typedef AZStd::unordered_map<const ProfilerRegister*, RegisterData> RegistersMap;
AZStd::thread_id m_id; ///< Thread id (same as AZStd::thread::id)
RegistersMap m_registers; ///< Map with all the registers (with history)
};
typedef AZStd::fixed_vector<ThreadData, Profiler::m_maxNumberOfThreads> ThreadDataArray; ///< Array with samplers for all threads
} // namespace FrameProfiler
} // namespace Debug
} // namespace AZ
#endif // AZCORE_FRAME_PROFILER_H
#pragma once
@@ -0,0 +1,42 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_FRAME_PROFILER_BUS_H
#define AZCORE_FRAME_PROFILER_BUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Debug/FrameProfiler.h>
namespace AZ
{
namespace Debug
{
class FrameProfilerComponent;
/**
* Interface class for frame profiler events.
*/
class FrameProfilerEvents
: public AZ::EBusTraits
{
public:
virtual ~FrameProfilerEvents() {}
/// Called when the frame profiler has computed a new frame (even is there is no new data).
virtual void OnFrameProfilerData(const FrameProfiler::ThreadDataArray& data) = 0;
};
typedef AZ::EBus<FrameProfilerEvents> FrameProfilerBus;
} // namespace Debug
} // namespace AZ
#endif // AZCORE_FRAME_PROFILER_BUS_H
#pragma once
@@ -0,0 +1,254 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/FrameProfilerComponent.h>
#include <AzCore/Debug/FrameProfilerBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Debug/Profiler.h>
namespace AZ
{
namespace Debug
{
//=========================================================================
// FrameProfilerComponent
// [12/5/2012]
//=========================================================================
FrameProfilerComponent::FrameProfilerComponent()
: m_numFramesStored(2)
, m_frameId(0)
, m_pauseOnFrame(0)
, m_currentThreadData(NULL)
{
}
//=========================================================================
// ~FrameProfilerComponent
// [12/5/2012]
//=========================================================================
FrameProfilerComponent::~FrameProfilerComponent()
{
}
//=========================================================================
// Activate
// [12/5/2012]
//=========================================================================
void FrameProfilerComponent::Activate()
{
if (!Profiler::IsReady())
{
Profiler::Create();
}
Profiler::AddReference();
TickBus::Handler::BusConnect();
AZ_Assert(m_numFramesStored >= 1, "We must have at least one frame to store, otherwise this component is useless!");
}
//=========================================================================
// Deactivate
// [12/5/2012]
//=========================================================================
void FrameProfilerComponent::Deactivate()
{
TickBus::Handler::BusDisconnect();
Profiler::ReleaseReference();
}
//=========================================================================
// OnTick
// [12/5/2012]
//=========================================================================
void FrameProfilerComponent::OnTick(float deltaTime, ScriptTimePoint time)
{
(void)deltaTime;
(void)time;
++m_frameId;
AZ_Error("Profiler", m_frameId != m_pauseOnFrame, "Triggered user pause/error on this frame! Check FrameProfilerComponent pauseOnFrame value!");
if (!Profiler::IsReady())
{
return; // we can't sample registers without profiler
}
// collect data from the profiler
m_currentThreadData = NULL;
Profiler::Instance().ReadRegisterValues(AZStd::bind(&FrameProfilerComponent::ReadProfilerRegisters, this, AZStd::placeholders::_1, AZStd::placeholders::_2));
// process all the resulting data here, not while reading the registers
for (size_t iThread = 0; iThread < m_threads.size(); ++iThread)
{
FrameProfiler::ThreadData& td = m_threads[iThread];
FrameProfiler::ThreadData::RegistersMap::iterator it = td.m_registers.begin();
FrameProfiler::ThreadData::RegistersMap::iterator last = td.m_registers.end();
for (; it != last; ++it)
{
// fix up parents
FrameProfiler::RegisterData& rd = it->second;
if (rd.m_type == ProfilerRegister::PRT_TIME)
{
const FrameProfiler::FrameData& fd = rd.m_frames.back();
if (fd.m_timeData.m_lastParent != nullptr)
{
FrameProfiler::ThreadData::RegistersMap::iterator parentIt = td.m_registers.find(fd.m_timeData.m_lastParent);
AZ_Assert(parentIt != td.m_registers.end(), "We have a parent register that is not in our register map. This should not happen!");
rd.m_lastParent = &parentIt->second;
}
else
{
rd.m_lastParent = NULL;
}
}
}
}
// send an even to whomever cares
EBUS_EVENT(FrameProfilerBus, OnFrameProfilerData, m_threads);
}
int FrameProfilerComponent::GetTickOrder()
{
// Even it's not critical we should tick last to capture the current frame
// so TICK_LAST (since it's not the last int +1 is a valid assumption)
return TICK_LAST + 1;
}
//=========================================================================
// ReadRegisterCallback
// [12/5/2012]
//=========================================================================
bool FrameProfilerComponent::ReadProfilerRegisters(const ProfilerRegister& reg, const AZStd::thread_id& id)
{
if (m_currentThreadData == NULL || m_currentThreadData->m_id != id)
{
m_currentThreadData = NULL;
// find the thread and cache it, as we will received registers thread by thread... so we don't search.
for (size_t i = 0; i < m_threads.size(); ++i)
{
FrameProfiler::ThreadData* td = &m_threads[i];
if (td->m_id == id)
{
m_currentThreadData = td;
break;
}
}
if (m_currentThreadData == NULL)
{
m_threads.push_back();
m_currentThreadData = &m_threads.back();
m_currentThreadData->m_id = id;
}
}
const ProfilerRegister* profReg = &reg;
FrameProfiler::ThreadData::RegistersMap::pair_iter_bool pairIterBool = m_currentThreadData->m_registers.insert_key(profReg);
FrameProfiler::RegisterData& regData = pairIterBool.first->second;
// now update dynamic data with as little as possible computation (we must be fast)
FrameProfiler::FrameData fd; // we can actually move this computation (FrameData and push) for later but we will need to use more memory
fd.m_frameId = m_frameId;
if (pairIterBool.second)
{
// when insert copy the static data only once
regData.m_name = profReg->m_name;
regData.m_function = profReg->m_function;
regData.m_line = profReg->m_line;
regData.m_systemId = profReg->m_systemId;
regData.m_frames.set_capacity(m_numFramesStored);
regData.m_type = static_cast<ProfilerRegister::Type>(profReg->m_type);
}
switch (regData.m_type)
{
case ProfilerRegister::PRT_TIME:
{
fd.m_timeData.m_time = profReg->m_timeData.m_time;
fd.m_timeData.m_childrenTime = profReg->m_timeData.m_childrenTime;
fd.m_timeData.m_calls = profReg->m_timeData.m_calls;
fd.m_timeData.m_childrenCalls = profReg->m_timeData.m_childrenCalls;
fd.m_timeData.m_lastParent = profReg->m_timeData.m_lastParent;
} break;
case ProfilerRegister::PRT_VALUE:
{
fd.m_userValues.m_value1 = profReg->m_userValues.m_value1;
fd.m_userValues.m_value2 = profReg->m_userValues.m_value2;
fd.m_userValues.m_value3 = profReg->m_userValues.m_value3;
fd.m_userValues.m_value4 = profReg->m_userValues.m_value4;
fd.m_userValues.m_value5 = profReg->m_userValues.m_value5;
} break;
}
regData.m_frames.push_back(fd);
return true;
}
//=========================================================================
// GetProvidedServices
//=========================================================================
void FrameProfilerComponent::GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("FrameProfilerService", 0x05d1bb90));
}
//=========================================================================
// GetIncompatibleServices
//=========================================================================
void FrameProfilerComponent::GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("FrameProfilerService", 0x05d1bb90));
}
//=========================================================================
// GetDependentServices
//=========================================================================
void FrameProfilerComponent::GetDependentServices(ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("MemoryService", 0x5c4d473c));
}
//=========================================================================
// Reflect
//=========================================================================
void FrameProfilerComponent::Reflect(ReflectContext* context)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<FrameProfilerComponent, AZ::Component>()
->Version(1)
->Field("numFramesStored", &FrameProfilerComponent::m_numFramesStored)
->Field("pauseOnFrame", &FrameProfilerComponent::m_pauseOnFrame)
;
if (EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<FrameProfilerComponent>(
"Frame Profiler", "Performs per frame profiling (FPS counter, registers, etc.)")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Profiling")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->DataElement(AZ::Edit::UIHandlers::SpinBox, &FrameProfilerComponent::m_numFramesStored, "Number of Frames", "How many frames we will keep with the RUNTIME buffers.")
->Attribute(AZ::Edit::Attributes::Min, 1)
->DataElement(AZ::Edit::UIHandlers::SpinBox, &FrameProfilerComponent::m_pauseOnFrame, "Pause on frame", "Paused the engine (debug break) on a specific frame. 0 means no pause!")
;
}
}
}
} // namespace Debug
} // namespace AZ
@@ -0,0 +1,79 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_FRAME_PROFILER_COMPONENT_H
#define AZCORE_FRAME_PROFILER_COMPONENT_H
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Debug/FrameProfiler.h>
#include <AzCore/std/parallel/threadbus.h>
#include <AzCore/Math/Crc.h>
namespace AZ
{
namespace Debug
{
/**
* Frame profiler component provides a frame profiling information
* (from FPS counter to profiler registers manipulation and so on).
* It's a debug system so it should not be active in release
*/
class FrameProfilerComponent
: public Component
, public AZ::TickBus::Handler
{
public:
AZ_COMPONENT(AZ::Debug::FrameProfilerComponent, "{B81739EF-ED77-4F67-9D05-6ADF94F0431A}")
FrameProfilerComponent();
virtual ~FrameProfilerComponent();
private:
//////////////////////////////////////////////////////////////////////////
// Component base
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Tick bus
void OnTick(float deltaTime, ScriptTimePoint time) override;
int GetTickOrder() override;
//////////////////////////////////////////////////////////////////////////
/// \ref ComponentDescriptor::GetProvidedServices
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided);
/// \ref ComponentDescriptor::GetIncompatibleServices
static void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible);
/// \ref ComponentDescriptor::GetDependentServices
static void GetDependentServices(ComponentDescriptor::DependencyArrayType& dependent);
/// \red ComponentDescriptor::Reflect
static void Reflect(ReflectContext* reflection);
/// callback for reading profiler registers
bool ReadProfilerRegisters(const ProfilerRegister& reg, const AZStd::thread_id& id);
// Keep in mind memory usage, increases quickly. Prefer remote tools (where the history is kept on the PC) instead of keeping long history
unsigned int m_numFramesStored; ///< Number of frames that we will store in history buffers. >= 1
unsigned int m_frameId; ///< Frame id (it's just counted from the start).
unsigned int m_pauseOnFrame; ///< Allows you to specify a frame the code will pause onto.
FrameProfiler::ThreadDataArray m_threads; ///< Array with samplers for all threads
FrameProfiler::ThreadData* m_currentThreadData; ///< Cached pointer to the last accessed thread data.
};
}
}
#endif // AZCORE_FRAME_PROFILER_COMPONENT_H
#pragma once
@@ -0,0 +1,57 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Debug/ProfileModuleInit.h>
#ifdef AZ_PROFILE_TELEMETRY
# include <RADTelemetry/ProfileTelemetryBus.h>
// Define the per-module RAD Telemetry instance pointer
struct tm_api;
tm_api* g_radTmApi;
#endif
namespace AZ
{
namespace Debug
{
void ProfileModuleInit()
{
#if defined(AZ_PROFILE_TELEMETRY)
{
if (!g_radTmApi)
{
using namespace RADTelemetry;
ProfileTelemetryRequestBus::BroadcastResult(g_radTmApi, &ProfileTelemetryRequests::GetApiInstance);
}
}
#endif
// Add additional per-DLL required profiler initialization here
}
ProfileModuleInitializer::ProfileModuleInitializer()
{
ProfilerNotificationBus::Handler::BusConnect();
}
ProfileModuleInitializer::~ProfileModuleInitializer()
{
ProfilerNotificationBus::Handler::BusDisconnect();
}
void ProfileModuleInitializer::OnProfileSystemInitialized()
{
ProfileModuleInit();
}
}
}
@@ -0,0 +1,40 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Debug/ProfilerBus.h>
namespace AZ
{
namespace Debug
{
//! Perform any required per-module initialization of the current profiler
void ProfileModuleInit();
/*!
* ProfileModuleInitializer
* Helper class that calls ProfileModuleInit when OnProfileSystemInitialized is fired.
*/
class ProfileModuleInitializer
: private AZ::Debug::ProfilerNotificationBus::Handler
{
public:
ProfileModuleInitializer();
~ProfileModuleInitializer() override;
private:
void OnProfileSystemInitialized() override;
};
}
}

Some files were not shown because too many files have changed in this diff Show More