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,29 @@
/*
* 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/intrusive_ptr.h>
namespace AZ
{
namespace Data
{
/**
* This is an alias of intrusive_ptr designed for any class which inherits from
* InstanceData. You're not required to use Instance<> over AZStd::intrusive_ptr<>,
* but it provides symmetry with Asset<>.
*/
template <typename T>
using Instance = AZStd::intrusive_ptr<T>;
}
}
@@ -0,0 +1,70 @@
/*
* 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 <AtomCore/Instance/InstanceData.h>
#include <AtomCore/Instance/InstanceDatabase.h>
namespace AZ
{
namespace Data
{
const InstanceId& InstanceData::GetId() const
{
return m_id;
}
const AssetId& InstanceData::GetAssetId() const
{
return m_assetId;
}
const AssetType& InstanceData::GetAssetType() const
{
return m_assetType;
}
void InstanceData::add_ref()
{
AZ_Assert(m_useCount >= 0, "m_useCount is negative");
++m_useCount;
}
void InstanceData::release()
{
// It is possible that some other thread, not us, will delete this InstanceData after we
// decrement m_useCount. For example, another thread could create and release an instance
// immediately after we decrement. So we copy the necessary data to the callstack before
// decrementing. This ensures the call to ReleaseInstance() below won't crash even if this
// InstanceData gets deleted by another thread first.
InstanceDatabaseInterface* parentDatabase = m_parentDatabase;
InstanceId instanceId = GetId();
const int prevUseCount = m_useCount.fetch_sub(1);
AZ_Assert(prevUseCount >= 1, "m_useCount is negative");
if (prevUseCount == 1)
{
if (parentDatabase)
{
parentDatabase->ReleaseInstance(this, instanceId);
}
else
{
// This is a standalone object not created through the InstanceDatabase so
// we can just delete it.
delete this;
}
}
}
}
}
@@ -0,0 +1,115 @@
/*
* 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 <AtomCore/Instance/InstanceId.h>
#include <AtomCore/Instance/Instance.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzCore/RTTI/RTTI.h>
namespace AZ
{
namespace Data
{
class InstanceDatabaseInterface;
/**
* InstanceData is an intrusive smart pointer base class for any class utilizing an InstanceDatabase.
* To use a class in an InstanceDatabase, you must inherit from InstanceData, and in your concrete class,
* define the AZ_INSTANCE_DATA() macro.
*
* InstanceData is compatible with AZStd::intrusive_ptr. The pointer is also typedef'd to AZ::Data::Instance<>
* to mirror AZ::Data::Asset<>.
*
* Each instance data is associated with an instance id and an asset id. These id's are only valid if the instance
* is created from an InstanceDatabase, otherwise they are null. It is valid to create a derived instance data class
* without using the InstanceDatabase, but the ids will all be null.
*
* By default, if the instance database did not create the instance, InstanceData will call 'delete' on itself when
* the reference count hits zero. If the instance database was the creator, it will assign a custom deleter. You
* do not have access to this deleter.
*/
class InstanceData
{
public:
AZ_RTTI(InstanceData, "{3B728818-A765-4749-A3A6-0C960E4DD65E}");
virtual ~InstanceData() = default;
/**
* Returns the id which uniquely identifies the instance in the instance database. If
* the concrete class was created outside of the database, the id is null.
*/
const InstanceId& GetId() const;
/**
* Returns the asset id used to create the instance.
*/
const AssetId& GetAssetId() const;
/**
* Returns the asset type used to create the instance.
*/
const AssetType& GetAssetType() const;
protected:
InstanceData() = default;
private:
///////////////////////////////////////////////////////////////////
// IntrusivePtCountPolicy template overrides
void add_ref();
void release();
///////////////////////////////////////////////////////////////////
template <typename Type>
friend struct AZStd::IntrusivePtrCountPolicy;
template <typename Type>
friend class InstanceDatabase;
// Pointer to the InstanceDatabase that owns this instance. Will be null if the InstanceData object
// is not held in an InstanceDatabase.
InstanceDatabaseInterface* m_parentDatabase = nullptr;
AZStd::atomic_int m_useCount = {0};
// The id which uniquely identifies the instance.
InstanceId m_id;
// Tracks the asset id used to create the instance.
AssetId m_assetId;
// Tracks the asset type used to create the instance.
AssetType m_assetType;
};
/// @cond EXCLUDE_DOCS
AZ_HAS_STATIC_MEMBER(InstanceDatabaseName, GetDatabaseName, const char*, ());
/// @endcond
/**
* Declares a concrete instance class. This macro is required if the instance is used in an InstanceDatabase.
* The class must derive from AZ::Data::InstanceData. The class may not be templated.
*
* AZ_INSTANCE_DATA(_InstanceClass, _ClassGUID, OtherBaseClasses...) AZ::Data::InstanceData is included automatically.
*/
#define AZ_INSTANCE_DATA(_InstanceClass, ...) \
AZ_RTTI(_InstanceClass, __VA_ARGS__, AZ::Data::InstanceData) \
static const char* GetDatabaseName() \
{ \
return "InstanceDatabase<" #_InstanceClass ">"; \
}
}
}
@@ -0,0 +1,556 @@
/*
* 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 <AtomCore/Instance/Instance.h>
#include <AtomCore/Instance/InstanceData.h>
#include <AtomCore/Instance/InstanceId.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Module/Environment.h>
#include <AzCore/std/parallel/shared_mutex.h>
namespace AZ
{
namespace Data
{
/**
* Provides create and delete functions for a specific InstanceData type, for use by @ref InstanceDatabase
*/
template <typename Type>
struct InstanceHandler
{
/**
* Creation takes an asset as input and produces a new instance as output.
* Ownership must be returned to the caller. Use this method to perform
* both allocation and initialization using the provided asset. The returned
* instance is assumed to be valid and usable by the client.
*
* Usage Examples:
* - The user may choose to allocate from a local pool or cache.
* - The concrete instance type may have a non-standard initialization path.
* - The user may wish to encode global context into the functor (an RHI device, for example).
*
* PERFORMANCE NOTE: Creation is currently done under a lock. Initialization should be quick.
*/
using CreateFunction = AZStd::function<Instance<Type>(AssetData*)>;
/**
* Deletion takes an asset as input and transfers ownership to the method.
*/
using DeleteFunction = AZStd::function<void(Type*)>;
/// [Required] The function to use when creating an instance.
/// The system will assert if no creation function is provided.
CreateFunction m_createFunction;
/// [Optional] The function to use when deleting an instance.
DeleteFunction m_deleteFunction = [](Type* t) { delete t; };
};
//! This class exists to allow InstanceData to access parts of InstanceDatabase without having
//! to know the instance data type, since InstanceDatabase is a template class.
class InstanceDatabaseInterface
{
friend class InstanceData;
protected:
virtual void ReleaseInstance(InstanceData* instance, const InstanceId& instanceId) = 0;
};
/**
* This class is a simple database of typed instances. An 'instance' in this context is any class
* which inherits from InstanceData, is created at runtime from an asset, and has a unique instance
* id. The purpose of this system is to control de-duplication of instances at runtime, and to
* associate instance types with their originating asset types.
*
* The database has itself singleton access, but it should be owned by the corresponding system (which
* is in charge of creation / destruction of the database). To use the database, you may instantiate it
* using one of the following approaches:
* 1) Instantiate one InstanceDatabase for each concrente instance class. Use this approach if all
* concrete instance classes are known at compile time.
* 2) Instantiate one InstanceDatabase for a known instance base class, and then register multiple
* InstanceHandlers for each concrete instance class. Use this approach if only the instance base
* class is known at compile time and the concrete instance classes are only known at runtime.
* For example, Atom provides abstract StreamingImageControllerAsset and StreamingImageController
* classes, and a game-specific gem can provide custom implementations by adding a handler to
* InstanceDatabase<StreamingImageController>.
*
* The database allows you to find an instance from its corresponding @ref InstanceId. Alternatively, you
* can 'find or create' an instance, which will create the instance if it doesn't already exist, or return you the
* existing instance. The 'find or create' operation takes an asset as input. Instances are designed to be trivially
* created from their parent asset.
*
* The database does NOT own instances. Ownership is returned to you in the form of a smart pointer (Data::Instance<>).
* This is the same ownership model used by the asset manager.
*
* The system is thread-safe. You can create / destroy instances from any thread, however Instances should not be
* copied between threads, they should always be retrieved from the InstanceDatabase directly.
*
* Example Usage (using instantiation approach #1 described above):
* @code{.cpp}
*
* // Create the database.
* Data::InstanceHandler<MyInstanceType> handler;
*
* // Provide your own creator (and optional deleter) to control allocation / initialization of your object.
* handler.m_createFunction = [] (Data::AssetData* assetData) { return aznew MyInstanceType(assetData); };
*
* Data::InstanceDatabase<MyInstanceType>::Create(azrtti_typeid<MyAssetType>(), handler);
*
* Data::Asset<MyAssetType> myAsset{ASSETID_1};
*
* // Create an instance id from the asset id (1-to-1 mapping).
* Data::InstanceId instanceId = Data::InstanceId::CreateFromAssetId(myAsset.GetId());
*
* // Find or create an instance from an asset.
* Data::Instance<MyInstanceType> instance = Data::InstanceDatabase<MyInstanceType>::Instance().FindOrCreate(instanceId, myAsset);
*
* // Create an instance by name.
* Data::InstanceId instanceIdName = Data::InstanceId::CreateName("HelloWorld");
*
* // Creates a new instance from the same asset (the old instance is de-ref'd).
* instance = Data::InstanceDatabase<MyInstanceType>::Instance().FindOrCreate(instanceIdName, myAsset);
*
* // Finds an existing instance.
* Data::Instance<MyInstanceType> instance2 = Data::InstanceDatabase<MyInstanceType>::Instance().Find(instanceIdName);
*
* instance == instance2; // true
*
* // Find or create an existing instance.
* Data::Instance<MyInstanceType> instance3 = Data::InstanceDatabase<MyInstanceType>::Instance().FindOrCreate(instanceIdName, myAsset);
*
* instance == instance2 == instance3; // true
*
* // INVALID: Create an instance using a different asset.
* Data::Asset<MyAssetType> myAsset2{ASSETID_2};
*
* // This will assert. You can only request an instance using the SAME asset each time. If the system detects a mismatch it
* // will throw an error.
* Data::Instance<MyInstanceType> instance3 = Data::InstanceDatabase<MyInstanceType>::Instance().FindOrCreate(instanceIdName, myAsset2);
*
* // After all objects are out of scope! The system will report an error if objects are still active on destruction.
* Data::InstanceDatabase<MyInstanceType>::Destroy();
*
* @endcode
*/
template <typename Type>
class InstanceDatabase final : public InstanceDatabaseInterface
{
static_assert(AZStd::is_base_of<InstanceData, Type>::value, "Type must inherit from Data::Instance to be used in Data::InstanceDatabase.");
public:
AZ_CLASS_ALLOCATOR(InstanceDatabase, AZ::SystemAllocator, 0);
/**
* Create the InstanceDatabase with a single handler.
* Use this function when creating an InstanceDatabase that will handle concrete classes of @ref Type.
* \param assetType - All instances will be based on subclasses of this asset type.
* \param handler - An InstanceHandler that creates instances of @ref assetType assets.
*/
static void Create(const AssetType& assetType, const InstanceHandler<Type>& handler);
/**
* Create the InstanceDatabase with no handlers. Individual handlers must be added using @ref AddHandler().
* Use this function when creating an InstanceDatabase that will handle subclasses of @ref Type.
* \param assetType - All instances will be based on subclasses of this asset type.
*/
static void Create(const AssetType& assetType);
static void Destroy();
static bool IsReady();
static InstanceDatabase& Instance();
/**
* Add an InstanceHandler that will create instances for assets of type @ref assetType.
*/
void AddHandler(const AssetType& assetType, const InstanceHandler<Type>& handler);
void AddHandler(const AssetType& assetType, typename InstanceHandler<Type>::CreateFunction createFunction);
void RemoveHandler(const AssetType& assetType);
/**
* Attempts to find an instance associated with the provided id. If the instance exists, it
* is returned. If no instance is found, nullptr is returned. If is safe to call this from
* multiple threads.
*
* @param id The id used to find an instance in the database.
*/
Data::Instance<Type> Find(const InstanceId& id) const;
/**
* Attempts to find an instance associated with the provided id. If it exists, it is returned.
* Otherwise, it is created using the provided asset data and then returned. It is safe to call
* this method from multiple threads, even with the same id. The call is synchronous and other threads
* will block until creation is complete.
*
* PERFORMANCE NOTE: If the asset data is not loaded and creation is required, the system will
* perform a BLOCKING load on the asset. If this behavior is not desired, the user should either
* ensure the asset is loaded prior to calling this method, or call @ref Find instead.
*
* @param id The id used to find or create an instance in the database.
* @param asset The asset used to initialize the instance, if it does NOT already exist.
* If the instance exists, the asset id is checked against the existing instance. If
* validation is enabled, the system will error if the created asset id does not match
* the provided asset id. It is required that you consistently provide the same asset
* when acquiring an instance.
* @return Returns a smart pointer to the instance, which was either found or created.
*/
Data::Instance<Type> FindOrCreate(const InstanceId& id, const Asset<AssetData>& asset);
//! Calls the above FindOrCreate using an InstanceId created from the asset
Data::Instance<Type> FindOrCreate(const Asset<AssetData>& asset);
//! Calls FindOrCreate using a random InstanceId
Data::Instance<Type> Create(const Asset<AssetData>& asset);
private:
InstanceDatabase(const AssetType& assetType);
~InstanceDatabase();
static const char* GetEnvironmentName();
// Utility function called by InstanceData to remove the instance from the database.
void ReleaseInstance(InstanceData* instance, const InstanceId& instanceId) override;
void ValidateSameAsset(InstanceData* instance, const Data::Asset<AssetData>& asset) const;
// Performs a thread-safe search for the InstanceHandler for a given asset type.
bool FindHandler(const AssetType& assetType, InstanceHandler<Type>& handlerOut);
mutable AZStd::shared_mutex m_handlersMutex;
AZStd::unordered_map<AssetType, InstanceHandler<Type>> m_handlers;
// m_database uses a recursive_mutex instead of a shared_mutex because it's possible to recursively
// create or destroy instances on the same thread while in the midst of creating or destroying an instance.
mutable AZStd::recursive_mutex m_databaseMutex;
AZStd::unordered_map<InstanceId, Type*> m_database;
// All instances created by this InstanceDatabase will be for assets derived from this type.
AssetType m_baseAssetType;
static EnvironmentVariable<InstanceDatabase*> ms_instance;
};
template <typename Type>
EnvironmentVariable<InstanceDatabase<Type>*> InstanceDatabase<Type>::ms_instance = nullptr;
template <typename Type>
InstanceDatabase<Type>::~InstanceDatabase()
{
#ifdef AZ_DEBUG_BUILD
for (const auto& keyValue : m_database)
{
const InstanceId& instanceId = keyValue.first;
const AZStd::string& stringValue = instanceId.ToString<AZStd::string>();
AZ_Printf("InstanceDatabase", "\tLeaked Instance: %s\n", stringValue.c_str());
}
#endif
AZ_Error(
"InstanceDatabase", m_database.empty(),
"AZ::Data::%s still has active references.", Type::GetDatabaseName());
}
template <typename Type>
void InstanceDatabase<Type>::AddHandler(const AssetType& assetType, const InstanceHandler<Type>& handler)
{
AZ_Assert(handler.m_createFunction, "You are required to provide a create function to InstanceDatabase.");
AZStd::unique_lock<AZStd::shared_mutex> lock(m_handlersMutex);
auto result = m_handlers.emplace(assetType, handler);
AZ_Assert(result.second, "An InstanceHandler already exists for this AssetType");
}
template <typename Type>
void InstanceDatabase<Type>::AddHandler(const AssetType& assetType, typename InstanceHandler<Type>::CreateFunction createFunction)
{
InstanceHandler<Type> instanceHandler;
instanceHandler.m_createFunction = createFunction;
AddHandler(assetType, instanceHandler);
}
template <typename Type>
void InstanceDatabase<Type>::RemoveHandler(const AssetType& assetType)
{
AZStd::unique_lock<AZStd::shared_mutex> lock(m_handlersMutex);
m_handlers.erase(assetType);
}
template <typename Type>
bool InstanceDatabase<Type>::FindHandler(const AssetType& assetType, InstanceHandler<Type>& handlerOut)
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_handlersMutex);
auto handlerIter = m_handlers.find(assetType);
if (handlerIter != m_handlers.end())
{
// Since the handler is just a couple pointers, we copy the handler so we can
// release the lock right away.
handlerOut = handlerIter->second;
return true;
}
else
{
return false;
}
}
template <typename Type>
Data::Instance<Type> InstanceDatabase<Type>::Find(const InstanceId& id) const
{
AZStd::scoped_lock<AZStd::recursive_mutex> lock(m_databaseMutex);
auto iter = m_database.find(id);
if (iter != m_database.end())
{
return iter->second;
}
return nullptr;
}
template <typename Type>
Data::Instance<Type> InstanceDatabase<Type>::FindOrCreate(const InstanceId& id, const Asset<AssetData>& asset)
{
if (!id.IsValid())
{
return nullptr;
}
// Try to find the entry
{
AZStd::scoped_lock<AZStd::recursive_mutex> lock(m_databaseMutex);
auto iter = m_database.find(id);
if (iter != m_database.end())
{
InstanceData* data = static_cast<InstanceData*>(iter->second);
ValidateSameAsset(data, asset);
return iter->second;
}
}
// Take a reference so we can mutate it.
Data::Asset<Data::AssetData> assetLocal = asset;
if (!assetLocal.IsReady())
{
assetLocal.QueueLoad();
if (assetLocal.IsLoading())
{
assetLocal.BlockUntilLoadComplete();
}
// Failed to load the asset
if (!assetLocal.IsReady())
{
return nullptr;
}
}
if (!azrtti_istypeof(m_baseAssetType, assetLocal.Get()))
{
InstanceHandler<Type> instanceHandler;
// If a handler was incorrectly registered for an unrelated asset type, this is the
// first chance we have to discover that fact, because up until now all we had was two
// TypeIds.
if (FindHandler(assetLocal.GetType(), instanceHandler))
{
AZ_Assert(false, "An InstanceHandler was added for asset type %s which is not a subclass of the base asset type %s.",
assetLocal.GetType().ToString<AZStd::string>().data(),
m_baseAssetType.ToString<AZStd::string>().data()
);
return nullptr;
}
}
// Take a lock to guard the insertion. Note that this will not guard against recursive insertions on the same thread.
AZStd::scoped_lock<AZStd::recursive_mutex> lock(m_databaseMutex);
// Search again in case someone else got here first.
auto iter = m_database.find(id);
if (iter != m_database.end())
{
InstanceData* data = static_cast<InstanceData*>(iter->second);
ValidateSameAsset(data, asset);
return iter->second;
}
// Emplace a new instance and return it.
InstanceHandler<Type> instanceHandler;
if (FindHandler(assetLocal.GetType(), instanceHandler))
{
// It's possible for the m_createFunction call to recursively trigger another FindOrCreate call, so be aware that
// the contents of m_database may change within this call.
Data::Instance<Type> instance = instanceHandler.m_createFunction(assetLocal.Get());
if (instance)
{
AZ_Assert(m_database.find(id) == m_database.end(),
"Instance creation for asset id %s resulted in a recursive creation of that asset, which was unexpected. "
"This asset might be erroneously referencing itself as a dependent asset.", id.ToString<AZStd::string>().c_str());
instance->m_id = id;
instance->m_parentDatabase = this;
instance->m_assetId = assetLocal.GetId();
instance->m_assetType = assetLocal.GetType();
m_database.emplace(id, instance.get());
}
return AZStd::move(instance);
}
else
{
AZ_Warning(
"InstanceDatabase", false,
"No InstanceHandler found for asset type %s", assetLocal.GetType().ToString<AZStd::string>().data());
return nullptr;
}
}
template <typename Type>
Data::Instance<Type> InstanceDatabase<Type>::FindOrCreate(const Asset<AssetData>& asset)
{
return FindOrCreate(Data::InstanceId::CreateFromAssetId(asset.GetId()), asset);
}
template <typename Type>
Data::Instance<Type> InstanceDatabase<Type>::Create(const Asset<AssetData>& asset)
{
return FindOrCreate(Data::InstanceId::CreateRandom(), asset);
}
template <typename Type>
void InstanceDatabase<Type>::ReleaseInstance(InstanceData* instance, const InstanceId& instanceId)
{
AZStd::scoped_lock<AZStd::recursive_mutex> lock(m_databaseMutex);
// If instanceId doesn't exist in m_database that means the instance was already deleted on another thread.
// We check and make sure the pointers match before erasing, just in case some other InstanceData was created with the same ID.
// We re-check the m_useCount in case some other thread requested an instance from the database after we decremented m_useCount.
// We change m_useCount to -1 to be sure another thread doesn't try to clean up the instance (though the other checks probably cover that).
auto instanceItr = m_database.find(instanceId);
int32_t expectedRefCount = 0;
if (instanceItr != m_database.end() &&
instanceItr->second == instance &&
instance->m_useCount.compare_exchange_strong(expectedRefCount, -1))
{
m_database.erase(instance->GetId());
InstanceHandler<Type> instanceHandler;
if (FindHandler(instance->GetAssetType(), instanceHandler))
{
instanceHandler.m_deleteFunction(static_cast<Type*>(instance));
}
else
{
AZ_Assert(false,
"Cannot delete Instance. No InstanceHandler found for asset type %s", instance->GetAssetType().ToString<AZStd::string>().data());
}
}
}
template <typename Type>
void InstanceDatabase<Type>::ValidateSameAsset(InstanceData* instance, const Data::Asset<AssetData>& asset) const
{
/**
* The following validation layer is disabled in release, but is designed to catch a couple related edge cases
* that might result in difficult to track bugs.
* - The user provides an id that collides with a different id.
* - The user attempts to provide a different asset when requesting the same instance id.
*
* In either case, the probable result is that an instance is returned that does not match the asset id provided
* by the caller, which is not valid and probably not what the user expected. The validation layer will throw an
* error to alert them.
*/
#if defined (AZ_DEBUG_BUILD)
AZ_Error("InstanceDatabase", instance->m_assetId == asset.GetId(),
"InstanceDatabase::FindOrCreate found the requested instance, but a different asset was used to create it. "
"Instances of a specific id should be acquired using the same asset. Either make sure the instance id "
"is actually unique, or that you are using the same asset each time for that particular id.");
#else
AZ_UNUSED(instance);
AZ_UNUSED(asset);
#endif
}
template <typename Type>
InstanceDatabase<Type>::InstanceDatabase(const AssetType& assetType)
: m_baseAssetType(assetType)
{
}
template <typename Type>
void InstanceDatabase<Type>::Create(const AssetType& assetType)
{
AZ_Assert(!ms_instance || !ms_instance.Get(), "InstanceDatabase already created!");
if (!ms_instance)
{
ms_instance = Environment::CreateVariable<InstanceDatabase*>(GetEnvironmentName());
}
if (!ms_instance.Get())
{
ms_instance.Set(aznew InstanceDatabase<Type>(assetType));
}
}
template <typename Type>
void InstanceDatabase<Type>::Create(const AssetType& assetType, const InstanceHandler<Type>& handler)
{
Create(assetType);
Instance().AddHandler(assetType, handler);
}
template <typename Type>
void InstanceDatabase<Type>::Destroy()
{
AZ_Assert(ms_instance, "InstanceDatabase not created!");
delete (*ms_instance);
*ms_instance = nullptr;
}
template <typename Type>
bool InstanceDatabase<Type>::IsReady()
{
if (!ms_instance)
{
ms_instance = Environment::FindVariable<InstanceDatabase*>(GetEnvironmentName());
}
return ms_instance && *ms_instance;
}
template <typename Type>
InstanceDatabase<Type>& InstanceDatabase<Type>::Instance()
{
if (!ms_instance)
{
ms_instance = Environment::FindVariable<InstanceDatabase*>(GetEnvironmentName());
}
AZ_Assert(ms_instance && *ms_instance, "InstanceDatabase<%s> has not been initialized yet.", AzTypeInfo<Type>::Name());
return *(*ms_instance);
}
template <typename Type>
const char* InstanceDatabase<Type>::GetEnvironmentName()
{
static_assert(HasInstanceDatabaseName<Type>::value, "All classes used as instances in an InstanceDatabase need to define AZ_INSTANCE_DATA in the class.");
return Type::GetDatabaseName();
}
}
}
@@ -0,0 +1,63 @@
/*
* 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 <AtomCore/Instance/InstanceId.h>
namespace AZ
{
namespace Data
{
InstanceId InstanceId::CreateFromAssetId(const AssetId& assetId)
{
return InstanceId(assetId.m_guid, assetId.m_subId);
}
InstanceId InstanceId::CreateName(const char* name)
{
return InstanceId(Uuid::CreateName(name));
}
InstanceId InstanceId::CreateData(const void* data, size_t dataSize)
{
return InstanceId(Uuid::CreateData(data, dataSize));
}
InstanceId InstanceId::CreateRandom()
{
return InstanceId(Uuid::CreateRandom());
}
InstanceId::InstanceId(const Uuid& guid)
: m_guid{guid}
{}
InstanceId::InstanceId(const Uuid& guid, uint32_t subId)
: m_guid{guid}
, m_subId{subId}
{}
bool InstanceId::IsValid() const
{
return m_guid != AZ::Uuid::CreateNull();
}
bool InstanceId::operator == (const InstanceId& rhs) const
{
return m_guid == rhs.m_guid && m_subId == rhs.m_subId;
}
bool InstanceId::operator != (const InstanceId& rhs) const
{
return m_guid != rhs.m_guid || m_subId != rhs.m_subId;
}
}
}
@@ -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.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
namespace AZ
{
namespace Data
{
/**
* InstanceId is a unique identifier for an Instance in an InstanceDatabase. Instances
* are used primarily to control de-duplication of 'instances' created from 'assets'.
* As a result, this class mirrors the structure of asset id (by including the sub-id)
* in order to make translation easy. However, the types are not related in order to add
* some type safety to the system.
*/
struct InstanceId
{
AZ_TYPE_INFO(InstanceId, "{0E59A635-07E8-419F-A0F2-90E0CE9C0AD6}");
/**
* Creates an instance id from an asset id. The two will share the same guid and
* sub id. This is an explicit create method rather than a constructor in order
* to make it explicit.
*/
static InstanceId CreateFromAssetId(const AssetId& assetId);
/**
* Creates an InstanceId by hashing the provided name.
*/
static InstanceId CreateName(const char* name);
/**
* Creates an InstanceId by hashing the provided data.
*/
static InstanceId CreateData(const void* data, size_t dataSize);
/**
* Creates a random InstanceId.
*/
static InstanceId CreateRandom();
// Create a null id by default.
InstanceId() = default;
explicit InstanceId(const Uuid& guid);
explicit InstanceId(const Uuid& guid, uint32_t subId);
bool IsValid() const;
bool operator==(const InstanceId& rhs) const;
bool operator!=(const InstanceId& rhs) const;
template<class StringType>
StringType ToString() const;
template<class StringType>
void ToString(StringType& result) const;
Uuid m_guid = Uuid::CreateNull();
uint32_t m_subId = 0;
};
template<class StringType>
inline StringType InstanceId::ToString() const
{
StringType result;
ToString(result);
return result;
}
template<class StringType>
inline void InstanceId::ToString(StringType& result) const
{
result = StringType::format("%s:%x", m_guid.ToString<StringType>().c_str(), m_subId);
}
}
}
namespace AZStd
{
// hash specialization
template <>
struct hash<AZ::Data::InstanceId>
{
typedef AZ::Uuid argument_type;
typedef size_t result_type;
AZ_FORCE_INLINE size_t operator()(const AZ::Data::InstanceId& id) const
{
return id.m_guid.GetHash() ^ static_cast<size_t>(id.m_subId);
}
};
}
@@ -0,0 +1,411 @@
/*
* 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 <AtomCore/Serialization/Json/JsonUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/base.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/TextStreamWriters.h>
#include <AzCore/JSON/error/error.h>
#include <AzCore/JSON/error/en.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Utils/Utils.h>
namespace AZ
{
namespace JsonSerializationUtils
{
static const char* FileTypeTag = "Type";
static const char* FileType = "JsonSerialization";
static const char* VersionTag = "Version";
static const char* ClassNameTag = "ClassName";
static const char* ClassIdTag = "ClassId";
static const char* ClassDataTag = "ClassData";
AZ::Outcome<void, AZStd::string> WriteJsonString(const rapidjson::Document& document, AZStd::string& jsonText, WriteJsonSettings settings)
{
AZ::IO::ByteContainerStream<AZStd::string> stream{&jsonText};
return WriteJsonStream(document, stream, settings);
}
AZ::Outcome<void, AZStd::string> WriteJsonFile(const rapidjson::Document& document, AZStd::string_view filePath, WriteJsonSettings settings)
{
// Write the json into memory first and then write the file, rather than passing a file stream to rapidjson.
// This should avoid creating a large number of micro-writes to the file.
AZStd::string fileContent;
auto outcome = WriteJsonString(document, fileContent, settings);
if (!outcome.IsSuccess())
{
return outcome;
}
return AZ::Utils::WriteFile(fileContent, filePath);
}
AZ::Outcome<void, AZStd::string> WriteJsonStream(const rapidjson::Document& document, IO::GenericStream& stream, WriteJsonSettings settings)
{
AZ::IO::RapidJSONStreamWriter jsonStreamWriter(&stream);
rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter> writer(jsonStreamWriter);
if (settings.m_maxDecimalPlaces >= 0)
{
writer.SetMaxDecimalPlaces(settings.m_maxDecimalPlaces);
}
if (document.Accept(writer))
{
return AZ::Success();
}
else
{
return AZ::Failure(AZStd::string{"Json Writer failed"});
}
}
AZ::Outcome<void, AZStd::string> SaveObjectToStreamByType(const void* objectPtr, const Uuid& classId, IO::GenericStream& stream,
const void* defaultObjectPtr, const JsonSerializerSettings* settings)
{
if (!stream.CanWrite())
{
return AZ::Failure(AZStd::string("The GenericStream can't be written to"));
}
JsonSerializerSettings saveSettings;
if (settings)
{
saveSettings = *settings;
}
AZ::SerializeContext* serializeContext = saveSettings.m_serializeContext;
if (!serializeContext)
{
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (!serializeContext)
{
return AZ::Failure(AZStd::string::format("Need SerializeContext for saving"));
}
saveSettings.m_serializeContext = serializeContext;
}
rapidjson::Document jsonDocument;
jsonDocument.SetObject();
jsonDocument.AddMember(rapidjson::StringRef(FileTypeTag), rapidjson::StringRef(FileType), jsonDocument.GetAllocator());
rapidjson::Value serializedObject;
JsonSerializationResult::ResultCode jsonResult = JsonSerialization::Store(serializedObject, jsonDocument.GetAllocator(),
objectPtr, defaultObjectPtr, classId, saveSettings);
if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
{
return AZ::Failure(jsonResult.ToString(""));
}
const SerializeContext::ClassData* classData = serializeContext->FindClassData(classId);
jsonDocument.AddMember(rapidjson::StringRef(VersionTag), 1, jsonDocument.GetAllocator());
jsonDocument.AddMember(rapidjson::StringRef(ClassNameTag), rapidjson::StringRef(classData->m_name), jsonDocument.GetAllocator());
jsonDocument.AddMember(rapidjson::StringRef(ClassDataTag), AZStd::move(serializedObject), jsonDocument.GetAllocator());
AZ::IO::RapidJSONStreamWriter jsonStreamWriter(&stream);
rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter> writer(jsonStreamWriter);
bool jsonWriteResult = jsonDocument.Accept(writer);
if (!jsonWriteResult)
{
return AZ::Failure(AZStd::string::format("Unable to write class %s with json serialization format'",
classId.ToString<AZStd::string>().data()));
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> SaveObjectToFileByType(const void* classPtr, const Uuid& classId, const AZStd::string& filePath,
const void* defaultClassPtr, const JsonSerializerSettings* settings)
{
AZStd::vector<char> buffer;
buffer.reserve(1024);
AZ::IO::ByteContainerStream<AZStd::vector<char> > byteStream(&buffer);
auto saveResult = SaveObjectToStreamByType(classPtr, classId, byteStream, defaultClassPtr, settings);
if (saveResult.IsSuccess())
{
AZ::IO::FileIOStream outputFileStream;
if (!outputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath | AZ::IO::OpenMode::ModeText))
{
return AZ::Failure(AZStd::string::format("Error opening file '%s' for writing", filePath.c_str()));
}
outputFileStream.Write(buffer.size(), buffer.data());
}
return saveResult;
}
// Helper function to check whether the load outcome was success (for loading json serialization file)
bool WasLoadSuccess(JsonSerializationResult::Outcomes outcome)
{
return (outcome == JsonSerializationResult::Outcomes::Success
|| outcome == JsonSerializationResult::Outcomes::DefaultsUsed
|| outcome == JsonSerializationResult::Outcomes::PartialDefaults);
}
AZ::Outcome<void, AZStd::string> PrepareDeserializerSettings(const JsonDeserializerSettings* inputSettings, JsonDeserializerSettings& returnSettings
, AZStd::string& deserializeError)
{
if (inputSettings)
{
returnSettings = *inputSettings;
}
if (!returnSettings.m_serializeContext)
{
AZ::ComponentApplicationBus::BroadcastResult(returnSettings.m_serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (!returnSettings.m_serializeContext)
{
return AZ::Failure(AZStd::string("Need SerializeContext for loading"));
}
}
// Report unused data field as error by default
auto reporting = returnSettings.m_reporting;
auto issueReportingCallback = [&deserializeError, reporting](AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view target) -> JsonSerializationResult::ResultCode
{
using namespace JsonSerializationResult;
if (!WasLoadSuccess(result.GetOutcome()))
{
// This if is a hack around fault in the JSON serialization system
// Jira: https://jira.agscollab.com/browse/LY-106587
if (message != "No part of the string could be interpreted as a uuid.")
{
deserializeError.append(message);
deserializeError.append(AZStd::string::format(" '%s' \n", target.data()));
}
}
if (reporting)
{
result = reporting(message, result, target);
}
return result;
};
returnSettings.m_reporting = issueReportingCallback;
return AZ::Success();
}
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonString(AZStd::string_view jsonText)
{
rapidjson::Document jsonDocument;
jsonDocument.Parse<rapidjson::kParseCommentsFlag>(jsonText.data(), jsonText.size());
if (jsonDocument.HasParseError())
{
size_t lineNumber = 1;
const size_t errorOffset = jsonDocument.GetErrorOffset();
for (size_t searchOffset = jsonText.find('\n');
searchOffset < errorOffset && searchOffset < AZStd::string::npos;
searchOffset = jsonText.find('\n', searchOffset + 1))
{
lineNumber++;
}
return AZ::Failure(AZStd::string::format("JSON parse error at line %zu: %s", lineNumber, rapidjson::GetParseError_En(jsonDocument.GetParseError())));
}
else
{
return AZ::Success(AZStd::move(jsonDocument));
}
}
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonStream(IO::GenericStream& stream)
{
IO::SizeType length = stream.GetLength();
if (length > AZ::Utils::DefaultMaxFileSize)
{
return AZ::Failure(AZStd::string{ "Data is too large." });
}
AZStd::vector<char> memoryBuffer;
memoryBuffer.resize_no_construct(static_cast<AZStd::vector<char>::size_type>(static_cast<AZStd::vector<char>::size_type>(length) + 1));
IO::SizeType bytesRead = stream.Read(length, memoryBuffer.data());
if (bytesRead != length)
{
return AZ::Failure(AZStd::string{"Cannot to read input stream."});
}
memoryBuffer.back() = 0;
return ReadJsonString(AZStd::string_view{memoryBuffer.data(), memoryBuffer.size()});
}
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonFile(AZStd::string_view filePath)
{
// Read into memory first and then parse the json, rather than passing a file stream to rapidjson.
// This should avoid creating a large number of micro-reads from the file.
auto readResult = AZ::Utils::ReadFile<AZStd::string>(filePath);
if(!readResult.IsSuccess())
{
return AZ::Failure(readResult.GetError());
}
AZStd::string jsonContent = readResult.TakeValue();
auto result = ReadJsonString(jsonContent);
if (!result.IsSuccess())
{
return AZ::Failure(AZStd::string::format("Failed to load '%.*s'. %s", AZ_STRING_ARG(filePath), result.GetError().c_str()));
}
else
{
return result;
}
}
// Helper function to validate the JSON is structured with the standard header for a generic class
AZ::Outcome<void, AZStd::string> ValidateJsonClassHeader(const rapidjson::Document& jsonDocument)
{
auto typeItr = jsonDocument.FindMember(FileTypeTag);
if (typeItr == jsonDocument.MemberEnd() || !typeItr->value.IsString() || azstricmp(typeItr->value.GetString(), FileType) != 0)
{
return AZ::Failure(AZStd::string::format("Not a valid JsonSerialization file"));
}
auto nameItr = jsonDocument.FindMember(ClassNameTag);
if (nameItr == jsonDocument.MemberEnd() || !nameItr->value.IsString())
{
return AZ::Failure(AZStd::string::format("File should contain ClassName"));
}
auto dataItr = jsonDocument.FindMember(ClassDataTag);
// data can be empty but it should be an object
if (dataItr != jsonDocument.MemberEnd() && !dataItr->value.IsObject())
{
return AZ::Failure(AZStd::string::format("ClassData should be an object"));
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> LoadObjectFromStreamByType(void* objectToLoad, const Uuid& classId, IO::GenericStream& stream,
const JsonDeserializerSettings* settings)
{
JsonDeserializerSettings loadSettings;
AZStd::string deserializeErrors;
auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors);
if (!prepare.IsSuccess())
{
return AZ::Failure(prepare.GetError());
}
auto parseResult = ReadJsonStream(stream);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
const rapidjson::Document& jsonDocument = parseResult.GetValue();
auto validateResult = ValidateJsonClassHeader(jsonDocument);
if (!validateResult.IsSuccess())
{
return AZ::Failure(validateResult.GetError());
}
const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString();
// validate class name
auto classData = loadSettings.m_serializeContext->FindClassData(classId);
if (azstricmp(classData->m_name, className) != 0)
{
return AZ::Failure(AZStd::string::format("Try to load class %s from class %s data", classData->m_name, className));
}
JsonSerializationResult::ResultCode result = JsonSerialization::Load(objectToLoad, classId, jsonDocument.FindMember(ClassDataTag)->value, loadSettings);
if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty())
{
return AZ::Failure(deserializeErrors);
}
return AZ::Success();
}
AZ::Outcome<AZStd::any, AZStd::string> LoadAnyObjectFromStream(IO::GenericStream& stream, const JsonDeserializerSettings* settings)
{
JsonDeserializerSettings loadSettings;
AZStd::string deserializeErrors;
auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors);
if (!prepare.IsSuccess())
{
return AZ::Failure(prepare.GetError());
}
auto parseResult = ReadJsonStream(stream);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
const rapidjson::Document& jsonDocument = parseResult.GetValue();
auto validateResult = ValidateJsonClassHeader(jsonDocument);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString();
AZStd::vector<AZ::Uuid> ids = loadSettings.m_serializeContext->FindClassId(AZ::Crc32(className));
// Load with first found class id
if (ids.size() >= 1)
{
auto classId = ids[0];
AZStd::any anyData = loadSettings.m_serializeContext->CreateAny(classId);
auto& objectData = jsonDocument.FindMember(ClassDataTag)->value;
JsonSerializationResult::ResultCode result = JsonSerialization::Load(AZStd::any_cast<void>(&anyData), classId, objectData, loadSettings);
if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty())
{
return AZ::Failure(deserializeErrors);
}
return AZ::Success(anyData);
}
return AZ::Failure(AZStd::string::format("Can't find serialize context for class %s", className));
}
AZ::Outcome<AZStd::any, AZStd::string> LoadAnyObjectFromFile(const AZStd::string& filePath, const JsonDeserializerSettings* settings)
{
AZ::IO::FileIOStream inputFileStream;
if (!inputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeText))
{
return AZ::Failure(AZStd::string::format("Error opening file '%s' for reading", filePath.c_str()));
}
return LoadAnyObjectFromStream(inputFileStream, settings);
}
} // namespace JsonSerializationUtils
} // 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.
*
*/
#pragma once
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/string/string.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/JSON/document.h>
#include <AzCore/JSON/writer.h>
#include <AzCore/Serialization/Json/JsonSerializationResult.h>
namespace AZ
{
namespace IO
{
class GenericStream;
}
struct JsonSerializerSettings;
struct JsonDeserializerSettings;
// Utility functions which use json serializer/deserializer to save/load object to file/stream
namespace JsonSerializationUtils
{
struct WriteJsonSettings
{
int m_maxDecimalPlaces = -1; // -1 means use default
};
///////////////////////////////////////////////////////////////////////////////////
// Save functions
//! Save a json document to text. Otherwise returns a failure with error message.
AZ::Outcome<void, AZStd::string> WriteJsonString(const rapidjson::Document& document, AZStd::string& jsonText, WriteJsonSettings settings = WriteJsonSettings{});
//! Save a json document to a file. Otherwise returns a failure with error message.
AZ::Outcome<void, AZStd::string> WriteJsonFile(const rapidjson::Document& document, AZStd::string_view filePath, WriteJsonSettings settings = WriteJsonSettings{});
//! Save a json document to a stream. Otherwise returns a failure with error message.
AZ::Outcome<void, AZStd::string> WriteJsonStream(const rapidjson::Document& document, IO::GenericStream& stream, WriteJsonSettings settings = WriteJsonSettings{});
AZ::Outcome<void, AZStd::string> SaveObjectToStreamByType(const void* objectPtr, const Uuid& objectType, IO::GenericStream& stream,
const void* defaultObjectPtr = nullptr, const JsonSerializerSettings* settings = nullptr);
AZ::Outcome<void, AZStd::string> SaveObjectToFileByType(const void* objectPtr, const Uuid& objectType, const AZStd::string& filePath,
const void* defaultObjectPtr = nullptr, const JsonSerializerSettings* settings = nullptr);
template <typename ObjectType>
AZ::Outcome<void, AZStd::string> SaveObjectToStream(const ObjectType* classPtr, IO::GenericStream& stream,
const ObjectType* defaultClassPtr = nullptr, const JsonSerializerSettings* settings = nullptr)
{
return SaveObjectToStreamByType(classPtr, AzTypeInfo<ObjectType>::Uuid(), stream, defaultClassPtr, settings);
}
template <typename ObjectType>
AZ::Outcome<void, AZStd::string> SaveObjectToFile(const ObjectType* classPtr, const AZStd::string& filePath,
const ObjectType* defaultClassPtr = nullptr, const JsonSerializerSettings* settings = nullptr)
{
return SaveObjectToFileByType(classPtr, AzTypeInfo<ObjectType>::Uuid(), filePath, defaultClassPtr, settings);
}
///////////////////////////////////////////////////////////////////////////////////
// Load functions
//! Parse json text. Returns a failure with error message if the content is not valid JSON.
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonString(AZStd::string_view jsonText);
//! Parse a json file. Returns a failure with error message if the content is not valid JSON.
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonFile(AZStd::string_view filePath);
//! Parse a json stream. Returns a failure with error message if the content is not valid JSON.
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonStream(IO::GenericStream& stream);
//! Load object with known class type
AZ::Outcome<void, AZStd::string> LoadObjectFromStreamByType(void* objectToLoad, const Uuid& objectType, IO::GenericStream& stream,
const JsonDeserializerSettings* settings = nullptr);
template <typename ObjectType>
AZ::Outcome<void, AZStd::string> LoadObjectFromStream(ObjectType& objectToLoad, IO::GenericStream& stream, const JsonDeserializerSettings* settings = nullptr)
{
return LoadObjectFromStreamByType(&objectToLoad, AzTypeInfo<ObjectType>::Uuid(), stream, settings);
}
template <typename ObjectType>
AZ::Outcome<void, AZStd::string> LoadObjectFromFile(ObjectType& objectToLoad, const AZStd::string& filePath, const JsonDeserializerSettings* settings = nullptr)
{
AZ::IO::FileIOStream inputFileStream;
if (!inputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeText))
{
return AZ::Failure(AZStd::string::format("Error opening file '%s' for reading", filePath.c_str()));
}
return LoadObjectFromStream(objectToLoad, inputFileStream, settings);
}
//! Load any object
AZ::Outcome<AZStd::any, AZStd::string> LoadAnyObjectFromStream(IO::GenericStream& stream, const JsonDeserializerSettings* settings = nullptr);
AZ::Outcome<AZStd::any, AZStd::string> LoadAnyObjectFromFile(const AZStd::string& filePath,const JsonDeserializerSettings* settings = nullptr);
} // namespace JsonSerializationUtils
} // namespace Az
@@ -0,0 +1,27 @@
#
# 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.
#
set(FILES
Instance/Instance.h
Instance/InstanceId.h
Instance/InstanceId.cpp
Instance/InstanceData.h
Instance/InstanceData.cpp
Instance/InstanceDatabase.h
Serialization/Json/JsonUtils.h
Serialization/Json/JsonUtils.cpp
std/containers/array_view.h
std/containers/fixed_vector_set.h
std/containers/lru_cache.h
std/containers/vector_set.h
std/containers/vector_set_base.h
std/parallel/concurrency_checker.h
)
@@ -0,0 +1,160 @@
/*
* 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/vector.h>
#include <AzCore/std/containers/fixed_vector.h>
#include <AzCore/std/containers/array.h>
namespace AZStd
{
/**
* Immutable wrapper for an array of data. It does not maintain storage for the data,
* but just holds pointers to mark the beginning and end of the array. It can be
* conveniently constructed from a variety of other container types like array,
* vector, and fixed_vector.
*
* Example:
* Given "void Func(AZStd::array_view<int> a) {...}" you can call...
* - Func({1,2,3});
* - AZStd::array<int,3> a = {1,2,3};
* Func(a);
* - AZStd::vector<int> v = {1,2,3};
* Func(v);
* - AZStd::fixed_vector<int,10> fv = {1,2,3};
* Func(fv);
*
* Since the array_view does not copy and store any data, it is only valid as long as the data used to create it is valid.
*/
template <class Element>
class array_view final
{
public:
using value_type = Element;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using size_type = AZStd::size_t;
using difference_type = AZStd::ptrdiff_t;
using iterator = const value_type*;
using const_iterator = const value_type*;
using reverse_iterator = AZStd::reverse_iterator<iterator>;
using const_reverse_iterator = AZStd::reverse_iterator<const_iterator>;
array_view()
: m_begin(nullptr)
, m_end(nullptr)
{ }
~array_view() = default;
array_view(const_pointer s, size_type length)
: m_begin(s)
, m_end(m_begin + length)
{
if (length == 0) erase();
}
array_view(const_pointer first, const_pointer last)
: m_begin(first)
, m_end(last)
{ }
// We explicitly delete this constructor because it's too easy to accidentally
// create an array_view to just the first element instead of an entire array.
array_view(const_pointer s) = delete;
template<AZStd::size_t N>
array_view(const AZStd::array<value_type, N>& data)
: m_begin(data.data())
, m_end(m_begin + data.size())
{ }
array_view(const AZStd::vector<value_type>& data)
: m_begin(data.data())
, m_end(m_begin + data.size())
{ }
template<AZStd::size_t N>
array_view(const AZStd::fixed_vector<value_type, N>& data)
: m_begin(data.data())
, m_end(m_begin + data.size())
{ }
array_view(const array_view&) = default;
array_view(array_view&& other)
: array_view(other.m_begin, other.m_end)
{
#if AZ_DEBUG_BUILD // Clearing the original pointers isn't necessary, but is good for debugging
other.m_begin = nullptr;
other.m_end = nullptr;
#endif
}
array_view& operator=(const array_view& other) = default;
array_view& operator=(array_view&& other)
{
m_begin = other.m_begin;
m_end = other.m_end;
#if AZ_DEBUG_BUILD // Clearing the original pointers isn't necessary, but is good for debugging
other.m_begin = nullptr;
other.m_end = nullptr;
#endif
return *this;
}
size_type size() const { return m_end - m_begin; }
bool empty() const { return m_end == m_begin; }
const_pointer data() const { return m_begin; }
const_reference operator[](size_type index) const
{
AZ_Assert(index < size(), "index value is out of range");
return m_begin[index];
}
void erase() { m_begin = m_end = nullptr; }
iterator begin() const { return m_begin; }
iterator end() const { return m_end; }
const_iterator cbegin() const { return m_begin; }
const_iterator cend() const { return m_end; }
reverse_iterator rbegin() const { return reverse_iterator(m_end); }
reverse_iterator rend() const { return reverse_iterator(m_begin); }
const_reverse_iterator crbegin() const { return const_reverse_iterator(cend()); }
const_reverse_iterator crend() const { return const_reverse_iterator(cbegin()); }
friend bool operator==(array_view lhs, array_view rhs)
{
return lhs.m_begin == rhs.m_begin && lhs.m_end == rhs.m_end;
}
friend bool operator!=(array_view lhs, array_view rhs) { return !(lhs == rhs); }
friend bool operator< (array_view lhs, array_view rhs) { return lhs.m_begin < rhs.m_begin || lhs.m_begin == rhs.m_begin && lhs.m_end < rhs.m_end; }
friend bool operator> (array_view lhs, array_view rhs) { return lhs.m_begin > rhs.m_begin || lhs.m_begin == rhs.m_begin && lhs.m_end > rhs.m_end; }
friend bool operator<=(array_view lhs, array_view rhs) { return lhs == rhs || lhs < rhs; }
friend bool operator>=(array_view lhs, array_view rhs) { return lhs == rhs || lhs > rhs; }
private:
const_pointer m_begin;
const_pointer m_end;
};
} // namespace AZStd
@@ -0,0 +1,38 @@
/*
* 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 <AtomCore/std/containers/vector_set_base.h>
namespace AZStd
{
template <typename Key, size_t Capacity, typename Compare = AZStd::less<Key>>
class fixed_vector_set
: public vector_set_base<Key, Compare, AZStd::fixed_vector<Key, Capacity>>
{
using base_type = vector_set_base<Key, Compare, AZStd::fixed_vector<Key, Capacity>>;
using this_type = fixed_vector_set<Key, Capacity, Compare>;
public:
explicit fixed_vector_set() = default;
template <typename InputIterator>
fixed_vector_set(InputIterator first, InputIterator last)
{
base_type::assign(first, last);
}
fixed_vector_set(const AZStd::initializer_list<Key> list)
{
base_type::assign(list.begin(), list.end());
}
};
}
@@ -0,0 +1,161 @@
/*
* 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/unordered_map.h>
#include <AzCore/std/containers/list.h>
namespace AZStd
{
/**
* This class is a simple map which keeps an least-recently-used list of elements. If the capacity
* of the map is exceeded with a new insertion, the oldest element is evicted.
*/
template<typename KeyType, typename MappedType, class Hasher = AZStd::hash<KeyType>, class EqualKey = AZStd::equal_to<KeyType>, class Allocator = AZStd::allocator>
class lru_cache
{
public:
typedef KeyType key_type;
typedef MappedType mapped_type;
typedef pair<KeyType, MappedType> value_type;
typedef list<value_type, Allocator> list_type;
typedef typename list_type::iterator iterator;
typedef typename list_type::const_iterator const_iterator;
typedef typename list_type::reverse_iterator reverse_iterator;
typedef typename list_type::const_reverse_iterator const_reverse_iterator;
typedef pair<iterator, bool> pair_iter_bool;
lru_cache() = default;
explicit lru_cache(size_t capacity) : m_capacity(capacity) {}
/**
* Inserts \rev value associated with \ref key. If the key already exists, replaces the existing value. The entry
* is promoted to the most-recently-used.
*/
pair_iter_bool insert(const KeyType& key, const MappedType& value)
{
return insert_impl(key, value);
}
/**
* Constructs a new \ref MappedType with the provided arguments, associated with \ref key. If the key already exists,
* replaces the existing mapped type. The entry is promoted to the most-recently-used.
*/
template <typename... Args>
pair_iter_bool emplace(const KeyType& key, Args&&... arguments)
{
return insert_impl(key, AZStd::forward<Args>(arguments)...);
}
/**
* Returns the mapped type based on the key. The entry is promoted to be the most recently used.
*/
iterator get(const KeyType& key)
{
auto it = m_cacheMap.find(key);
if (it != m_cacheMap.end())
{
m_cacheList.splice(m_cacheList.begin(), m_cacheList, it->second);
return begin();
}
return end();
}
/**
* Returns whether the key exists in the container. Does *not* promote the entry.
*/
bool exists(const key_type& key) const
{
return m_cacheMap.find(key) != m_cacheMap.end();
}
/**
* Adjusts the capacity of the container. If the new capacity is smaller than the existing size, elements
* will be evicted until the capacity is reached.
*/
void set_capacity(size_t capacity)
{
m_capacity = capacity;
trim_to_fit();
}
void clear()
{
m_cacheMap.clear();
m_cacheList.clear();
}
size_t capacity() const { return m_capacity; }
size_t size() const { return m_cacheMap.size(); }
bool empty() const { return m_cacheMap.empty(); }
iterator begin() { return m_cacheList.begin(); }
const_iterator begin() const { return m_cacheList.begin(); }
iterator end() { return m_cacheList.end(); }
const_iterator end() const { return m_cacheList.end(); }
reverse_iterator rbegin() { return m_cacheList.rbegin(); }
const_reverse_iterator rbegin() const { return m_cacheList.rbegin(); }
reverse_iterator rend() { return m_cacheList.rend(); }
const_reverse_iterator rend() const { return m_cacheList.rend(); }
private:
template <typename... Args>
pair_iter_bool insert_impl(const key_type& key, Args&&... arguments)
{
AZSTD_CONTAINER_ASSERT(m_capacity != 0, "Attempting to insert an element into cache with no capacity.");
auto it = m_cacheMap.find(key);
m_cacheList.emplace_front(key, AZStd::forward<Args>(arguments)...);
const bool keyExisted = it != m_cacheMap.end();
if (keyExisted)
{
m_cacheList.erase(it->second);
m_cacheMap.erase(it);
}
m_cacheMap.emplace(key, m_cacheList.begin());
trim_to_fit();
return pair_iter_bool(begin(), keyExisted);
}
void trim_to_fit()
{
while (m_cacheMap.size() > m_capacity)
{
auto last = m_cacheList.end();
last--;
m_cacheMap.erase(last->first);
m_cacheList.pop_back();
}
}
/**
* The map holds an iterator into the list. The list is ordered by most-to-least recently used, and holds the
* key / value pair values. This allows us to expose the list iterator externally for easy traversal. The downside
* is that this does require one more indirection at look-up time, however the list iterator is going to be promoted
* to the front of the list on access anyway.
*/
typedef AZStd::unordered_map<key_type, iterator, Hasher, EqualKey, Allocator> map_type;
/// Contains the flat LRU list, sorted from most used to least recently used.
list_type m_cacheList;
/// Stores the key -> list iterator association.
map_type m_cacheMap;
/// Old elements will be evicted if the capacity is exceeded.
size_t m_capacity = 0;
};
} // namespace AZStd
@@ -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.
*
*/
#pragma once
#include <AtomCore/std/containers/vector_set_base.h>
#include <AzCore/std/containers/vector.h>
namespace AZStd
{
template <typename Key, typename Compare = AZStd::less<Key>, typename Allocator = AZStd::allocator>
class vector_set
: public vector_set_base<Key, Compare, AZStd::vector<Key, Allocator>>
{
using base_type = vector_set_base<Key, Compare, AZStd::vector<Key, Allocator>>;
using this_type = vector_set<Key, Compare, Allocator>;
public:
using allocator_type = Allocator;
AZ_FORCE_INLINE vector_set() = default;
AZ_FORCE_INLINE vector_set(const allocator_type& allocator)
: base_type(allocator)
{}
template <typename InputIterator>
vector_set(InputIterator first, InputIterator last)
: base_type(allocator_type())
{
base_type::assign(first, last);
}
template <typename InputIterator>
vector_set(InputIterator first, InputIterator last, const allocator_type& allocator)
: base_type(allocator)
{
base_type::assign(first, last);
}
vector_set(const AZStd::initializer_list<Key> list)
: base_type(allocator_type())
{
base_type::assign(list.begin(), list.end());
}
vector_set(const AZStd::initializer_list<Key> list, const allocator_type& allocator)
: base_type(allocator)
{
base_type::assign(list.begin(), list.end());
}
void reserve(size_t capacity)
{
base_type::m_container.reserve(capacity);
}
void shrink_to_fit()
{
base_type::m_container.shrink_to_fit();
}
allocator_type& get_allocator() { return base_type::m_allocator; }
const allocator_type& get_allocator() const { return base_type::m_allocator; }
void set_allocator(const allocator_type& allocator)
{
base_type::m_container.set_allocator(allocator);
}
};
}
@@ -0,0 +1,234 @@
/*
* 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/algorithm.h>
#include <AzCore/std/sort.h>
namespace AZStd
{
/**
* This class is an ordered set implementation which uses a sorted vector. Insertions / removals
* are slower, but searches and iteration are very cache friendly. This container wraps an
* AZStd::vector by default, but can wrap any random access container adhering to the interface.
* See @ref fixed_vector_set for a version that does not perform any allocations. The iterator
* invalidation behavior is directly inherited from the underlying container.
*/
template <typename Key, typename Compare, typename RandomAccessContainer>
class vector_set_base
{
using this_type = vector_set_base<Key, Compare, RandomAccessContainer>;
public:
using pointer = typename RandomAccessContainer::pointer;
using reference = typename RandomAccessContainer::reference;
using iterator = typename RandomAccessContainer::iterator;
using reverse_iterator = typename RandomAccessContainer::reverse_iterator;
using const_pointer = typename RandomAccessContainer::const_pointer;
using const_reference = typename RandomAccessContainer::const_reference;
using const_iterator = typename RandomAccessContainer::const_iterator;
using const_reverse_iterator = typename RandomAccessContainer::const_reverse_iterator;
using size_type = typename RandomAccessContainer::size_type;
using difference_type = typename RandomAccessContainer::difference_type;
using key_type = Key;
using pair_iter_bool = AZStd::pair<iterator, bool>;
template <typename ... Args>
AZ_FORCE_INLINE vector_set_base(Args&&... arguments)
: m_container{AZStd::forward<Args>(arguments)...}
{}
iterator begin() { return m_container.begin(); }
const_iterator begin() const { return m_container.begin(); }
iterator end() { return m_container.end(); }
const_iterator end() const { return m_container.end(); }
reverse_iterator rbegin() { return m_container.rbegin(); }
const_reverse_iterator rbegin() const { return m_container.rbegin(); }
reverse_iterator rend() { return m_container.rend(); }
const_reverse_iterator rend() const { return m_container.rend(); }
reference front() { return m_container.front(); }
const_reference front() const { return m_container.front(); }
reference back() { return m_container.back(); }
const_reference back() const { return m_container.back(); }
size_type size() const { return m_container.size(); }
size_type capacity() const { return m_container.capacity(); }
bool empty() const { return m_container.empty(); }
pointer data() { return m_container.data(); }
const_pointer data() const { return m_container.data(); }
template <typename ... Args>
pair_iter_bool emplace(Args&&... arguments)
{
return insert(Key(AZStd::forward<Args>(arguments) ...));
}
pair_iter_bool insert(key_type&& key)
{
Compare comp;
iterator first = lower_bound(key);
if (first != m_container.end())
{
if (comp(key, *first))
{
return pair_iter_bool(m_container.insert(first, AZStd::move(key)), true);
}
else
{
return pair_iter_bool(first, false);
}
}
return pair_iter_bool(m_container.insert(first, AZStd::move(key)), true);
}
pair_iter_bool insert(const_reference key)
{
Compare comp;
iterator first = lower_bound(key);
if (first != m_container.end())
{
if (comp(key, *first))
{
return pair_iter_bool(m_container.insert(first, key), true);
}
else
{
return pair_iter_bool(first, false);
}
}
return pair_iter_bool(m_container.insert(first, key), true);
}
template <typename InputIterator>
void assign(InputIterator first, InputIterator last)
{
m_container.assign(first, last);
// Sort the whole container.
AZStd::sort(m_container.begin(), m_container.end(), Compare());
// De-duplicate entries and resize.
iterator newEnd = AZStd::unique(m_container.begin(), m_container.end());
m_container.erase(newEnd, m_container.end());
}
template <typename InputIterator>
void insert(InputIterator first, InputIterator last)
{
for (; first != last; ++first)
{
insert(*first);
}
}
iterator lower_bound(const key_type& key)
{
return AZStd::lower_bound(m_container.begin(), m_container.end(), key, Compare());
}
const_iterator lower_bound(const key_type& key) const
{
return AZStd::lower_bound(m_container.begin(), m_container.end(), key, Compare());
}
iterator upper_bound(const key_type& key)
{
return AZStd::upper_bound(m_container.begin(), m_container.end(), key, Compare());
}
const_iterator upper_bound(const key_type& key) const
{
return AZStd::upper_bound(m_container.begin(), m_container.end(), key, Compare());
}
iterator find(const key_type& key)
{
Compare comp;
iterator first = lower_bound(key);
if (first != m_container.end() && !comp(key, *first))
{
return first;
}
return m_container.end();
}
const_iterator find(const key_type& key) const
{
Compare comp;
const_iterator first = lower_bound(key);
if (first != m_container.end() && !comp(key, *first))
{
return first;
}
return m_container.end();
}
size_t erase(const key_type& key)
{
Compare comp;
iterator first = lower_bound(key);
if (first != m_container.end() && !comp(key, *first))
{
m_container.erase(first);
return 1;
}
return 0;
}
reference at(size_type position)
{
return m_container.at(position);
}
const_reference at(size_type position) const
{
return m_container.at(position);
}
reference operator[](size_type position)
{
return m_container[position];
}
const_reference operator[](size_type position) const
{
return m_container[position];
}
void swap(this_type& rhs)
{
m_container.swap(rhs.m_container);
}
void clear()
{
m_container.clear();
}
friend bool operator==(const this_type& lhs, const this_type& rhs)
{
return lhs.m_container == rhs.m_container;
}
friend bool operator!=(const this_type& lhs, const this_type& rhs)
{
return lhs.m_container != rhs.m_container;
}
protected:
RandomAccessContainer m_container;
};
}
@@ -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.
*
*/
#pragma once
#include <AzCore/std/parallel/atomic.h>
#include <AzCore/Debug/Trace.h>
#if defined(AZ_PROFILE_BUILD) || defined(AZ_DEBUG_BUILD)
#define AZ_CONCURRENCY_CHECKER_ENABLED
#endif
namespace AZStd
{
//! Simple class for verifying that no concurrent access is occuring.
//! This is *not* a synchronization primitive, and is intended simply for checking that no concurrency issues exist.
//! It will be compiled out in release builds.
//! Use concurrency_checker like a mutex (i.e. call soft_lock() and soft_unlock() around all instances of your data access).
//! It will assert if there are multiple threads accessing the locked code/data at the same time.
//! Expected use case is for defensive programming: when you do not expect any concurrent access within a system,
//! but want to verify that it stays that way in the future, without incurring the overhead of a mutex.
class concurrency_checker
{
public:
AZ_FORCE_INLINE void soft_lock()
{
#ifdef AZ_CONCURRENCY_CHECKER_ENABLED
uint32_t count = ++m_concurrencyCounter;
AZ_Assert(count == 1, "Concurrency check failed. Multiple threads are trying to access data at the same time, or there is a lock/unlock mismatch.");
#endif
}
AZ_FORCE_INLINE void soft_unlock()
{
#ifdef AZ_CONCURRENCY_CHECKER_ENABLED
uint32_t count = --m_concurrencyCounter;
AZ_Assert(count == 0, "Concurrency check failed. If the assert in soft_lock() has not triggered already, then most likely there is a lock/unlock mismatch.");
#endif
}
private:
#ifdef AZ_CONCURRENCY_CHECKER_ENABLED
AZStd::atomic_uint32_t m_concurrencyCounter = 0;
#endif
};
//! Simple scope wrapper for concurrency check (so you don't have to manually call soft_lock() and soft_unlock())
class concurrency_check_scope
{
public:
AZ_FORCE_INLINE explicit concurrency_check_scope(concurrency_checker& checker)
: m_checker(checker)
{
m_checker.soft_lock();
}
AZ_FORCE_INLINE ~concurrency_check_scope()
{
m_checker.soft_unlock();
}
private:
AZ_FORCE_INLINE concurrency_check_scope() = delete;
AZ_FORCE_INLINE concurrency_check_scope(concurrency_check_scope const &) = delete;
concurrency_checker& m_checker;
};
} //namespace AZStd
+49
View File
@@ -0,0 +1,49 @@
#
# 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.
#
ly_add_target(
NAME AtomCore STATIC
NAMESPACE AZ
FILES_CMAKE
AtomCore/atomcore_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
PUBLIC
3rdParty::RapidJSON
)
################################################################################
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME AtomCore.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE AZ
FILES_CMAKE
Tests/atomcore_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AtomCore
)
ly_add_googletest(
NAME AZ::AtomCore.Tests
)
endif()
+307
View File
@@ -0,0 +1,307 @@
/*
* 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 <AtomCore/std/containers/array_view.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
using namespace AZStd;
class ArrayView : public AllocatorsTestFixture
{
protected:
template<typename T>
void ExpectEqual(initializer_list<T> expectedValues, array_view<T> arrayView)
{
EXPECT_EQ(false, arrayView.empty());
EXPECT_EQ(expectedValues.size(), arrayView.size());
typename AZStd::vector<T>::const_iterator iterator = arrayView.begin();
for (int i = 0; i < expectedValues.size(); ++i, ++iterator)
{
EXPECT_EQ(expectedValues.begin()[i], arrayView[i]);
EXPECT_EQ(expectedValues.begin()[i], *iterator);
}
EXPECT_EQ(iterator, arrayView.end());
}
};
TEST_F(ArrayView, DefaultConstructor)
{
array_view<bool> defaultView;
EXPECT_EQ(nullptr, defaultView.begin());
EXPECT_EQ(nullptr, defaultView.end());
EXPECT_EQ(0, defaultView.size());
EXPECT_EQ(true, defaultView.empty());
}
TEST_F(ArrayView, PointerConstructor1)
{
int originalValues[4] = { 2,3,4,5 };
array_view<int> view(originalValues, AZ_ARRAY_SIZE(originalValues));
ExpectEqual({ 2,3,4,5 }, view);
EXPECT_EQ(originalValues, view.begin());
EXPECT_EQ(&originalValues[4], view.end());
}
TEST_F(ArrayView, PointerConstructor2)
{
int originalValues[3] = { 6,7,8 };
array_view<int> view(originalValues, &originalValues[3]);
ExpectEqual({ 6,7,8 }, view);
EXPECT_EQ(originalValues, view.begin());
EXPECT_EQ(&originalValues[3], view.end());
}
TEST_F(ArrayView, ArrayConstructor)
{
array<int, 4> originalValues = { 9,10,11,12 };
array_view<int> view(originalValues);
ExpectEqual({ 9,10,11,12 }, view);
EXPECT_EQ(originalValues.begin(), view.begin());
EXPECT_EQ(originalValues.end(), view.end());
}
TEST_F(ArrayView, VectorConstructor)
{
vector<int> originalValues = { 13,14,15,16,17,18 };
array_view<int> view(originalValues);
ExpectEqual({ 13,14,15,16,17,18 }, view);
EXPECT_EQ(originalValues.begin(), view.begin());
EXPECT_EQ(originalValues.end(), view.end());
}
TEST_F(ArrayView, FixedVectorConstructor)
{
fixed_vector<int, 10> originalValues = { 17,18,19 }; // Note that even though the fixed_vector capacity is 10, it's size is 3, so the view size will be 3 as well
array_view<int> view(originalValues);
ExpectEqual({ 17,18,19 }, view);
EXPECT_EQ(originalValues.begin(), view.begin());
EXPECT_EQ(originalValues.end(), view.end());
}
TEST_F(ArrayView, CopyConstructor)
{
fixed_vector<int, 2> originalValues = { 27,28 };
array_view<int> view1(originalValues);
array_view<int> view2(view1);
ExpectEqual({ 27,28 }, view2);
EXPECT_EQ(view1.begin(), view2.begin());
EXPECT_EQ(view1.end(), view2.end());
}
TEST_F(ArrayView, MoveConstructor)
{
int originalValues[] = { 29,30,31 };
array_view<int> view1(originalValues, AZ_ARRAY_SIZE(originalValues));
array_view<int> view2(AZStd::move(view1));
ExpectEqual({ 29,30,31 }, view2);
EXPECT_EQ(originalValues, view2.begin());
EXPECT_EQ(&originalValues[3], view2.end());
// This isn't strictly necessary but is a good way to make sure the move
// constructor actually exists and it itn't just calling the copy constructor
#if AZ_DEBUG_BUILD // The pointers are only cleared in debug
EXPECT_EQ(nullptr, view1.begin());
EXPECT_EQ(nullptr, view1.end());
#endif
}
TEST_F(ArrayView, AssignmentOperator)
{
fixed_vector<int, 4> originalValues = { 32,33,34,35 };
array_view<int> view1(originalValues);
array_view<int> view2;
view2 = view1;
ExpectEqual({ 32,33,34,35 }, view2);
EXPECT_EQ(view1.begin(), view2.begin());
EXPECT_EQ(view1.end(), view2.end());
}
TEST_F(ArrayView, MoveAssignmentOperator)
{
int originalValues[] = { 36,37,38,39,40 };
array_view<int> view1(originalValues, AZ_ARRAY_SIZE(originalValues));
array_view<int> view2;
view2 = AZStd::move(view1);
ExpectEqual({ 36,37,38,39,40 }, view2);
EXPECT_EQ(originalValues, view2.begin());
EXPECT_EQ(&originalValues[5], view2.end());
// This isn't strictly necessary but is a good way to make sure the move
// assignment operator actually exists and it itn't just calling the norm
// assignment operator
#if AZ_DEBUG_BUILD // The pointers are only cleared in debug
EXPECT_EQ(nullptr, view1.begin());
EXPECT_EQ(nullptr, view1.end());
#endif
}
TEST_F(ArrayView, Erase)
{
fixed_vector<int, 4> originalValues = { 1,2,3,4 };
array_view<int> view(originalValues);
view.erase();
EXPECT_EQ(nullptr, view.begin());
EXPECT_EQ(nullptr, view.end());
EXPECT_EQ(0, view.size());
EXPECT_EQ(true, view.empty());
}
TEST_F(ArrayView, BeginAndEnd)
{
fixed_vector<int, 4> originalValues = { 1,2,3,4 };
array_view<int> view(originalValues);
EXPECT_EQ(1, view.begin()[0]);
EXPECT_EQ(4, view.end()[-1]);
EXPECT_EQ(1, view.cbegin()[0]);
EXPECT_EQ(4, view.cend()[-1]);
EXPECT_EQ(4, view.rbegin()[0]);
EXPECT_EQ(1, view.rend()[-1]);
EXPECT_EQ(4, view.crbegin()[0]);
EXPECT_EQ(1, view.crend()[-1]);
}
TEST_F(ArrayView, ImplicitConstruction)
{
// This test verifies that we can pass in various non-array_view types
// into functions that take an array_view
// The compile cannot detect the correct template type so that has to be specified explicitly
ExpectEqual<int>({ 1,2,3 }, vector<int>({ 1,2,3 }));
ExpectEqual<int>({ 1,2,3 }, fixed_vector<int, 3>({ 1,2,3 }));
ExpectEqual<int>({ 1,2,3 }, array<int, 3>({ 1,2,3 }));
}
void CheckComparisonOperators(bool areEqual, array_view<int> a, array_view<int> b)
{
EXPECT_EQ(areEqual, a == b);
// For less/greater operators, the exact order doesn't really matter;
// We just check for internal consistency
if (areEqual)
{
EXPECT_EQ(false, a != b);
EXPECT_EQ(false, a < b);
EXPECT_EQ(false, a > b);
EXPECT_EQ(true, a <= b);
EXPECT_EQ(true, a >= b);
}
else
{
EXPECT_EQ(true, a != b);
EXPECT_EQ(a > b, a >= b);
EXPECT_EQ(a < b, a <= b);
EXPECT_NE(a > b, a < b);
EXPECT_NE(a >= b, a <= b);
EXPECT_NE(a >= b, a < b);
EXPECT_NE(a > b, a <= b);
EXPECT_NE(a <= b, a > b);
EXPECT_NE(a < b, a >= b);
}
}
TEST_F(ArrayView, ComparisonOperators)
{
int arrayA[] = { 1,2,3 };
int arrayB[] = { 1,2,3 };
array_view<int> arrayA_view(arrayA, 3);
array_view<int> arrayB_view(arrayB, 3);
array_view<int> arrayA_otherView(arrayA, 3);
// view of a sub-array aligned to the beginning of the array
array_view<int> arrayA_headView(arrayA, 2);
array_view<int> arrayB_headView(arrayB, 2);
// view of a sub-array aligned to the end of the array
array_view<int> arrayA_tailView(&arrayA[1], 2);
array_view<int> arrayB_tailView(&arrayB[1], 2);
// view of a sub-array in the middle of the array
array_view<int> arrayA_centerView(&arrayA[1], 1);
array_view<int> arrayB_centerView(&arrayB[1], 1);
// Same view
CheckComparisonOperators(true, arrayA_view, arrayA_view);
// Different view, same array
CheckComparisonOperators(true, arrayA_view, arrayA_otherView);
CheckComparisonOperators(true, arrayA_otherView, arrayA_view);
// Different arrays
CheckComparisonOperators(false, arrayA_view, arrayB_view);
CheckComparisonOperators(false, arrayB_view, arrayA_view);
// Same arrays, but one is a just a subset of the array
CheckComparisonOperators(false, arrayA_view, arrayA_headView);
CheckComparisonOperators(false, arrayA_view, arrayA_tailView);
CheckComparisonOperators(false, arrayA_view, arrayA_centerView);
CheckComparisonOperators(false, arrayA_headView, arrayA_view);
CheckComparisonOperators(false, arrayA_tailView, arrayA_view);
CheckComparisonOperators(false, arrayA_centerView, arrayA_view);
// Different arrays, different lengths
CheckComparisonOperators(false, arrayA_view, arrayB_headView);
CheckComparisonOperators(false, arrayB_view, arrayA_headView);
CheckComparisonOperators(false, arrayB_headView, arrayA_view);
CheckComparisonOperators(false, arrayA_headView, arrayB_view);
}
TEST_F(ArrayView, AssertOutOfBounds)
{
array_view<int> view({ 1,2,3,4 });
UnitTest::TestRunner::Instance().StartAssertTests();
EXPECT_EQ(0, UnitTest::TestRunner::Instance().m_numAssertsFailed);
view[4];
EXPECT_EQ(1, UnitTest::TestRunner::Instance().m_numAssertsFailed);
view[5];
EXPECT_EQ(2, UnitTest::TestRunner::Instance().m_numAssertsFailed);
UnitTest::TestRunner::Instance().StopAssertTests();
}
}
@@ -0,0 +1,627 @@
/*
* 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 <AtomCore/Instance/InstanceDatabase.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/std/parallel/conditional_variable.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Debug/Timer.h>
using namespace AZ;
using namespace AZ::Data;
namespace UnitTest
{
static const InstanceId s_instanceId0{ Uuid("{5B29FE2B-6B41-48C9-826A-C723951B0560}") };
static const InstanceId s_instanceId1{ Uuid("{BD354AE5-B5D5-402A-A12E-BE3C96F6522B}") };
static const InstanceId s_instanceId2{ Uuid("{EE99215B-7AB4-4757-B8AF-F78BD4903AC4}") };
static const InstanceId s_instanceId3{ Uuid("{D9CDAB04-D206-431E-BDC0-1DD615D56197}") };
static const AssetId s_assetId0{ Uuid("{5B29FE2B-6B41-48C9-826A-C723951B0560}") };
static const AssetId s_assetId1{ Uuid("{BD354AE5-B5D5-402A-A12E-BE3C96F6522B}") };
static const AssetId s_assetId2{ Uuid("{EE99215B-7AB4-4757-B8AF-F78BD4903AC4}") };
static const AssetId s_assetId3{ Uuid("{D9CDAB04-D206-431E-BDC0-1DD615D56197}") };
// test asset type
class TestAssetType
: public AssetData
{
public:
AZ_CLASS_ALLOCATOR(TestAssetType, AZ::SystemAllocator, 0);
AZ_RTTI(TestAssetType, "{73D60606-BDE5-44F9-9420-5649FE7BA5B8}", AssetData);
TestAssetType()
{
m_status = AssetStatus::Ready;
}
};
class TestInstanceA
: public InstanceData
{
public:
AZ_INSTANCE_DATA(TestInstanceA, "{65CBF1C8-F65F-4A84-8A11-B510BC435DB0}");
AZ_CLASS_ALLOCATOR(TestInstanceA, AZ::SystemAllocator, 0);
TestInstanceA(TestAssetType* asset)
: m_asset{asset, AZ::Data::AssetLoadBehavior::Default}
{}
Asset<TestAssetType> m_asset;
};
class TestInstanceB
: public InstanceData
{
public:
AZ_INSTANCE_DATA(TestInstanceB, "{4ED0A8BF-7800-44B2-AC73-2CB759C61C37}");
AZ_CLASS_ALLOCATOR(TestInstanceB, AZ::SystemAllocator, 0);
TestInstanceB(TestAssetType* asset)
: m_asset{asset, AZ::Data::AssetLoadBehavior::Default }
{}
~TestInstanceB()
{
if (m_onDeleteCallback)
{
m_onDeleteCallback();
}
}
Asset<TestAssetType> m_asset;
AZStd::function<void()> m_onDeleteCallback;
};
// test asset handler
template<typename AssetDataT>
class MyAssetHandler
: public AssetHandler
{
public:
AZ_CLASS_ALLOCATOR(MyAssetHandler, AZ::SystemAllocator, 0);
AssetPtr CreateAsset(const AssetId& id, const AssetType& type) override
{
(void)id;
EXPECT_TRUE(type == AzTypeInfo<AssetDataT>::Uuid());
if (type == AzTypeInfo<AssetDataT>::Uuid())
{
return aznew AssetDataT();
}
return nullptr;
}
LoadResult LoadAssetData(const Asset<AssetData>&, AZStd::shared_ptr<AssetDataStream>, const AZ::Data::AssetFilterCB&) override
{
return LoadResult::Error;
}
void DestroyAsset(AssetPtr ptr) override
{
EXPECT_TRUE(ptr->GetType() == AzTypeInfo<AssetDataT>::Uuid());
delete ptr;
}
void GetHandledAssetTypes(AZStd::vector<AssetType>& assetTypes) override
{
assetTypes.push_back(AzTypeInfo<AssetDataT>::Uuid());
}
};
class InstanceDatabaseTest
: public AllocatorsFixture
{
protected:
MyAssetHandler<TestAssetType>* m_assetHandler;
public:
void SetUp() override
{
AllocatorsFixture::SetUp();
AllocatorInstance<PoolAllocator>::Create();
AllocatorInstance<ThreadPoolAllocator>::Create();
// create the asset database
{
AssetManager::Descriptor desc;
AssetManager::Create(desc);
}
// create the instance database
{
InstanceHandler<TestInstanceA> instanceHandler;
instanceHandler.m_createFunction = [](AssetData* assetData)
{
EXPECT_TRUE(azrtti_istypeof<TestAssetType>(assetData));
return aznew TestInstanceA(static_cast<TestAssetType*>(assetData));
};
InstanceDatabase<TestInstanceA>::Create(azrtti_typeid<TestAssetType>(), instanceHandler);
}
// create and register an asset handler
m_assetHandler = aznew MyAssetHandler<TestAssetType>;
AssetManager::Instance().RegisterHandler(m_assetHandler, AzTypeInfo<TestAssetType>::Uuid());
}
void TearDown() override
{
// destroy the database
AssetManager::Destroy();
InstanceDatabase<TestInstanceA>::Destroy();
AllocatorInstance<ThreadPoolAllocator>::Destroy();
AllocatorInstance<PoolAllocator>::Destroy();
AllocatorsFixture::TearDown();
}
};
TEST_F(InstanceDatabaseTest, InstanceCreate)
{
auto& assetManager = AssetManager::Instance();
auto& instanceDatabase = InstanceDatabase<TestInstanceA>::Instance();
Asset<TestAssetType> someAsset = assetManager.CreateAsset<TestAssetType>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
Instance<TestInstanceA> instance = instanceDatabase.Find(s_instanceId0);
EXPECT_EQ(instance, nullptr);
instance = instanceDatabase.FindOrCreate(s_instanceId0, someAsset);
EXPECT_NE(instance, nullptr);
Instance<TestInstanceA> instance2 = instanceDatabase.FindOrCreate(s_instanceId0, someAsset);
EXPECT_EQ(instance, instance2);
Instance<TestInstanceA> instance3 = instanceDatabase.Find(s_instanceId0);
EXPECT_EQ(instance, instance3);
}
void ParallelInstanceCreateHelper(size_t threadCountMax, size_t assetIdCount, size_t durationSeconds)
{
printf("Testing threads=%zu assetIds=%zu ... ", threadCountMax, assetIdCount);
AZ::Debug::Timer timer;
timer.Stamp();
auto& assetManager = AssetManager::Instance();
auto& instanceManager = InstanceDatabase<TestInstanceA>::Instance();
AZStd::vector<Uuid> guids;
AZStd::vector<Asset<TestAssetType>> assets;
for (size_t i = 0; i < assetIdCount; ++i)
{
Uuid guid = Uuid::CreateRandom();
guids.emplace_back(guid);
// Pre-create asset so we don't attempt to load it from the catalog.
assets.emplace_back(assetManager.CreateAsset<TestAssetType>(guid, AZ::Data::AssetLoadBehavior::Default));
}
AZStd::vector<AZStd::thread> threads;
AZStd::mutex mutex;
AZStd::atomic<int> threadCount((int)threadCountMax);
AZStd::condition_variable cv;
AZStd::atomic_bool keepDispatching(true);
auto dispatch = [&keepDispatching]()
{
while (keepDispatching)
{
AssetManager::Instance().DispatchEvents();
}
};
srand(0);
AZStd::thread dispatchThread(dispatch);
for (size_t i = 0; i < threadCountMax; ++i)
{
threads.emplace_back([&instanceManager, &threadCount, &cv, &guids, &assets, &durationSeconds]()
{
AZ::Debug::Timer timer;
timer.Stamp();
while(timer.GetDeltaTimeInSeconds() < durationSeconds)
{
const size_t index = rand() % guids.size();
const Uuid uuid = guids[index];
const InstanceId instanceId{uuid};
const AssetId assetId{uuid};
Instance<TestInstanceA> instance = instanceManager.FindOrCreate(instanceId, Asset<TestAssetType>(assetId, azrtti_typeid<TestAssetType>()));
EXPECT_NE(instance, nullptr);
EXPECT_EQ(instance->GetId(), instanceId);
EXPECT_EQ(instance->m_asset, assets[index]);
}
threadCount--;
cv.notify_one();
});
}
bool timedOut = false;
// Used to detect a deadlock. If we wait for more than 10 seconds, it's likely a deadlock has occurred
while (threadCount > 0 && !timedOut)
{
AZStd::unique_lock<AZStd::mutex> lock(mutex);
timedOut = (AZStd::cv_status::timeout == cv.wait_until(lock, AZStd::chrono::system_clock::now() + AZStd::chrono::seconds(durationSeconds * 2)));
}
EXPECT_TRUE(threadCount == 0) << "One or more threads appear to be deadlocked at " << timer.GetDeltaTimeInSeconds() << " seconds";
for (auto& thread : threads)
{
thread.join();
}
keepDispatching = false;
dispatchThread.join();
printf("Took %f seconds\n", timer.GetDeltaTimeInSeconds());
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreate)
{
// This is the original test scenario from when InstanceDatabase was first implemented
// threads, AssetIds, seconds
ParallelInstanceCreateHelper( 8, 100, 5 );
// This value is checked in as 1 so this test doesn't take too much time, but can be increased locally to soak the test.
const size_t attempts = 1;
for (size_t i = 0; i < attempts; ++i)
{
printf("Attempt %zu of %zu... \n", i, attempts);
// The idea behind this series of tests is that there are two threads sharing one Instance, and both threads try to
// create or release that instance at the same time.
// At the time, this set of scenarios has something like a 10% failure rate.
const size_t duration = 2;
// threads, AssetIds, seconds
ParallelInstanceCreateHelper(2, 1, duration);
ParallelInstanceCreateHelper(4, 1, duration);
ParallelInstanceCreateHelper(8, 1, duration);
}
for (size_t i = 0; i < attempts; ++i)
{
printf("Attempt %zu of %zu... \n", i, attempts);
// Here we try a bunch of different threadCount:assetCount ratios to be thorough
const size_t duration = 2;
// threads, AssetIds, seconds
ParallelInstanceCreateHelper(2, 1, duration);
ParallelInstanceCreateHelper(4, 1, duration);
ParallelInstanceCreateHelper(4, 2, duration);
ParallelInstanceCreateHelper(4, 4, duration);
ParallelInstanceCreateHelper(8, 1, duration);
ParallelInstanceCreateHelper(8, 2, duration);
ParallelInstanceCreateHelper(8, 3, duration);
ParallelInstanceCreateHelper(8, 4, duration);
}
}
TEST_F(InstanceDatabaseTest, InstanceCreateNoDatabase)
{
bool m_deleted = false;
{
Instance<TestInstanceB> instance = aznew TestInstanceB(nullptr);
EXPECT_FALSE(instance->GetId().IsValid());
// Tests whether the deleter actually calls delete properly without
// a parent database.
instance->m_onDeleteCallback = [this, &m_deleted] () { m_deleted = true; };
}
EXPECT_TRUE(m_deleted);
}
TEST_F(InstanceDatabaseTest, InstanceCreateMultipleDatabases)
{
// create a second instance database.
{
InstanceHandler<TestInstanceB> instanceHandler;
instanceHandler.m_createFunction = [](AssetData* assetData)
{
EXPECT_TRUE(azrtti_istypeof<TestAssetType>(assetData));
return aznew TestInstanceB(static_cast<TestAssetType*>(assetData));
};
InstanceDatabase<TestInstanceB>::Create(azrtti_typeid<TestAssetType>(), instanceHandler);
}
auto& assetManager = AssetManager::Instance();
auto& instanceDatabaseA = InstanceDatabase<TestInstanceA>::Instance();
auto& instanceDatabaseB = InstanceDatabase<TestInstanceB>::Instance();
{
Asset<TestAssetType> someAsset = assetManager.CreateAsset<TestAssetType>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
// Run the creation tests on 'A' first.
Instance<TestInstanceA> instanceA = instanceDatabaseA.Find(s_instanceId0);
EXPECT_EQ(instanceA, nullptr);
instanceA = instanceDatabaseA.FindOrCreate(s_instanceId0, someAsset);
EXPECT_NE(instanceA, nullptr);
Instance<TestInstanceA> instanceA2 = instanceDatabaseA.FindOrCreate(s_instanceId0, someAsset);
EXPECT_EQ(instanceA, instanceA2);
Instance<TestInstanceA> instanceA3 = instanceDatabaseA.Find(s_instanceId0);
EXPECT_EQ(instanceA, instanceA3);
// Run the same test on 'B' to make sure it works independently.
Instance<TestInstanceB> instanceB = instanceDatabaseB.Find(s_instanceId0);
EXPECT_EQ(instanceB, nullptr);
instanceB = instanceDatabaseB.FindOrCreate(s_instanceId0, someAsset);
EXPECT_NE(instanceB, nullptr);
Instance<TestInstanceB> instanceB2 = instanceDatabaseB.FindOrCreate(s_instanceId0, someAsset);
EXPECT_EQ(instanceB, instanceB2);
Instance<TestInstanceB> instanceB3 = instanceDatabaseB.Find(s_instanceId0);
EXPECT_EQ(instanceB, instanceB3);
}
InstanceDatabase<TestInstanceB>::Destroy();
}
class InstanceDatabaseTestWithMultipleSubclasses
: public AllocatorsFixture
{
protected:
// We have "BaseAsset" with subclasses "FooAsset" and "BarAsset",
// and corresponding "BaseInstance" with subclasses "FooInstance" and "BarInstance".
// There is one "InstanceDatabse<BaseInstance>" that can create instances of both subtypes.
class BaseAsset
: public AssetData
{
public:
AZ_CLASS_ALLOCATOR(BaseAsset, AZ::SystemAllocator, 0);
AZ_RTTI(FooAsset, "{35B443A6-D8ED-4C3C-A3F0-D642251F0AA5}", AssetData);
BaseAsset()
{
m_status = AssetStatus::Ready;
}
};
class BaseInstance
: public InstanceData
{
public:
AZ_INSTANCE_DATA(BaseInstance, "{EFEC3406-2CB7-462E-A676-C22177E143E6}");
AZ_CLASS_ALLOCATOR(BaseInstance, AZ::SystemAllocator, 0);
BaseInstance(BaseAsset* asset)
: m_asset{ asset, AZ::Data::AssetLoadBehavior::Default }
{}
Asset<BaseAsset> m_asset;
};
class FooAsset
: public BaseAsset
{
public:
AZ_CLASS_ALLOCATOR(FooAsset, AZ::SystemAllocator, 0);
AZ_RTTI(FooAsset, "{74BAE278-3DCA-4ADD-807E-2A6873F9EA3C}", BaseAsset);
};
class BarAsset
: public BaseAsset
{
public:
AZ_CLASS_ALLOCATOR(BarAsset, AZ::SystemAllocator, 0);
AZ_RTTI(FooAsset, "{2BCD66F5-768B-4569-9FC2-DE92ABC9C0BF}", BaseAsset);
};
class FooInstance
: public BaseInstance
{
public:
AZ_RTTI(FooInstance, "{B5487509-5518-4591-AC96-03E623A584B7}", BaseInstance);
AZ_CLASS_ALLOCATOR(FooInstance, AZ::SystemAllocator, 0);
FooInstance(BaseAsset* asset)
: BaseInstance(asset)
{
EXPECT_TRUE(azrtti_typeid<FooAsset>() == asset->GetType());
}
};
class BarInstance
: public BaseInstance
{
public:
AZ_RTTI(BarInstance, "{CE9C844A-625D-4899-B7DB-8127D4618D25}", BaseInstance);
AZ_CLASS_ALLOCATOR(BarInstance, AZ::SystemAllocator, 0);
BarInstance(BaseAsset* asset)
: BaseInstance(asset)
{
EXPECT_TRUE(azrtti_typeid<BarAsset>() == asset->GetType());
}
};
MyAssetHandler<FooAsset> m_fooAssetHandler;
MyAssetHandler<BarAsset> m_barAssetHandler;
public:
void SetUp() override
{
AllocatorsFixture::SetUp();
AllocatorInstance<PoolAllocator>::Create();
AllocatorInstance<ThreadPoolAllocator>::Create();
// create the asset database
{
AssetManager::Descriptor desc;
AssetManager::Create(desc);
}
// create the instance database
{
InstanceDatabase<BaseInstance>::Create(azrtti_typeid<BaseAsset>());
InstanceHandler<BaseInstance> fooHandler;
fooHandler.m_createFunction = [](AssetData* assetData)
{
EXPECT_TRUE(azrtti_istypeof<FooAsset>(assetData));
return aznew FooInstance(static_cast<FooAsset*>(assetData));
};
InstanceDatabase<BaseInstance>::Instance().AddHandler(azrtti_typeid<FooAsset>(), fooHandler);
// Using a different overload of AddHandler()
InstanceDatabase<BaseInstance>::Instance().AddHandler(azrtti_typeid<BarAsset>(), [](AssetData* assetData)
{
EXPECT_TRUE(azrtti_istypeof<BarAsset>(assetData));
return aznew BarInstance(static_cast<BarAsset*>(assetData));
});
}
AssetManager::Instance().RegisterHandler(&m_fooAssetHandler, AzTypeInfo<FooAsset>::Uuid());
AssetManager::Instance().RegisterHandler(&m_barAssetHandler, AzTypeInfo<BarAsset>::Uuid());
}
void TearDown() override
{
AssetManager::Instance().UnregisterHandler(&m_fooAssetHandler);
AssetManager::Instance().UnregisterHandler(&m_barAssetHandler);
AssetManager::Destroy();
InstanceDatabase<BaseInstance>::Destroy();
AllocatorInstance<ThreadPoolAllocator>::Destroy();
AllocatorInstance<PoolAllocator>::Destroy();
AllocatorsFixture::TearDown();
}
};
TEST_F(InstanceDatabaseTestWithMultipleSubclasses, InstanceCreate)
{
auto& assetManager = AssetManager::Instance();
auto& instanceDatabase = InstanceDatabase<BaseInstance>::Instance();
Asset<FooAsset> fooAsset = assetManager.CreateAsset<FooAsset>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
Asset<BarAsset> barAsset = assetManager.CreateAsset<BarAsset>(s_assetId1, AZ::Data::AssetLoadBehavior::Default);
// Run the creation tests on 'A' first.
Instance<BaseInstance> fooInstanceA = instanceDatabase.Find(s_instanceId0);
EXPECT_EQ(fooInstanceA, nullptr);
Instance<BaseInstance> barInstanceA = instanceDatabase.Find(s_instanceId1);
EXPECT_EQ(barInstanceA, nullptr);
fooInstanceA = instanceDatabase.FindOrCreate(s_instanceId0, fooAsset);
EXPECT_NE(fooInstanceA, nullptr);
EXPECT_EQ(fooInstanceA->m_asset, fooAsset);
EXPECT_TRUE(azrtti_typeid<FooInstance>() == fooInstanceA->RTTI_GetType());
EXPECT_EQ(fooInstanceA, instanceDatabase.Find(s_instanceId0));
barInstanceA = instanceDatabase.FindOrCreate(s_instanceId1, barAsset);
EXPECT_NE(barInstanceA, nullptr);
EXPECT_EQ(barInstanceA->m_asset, barAsset);
EXPECT_TRUE(azrtti_typeid<BarInstance>() == barInstanceA->RTTI_GetType());
EXPECT_EQ(barInstanceA, instanceDatabase.Find(s_instanceId1));
// Run the same test on 'B' to make sure it works independently.
Instance<BaseInstance> fooInstanceB = instanceDatabase.Find(s_instanceId2);
EXPECT_EQ(fooInstanceB, nullptr);
Instance<BaseInstance> barInstanceB = instanceDatabase.Find(s_instanceId3);
EXPECT_EQ(barInstanceB, nullptr);
fooInstanceB = instanceDatabase.FindOrCreate(s_instanceId2, fooAsset);
EXPECT_NE(fooInstanceB, nullptr);
EXPECT_EQ(fooInstanceB->m_asset, fooAsset);
EXPECT_TRUE(azrtti_typeid<FooInstance>() == fooInstanceB->RTTI_GetType());
EXPECT_EQ(fooInstanceB, instanceDatabase.Find(s_instanceId2));
barInstanceB = instanceDatabase.FindOrCreate(s_instanceId3, barAsset);
EXPECT_NE(barInstanceB, nullptr);
EXPECT_EQ(barInstanceB->m_asset, barAsset);
EXPECT_TRUE(azrtti_typeid<BarInstance>() == barInstanceB->RTTI_GetType());
EXPECT_EQ(barInstanceB, instanceDatabase.Find(s_instanceId3));
// Make sure the instances are unique
EXPECT_NE(fooInstanceA, fooInstanceB);
EXPECT_NE(barInstanceA, barInstanceB);
}
TEST_F(InstanceDatabaseTestWithMultipleSubclasses, TestError_AddHandler_AssetTypeIsNotSubclass)
{
MyAssetHandler<TestAssetType> testAssetHandler;
AssetManager::Instance().RegisterHandler(&testAssetHandler, azrtti_typeid<TestAssetType>());
// Register an instance handler with an unrelated asset type. This can't actually
// check the AssetType yet because all it has are AssetType GUIDs, no actual data.
{
InstanceHandler<BaseInstance> instanceHandler;
instanceHandler.m_createFunction = [](AssetData* assetData)
{
return aznew BaseInstance(static_cast<BaseAsset*>(assetData));
};
AssetType unrelatedAssetType = azrtti_typeid<TestAssetType>();
InstanceDatabase<BaseInstance>::Instance().AddHandler(unrelatedAssetType, instanceHandler);
}
// Try to use the unrelated handler. This is where we'll actually get an error.
{
AZ_TEST_START_ASSERTTEST;
Asset<TestAssetType> testAsset = AssetManager::Instance().CreateAsset<TestAssetType>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
EXPECT_EQ(nullptr, InstanceDatabase<BaseInstance>::Instance().FindOrCreate(s_instanceId0, testAsset));
AZ_TEST_STOP_ASSERTTEST(1);
}
AssetManager::Instance().UnregisterHandler(&testAssetHandler);
}
TEST_F(InstanceDatabaseTestWithMultipleSubclasses, TestError_AddHandler_AlreadyExists)
{
InstanceHandler<BaseInstance> instanceHandler;
instanceHandler.m_createFunction = [](AssetData*)
{
return nullptr; // Doesn't matter
};
AZ_TEST_START_ASSERTTEST;
// The SetUp() function already registered a handler for FooAsset so this should fail
InstanceDatabase<BaseInstance>::Instance().AddHandler(azrtti_typeid<FooAsset>(), instanceHandler);
InstanceDatabase<BaseInstance>::Instance().AddHandler(azrtti_typeid<FooAsset>(), [](AssetData*) { return nullptr; });
AZ_TEST_STOP_ASSERTTEST(2);
}
}
@@ -0,0 +1,496 @@
/*
* 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/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzTest/AzTest.h>
#include <AtomCore/Serialization/Json/JsonUtils.h>
namespace UnitTest
{
using namespace AZ;
namespace Test1
{
class TestClass
{
public:
AZ_TYPE_INFO(TestClass, "{731F8B22-086E-4CDE-9645-23078C6277C1}");
static void Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<TestClass>()
->Version(1)
->Field("int", &TestClass::m_int)
->Field("float", &TestClass::m_float)
->Field("string", &TestClass::m_string)
->Field("unordered_map", &TestClass::m_unorderedMap)
->Field("array", &TestClass::m_array)
->Field("vector", &TestClass::m_vector)
;
}
}
int m_int = 0;
float m_float = 0;
AZStd::string m_string = "TestClass";
AZStd::unordered_map<int, AZStd::string> m_unorderedMap;
AZStd::array<AZStd::string, 2> m_array;
AZStd::vector<AZStd::string> m_vector;
void Init()
{
m_unorderedMap.emplace(1, "one");
m_unorderedMap.emplace(5, "five");
m_array[1] = "ONE";
m_vector.push_back("anything");
m_vector.push_back("something");
}
bool operator == (const TestClass& other) const
{
return m_int == other.m_int
&& m_float == other.m_float
&& m_string == other.m_string
&& m_unorderedMap == other.m_unorderedMap
&& m_array == other.m_array
&& m_vector == other.m_vector
;
}
};
}
namespace Test2
{
// Test class which has same class name with TestClass but difference class id reflected in SerializeContext
class TestClass
{
public:
AZ_TYPE_INFO(TestClass, "{DAC825C5-AB14-4D9D-AAC2-124E56E1F8FD}");
static void Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<TestClass>()
->Version(1)
->Field("SomeData", &TestClass::m_someData)
;
}
}
int m_someData = 0;
};
}
class JsonSerializationUtilsTests
: public AllocatorsTestFixture
{
protected:
void SetUp() override
{
AllocatorsTestFixture::SetUp();
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
m_jsonRegistrationContext = AZStd::make_unique<AZ::JsonRegistrationContext>();
m_jsonSystemComponent = AZStd::make_unique<AZ::JsonSystemComponent>();
m_serializationSettings.m_serializeContext = m_serializeContext.get();
m_serializationSettings.m_registrationContext = m_jsonRegistrationContext.get();
m_deserializationSettings.m_serializeContext = m_serializeContext.get();
m_deserializationSettings.m_registrationContext = m_jsonRegistrationContext.get();
m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get());
Test1::TestClass::Reflect(m_serializeContext.get());
Test2::TestClass::Reflect(m_serializeContext.get());
}
void TearDown() override
{
m_jsonRegistrationContext->EnableRemoveReflection();
m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get());
m_jsonRegistrationContext->DisableRemoveReflection();
m_serializeContext->EnableRemoveReflection();
Test1::TestClass::Reflect(m_serializeContext.get());
Test2::TestClass::Reflect(m_serializeContext.get());
m_serializeContext->DisableRemoveReflection();
m_jsonRegistrationContext.reset();
m_serializeContext.reset();
m_jsonSystemComponent.reset();
AllocatorsTestFixture::TearDown();
}
AZStd::unique_ptr<SerializeContext> m_serializeContext;
AZStd::unique_ptr<JsonRegistrationContext> m_jsonRegistrationContext;
AZStd::unique_ptr<JsonSystemComponent> m_jsonSystemComponent;
JsonSerializerSettings m_serializationSettings;
JsonDeserializerSettings m_deserializationSettings;
};
TEST_F(JsonSerializationUtilsTests, SaveLoadObjectToStream_Success)
{
char buffer[1024];
IO::MemoryStream stream(buffer, 1024, 0);
m_serializationSettings.m_keepDefaults = true;
Test1::TestClass dataToSave;
dataToSave.Init();
dataToSave.m_float = 10;
dataToSave.m_string = "SaveObjectToStreamSuccess";
Outcome<void, AZStd::string> saveResult = JsonSerializationUtils::SaveObjectToStream(&dataToSave, stream, (Test1::TestClass*)nullptr, &m_serializationSettings);
EXPECT_TRUE(saveResult.IsSuccess());
Test1::TestClass loadedData;
stream.Seek(0, IO::GenericStream::ST_SEEK_BEGIN);
Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(loadedData, stream, &m_deserializationSettings);
EXPECT_TRUE(loadResult.IsSuccess());
EXPECT_TRUE(dataToSave == loadedData);
}
TEST_F(JsonSerializationUtilsTests, SaveObjectToStream_Failed_NoSerializationContext)
{
char buffer[1024];
IO::MemoryStream stream(buffer, 1024, 0);
m_serializationSettings.m_keepDefaults = true;
Test1::TestClass dataToSave;
dataToSave.m_float = 10;
Outcome<void, AZStd::string> saveResult = JsonSerializationUtils::SaveObjectToStream(&dataToSave, stream);
EXPECT_TRUE(!saveResult.IsSuccess());
}
TEST_F(JsonSerializationUtilsTests, WriteJson)
{
rapidjson::Document document;
document.SetObject();
document.AddMember("a", 1, document.GetAllocator());
document.AddMember("b", 2, document.GetAllocator());
document.AddMember("c", 3, document.GetAllocator());
const char* expectedJsonText =
"{\n"
" \"a\": 1,\n"
" \"b\": 2,\n"
" \"c\": 3\n"
"}";
AZStd::string outString;
AZ::Outcome<void, AZStd::string> result1 = JsonSerializationUtils::WriteJsonString(document, outString);
EXPECT_TRUE(result1.IsSuccess());
EXPECT_STREQ(expectedJsonText, outString.c_str());
AZStd::vector<char> outBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char>> outStream{&outBuffer};
AZ::Outcome<void, AZStd::string> result2 = JsonSerializationUtils::WriteJsonStream(document, outStream);
EXPECT_TRUE(result2.IsSuccess());
outBuffer.push_back(0);
EXPECT_STREQ(expectedJsonText, outBuffer.data());
// Unfortunately we can't unit test WriteJsonFile because core unit tests don't have access to the local file IO system.
}
TEST_F(JsonSerializationUtilsTests, ReadJsonString)
{
const char* jsonText =
R"(
{
"a": 1,
"b": 2,
"c": 3
})";
AZ::Outcome<rapidjson::Document, AZStd::string> result = JsonSerializationUtils::ReadJsonString(jsonText);
EXPECT_TRUE(result.IsSuccess());
EXPECT_TRUE(result.GetValue().IsObject());
EXPECT_TRUE(result.GetValue().HasMember("a"));
EXPECT_TRUE(result.GetValue().HasMember("b"));
EXPECT_TRUE(result.GetValue().HasMember("c"));
EXPECT_EQ(result.GetValue()["a"].GetInt(), 1);
EXPECT_EQ(result.GetValue()["b"].GetInt(), 2);
EXPECT_EQ(result.GetValue()["c"].GetInt(), 3);
}
TEST_F(JsonSerializationUtilsTests, ReadJsonString_ErrorReportsLineNumber)
{
const char* jsonText =
R"(
{
"a": "This line is missing a comma"
"b": 2,
"c": 3
}
)";
AZ::Outcome<rapidjson::Document, AZStd::string> result = JsonSerializationUtils::ReadJsonString(jsonText);
EXPECT_FALSE(result.IsSuccess());
EXPECT_TRUE(result.GetError().find("JSON parse error at line 4:") == 0);
}
TEST_F(JsonSerializationUtilsTests, LoadJsonStream)
{
const char* jsonText =
R"(
{
"a": 1,
"b": 2,
"c": 3
})";
IO::MemoryStream stream(jsonText, strlen(jsonText));
AZ::Outcome<rapidjson::Document, AZStd::string> result = JsonSerializationUtils::ReadJsonStream(stream);
EXPECT_TRUE(result.IsSuccess());
EXPECT_TRUE(result.GetValue().IsObject());
EXPECT_TRUE(result.GetValue().HasMember("a"));
EXPECT_TRUE(result.GetValue().HasMember("b"));
EXPECT_TRUE(result.GetValue().HasMember("c"));
EXPECT_EQ(result.GetValue()["a"].GetInt(), 1);
EXPECT_EQ(result.GetValue()["b"].GetInt(), 2);
EXPECT_EQ(result.GetValue()["c"].GetInt(), 3);
}
TEST_F(JsonSerializationUtilsTests, LoadJsonStream_ErrorReportsLineNumber)
{
const char* jsonText =
R"(
{
"a": 1,
"b": "This line is missing a comma"
"c": 3
}
)";
IO::MemoryStream stream(jsonText, strlen(jsonText));
AZ::Outcome<rapidjson::Document, AZStd::string> result = JsonSerializationUtils::ReadJsonStream(stream);
EXPECT_FALSE(result.IsSuccess());
EXPECT_TRUE(result.GetError().find("JSON parse error at line 5:") == 0);
}
TEST_F(JsonSerializationUtilsTests, LoadObjectFromStream_Failed_ParseError)
{
char buffer[1024] = "Not a Json";
IO::MemoryStream stream(buffer, 1024);
Test1::TestClass dataToLoad;
Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(dataToLoad, stream, &m_deserializationSettings);
EXPECT_TRUE(!loadResult.IsSuccess());
}
TEST_F(JsonSerializationUtilsTests, LoadObjectFromStream_Failed_NotJsonSerialization)
{
char buffer[1024] = "{}";
IO::MemoryStream stream(buffer, 1024);
Test1::TestClass dataToLoad;
Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(dataToLoad, stream, &m_deserializationSettings);
EXPECT_TRUE(!loadResult.IsSuccess());
}
TEST_F(JsonSerializationUtilsTests, LoadObjectFromStream_Failed_NoClassInfo)
{
char buffer[1024] =
"{ "
" \"Type\": \"JsonSerialization\" "
"} ";
IO::MemoryStream stream(buffer, 1024);
Test1::TestClass dataToLoad;
Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(dataToLoad, stream, &m_deserializationSettings);
EXPECT_TRUE(!loadResult.IsSuccess());
}
TEST_F(JsonSerializationUtilsTests, LoadObjectFromStream_Failed_MismatchClassName)
{
char buffer[1024] =
"{ "
" \"Type\": \"JsonSerialization\", "
" \"ClassName\": \"NotTestClass\", "
" \"ClassData\" : {} "
"} ";
IO::MemoryStream stream(buffer, 1024);
Test1::TestClass dataToLoad;
Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(dataToLoad, stream, &m_deserializationSettings);
EXPECT_TRUE(!loadResult.IsSuccess());
}
TEST_F(JsonSerializationUtilsTests, LoadObjectFromStream_Failed_NoSerializeContext)
{
char buffer[1024] =
"{ "
" \"Type\": \"JsonSerialization\", "
" \"ClassName\": \"TestClass\", "
" \"ClassData\" : {} "
"} ";
IO::MemoryStream stream(buffer, 1024);
Test1::TestClass dataToLoad;
Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(dataToLoad, stream);
EXPECT_TRUE(!loadResult.IsSuccess());
}
TEST_F(JsonSerializationUtilsTests, LoadObjectFromStream_Failed_HaltMismatchClassMember)
{
char buffer[1024] =
"{ "
" \"Type\": \"JsonSerialization\", "
" \"ClassName\": \"TestClass\", "
" \"ClassData\" : { \"uint\":\"10\", "
" \"bad name2\":\"blabla\"} "
"} ";
IO::MemoryStream stream(buffer, 1024);
Test1::TestClass dataToLoad;
Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(dataToLoad, stream, &m_deserializationSettings);
EXPECT_TRUE(!loadResult.IsSuccess());
}
TEST_F(JsonSerializationUtilsTests, LoadObjectFromStream_Failed_WrongValueType)
{
char buffer[1024] =
"{ "
" \"Type\": \"JsonSerialization\", "
" \"ClassName\": \"TestClass\", "
" \"ClassData\" : { \"int\":\"Ten\"} "
"} ";
IO::MemoryStream stream(buffer, 1024);
Test1::TestClass dataToLoad;
Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(dataToLoad, stream, &m_deserializationSettings);
EXPECT_TRUE(!loadResult.IsSuccess());
}
TEST_F(JsonSerializationUtilsTests, LoadObjectFromStream_Success_LessField)
{
char buffer[1024] =
"{ "
" \"Type\": \"JsonSerialization\", "
" \"ClassName\": \"TestClass\", "
" \"ClassData\" : { \"int\":\"10\"} "
"} ";
IO::MemoryStream stream(buffer, 1024);
Test1::TestClass dataToLoad;
Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(dataToLoad, stream, &m_deserializationSettings);
EXPECT_TRUE(loadResult.IsSuccess());
EXPECT_TRUE(dataToLoad.m_int == 10);
}
TEST_F(JsonSerializationUtilsTests, LoadObjectFromStream_Success_CustomizeCallback)
{
char buffer[1024] =
"{ "
" \"Type\": \"JsonSerialization\", "
" \"ClassName\": \"TestClass\", "
" \"ClassData\" : { \"int\":\"10\"} "
"} ";
IO::MemoryStream stream(buffer, 1024);
Test1::TestClass dataToLoad;
AZStd::string callbackString;
auto issueReportingCallback = [&callbackString](AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view target) -> JsonSerializationResult::ResultCode
{
using namespace JsonSerializationResult;
AZ_UNUSED(message);
AZ_UNUSED(target);
callbackString = "issueReportingCallback";
return result;
};
auto settings = m_deserializationSettings;
settings.m_reporting = issueReportingCallback;
Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromStream(dataToLoad, stream, &settings);
EXPECT_TRUE(loadResult.IsSuccess());
EXPECT_TRUE(dataToLoad.m_int == 10);
EXPECT_TRUE(!callbackString.empty());
}
TEST_F(JsonSerializationUtilsTests, LoadAnyObjectFromStream_Success)
{
char buffer[1024] =
"{ "
" \"Type\": \"JsonSerialization\", "
" \"ClassName\": \"TestClass\", "
" \"ClassData\" : { \"int\":\"10\"} "
"} ";
IO::MemoryStream stream(buffer, 1024);
Outcome<AZStd::any, AZStd::string> loadResult = JsonSerializationUtils::LoadAnyObjectFromStream(stream, &m_deserializationSettings);
EXPECT_TRUE(loadResult.IsSuccess());
EXPECT_TRUE(loadResult.GetValue().type() == Test1::TestClass::TYPEINFO_Uuid());
Test1::TestClass test = AZStd::any_cast<Test1::TestClass>(loadResult.GetValue());
EXPECT_TRUE(test.m_int == 10);
}
TEST_F(JsonSerializationUtilsTests, LoadAnyObjectFromStream_Failed_WrongValueType)
{
char buffer[1024] =
"{ "
" \"Type\": \"JsonSerialization\", "
" \"ClassName\": \"TestClass\", "
" \"ClassData\" : { \"int\":\"Ten\"} "
"} ";
IO::MemoryStream stream(buffer, 1024);
Outcome<AZStd::any, AZStd::string> loadResult = JsonSerializationUtils::LoadAnyObjectFromStream(stream, &m_deserializationSettings);
EXPECT_TRUE(!loadResult.IsSuccess());
}
} // namespace UnitTest
+66
View File
@@ -0,0 +1,66 @@
/*
* 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/Timer.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/std/typetraits/typetraits.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzTest/AzTest.h>
#include <AzCore/Memory/OSAllocator.h>
DECLARE_AZ_UNIT_TEST_MAIN()
namespace AZ
{
inline void* AZMemAlloc(AZStd::size_t byteSize, AZStd::size_t alignment, const char* name = "No name allocation")
{
(void)name;
return AZ_OS_MALLOC(byteSize, alignment);
}
inline void AZFree(void* ptr, AZStd::size_t byteSize = 0, AZStd::size_t alignment = 0)
{
(void)byteSize;
(void)alignment;
AZ_OS_FREE(ptr);
}
}
// END OF TEMP MEMORY ALLOCATIONS
using namespace AZ;
// Handle asserts
class TraceDrillerHook
: public AZ::Test::ITestEnvironment
, public UnitTest::TraceBusRedirector
{
public:
void SetupEnvironment() override
{
AllocatorInstance<OSAllocator>::Create(); // used by the bus
BusConnect();
}
void TeardownEnvironment() override
{
BusDisconnect();
AllocatorInstance<OSAllocator>::Destroy(); // used by the bus
}
};
AZ_UNIT_TEST_HOOK(new TraceDrillerHook());
@@ -0,0 +1,142 @@
/*
* 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 "RHITestFixture.h"
#include <AzFramework/IO/LocalFileIO.h>
#include <Atom/RHI.Edit/Utils.h>
#include <Atom/RHI.Reflect/NameIdReflectionMap.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/Serialization/Utils.h>
namespace UnitTest
{
class NamedReflectionTests
: public RHITestFixture
{
protected:
void SetUp() override
{
RHITestFixture::SetUp();
AZ::IO::FileIOBase::SetInstance(aznew AZ::IO::LocalFileIO());
}
void TearDown() override
{
delete AZ::IO::FileIOBase::GetInstance();
AZ::IO::FileIOBase::SetInstance(nullptr);
RHITestFixture::TearDown();
}
};
TEST_F(NamedReflectionTests, NameIdReflectionMap_Empty)
{
AZ::RHI::NameIdReflectionMap<AZ::RHI::Handle<>> map;
EXPECT_EQ(map.Size(), 0);
}
TEST_F(NamedReflectionTests, NameIdReflectionMap_Insert)
{
AZ::RHI::NameIdReflectionMap<AZ::RHI::Handle<>> map;
// insert() also sorts the vector
map.Insert(AZ::Name("name1"), AZ::RHI::Handle<>(3));
map.Insert(AZ::Name("name2"), AZ::RHI::Handle<>(2));
map.Insert(AZ::Name("name3"), AZ::RHI::Handle<>(1));
EXPECT_EQ(map.Size(), 3);
}
TEST_F(NamedReflectionTests, NameIdReflectionMap_Serialize)
{
AZ::SerializeContext serializeContext;
AZ::Name::Reflect(&serializeContext);
AZ::RHI::Handle<>::Reflect(&serializeContext);
AZ::RHI::NameIdReflectionMap<AZ::RHI::Handle<>>::Reflect(&serializeContext);
AZ::RHI::NameIdReflectionMap<AZ::RHI::Handle<>> map;
map.Insert(AZ::Name("name1"), AZ::RHI::Handle<>(3));
map.Insert(AZ::Name("name2"), AZ::RHI::Handle<>(2));
map.Insert(AZ::Name("name3"), AZ::RHI::Handle<>(1));
// XML
AZStd::vector<char> xmlBuffer;
AZ::IO::ByteContainerStream<AZStd::vector<char> > xmlStream(&xmlBuffer);
AZ::ObjectStream* xmlObjStream = AZ::ObjectStream::Create(&xmlStream, serializeContext, AZ::ObjectStream::ST_XML);
xmlObjStream->WriteClass(&map);
xmlObjStream->Finalize();
const AZStd::string output(xmlBuffer.data(), xmlBuffer.size());
EXPECT_NE(output.size(), 0);
}
TEST_F(NamedReflectionTests, NameIdReflectionMap_Deserialize)
{
const char* serializeDataFormat = R"(<ObjectStream version="3">
<Class name = "AZ::RHI::NameIdReflectionMap&lt;AZ::RHI::Handle&lt;unsigned int, DefaultNamespaceType&gt;&gt;" type = "{4EAD7B2D-6190-5CB1-898D-5B96EB36EB46}" >
<Class name = "AZStd::vector" field = "ReflectionMap" type = "{74463005-1C3D-5949-A2FB-90E795144DD6}">
<Class name = "AZ::RHI::ReflectionNamePair&lt;AZ::RHI::Handle&lt;unsigned int, DefaultNamespaceType&gt;&gt;" field = "element" version = "2" type = "{A9301E84-7228-5301-9B2A-8A096DE3C712}">
<Class name = "Name" field = "Name" value = "%s" type = "{3D2B920C-9EFD-40D5-AAE0-DF131C3D4931}" />
<Class name = "AZ::RHI::Handle&lt;unsigned int, DefaultNamespaceType&gt;" field = "Index" version = "1" type = "{1811456D-0C3D-58C8-ACE8-FD47F4E80E25}">
<Class name = "unsigned int" field = "m_index" value = "%s" type = "{43DA906B-7DEF-4CA8-9790-854106D3F983}" />
</Class>
</Class>
<Class name = "AZ::RHI::ReflectionNamePair&lt;AZ::RHI::Handle&lt;unsigned int, DefaultNamespaceType&gt;&gt;" field = "element" version = "2" type = "{A9301E84-7228-5301-9B2A-8A096DE3C712}">
<Class name = "Name" field = "Name" value = "%s" type = "{3D2B920C-9EFD-40D5-AAE0-DF131C3D4931}" />
<Class name = "AZ::RHI::Handle&lt;unsigned int, DefaultNamespaceType&gt;" field = "Index" version = "1" type = "{1811456D-0C3D-58C8-ACE8-FD47F4E80E25}">
<Class name = "unsigned int" field = "m_index" value = "%s" type = "{43DA906B-7DEF-4CA8-9790-854106D3F983}" />
</Class>
</Class>
<Class name = "AZ::RHI::ReflectionNamePair&lt;AZ::RHI::Handle&lt;unsigned int, DefaultNamespaceType&gt;&gt;" field = "element" version = "2" type = "{A9301E84-7228-5301-9B2A-8A096DE3C712}">
<Class name = "Name" field = "Name" value = "%s" type = "{3D2B920C-9EFD-40D5-AAE0-DF131C3D4931}" />
<Class name = "AZ::RHI::Handle&lt;unsigned int, DefaultNamespaceType&gt;" field = "Index" version = "1" type = "{1811456D-0C3D-58C8-ACE8-FD47F4E80E25}">
<Class name = "unsigned int" field = "m_index" value = "%s" type = "{43DA906B-7DEF-4CA8-9790-854106D3F983}" />
</Class>
</Class>
</Class>
</Class>
</ObjectStream>)";
// The internal storage sorts by the hash value of strings, so name2 comes before name3, which comes before name1.
// So the inpuit is specifically putting them out order with how it appears sorted when inserted.
AZStd::string inputData = AZStd::string::format(serializeDataFormat, "name3", "3", "name2", "2", "name1", "1");
AZ::SerializeContext serializeContext;
AZ::Name::Reflect(&serializeContext);
AZ::RHI::Handle<>::Reflect(&serializeContext);
AZ::RHI::NameIdReflectionMap<AZ::RHI::Handle<>>::Reflect(&serializeContext);
AZStd::vector<AZ::u8> binaryData(inputData.begin(), inputData.end());
AZ::IO::ByteContainerStream<const AZStd::vector<AZ::u8> > binaryStream(&binaryData);
binaryStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
{
AZ::RHI::NameIdReflectionMap<AZ::RHI::Handle<>> map;
EXPECT_TRUE(AZ::Utils::LoadObjectFromStreamInPlace(binaryStream, map, &serializeContext));
EXPECT_EQ(map.Size(), 3);
EXPECT_EQ(map.Find(AZ::Name("name1")).m_index, 1);
EXPECT_EQ(map.Find(AZ::Name("name2")).m_index, 2);
EXPECT_EQ(map.Find(AZ::Name("name3")).m_index, 3);
}
}
}
@@ -0,0 +1,19 @@
#
# 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.
#
set(FILES
ArrayView.cpp
InstanceDatabase.cpp
JsonSerializationUtilsTests.cpp
lru_cache.cpp
Main.cpp
vector_set.cpp
)
+172
View File
@@ -0,0 +1,172 @@
/*
* 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 <AtomCore/std/containers/lru_cache.h>
#include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/UnitTest/TestTypes.h>
using namespace AZStd;
namespace UnitTest
{
using HashedContainers = AllocatorsFixture;
TEST_F(HashedContainers, LRUCacheBasic)
{
lru_cache<int, int> intint_cache;
EXPECT_EQ(intint_cache.capacity(), 0);
EXPECT_EQ(intint_cache.empty(), true);
EXPECT_EQ(intint_cache.size(), 0);
EXPECT_EQ(intint_cache.begin(), intint_cache.end());
EXPECT_EQ(intint_cache.rbegin(), intint_cache.rend());
// should assert since capacity is 0.
AZ_TEST_START_ASSERTTEST;
intint_cache.insert(0, 0);
AZ_TEST_STOP_ASSERTTEST(1);
intint_cache.set_capacity(10);
EXPECT_EQ(intint_cache.capacity(), 10);
int i = 0;
for (; i < 10; ++i)
{
intint_cache.insert(i, 2 * i);
}
EXPECT_EQ(intint_cache.size(), 10);
// We should now have [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], with 9 as the most recent (i.e. at begin()).
i = 0;
for (auto it = intint_cache.rbegin(); it != intint_cache.rend(); ++it, ++i)
{
EXPECT_EQ(it->first, i);
EXPECT_EQ(it->second, 2 * i);
}
EXPECT_EQ(intint_cache.get(9)->first, 9);
EXPECT_EQ(intint_cache.get(9)->second, 9 * 2);
// Bump 2 to most recent.
EXPECT_EQ(intint_cache.get(2)->first, 2);
EXPECT_EQ(intint_cache.get(2)->second, 2 * 2);
// Make sure it's most recent.
EXPECT_EQ(intint_cache.begin()->first, 2);
for (i = 10; i < 20; ++i)
{
intint_cache.insert(i, 2 * i);
}
EXPECT_EQ(intint_cache.size(), 10);
// We should now have [10, 11, 12, 13, 14, 15, 16, 17, 18, 19], with 19 as the most recent (i.e. at begin()).
i = 10;
for (auto it = intint_cache.rbegin(); it != intint_cache.rend(); ++it, ++i)
{
EXPECT_EQ(it->first, i);
EXPECT_EQ(it->second, 2 * i);
}
intint_cache.set_capacity(1);
EXPECT_EQ(intint_cache.size(), 1);
EXPECT_EQ(intint_cache.capacity(), 1);
EXPECT_EQ(intint_cache.begin()->first, 19);
intint_cache.set_capacity(8);
for (i = 0; i < 8; ++i)
{
intint_cache.insert(i, 2 * i);
}
{
auto it = intint_cache.get(5);
EXPECT_EQ(it, intint_cache.begin());
EXPECT_EQ(it->first, 5);
EXPECT_EQ(it->second, 10);
}
// Test adding the same key 10 times.
for (i = 0; i < 10; ++i)
{
intint_cache.insert(0, 0);
}
// the first element should be 0, the rest should be shifted.
EXPECT_EQ(intint_cache.begin()->first, 0);
EXPECT_EQ(intint_cache.begin()->second, 0);
// Asset the second element is (5, 10) the previously added element.
{
auto it = intint_cache.begin();
it++;
EXPECT_EQ(it->first, 5);
EXPECT_EQ(it->second, 10);
}
}
TEST_F(HashedContainers, LRUCacheMoveConstruct)
{
using PtrType = AZStd::unique_ptr<int>;
lru_cache<int, PtrType> intintptr_cache(10);
int i = 0;
for (; i < 10; ++i)
{
intintptr_cache.emplace(i, new int(2 * i));
}
EXPECT_EQ(intintptr_cache.size(), 10);
i = 0;
for (auto it = intintptr_cache.rbegin(); it != intintptr_cache.rend(); ++it, ++i)
{
EXPECT_EQ(it->first, i);
EXPECT_EQ(*(it->second), i * 2);
}
}
TEST_F(HashedContainers, LRUCacheRefCount)
{
class X : public AZStd::intrusive_base
{
public:
X(uint32_t value) : m_value{value} {}
uint32_t m_value;
};
const int TestValue = 123;
using PtrType = AZStd::intrusive_ptr<X>;
lru_cache<int, PtrType> intintptr_cache(10);
PtrType p(new X(TestValue));
intintptr_cache.emplace(0, p);
auto beginIt = intintptr_cache.begin();
EXPECT_EQ(beginIt->second->m_value, TestValue);
EXPECT_EQ(p->use_count(), 2);
int i = 0;
for (; i < 10; ++i)
{
intintptr_cache.emplace(i, p);
}
// Should have all 10 references + the one we hold.
EXPECT_EQ(p->use_count(), 11);
intintptr_cache.clear();
EXPECT_EQ(p->use_count(), 1);
}
}
@@ -0,0 +1,285 @@
/*
* 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 <AtomCore/std/containers/vector_set.h>
#include <AtomCore/std/containers/fixed_vector_set.h>
#include <AzCore/UnitTest/TestTypes.h>
using namespace AZStd;
namespace UnitTest
{
class VectorSets
: public AllocatorsFixture
{
void SetUp() override
{
AllocatorsFixture::SetUp();
}
};
class FixedVectorSets
: public AllocatorsFixture
{
void SetUp() override
{
AllocatorsFixture::SetUp();
}
};
template <typename SetType>
struct VectorSetTester
{
using this_type = VectorSetTester<SetType>;
const AZStd::vector<int32_t> m_expected = { 0, 1, 4, 9, 11, 14, 21, 23, 25, 27, 31 };
const AZStd::vector<int32_t> m_unexpected = { 5, -2 };
const SetType m_vectorSet = { 25, 0, 9, 21, 27, 1, 9, 23, 4, 14, 31, 0, 11 };
void TestFindConst() const
{
for (int32_t value : m_expected)
{
auto it = m_vectorSet.find(value);
EXPECT_EQ(*it, value);
}
for (int32_t value : m_unexpected)
{
auto it = m_vectorSet.find(value);
EXPECT_EQ(it, m_vectorSet.end());
}
EXPECT_EQ(m_vectorSet.size(), m_expected.size());
for (size_t i = 0; i < m_vectorSet.size(); ++i)
{
EXPECT_EQ(m_vectorSet[i], m_expected[i]);
}
}
void TestFind()
{
for (int32_t value : m_expected)
{
auto it = m_vectorSet.find(value);
EXPECT_EQ(*it, value);
}
for (int32_t value : m_unexpected)
{
auto it = m_vectorSet.find(value);
EXPECT_EQ(it, m_vectorSet.end());
}
EXPECT_EQ(m_vectorSet.size(), m_expected.size());
for (size_t i = 0; i < m_vectorSet.size(); ++i)
{
EXPECT_EQ(m_vectorSet[i], m_expected[i]);
}
}
void TestInsertion()
{
auto vectorSet = m_vectorSet;
EXPECT_EQ(vectorSet.erase(9), 1);
EXPECT_EQ(vectorSet.erase(8), 0);
EXPECT_EQ(vectorSet.find(9), vectorSet.end());
EXPECT_EQ(vectorSet.insert(9).second, true);
EXPECT_EQ(*vectorSet.find(9), 9);
EXPECT_EQ(vectorSet.erase(25), 1);
EXPECT_EQ(vectorSet.find(25), vectorSet.end());
EXPECT_EQ(*vectorSet.lower_bound(25), 27);
EXPECT_EQ(*vectorSet.upper_bound(25), 27);
auto iterBoolPair = vectorSet.emplace(25);
EXPECT_EQ(*iterBoolPair.first, 25);
EXPECT_TRUE(iterBoolPair.second);
iterBoolPair = vectorSet.insert(25);
EXPECT_EQ(*iterBoolPair.first, 25);
EXPECT_FALSE(iterBoolPair.second);
}
void TestCompare()
{
auto vectorSet = m_vectorSet;
EXPECT_FALSE(vectorSet.empty());
SetType intSet2 = vectorSet;
EXPECT_EQ(vectorSet, intSet2);
intSet2.erase(9);
EXPECT_NE(vectorSet, intSet2);
intSet2.clear();
EXPECT_EQ(intSet2.size(), 0);
EXPECT_TRUE(intSet2.empty());
}
void TestAssignment()
{
auto vectorSet = m_vectorSet;
vectorSet.assign(m_expected.begin(), m_expected.end());
vectorSet.insert(m_expected.begin(), m_expected.end());
for (size_t i = 0; i < vectorSet.size(); ++i)
{
EXPECT_EQ(vectorSet[i], m_expected[i]);
}
}
void TestIterators()
{
EXPECT_EQ(m_expected.size(), m_vectorSet.size());
{
auto it1 = m_expected.begin();
auto it2 = m_vectorSet.begin();
for (; it1 != m_expected.end(); ++it1, ++it2)
{
EXPECT_NE(it2, m_vectorSet.end());
EXPECT_EQ(*it1, *it2);
}
}
{
auto it1 = m_expected.rbegin();
auto it2 = m_vectorSet.rbegin();
for (; it1 != m_expected.rend(); ++it1, ++it2)
{
EXPECT_NE(it2, m_vectorSet.rend());
EXPECT_EQ(*it1, *it2);
}
}
}
void TestIteratorsConst() const
{
EXPECT_EQ(m_expected.size(), m_vectorSet.size());
{
auto it1 = m_expected.begin();
auto it2 = m_vectorSet.begin();
for (; it1 != m_expected.end(); ++it1, ++it2)
{
EXPECT_NE(it2, m_vectorSet.end());
EXPECT_EQ(*it1, *it2);
}
}
{
auto it1 = m_expected.rbegin();
auto it2 = m_vectorSet.rbegin();
for (; it1 != m_expected.rend(); ++it1, ++it2)
{
EXPECT_NE(it2, m_vectorSet.rend());
EXPECT_EQ(*it1, *it2);
}
}
}
};
TEST_F(VectorSets, Find)
{
VectorSetTester<AZStd::vector_set<int32_t>> tester;
tester.TestFind();
}
TEST_F(VectorSets, FindConst)
{
VectorSetTester<AZStd::vector_set<int32_t>> tester;
tester.TestFindConst();
}
TEST_F(VectorSets, Insertion)
{
VectorSetTester<AZStd::vector_set<int32_t>> tester;
tester.TestInsertion();
}
TEST_F(VectorSets, Compare)
{
VectorSetTester<AZStd::vector_set<int32_t>> tester;
tester.TestCompare();
}
TEST_F(VectorSets, Assignment)
{
VectorSetTester<AZStd::vector_set<int32_t>> tester;
tester.TestAssignment();
}
TEST_F(VectorSets, Iterators)
{
VectorSetTester<AZStd::vector_set<int32_t>> tester;
tester.TestIterators();
}
TEST_F(VectorSets, IteratorsConst)
{
VectorSetTester<AZStd::vector_set<int32_t>> tester;
tester.TestIteratorsConst();
}
TEST_F(FixedVectorSets, Find)
{
VectorSetTester<AZStd::fixed_vector_set<int32_t, 64>> tester;
tester.TestFind();
}
TEST_F(FixedVectorSets, FindConst)
{
VectorSetTester<AZStd::fixed_vector_set<int32_t, 64>> tester;
tester.TestFindConst();
}
TEST_F(FixedVectorSets, Insertion)
{
VectorSetTester<AZStd::fixed_vector_set<int32_t, 64>> tester;
tester.TestInsertion();
}
TEST_F(FixedVectorSets, Compare)
{
VectorSetTester<AZStd::fixed_vector_set<int32_t, 64>> tester;
tester.TestCompare();
}
TEST_F(FixedVectorSets, Assignment)
{
VectorSetTester<AZStd::fixed_vector_set<int32_t, 64>> tester;
tester.TestAssignment();
}
TEST_F(FixedVectorSets, Iterators)
{
VectorSetTester<AZStd::fixed_vector_set<int32_t, 64>> tester;
tester.TestIterators();
}
TEST_F(FixedVectorSets, IteratorsConst)
{
VectorSetTester<AZStd::fixed_vector_set<int32_t, 64>> tester;
tester.TestIteratorsConst();
}
}
@@ -0,0 +1,28 @@
/*
* 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.
*
*/
package com.amazon.lumberyard;
import android.app.Activity;
import android.content.Intent;
////////////////////////////////////////////////////////////////
public abstract class ActivityResultsListener
{
public ActivityResultsListener(Activity activity)
{
((LumberyardActivity)activity).RegisterActivityResultsListener(this);
}
public abstract boolean ProcessActivityResult(int requestCode, int resultCode, Intent data);
}
@@ -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.
*
*/
package com.amazon.lumberyard;
import android.app.ActivityManager;
import android.content.Context;
////////////////////////////////////////////////////////////////////////////////////////////////////
public class AndroidDeviceManager
{
public static Context context;
public static final float bytesInGB = (1024.0f * 1024.0f * 1024.0f);
public static float GetDeviceRamInGB()
{
ActivityManager actManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
actManager.getMemoryInfo(memInfo);
float totalMemory = memInfo.totalMem / bytesInGB;
return totalMemory;
}
}
@@ -0,0 +1,506 @@
/*
* 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.
*
*/
package com.amazon.lumberyard;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.NativeActivity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Point;
import android.Manifest;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Looper;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.lang.InterruptedException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.amazon.lumberyard.io.APKHandler;
import com.amazon.lumberyard.io.obb.ObbDownloaderActivity;
////////////////////////////////////////////////////////////////
public class LumberyardActivity extends NativeActivity
{
////////////////////////////////////////////////////////////////
// Native methods
public static native void nativeOnRequestPermissionsResult(boolean granted);
////////////////////////////////////////////////////////////////
@Override
public void onBackPressed()
{
// by doing nothing here will prevent the activity from being exited (the default behaviour)
}
////////////////////////////////////////////////////////////////
// called from the native to get the application package name
// e.g. com.lumberyard.samples for SamplesProject
public String GetPackageName()
{
return getApplicationContext().getPackageName();
}
////////////////////////////////////////////////////////////////
// called from the native to get the app version code
// android:versionCode in the AndroidManifest.xml.
public int GetAppVersionCode()
{
try
{
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
return pInfo.versionCode;
}
catch (NameNotFoundException e)
{
return 0;
}
}
////////////////////////////////////////////////////////////////
// called from the native code to show the Java splash screen
public void ShowSplashScreen()
{
Log.d(TAG, "ShowSplashScreen called");
this.runOnUiThread(new Runnable() {
@Override
public void run()
{
if (!m_splashShowing)
{
ShowSplashScreenImpl();
}
else
{
Log.d(TAG, "The splash screen is already showing");
}
}
});
}
////////////////////////////////////////////////////////////////
// called from the native code to dismiss the Java splash screen
public void DismissSplashScreen()
{
Log.d(TAG, "DismissSplashScreen called");
if (m_splashShowing)
{
this.runOnUiThread(new Runnable() {
@Override
public void run()
{
if (m_slashWindow != null)
{
Log.d(TAG, "Dismissing the splash screen");
m_slashWindow.dismiss();
m_slashWindow = null;
}
else
{
Log.d(TAG, "There is no splash screen to dismiss");
}
}
});
m_splashShowing = false;
}
}
////////////////////////////////////////////////////////////////
public void RegisterActivityResultsListener(ActivityResultsListener listener)
{
m_activityResultsListeners.add(listener);
}
////////////////////////////////////////////////////////////////
public void UnregisterActivityResultsListener(ActivityResultsListener listener)
{
m_activityResultsListeners.remove(listener);
}
////////////////////////////////////////////////////////////////
// Starts the download of the obb files and waits (block) until the activity finishes.
// Return true in case of success, false otherwise.
public boolean DownloadObb()
{
Intent downloadIntent = new Intent(this, ObbDownloaderActivity.class);
ActivityResult result = new ActivityResult();
if (launchActivity(downloadIntent, DOWNLOAD_OBB_REQUEST, true, result))
{
return result.m_result == Activity.RESULT_OK;
}
return false;
}
////////////////////////////////////////////////////////////////
// Returns the value of a boolean resource.
public boolean GetBooleanResource(String resourceName)
{
Resources resources = this.getResources();
int resourceId = resources.getIdentifier(resourceName, "bool", this.getPackageName());
return resources.getBoolean(resourceId);
}
////////////////////////////////////////////////////////////////
// Request permissions at runtime.
public void RequestPermission(final String permission, final String rationale)
{
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED)
{
Random rand = new Random();
m_runtimePermissionRequestCode = rand.nextInt(500);
final int requestCode = m_runtimePermissionRequestCode;
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission))
{
final LumberyardActivity activity = this;
Runnable uiDialog = new Runnable()
{
public void run()
{
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
TextView textView = new TextView(activity);
String title = new String("Reason for requesting " + permission);
textView.setText(title + "\n" + rationale);
builder.setCustomTitle(textView);
builder.setItems(new String[]{"OK"}, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int index) {
ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
};
activity.runOnUiThread(uiDialog);
}
else
{
ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode);
}
}
else
{
nativeOnRequestPermissionsResult(true);
}
}
// ----
////////////////////////////////////////////////////////////////
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (GetBooleanResource("enable_keep_screen_on"))
{
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
ProcessImmersiveModeSetting();
APKHandler.SetAssetManager(getAssets());
AndroidDeviceManager.context = this;
boolean useMainObb = GetBooleanResource("use_main_obb");
boolean usePatchObb = GetBooleanResource("use_patch_obb");
if (IsBootstrapInAPK() && (useMainObb || usePatchObb))
{
Log.d(TAG, "Using OBB expansion files for game assets");
File obbRootPath = getApplicationContext().getObbDir();
String packageName = GetPackageName();
int appVersionCode = GetAppVersionCode();
String mainObbFilePath = String.format("%s/main.%d.%s.obb", obbRootPath, appVersionCode, packageName);
String patchObbFilePath = String.format("%s/patch.%d.%s.obb", obbRootPath, appVersionCode, packageName);
File mainObbFile = new File(mainObbFilePath);
File patchObbFile = new File(patchObbFilePath);
boolean needToDownload = ( (useMainObb && !mainObbFile.canRead())
|| (usePatchObb && !patchObbFile.canRead()));
if (needToDownload)
{
Log.d(TAG, "Attempting to download the OBB expansion files");
boolean downloadResult = DownloadObb();
if (!downloadResult)
{
Log.e(TAG, "****************************************************************");
Log.e(TAG, "Failed to download the OBB expansion file. Exiting...");
Log.e(TAG, "****************************************************************");
finish();
}
}
}
else
{
Log.d(TAG, "Assets already on the device, not using the OBB expansion files.");
}
// ensure we use the music media stream
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
////////////////////////////////////////////////////////////////
@Override
protected void onDestroy()
{
// Signal any thread that is waiting for the result of an activity
for(ActivityResult result : m_waitingResultList)
{
synchronized(result)
{
result.m_isRunning = false;
result.notifyAll();
}
}
// Ideally we should be calling super.onDestroy() here and going through the "graceful" shutdown process,
// however some deadlock(s) happen in the static de-allocation preventing the process to naturally exit.
// On phones, and most tablets, this doesn't happen because the process is terminated by the system but
// while running in Samsung DEX mode it's kept alive until it seemingly exits naturally. Manually killing
// the process in the onDestroy is probably the best compromise until the graceful exit is fixed with LY-70527
android.os.Process.killProcess(android.os.Process.myPid());
}
////////////////////////////////////////////////////////////////
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
if (hasFocus)
{
ProcessImmersiveModeSetting();
}
}
////////////////////////////////////////////////////////////////
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
for (ActivityResultsListener listener : m_activityResultsListeners)
{
listener.ProcessActivityResult(requestCode, resultCode, data);
}
}
////////////////////////////////////////////////////////////////
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
{
if (requestCode == m_runtimePermissionRequestCode)
{
if (grantResults.length > 0)
{
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
Log.d(TAG, "Permission Granted");
nativeOnRequestPermissionsResult(true);
}
else
{
Log.d(TAG, "Permission Denied");
nativeOnRequestPermissionsResult(false);
}
}
else
{
// Request was cancelled
nativeOnRequestPermissionsResult(false);
}
m_runtimePermissionRequestCode = -1;
}
}
// ----
////////////////////////////////////////////////////////////////
private boolean launchActivity(Intent intent, final int activityRequestCode, boolean waitForResult, final ActivityResult result)
{
if (waitForResult)
{
if (Looper.myLooper() == Looper.getMainLooper())
{
// Can't block if we are on the UI Thread.
return false;
}
ActivityResultsListener activityListener = new ActivityResultsListener(this)
{
@Override
public boolean ProcessActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == activityRequestCode)
{
synchronized(result)
{
result.m_result = resultCode;
result.m_isRunning = false;
result.notify();
}
return true;
}
return false;
}
};
this.RegisterActivityResultsListener(activityListener);
m_waitingResultList.add(result);
result.m_isRunning = true;
startActivityForResult(intent, activityRequestCode);
synchronized(result)
{
// Wait until the downloader activity finishes.
boolean ret = true;
while (result.m_isRunning)
{
try
{
result.wait();
}
catch(InterruptedException exception)
{
ret = false;
}
}
this.UnregisterActivityResultsListener(activityListener);
m_waitingResultList.remove(result);
return ret;
}
}
else
{
startActivityForResult(intent, activityRequestCode);
return true;
}
}
////////////////////////////////////////////////////////////////
private boolean IsBootstrapInAPK()
{
try
{
InputStream bootstrap = getAssets().open("bootstrap.cfg", AssetManager.ACCESS_UNKNOWN);
bootstrap.close();
return true;
}
catch (IOException exception)
{
return false;
}
}
////////////////////////////////////////////////////////////////
private void ShowSplashScreenImpl()
{
Log.d(TAG, "Showing the Splash Screen");
// load the splash screen view
Resources resources = getResources();
int layoutId = resources.getIdentifier("splash_screen", "layout", getPackageName());
LayoutInflater factory = LayoutInflater.from(this);
View splashView = factory.inflate(layoutId, null);
// get the resolution of the display
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
// create the popup with the splash screen layout. this is because the standard
// view hierarchy for Android apps doesn't exist when using the NativeActivity
m_slashWindow = new PopupWindow(splashView, size.x, size.y);
m_slashWindow.setClippingEnabled(false);
// add a dummy layout to the main view for the splash popup window
LinearLayout mainLayout = new LinearLayout(this);
setContentView(mainLayout);
// show the splash window
m_slashWindow.showAtLocation(mainLayout, Gravity.CENTER, 0, 0);
m_slashWindow.update();
m_splashShowing = true;
}
////////////////////////////////////////////////////////////////
private void ProcessImmersiveModeSetting()
{
int systemUiFlags = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
if (!GetBooleanResource("disable_immersive_mode"))
{
systemUiFlags |= (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
getWindow().getDecorView().setSystemUiVisibility(systemUiFlags);
}
// ----
////////////////////////////////////////////////////////////////
private class ActivityResult
{
public int m_result;
public boolean m_isRunning;
}
// ----
private static final int DOWNLOAD_OBB_REQUEST = 1337;
private static final String TAG = "LMBR";
private PopupWindow m_slashWindow = null;
private boolean m_splashShowing = false;
private int m_runtimePermissionRequestCode = -1;
private List<ActivityResultsListener> m_activityResultsListeners = new ArrayList<ActivityResultsListener>();
private List<ActivityResult> m_waitingResultList = new ArrayList<ActivityResult>();
}
@@ -0,0 +1,61 @@
/*
* 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.
*
*/
package com.amazon.lumberyard.NativeUI;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.util.Log;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;
public class LumberyardNativeUI
{
public static void DisplayDialog(final Activity activity, final String title, final String message, final String[] options)
{
Log.d("LMBR", "DisplayDialog called");
userSelection = new AtomicReference<String>("");
userSelection.set("");
Runnable uiDialog = new Runnable()
{
public void run()
{
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
TextView textView = new TextView(activity);
textView.setText(title + "\n" + message);
builder.setCustomTitle(textView);
builder.setItems(options, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int index) {
userSelection.set(options[index]);
Log.d("LMBR", "Selected option: " + userSelection.get());
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
};
activity.runOnUiThread(uiDialog);
}
public static String GetUserSelection()
{
return userSelection.get();
}
public static AtomicReference<String> userSelection;
}
@@ -0,0 +1,196 @@
/*
* 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.
*
*/
package com.amazon.lumberyard.input;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.KeyEvent;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.view.View;
////////////////////////////////////////////////////////////////
public class KeyboardHandler
{
// ----
// KeyboardHandler (public)
// ----
public static native void SendUnicodeText(String unicodeText);
////////////////////////////////////////////////////////////////
public KeyboardHandler(Activity activity)
{
m_activity = activity;
m_inputManager = (InputMethodManager)m_activity.getSystemService(Context.INPUT_METHOD_SERVICE);
}
////////////////////////////////////////////////////////////////
public void ShowTextInput()
{
m_activity.runOnUiThread(new Runnable() {
@Override
public void run()
{
if (m_textView == null)
{
m_textView = new DummyTextView(m_activity);
ViewGroup viewGroup = (ViewGroup)GetView();
viewGroup.addView(m_textView);
}
m_textView.Show();
m_inputManager.showSoftInput(m_textView, 0);
}
});
}
////////////////////////////////////////////////////////////////
public void HideTextInput()
{
if (m_textView != null)
{
m_activity.runOnUiThread(new Runnable() {
@Override
public void run()
{
m_inputManager.hideSoftInputFromWindow(m_textView.getWindowToken(), 0);
m_textView.Hide();
}
});
}
}
////////////////////////////////////////////////////////////////
public boolean IsShowing()
{
if (m_textView != null)
{
return m_textView.IsShowing();
}
return false;
}
// ----
private class DummyTextView extends View
{
////////////////////////////////////////////////////////////////
public DummyTextView(Context context)
{
super(context);
setFocusableInTouchMode(true);
setFocusable(true);
m_isShowing = false;
}
////////////////////////////////////////////////////////////////
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (event.isPrintingKey())
{
int unicode = event.getUnicodeChar();
String character = String.valueOf((char)unicode);
Log.d(s_tag, String.format("OnKeyDown - Unicode: %s - Printed character: %s", unicode, character));
SendUnicodeText(character);
}
return super.onKeyDown(keyCode, event);
}
////////////////////////////////////////////////////////////////
@Override
public boolean onKeyMultiple(int keyCode, int count, KeyEvent event)
{
if(event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN)
{
String text = event.getCharacters();
Log.d(s_tag, String.format("onKeyMultiple - Text: %s", text));
if (text != null)
{
SendUnicodeText(text);
}
}
return super.onKeyMultiple(keyCode, count, event);
}
////////////////////////////////////////////////////////////////
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK)
{
Hide();
}
return super.onKeyPreIme(keyCode, event);
}
////////////////////////////////////////////////////////////////
public boolean IsShowing()
{
return m_isShowing;
}
////////////////////////////////////////////////////////////////
public void Show()
{
m_windowFlags = GetView().getSystemUiVisibility();
setVisibility(View.VISIBLE);
requestFocus();
m_isShowing = true;
}
////////////////////////////////////////////////////////////////
public void Hide()
{
setVisibility(View.GONE);
m_isShowing = false;
GetView().setSystemUiVisibility(m_windowFlags);
}
// ----
private boolean m_isShowing;
private int m_windowFlags;
}
// ----
////////////////////////////////////////////////////////////////
private View GetView()
{
return m_activity.getWindow().getDecorView();
}
// ----
private static final String s_tag = "KeyboardHandler";
private Activity m_activity;
private InputMethodManager m_inputManager;
private DummyTextView m_textView;
}
@@ -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.
*
*/
package com.amazon.lumberyard.input;
import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.view.Display;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import java.lang.Math;
////////////////////////////////////////////////////////////////////////////////////////////////////
public class MotionSensorManager implements SensorEventListener
{
private class MotionSensorData
{
private class SensorData3D
{
private float x;
private float y;
private float z;
private boolean updated;
}
private class SensorData4D
{
private float x;
private float y;
private float z;
private float w;
private boolean updated;
}
private SensorData3D accelerationRaw = new SensorData3D();
private SensorData3D accelerationUser = new SensorData3D();
private SensorData3D accelerationGravity = new SensorData3D();
private SensorData3D rotationRateRaw = new SensorData3D();
private SensorData3D rotationRateUnbiased = new SensorData3D();
private SensorData3D magneticFieldRaw = new SensorData3D();
private SensorData3D magneticFieldUnbiased = new SensorData3D();
private SensorData4D orientation = new SensorData4D();
}
private static final float METRES_PER_SECOND_SQUARED_TO_GFORCE = -1.0f / SensorManager.GRAVITY_EARTH;
private static final int MOTION_SENSOR_DATA_PACKED_LENGTH = 34;
private MotionSensorData m_motionSensorData = new MotionSensorData();
private float[] m_motionSensorDataPacked = new float[MOTION_SENSOR_DATA_PACKED_LENGTH];
private SensorManager m_sensorManager = null;
private Display m_defaultDisplay = null;
private float m_orientationAdjustmentRadiansZ = 0.0f;
private int m_orientationSensorToUse = Sensor.TYPE_GAME_ROTATION_VECTOR;
public MotionSensorManager(Activity activity)
{
m_sensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
m_defaultDisplay = ((WindowManager)activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
// If the game rotation vector is not available, default to the regular rotation vector.
if ((m_sensorManager != null) && (m_sensorManager.getDefaultSensor(m_orientationSensorToUse) == null))
{
m_orientationSensorToUse = Sensor.TYPE_ROTATION_VECTOR;
}
}
// Called when a motion sensor's accuracy changes
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
}
// Called when a motion sensor event is dispatched
@Override
public void onSensorChanged(SensorEvent event)
{
int currentDisplayRotation = m_defaultDisplay != null ?
m_defaultDisplay.getRotation() :
Surface.ROTATION_0;
Sensor sensor = event.sensor;
switch (sensor.getType())
{
case Sensor.TYPE_ACCELEROMETER:
{
// Convert to the same unit of measurement as returned natively by iOS,
// which is (arguably) more useful to use directly for game development.
AlignWithDisplay(m_motionSensorData.accelerationRaw,
event.values[0] * METRES_PER_SECOND_SQUARED_TO_GFORCE,
event.values[1] * METRES_PER_SECOND_SQUARED_TO_GFORCE,
event.values[2] * METRES_PER_SECOND_SQUARED_TO_GFORCE,
currentDisplayRotation);
}
break;
case Sensor.TYPE_LINEAR_ACCELERATION:
{
// Convert to the same unit of measurement as returned natively by iOS,
// which is (arguably) more useful to use directly for game development.
AlignWithDisplay(m_motionSensorData.accelerationUser,
event.values[0] * METRES_PER_SECOND_SQUARED_TO_GFORCE,
event.values[1] * METRES_PER_SECOND_SQUARED_TO_GFORCE,
event.values[2] * METRES_PER_SECOND_SQUARED_TO_GFORCE,
currentDisplayRotation);
}
break;
case Sensor.TYPE_GRAVITY:
{
// Convert to the same unit of measurement as returned natively by iOS,
// which is (arguably) more useful to use directly for game development.
AlignWithDisplay(m_motionSensorData.accelerationGravity,
event.values[0] * METRES_PER_SECOND_SQUARED_TO_GFORCE,
event.values[1] * METRES_PER_SECOND_SQUARED_TO_GFORCE,
event.values[2] * METRES_PER_SECOND_SQUARED_TO_GFORCE,
currentDisplayRotation);
}
break;
case Sensor.TYPE_GYROSCOPE_UNCALIBRATED:
{
AlignWithDisplay(m_motionSensorData.rotationRateRaw,
event.values[0],
event.values[1],
event.values[2],
currentDisplayRotation);
}
break;
case Sensor.TYPE_GYROSCOPE:
{
AlignWithDisplay(m_motionSensorData.rotationRateUnbiased,
event.values[0],
event.values[1],
event.values[2],
currentDisplayRotation);
}
break;
case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED:
{
AlignWithDisplay(m_motionSensorData.magneticFieldRaw,
event.values[0],
event.values[1],
event.values[2],
currentDisplayRotation);
}
break;
case Sensor.TYPE_MAGNETIC_FIELD:
{
AlignWithDisplay(m_motionSensorData.magneticFieldUnbiased,
event.values[0],
event.values[1],
event.values[2],
currentDisplayRotation);
}
break;
case Sensor.TYPE_GAME_ROTATION_VECTOR:
case Sensor.TYPE_ROTATION_VECTOR:
{
m_motionSensorData.orientation.x = event.values[0];
m_motionSensorData.orientation.y = event.values[1];
m_motionSensorData.orientation.z = event.values[2];
m_motionSensorData.orientation.w = event.values[3];
// Android doesn't provide us with any quaternion math,
// so we do the alignment in MotionSensorinputDevice.cpp
m_orientationAdjustmentRadiansZ = GetOrientationAdjustmentRadiansZ(currentDisplayRotation);
m_motionSensorData.orientation.updated = true;
}
break;
}
}
private void AlignWithDisplay(MotionSensorData.SensorData3D o_sensorData, float x, float y, float z, int displayRotation)
{
switch (displayRotation)
{
case Surface.ROTATION_90:
{
o_sensorData.x = -y;
o_sensorData.y = -z;
o_sensorData.z = x;
}
break;
case Surface.ROTATION_180:
{
o_sensorData.x = -x;
o_sensorData.y = -z;
o_sensorData.z = -y;
}
break;
case Surface.ROTATION_270:
{
o_sensorData.x = y;
o_sensorData.y = -z;
o_sensorData.z = -x;
}
break;
case Surface.ROTATION_0:
default:
{
o_sensorData.x = x;
o_sensorData.y = -z;
o_sensorData.z = y;
}
break;
}
o_sensorData.updated = true;
}
private float GetOrientationAdjustmentRadiansZ(int displayRotation)
{
switch (displayRotation)
{
case Surface.ROTATION_90:
{
return (float)(-Math.PI * 0.5d);
}
case Surface.ROTATION_180:
{
return (float)Math.PI;
}
case Surface.ROTATION_270:
{
return (float)(Math.PI * 0.5d);
}
case Surface.ROTATION_0:
default:
{
return 0.0f;
}
}
}
// Called from native code to query availability of motion sensor data.
public boolean IsMotionSensorDataAvailable(boolean accelerometerRaw,
boolean accelerometerUser,
boolean accelerometerGravity,
boolean rotationRateRaw,
boolean rotationRateUnbiased,
boolean magneticFieldRaw,
boolean magneticFieldUnbiased,
boolean orientation)
{
if (m_sensorManager == null)
{
return false;
}
if (accelerometerRaw && m_sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) == null)
{
return false;
}
if (accelerometerUser && m_sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION) == null)
{
return false;
}
if (accelerometerGravity && m_sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) == null)
{
return false;
}
if (rotationRateRaw && m_sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED) == null)
{
return false;
}
if (rotationRateUnbiased && m_sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) == null)
{
return false;
}
if (magneticFieldRaw && m_sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED) == null)
{
return false;
}
if (magneticFieldUnbiased && m_sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) == null)
{
return false;
}
if (orientation && m_sensorManager.getDefaultSensor(m_orientationSensorToUse) == null)
{
return false;
}
return false;
}
// Called from native code to refresh motion sensors.
// state: -1 = disable, 0 = unchanged, 1 = enable
public void RefreshMotionSensors(float updateIntervalSeconds,
int accelerometerRawState,
int accelerometerUserState,
int accelerometerGravityState,
int rotationRateRawState,
int rotationRateUnbiasedState,
int magneticFieldRawState,
int magneticFieldUnbiasedState,
int orientationState)
{
int updateIntervalMicroeconds = (int)(updateIntervalSeconds * 1000000);
RefreshMotionSensor(Sensor.TYPE_ACCELEROMETER, accelerometerRawState, updateIntervalMicroeconds);
RefreshMotionSensor(Sensor.TYPE_LINEAR_ACCELERATION, accelerometerUserState, updateIntervalMicroeconds);
RefreshMotionSensor(Sensor.TYPE_GRAVITY, accelerometerGravityState, updateIntervalMicroeconds);
RefreshMotionSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, rotationRateRawState, updateIntervalMicroeconds);
RefreshMotionSensor(Sensor.TYPE_GYROSCOPE, rotationRateUnbiasedState, updateIntervalMicroeconds);
RefreshMotionSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED, magneticFieldRawState, updateIntervalMicroeconds);
RefreshMotionSensor(Sensor.TYPE_MAGNETIC_FIELD, magneticFieldUnbiasedState, updateIntervalMicroeconds);
RefreshMotionSensor(m_orientationSensorToUse, orientationState, updateIntervalMicroeconds);
}
private void RefreshMotionSensor(int sensorType, int state, int updateIntervalMicroeconds)
{
if (m_sensorManager == null)
{
return;
}
// state: -1 = disable, 0 = unchanged, 1 = enable
switch (state)
{
case 1:
{
Sensor defaultSensor = m_sensorManager.getDefaultSensor(sensorType);
if (defaultSensor != null)
{
m_sensorManager.registerListener(this, defaultSensor, updateIntervalMicroeconds);
}
}
break;
case -1:
{
Sensor defaultSensor = m_sensorManager.getDefaultSensor(sensorType);
if (defaultSensor != null)
{
m_sensorManager.unregisterListener(this, defaultSensor);
}
}
break;
}
}
// Called from native code to retrieve the latest motion sensor data.
public float[] RequestLatestMotionSensorData()
{
// While we would ideally like to just return m_motionSensorData directly,
// the native C++ code would then need to 'reach back' through the JNI
// for each field just to access the raw data. Simply returning a float
// array packed with all the required values is far more efficient, at
// the expense of the native C++ code having to access the data through
// 'magic' array indices instead of explcitly naming the class fields.
//
// Additionally, while explicitly calling out the class fields provides
// a modicum of safety, lots of boilerplate code is needed, and while we
// may in future append additional sensor data the existing elements are
// unlikely to ever change. Combined with the above mentioned performance
// considerations, this approach seems preferable on most fronts.
m_motionSensorDataPacked[0] = m_motionSensorData.accelerationRaw.updated ? 1 : 0;
m_motionSensorDataPacked[1] = m_motionSensorData.accelerationRaw.x;
m_motionSensorDataPacked[2] = m_motionSensorData.accelerationRaw.y;
m_motionSensorDataPacked[3] = m_motionSensorData.accelerationRaw.z;
m_motionSensorData.accelerationRaw.updated = false;
m_motionSensorDataPacked[4] = m_motionSensorData.accelerationUser.updated ? 1 : 0;
m_motionSensorDataPacked[5] = m_motionSensorData.accelerationUser.x;
m_motionSensorDataPacked[6] = m_motionSensorData.accelerationUser.y;
m_motionSensorDataPacked[7] = m_motionSensorData.accelerationUser.z;
m_motionSensorData.accelerationUser.updated = false;
m_motionSensorDataPacked[8] = m_motionSensorData.accelerationGravity.updated ? 1 : 0;
m_motionSensorDataPacked[9] = m_motionSensorData.accelerationGravity.x;
m_motionSensorDataPacked[10] = m_motionSensorData.accelerationGravity.y;
m_motionSensorDataPacked[11] = m_motionSensorData.accelerationGravity.z;
m_motionSensorData.accelerationGravity.updated = false;
m_motionSensorDataPacked[12] = m_motionSensorData.rotationRateRaw.updated ? 1 : 0;
m_motionSensorDataPacked[13] = m_motionSensorData.rotationRateRaw.x;
m_motionSensorDataPacked[14] = m_motionSensorData.rotationRateRaw.y;
m_motionSensorDataPacked[15] = m_motionSensorData.rotationRateRaw.z;
m_motionSensorData.rotationRateRaw.updated = false;
m_motionSensorDataPacked[16] = m_motionSensorData.rotationRateUnbiased.updated ? 1 : 0;
m_motionSensorDataPacked[17] = m_motionSensorData.rotationRateUnbiased.x;
m_motionSensorDataPacked[18] = m_motionSensorData.rotationRateUnbiased.y;
m_motionSensorDataPacked[19] = m_motionSensorData.rotationRateUnbiased.z;
m_motionSensorData.rotationRateUnbiased.updated = false;
m_motionSensorDataPacked[20] = m_motionSensorData.magneticFieldRaw.updated ? 1 : 0;
m_motionSensorDataPacked[21] = m_motionSensorData.magneticFieldRaw.x;
m_motionSensorDataPacked[22] = m_motionSensorData.magneticFieldRaw.y;
m_motionSensorDataPacked[23] = m_motionSensorData.magneticFieldRaw.z;
m_motionSensorData.magneticFieldRaw.updated = false;
m_motionSensorDataPacked[24] = m_motionSensorData.magneticFieldUnbiased.updated ? 1 : 0;
m_motionSensorDataPacked[25] = m_motionSensorData.magneticFieldUnbiased.x;
m_motionSensorDataPacked[26] = m_motionSensorData.magneticFieldUnbiased.y;
m_motionSensorDataPacked[27] = m_motionSensorData.magneticFieldUnbiased.z;
m_motionSensorData.magneticFieldUnbiased.updated = false;
m_motionSensorDataPacked[28] = m_motionSensorData.orientation.updated ? 1 : 0;
m_motionSensorDataPacked[29] = m_motionSensorData.orientation.x;
m_motionSensorDataPacked[30] = m_motionSensorData.orientation.y;
m_motionSensorDataPacked[31] = m_motionSensorData.orientation.z;
m_motionSensorDataPacked[32] = m_motionSensorData.orientation.w;
m_motionSensorDataPacked[33] = m_orientationAdjustmentRadiansZ;
m_motionSensorData.orientation.updated = false;
return m_motionSensorDataPacked;
}
}
@@ -0,0 +1,112 @@
/*
* 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.
*
*/
package com.amazon.lumberyard.input;
import android.app.Activity;
import android.content.Context;
import android.hardware.input.InputManager;
import android.view.InputDevice;
import java.util.HashSet;
import java.util.Set;
public class MouseDevice
implements InputManager.InputDeviceListener
{
public native void OnMouseConnected();
public native void OnMouseDisconnected();
public MouseDevice(Activity activity)
{
m_inputManager = (InputManager)activity.getSystemService(Context.INPUT_SERVICE);
int[] devices = m_inputManager.getInputDeviceIds();
for (int deviceId : devices)
{
if (IsMouseDevice(deviceId))
{
m_mouseDeviceIds.add(deviceId);
}
}
final InputManager.InputDeviceListener listener = this;
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
// run the registration on the main thread to use it's looper as the handler
// instead of creating one specifically for listening to mouse [dis]connections
m_inputManager.registerInputDeviceListener(listener, null);
}
});
}
@Override
public void onInputDeviceAdded(int deviceId)
{
if (IsMouseDevice(deviceId))
{
m_mouseDeviceIds.add(deviceId);
// only inform the native code if we change from having no mice connected, extra
// are effectively ignored and folded into one "master" device
if (m_mouseDeviceIds.size() == 1)
{
OnMouseConnected();
}
}
}
@Override
public void onInputDeviceChanged(int deviceId)
{
// do nothing
}
@Override
public void onInputDeviceRemoved(int deviceId)
{
if (m_mouseDeviceIds.contains(deviceId))
{
m_mouseDeviceIds.remove(deviceId);
// only inform the native code if we change to having no mice connected
if (m_mouseDeviceIds.size() == 0)
{
OnMouseDisconnected();
}
}
}
public boolean IsConnected()
{
return (m_mouseDeviceIds.size() > 0);
}
private boolean IsMouseDevice(int deviceId)
{
InputDevice device = m_inputManager.getInputDevice(deviceId);
if (device == null)
{
return false;
}
int sources = device.getSources();
return (sources == InputDevice.SOURCE_MOUSE);
}
private InputManager m_inputManager = null;
private Set<Integer> m_mouseDeviceIds = new HashSet<>();
}
@@ -0,0 +1,91 @@
/*
* 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.
*
*/
package com.amazon.lumberyard.io;
import android.content.res.AssetManager;
import android.util.Log;
import java.io.IOException;
import android.app.Activity;
////////////////////////////////////////////////////////////////
public class APKHandler
{
////////////////////////////////////////////////////////////////
public static void SetAssetManager(AssetManager assetManager)
{
s_assetManager = assetManager;
}
////////////////////////////////////////////////////////////////
public static String[] GetFilesAndDirectoriesInPath(String path)
{
String[] filelist = {};
try
{
filelist = s_assetManager.list(path);
}
catch (IOException e)
{
Log.e(s_tag, String.format("File I/O error: %s", e.getMessage()));
e.printStackTrace();
}
finally
{
if (s_debug)
{
Log.d(s_tag, String.format("Files in path: %s", path));
for(String name : filelist)
{
Log.d(s_tag, String.format(" -- %s", name));
}
}
return filelist;
}
}
////////////////////////////////////////////////////////////////
public static boolean IsDirectory(String path)
{
String[] filelist = {};
boolean retVal = false;
try
{
filelist = s_assetManager.list(path);
if(filelist.length > 0)
{
retVal = true;
}
}
catch (IOException e)
{
Log.e(s_tag, String.format("File I/O error: %s", e.getMessage()));
e.printStackTrace();
}
finally
{
return retVal;
}
}
// ----
private static final String s_tag = "LMBR";
private static AssetManager s_assetManager = null;
private static boolean s_debug = false;
}
@@ -0,0 +1,323 @@
/*
* 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.
*
*/
package com.amazon.lumberyard.io.obb;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Messenger;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.android.vending.expansion.downloader.Constants;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;
import com.google.android.vending.expansion.downloader.IStub;
import java.lang.Exception;
////////////////////////////////////////////////////////////////
// Activity that handles the download of the APK expansion package (Obb)
public class ObbDownloaderActivity extends Activity implements IDownloaderClient
{
////////////////////////////////////////////////////////////////
@Override
public void onServiceConnected(Messenger messenger)
{
m_remoteService = DownloaderServiceMarshaller.CreateProxy(messenger);
m_remoteService.onClientUpdated(m_downloaderClientStub.getMessenger());
}
////////////////////////////////////////////////////////////////
@Override
public void onDownloadStateChanged(int newState)
{
setState(newState);
boolean showDashboard = true;
boolean showCellMessage = false;
boolean paused;
boolean indeterminate;
switch (newState)
{
case IDownloaderClient.STATE_IDLE:
// STATE_IDLE means the service is listening, so it's
// safe to start making calls via m_remoteService.
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_CONNECTING:
case IDownloaderClient.STATE_FETCHING_URL:
showDashboard = true;
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_DOWNLOADING:
paused = false;
showDashboard = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_FAILED_CANCELED:
case IDownloaderClient.STATE_FAILED:
case IDownloaderClient.STATE_FAILED_FETCHING_URL:
case IDownloaderClient.STATE_FAILED_UNLICENSED:
paused = true;
showDashboard = false;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
showDashboard = false;
paused = true;
indeterminate = false;
showCellMessage = true;
break;
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_ROAMING:
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_COMPLETED:
showDashboard = false;
paused = false;
indeterminate = false;
endActivity(Activity.RESULT_OK);
return;
default:
paused = true;
indeterminate = true;
showDashboard = true;
}
int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE;
if (m_dashboard.getVisibility() != newDashboardVisibility)
{
m_dashboard.setVisibility(newDashboardVisibility);
}
int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE;
if (m_cellMessage.getVisibility() != cellMessageVisibility)
{
m_cellMessage.setVisibility(cellMessageVisibility);
}
m_progressBar.setIndeterminate(indeterminate);
setButtonPausedState(paused);
}
////////////////////////////////////////////////////////////////
@Override
public void onDownloadProgress(DownloadProgressInfo progress)
{
m_averageSpeed.setText(getString(m_kbPerSecondTextId, Helpers.getSpeedString(progress.mCurrentSpeed)));
m_timeRemaining.setText(getString(m_timeRemainingTextId, Helpers.getTimeRemaining(progress.mTimeRemaining)));
progress.mOverallTotal = progress.mOverallTotal;
m_progressBar.setMax((int) (progress.mOverallTotal >> 8));
m_progressBar.setProgress((int) (progress.mOverallProgress >> 8));
m_progressPercent.setText(Long.toString(progress.mOverallProgress * 100 / progress.mOverallTotal) + "%");
m_progressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal));
}
////////////////////////////////////////////////////////////////
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Build an Intent to start this activity from the Notification
Intent notifierIntent = new Intent(ObbDownloaderActivity.this, ObbDownloaderActivity.this.getClass());
notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
try
{
// Start the download service (if required)
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, ObbDownloaderService.class);
// If download has started, initialize this activity to show download progress
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED)
{
initializeUI();
return;
}
}
catch (Exception e)
{
endActivity(Activity.RESULT_CANCELED);
return;
}
endActivity(Activity.RESULT_OK);
}
////////////////////////////////////////////////////////////////
@Override
protected void onResume()
{
if (m_downloaderClientStub != null)
{
m_downloaderClientStub.connect(this);
}
super.onResume();
}
////////////////////////////////////////////////////////////////
@Override
protected void onStop()
{
if (m_downloaderClientStub != null)
{
m_downloaderClientStub.disconnect(this);
}
super.onStop();
}
////////////////////////////////////////////////////////////////
protected void endActivity(int result)
{
if (isFinishing())
{
return;
}
Intent returnIntent = new Intent();
setResult(result, returnIntent);
finish();
}
////////////////////////////////////////////////////////////////
private void setState(int newState)
{
if (m_state != newState)
{
m_state = newState;
m_statusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState));
}
}
////////////////////////////////////////////////////////////////
private void setButtonPausedState(boolean paused)
{
m_statePaused = paused;
int stringResourceID = paused ? m_buttonResumeTextId : m_buttonPauseTextId;
m_pauseButton.setText(stringResourceID);
}
////////////////////////////////////////////////////////////////
private void initializeUI()
{
Resources resources = this.getResources();
String packageName = getApplicationContext().getPackageName();
m_downloaderClientStub = DownloaderClientMarshaller.CreateStub(this, ObbDownloaderService.class);
setContentView(resources.getIdentifier("obb_downloader", "layout", packageName));
m_progressBar = (ProgressBar) findViewById(resources.getIdentifier("progressBar", "id", packageName));
m_statusText = (TextView) findViewById(resources.getIdentifier("statusText", "id", packageName));
m_progressFraction = (TextView) findViewById(resources.getIdentifier("progressAsFraction", "id", packageName));
m_progressPercent = (TextView) findViewById(resources.getIdentifier("progressAsPercentage", "id", packageName));
m_averageSpeed = (TextView) findViewById(resources.getIdentifier("progressAverageSpeed", "id", packageName));
m_timeRemaining = (TextView) findViewById(resources.getIdentifier("progressTimeRemaining", "id", packageName));
m_dashboard = findViewById(resources.getIdentifier("downloaderDashboard", "id", packageName));
m_cellMessage = findViewById(resources.getIdentifier("approveCellular", "id", packageName));
m_pauseButton = (Button) findViewById(resources.getIdentifier("pauseButton", "id", packageName));
m_wiFiSettingsButton = (Button) findViewById(resources.getIdentifier("wifiSettingsButton", "id", packageName));
m_buttonResumeTextId = resources.getIdentifier("text_button_resume", "string", packageName);
m_buttonPauseTextId = resources.getIdentifier("text_button_pause", "string", packageName);
m_timeRemainingTextId = resources.getIdentifier("time_remaining", "string", packageName);
m_kbPerSecondTextId = resources.getIdentifier("kilobytes_per_second", "string", packageName);
m_pauseButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
if (m_statePaused)
{
m_remoteService.requestContinueDownload();
}
else
{
m_remoteService.requestPauseDownload();
}
setButtonPausedState(!m_statePaused);
}
});
m_wiFiSettingsButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
}
});
Button resumeOnCell = (Button) findViewById(resources.getIdentifier("resumeOverCellular", "id", packageName));
resumeOnCell.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
m_remoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
m_remoteService.requestContinueDownload();
m_cellMessage.setVisibility(View.GONE);
}
});
}
private static final String TAG = "ObbDownloaderActivity";
private ProgressBar m_progressBar;
private TextView m_statusText;
private TextView m_progressFraction;
private TextView m_progressPercent;
private TextView m_averageSpeed;
private TextView m_timeRemaining;
private View m_dashboard;
private View m_cellMessage;
private Button m_pauseButton;
private Button m_wiFiSettingsButton;
private boolean m_statePaused;
private int m_state;
private IDownloaderService m_remoteService;
private IStub m_downloaderClientStub;
private int m_buttonResumeTextId;
private int m_buttonPauseTextId;
private int m_kbPerSecondTextId;
private int m_timeRemainingTextId;
}
@@ -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.
*
*/
package com.amazon.lumberyard.io.obb;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
////////////////////////////////////////////////////////////////
// Alarm receiver needeed by the Downloader library for reasuming the download of the Obb.
public class ObbDownloaderAlarmReceiver extends BroadcastReceiver
{
////////////////////////////////////////////////////////////////
@Override
public void onReceive(Context context, Intent intent)
{
try
{
DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, ObbDownloaderService.class);
}
catch (NameNotFoundException e)
{
e.printStackTrace();
}
}
}
@@ -0,0 +1,78 @@
/*
* 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.
*
*/
package com.amazon.lumberyard.io.obb;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
import com.google.android.vending.licensing.util.Base64;
import com.google.android.vending.licensing.util.Base64DecoderException;
////////////////////////////////////////////////////////////////
// Service needed by the downloader library in order to get the public key, salt and alarm receiver class.
public class ObbDownloaderService extends DownloaderService
{
////////////////////////////////////////////////////////////////
@Override
public void onCreate ()
{
super.onCreate();
Context context = getApplicationContext();
Resources resources = getResources();
int stringId = resources.getIdentifier("public_key", "string", context.getPackageName());
m_base64PublicKey = resources.getString(stringId);
stringId = resources.getIdentifier("obfuscator_salt", "string", context.getPackageName());
String base64Salt = resources.getString(stringId);
if (!base64Salt.isEmpty())
{
try
{
m_salt = Base64.decode(base64Salt);
}
catch (Base64DecoderException e)
{
Log.e("ObbDownloaderService", "Failed to decode the salt string");
}
}
}
////////////////////////////////////////////////////////////////
@Override
public String getPublicKey()
{
return m_base64PublicKey;
}
////////////////////////////////////////////////////////////////
@Override
public byte[] getSALT()
{
return m_salt;
}
////////////////////////////////////////////////////////////////
@Override
public String getAlarmReceiverClassName()
{
return ObbDownloaderAlarmReceiver.class.getName();
}
////////////////////////////////////////////////////////////////
private String m_base64PublicKey;
private byte[] m_salt = new byte[] { 23, 12, 4, -12, -34, 23,
-120, 122, -23, -104, -2, -4, 12, 3, -21, 123, -11, 4, -11, 32
};
}
@@ -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.
*
*/
package com.amazon.test;
import java.util.Random;
public class SimpleObject
{
public class Foo
{
public Foo(int seed)
{
Random rand = new Random(seed);
m_value = rand.nextInt();
}
// ----
public int m_value;
}
// ----
public SimpleObject() {}
public boolean GetBool() { return true; }
public boolean[] GetBoolArray() { return new boolean[] { true, false, true, false }; }
public char GetChar() { return 'L'; }
public char[] GetCharArray() { return new char[] { 'L', 'u', 'm', 'b', 'e', 'r', 'y', 'a', 'r', 'd' }; }
public byte GetByte() { return 1; }
public byte[] GetByteArray() { return new byte[] { 1, 2, 4, 8, 16, 32 }; }
public short GetShort() { return 128; }
public short[] GetShortArray() { return new short[] { 128, 256, 512, 1024, 2048, 8192 }; }
public int GetInt() { return 32768; }
public int[] GetIntArray() { return new int[] { 32768, 65536, 131072, 262144, 524288, 1048576 }; }
public float GetFloat() { return (float)Math.PI; }
public float[] GetFloatArray()
{
float[] result = new float[6];
for (int i = 0; i < 6; ++i)
{
result[i] = (float)(i + 1) / 7.0f;
}
return result;
}
public double GetDouble() { return Math.PI; }
public double[] GetDoubleArray()
{
double[] result = new double[6];
for (int i = 0; i < 6; ++i)
{
result[i] = (double)(i + 1) / 7.0;
}
return result;
}
public Class GetClass() { return this.getClass(); }
public String GetString() { return "Amazon Lumberyard"; }
public Foo GetObject() { return new Foo(1); }
public Foo[] GetObjectArray() { return new Foo[] { new Foo(1), new Foo(2), new Foo(3), new Foo(4) }; }
// ----
private static final String TAG = "SimpleObject";
}
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup>
<DisableFastUpToDateCheck>True</DisableFastUpToDateCheck>
</PropertyGroup>
</Project>
+392
View File
@@ -0,0 +1,392 @@
#!/usr/bin/python
# 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.
import io
import os
import re
import sys
import time
import errno
import shutil
import fnmatch
import filecmp
import fileinput
import importlib
import argparse
import hashlib
from xml.sax.saxutils import escape, unescape, quoteattr
# Maximum number of errors before bailing on AutoGen
MAX_ERRORS = 100
errorCount = 0
def PrintError(*objs):
print(*objs, file=sys.stderr)
global errorCount
errorCount += 1
if errorCount > MAX_ERRORS:
print("Maximum errors exceeded (%d) please check the tty for errors" % MAX_ERRORS, file=sys.stderr)
sys.exit(1)
def PrintUnhandledExcptionInfo():
print("An unexpected error occurred, please report the error you encountered and include your build output", file=sys.stderr)
def TransformEscape(string):
return escape(quoteattr(unescape(string)))
def BooleanTrue(string):
testString = string.lower().strip()
return testString == "true" or testString == "1"
def CamelToHuman(string):
return string[0].upper() + re.sub(r'((?<=[a-z])[A-Z]|(?<!\A)[A-Z](?=[a-z]))', r' \1', string[1:])
def StripFloat(string):
return re.sub(r'(\d+(\.\d*)?|\.\d+)f', r'\g<1>0', string)
def CreateHashGuid(string):
hash = hashlib.new('md5')
hash.update(string.encode('utf-8'))
hashStr = hash.hexdigest()
return ("{" + hashStr[0:8] + "-" + hashStr[8:12] + "-" + hashStr[12:16] + "-" + hashStr[16:20] + "-" + hashStr[20:] + "}").upper()
def EtreeToString(xmlNode):
return etree.tostring(xmlNode)
def SanitizePath(path):
return (path or '').replace('\\', '/').replace('//', '/')
def SearchPaths(filename, paths=[]):
if len(paths) > 0:
for path in paths:
testFile = os.path.join(path, filename)
if os.path.exists(testFile):
return os.path.abspath(testFile)
if os.path.exists(filename):
return os.path.abspath(filename)
return None
def ComputeOutputPath(inputFiles, projectDir, outputDir):
commonInputPath = os.path.commonprefix(inputFiles) # If we've globbed many source files, this finds the common prefix
if os.path.isfile(commonInputPath): # If the commonInputPath resolves to an actual file, slice off the filename
commonInputPath = os.path.dirname(commonInputPath)
commonPath = os.path.commonprefix([commonInputPath, projectDir]) # Finds the common path between the data source files and our project directory (//depot/dev/Code/Framework/AzCore/)
inputRelativePath = os.path.relpath(commonInputPath, commonPath) # Computes the relative path for the project source directory (Code/Framework/AzCore/AutoGen/)
return os.path.join(outputDir, inputRelativePath) # Returns a suitable output directory (//depot/dev/Generated/Code/Framework/AzCore/AutoGen/)
def ProcessTemplateConversion(dataInputSet, dataInputFiles, templateFile, outputFile, templateCache, dryrun, verbose):
if dryrun or not dataInputFiles:
return
try:
outputFile = os.path.abspath(outputFile)
outputPath = os.path.dirname(outputFile)
treeRoots = []
for dataInputFile in sorted(dataInputFiles):
try:
if dataInputFile in dataInputSet.keys():
treeRoots.append(dataInputSet.get(dataInputFile))
elif os.path.splitext(dataInputFile)[1] == ".xml":
xml = etree.parse(dataInputFile)
# xml.xinclude()
xmlroot = xml.getroot()
# look for an xml schema link for this document
# xmlSchema = None
# if 'xsi' in xmlroot.nsmap:
# XMLSchemaNamespace = xmlroot.nsmap['xsi']
# schemaLink = xmlroot.get('{' + XMLSchemaNamespace + '}schemaLocation')
# if schemaLink is None:
# schemaLink = xmlroot.attrib['{' + XMLSchemaNamespace + '}noNamespaceSchemaLocation']
# if schemaLink:
# # if we have a schemaLink, then we need to strip off the relative pathing and use our search paths
# # relative pathing on the xml file itself is purely a nicety for Visual Studio to find the correct XSD for inline validation
# xmlSchema = os.path.basename(schemaLink)
# if xmlSchema:
# # check the template directory, the template include dir, and the folder that houses the nvdef file, and the xml's location for the xsd
# searchPaths = [os.path.dirname(templateFile)]
# searchPaths += [os.path.dirname(dataInputFile)]
# xmlShemaLoc = SearchPaths(xmlSchema, searchPaths)
# try:
# xmlSchemaDoc = etree.parse(xmlShemaLoc)
# xmlSchemaObj = etree.XMLSchema(xmlSchemaDoc, attribute_defaults=True)
# xmlSchemaObj.assertValid(xmlroot)
# except etree.DocumentInvalid as e:
# for error in e.error_log:
# PrintError('%s(%d) : error InvalidXML %s' % (os.path.abspath(dataInputFile), error.line, error.message))
# except IOError as e:
# PrintError('%s(%s) : %s' % (os.path.abspath(dataInputFile), str(1), e.message))
xmlroot = xml.getroot()
dataInputSet[dataInputFile] = xml.getroot()
treeRoots.append(xml.getroot())
else:
with open(dataInputFile) as jsonFile:
jsonData = json.load(jsonFile)
dataInputSet[dataInputFile] = jsonData
treeRoots.append(jsonData)
except IOError as e:
PrintError('%s(%s) : %s' % (fileinput.filename(), str(fileinput.filelineno()), e.message))
# except etree.XMLSyntaxError as e:
# for error in e.error_log:
# PrintError('%s(%s) : error XMLSyntaxError %s' % (os.path.abspath(dataInputFile), error.line, error.message))
compareFD = io.StringIO()
searchPaths = [os.path.dirname(templateFile)]
templateLoader = jinja2.FileSystemLoader(searchpath = searchPaths)
templateEnv = jinja2.Environment(bytecode_cache = templateCache, loader = templateLoader, trim_blocks = True, extensions = ["jinja2.ext.do",])
templateEnv.filters['relpath' ] = lambda x: os.path.relpath(x, outputPath)
templateEnv.filters['dirname' ] = os.path.dirname
templateEnv.filters['basename' ] = os.path.basename
templateEnv.filters['splitext' ] = os.path.splitext
templateEnv.filters['split' ] = os.path.split
templateEnv.filters['startswith' ] = str.startswith
templateEnv.filters['int' ] = int
templateEnv.filters['str' ] = str
templateEnv.filters['escape' ] = TransformEscape
templateEnv.filters['len' ] = len
templateEnv.filters['range' ] = range
templateEnv.filters['stripFloat' ] = StripFloat
templateEnv.filters['camelToHuman' ] = CamelToHuman
templateEnv.filters['booleanTrue' ] = BooleanTrue
templateEnv.filters['createHashGuid'] = CreateHashGuid
templateEnv.filters['etreeToString' ] = EtreeToString
templateJinja = templateEnv.get_template(os.path.basename(templateFile))
templateVars = \
{ \
"dataFiles" : treeRoots, \
"dataFileNames" : dataInputFiles, \
"templateName" : templateFile, \
"outputFile" : outputFile, \
"filename" : os.path.splitext(os.path.basename(outputFile))[0], \
}
try:
outputExtension = os.path.splitext(outputFile)[1]
if outputExtension == ".xml" or outputExtension == ".xhtml" or outputExtension == ".xsd":
compareFD.write('<?xml version="1.0"?>\n')
compareFD.write('<!-- All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or its licensors. -->\n')
compareFD.write('\n')
compareFD.write('<!-- For complete copyright and license terms please see the LICENSE at the root of this\n')
compareFD.write(' distribution (the "License"). All use of this software is governed by the License,\n')
compareFD.write(' or, if provided, by the license below or the license accompanying this file. Do not\n')
compareFD.write(' remove or modify any license notices. This file is distributed on an "AS IS" BASIS,\n')
compareFD.write(' WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -->\n')
compareFD.write('\n')
compareFD.write('<!-- This file is generated automatically at compile time, DO NOT EDIT BY HAND-->\n')
compareFD.write('<!-- Template Source {0}; XML Sources {1}-->\n'.format(templateFile, ', '.join(dataInputFiles)))
compareFD.write('\n')
elif outputExtension == ".lua":
compareFD.write('-- All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or its licensors.\n')
compareFD.write('\n')
compareFD.write('-- For complete copyright and license terms please see the LICENSE at the root of this\n')
compareFD.write('-- distribution (the "License"). All use of this software is governed by the License,\n')
compareFD.write('-- or, if provided, by the license below or the license accompanying this file. Do not\n')
compareFD.write('-- remove or modify any license notices. This file is distributed on an "AS IS" BASIS,\n')
compareFD.write('-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n')
compareFD.write('\n')
compareFD.write('-- This file is generated automatically at compile time, DO NOT EDIT BY HAND\n')
compareFD.write('-- Template Source {0}; XML Sources {1}\n'.format(templateFile, ', '.join(dataInputFiles)))
compareFD.write('\n')
elif outputExtension == ".h" or outputExtension == ".hpp" or outputExtension == ".inl" or outputExtension == ".c" or outputExtension == ".cpp":
compareFD.write('/*\n')
compareFD.write(' * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or its licensors.\n')
compareFD.write(' *\n')
compareFD.write(' * For complete copyright and license terms please see the LICENSE at the root of this\n')
compareFD.write(' * distribution (the "License"). All use of this software is governed by the License,\n')
compareFD.write(' * or, if provided, by the license below or the license accompanying this file. Do not\n')
compareFD.write(' * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,\n')
compareFD.write(' * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n')
compareFD.write(' *\n')
compareFD.write(' * This file is generated automatically at compile time, DO NOT EDIT BY HAND\n')
compareFD.write(' * Template Source {0}; Data Sources {1}\n'.format(templateFile, ', '.join(dataInputFiles)))
compareFD.write(' */\n')
compareFD.write('\n')
compareFD.write(templateJinja.render(templateVars))
compareFD.write('\n')
except jinja2.exceptions.TemplateNotFound as e:
PrintError('%s(1) : error TemplateNotFound %s' % (os.path.abspath(templateFile), e.message))
except IOError as e:
PrintError('%s(%s) : error I/O(%s) accessing %s : %s' % (fileinput.filename(), str(fileinput.filelineno()), e.errno, e.filename, e.strerror))
except jinja2.exceptions.TemplateSyntaxError as e:
PrintError('%s(%s) : error Template processing error: %s' % (os.path.abspath(e.filename), e.lineno, e.message))
except jinja2.exceptions.UndefinedError as e:
# Sadly, jinja doesn't provide the exact line of the template that had this error since the template is compiled directly to python code
PrintError('%s(1) : error Template processing error: %s with %s' % (os.path.abspath(templateFile), e.message, ', '.join([os.path.basename(dataInputFile) for dataInputFile in dataInputFiles])))
try:
os.makedirs(os.path.dirname(outputFile))
except OSError as e:
if e.errno == errno.EEXIST:
pass
else:
raise
try:
if os.path.isfile(outputFile):
with open(outputFile, 'r+') as currentFile:
currentFileStringData = currentFile.read()
if currentFileStringData == compareFD.getvalue():
if verbose == True:
print('Generated file %s is unchanged, skipping' % (outputFile))
else:
currentFile.truncate()
with open(outputFile, 'w+') as currentFile:
currentFile.write(compareFD.getvalue())
print('Generating %s with template %s and inputs %s' % (outputFile, templateFile, ", ".join(dataInputFiles)))
else:
with open(outputFile, 'w+') as outputFD:
outputFD.write(compareFD.getvalue())
print('Generating %s using template %s and inputs %s' % (outputFile, templateFile, ", ".join(dataInputFiles)))
except IOError as e:
PrintError('%s(%s) : error I/O(%s) accessing %s : %s' % (fileinput.filename(), str(fileinput.filelineno()), e.errno, e.filename, e.strerror))
except:
PrintError('%s(%s) : error Processing: %s' % (fileinput.filename(), str(fileinput.filelineno()), line))
PrintUnhandledExcptionInfo()
raise
compareFD.close()
def ProcessExpansionRule(sourceFiles, templateFiles, templateCache, outputDir, projectDir, expansionRule, dryrun, verbose, dataInputSet, outputFiles):
try:
# should be of the format inputFile(s),templateFile,outputFile, where inputFile and outputFile are subject to wildcarding and substitutions
expansionRuleSet = expansionRule.split(",")
inputFiles = expansionRuleSet[0]
templateFile = None
outputFile = expansionRuleSet[2]
for fullPathTemplate in templateFiles:
if expansionRuleSet[1] in fullPathTemplate:
templateFile = fullPathTemplate
break
if templateFile is None:
print("No matching template file found for %s, template may be missing from your _files.cmake" % expansionRuleSet[1])
return
# We have a few potential modes of input to output mapping that we'll have to handle depending on how the user formatted their azdef expansion rule
# if the data input file was explicit
# then output a single file for that explicit data
# else the data is wildcarded
# if the output contains $file or $fileprefix
# then we can generate a *unique* name for each data input, we're in one-to-one mapping mode, create a unique output for each input
# else if the output contains $path
# then we can generate a unique name for each *directory* of data inputs, we're in many-to-one mapping mode, create a unique output for each directory
# else the output is explicit, not wildcarded
# generate a single output file containing all matching data file's
# endif
# endif
testSingle = os.path.join(projectDir, inputFiles)
if os.path.isfile(testSingle):
# If we specified an *explicit* file to be processed (no wildcards for the data input file foo.json not *.foo.json), this is the branch that handles this case
# This is explicitly one-to-one mapping
dataInputFiles = [os.path.abspath(testSingle)]
outputFileAbsolute = outputFile.replace("$path", ComputeOutputPath(dataInputFiles, projectDir, outputDir))
outputFileAbsolute = outputFileAbsolute.replace("$fileprefix", os.path.splitext(os.path.basename(testSingle))[0].split(".")[0])
outputFileAbsolute = outputFileAbsolute.replace("$file", os.path.splitext(os.path.basename(testSingle))[0])
outputFileAbsolute = SanitizePath(outputFileAbsolute)
ProcessTemplateConversion(dataInputSet, dataInputFiles, templateFile, outputFileAbsolute, templateCache, dryrun, verbose)
outputFiles.append(outputFileAbsolute)
else:
# We've wildcarded the data input field, so we may have to handle one-to-one mapping of data files to output, or many-to-one mapping of data files to output
if "$fileprefix" in outputFile or "$file" in outputFile:
# Due to the wildcards in the output file, we've determined we'll do a one-to-one mapping of data files to output
for filename in fnmatch.filter(sourceFiles, inputFiles):
dataInputFiles = [os.path.abspath(filename)]
outputFileAbsolute = outputFile.replace("$path", ComputeOutputPath(dataInputFiles, projectDir, outputDir))
outputFileAbsolute = outputFileAbsolute.replace("$fileprefix", os.path.splitext(os.path.basename(filename))[0].split(".")[0])
outputFileAbsolute = outputFileAbsolute.replace("$file", os.path.splitext(os.path.basename(filename))[0])
outputFileAbsolute = SanitizePath(outputFileAbsolute)
ProcessTemplateConversion(dataInputSet, dataInputFiles, templateFile, outputFileAbsolute, templateCache, dryrun, verbose)
outputFiles.append(outputFileAbsolute)
else:
# Process all matches in one batch
# Due to the lack of wildcards in the output file, we've determined we'll glob all matching input files into the template conversion
for filename in fnmatch.filter(sourceFiles, inputFiles):
dataInputFiles = [os.path.abspath(file) for file in fnmatch.filter(sourceFiles, inputFiles)]
outputFileAbsolute = outputFile.replace("$path", ComputeOutputPath(dataInputFiles, projectDir, outputDir))
outputFileAbsolute = SanitizePath(outputFileAbsolute)
ProcessTemplateConversion(dataInputSet, dataInputFiles, templateFile, outputFileAbsolute, templateCache, dryrun, verbose)
outputFiles.append(outputFileAbsolute)
except IOError as e:
PrintError('%s : error I/O(%s) accessing %s : %s' % (expansionRule, e.errno, e.filename, e.strerror))
except:
PrintError('%s : error Processing expansion rule' % expansionRule)
PrintUnhandledExcptionInfo()
raise
def ExecuteExpansionRules(cacheDir, outputDir, projectDir, inputFiles, expansionRules, dryrun, verbose, dataInputSet, outputFiles):
# Get Globals
global MAX_ERRORS
global errorCount
currentPath = os.getcwd()
startTime = time.time()
# Ensure jinja2 template cache dir actually exists...
try:
os.makedirs(cacheDir)
except OSError as e:
if e.errno == errno.EEXIST:
pass
else:
raise
sourceFiles = []
templateFiles = []
for inputFile in inputFiles:
if inputFile.endswith(".xml") or inputFile.endswith(".json"):
sourceFiles.append(os.path.join(projectDir, inputFile))
elif inputFile.endswith(".jinja"):
templateFiles.append(os.path.join(projectDir, inputFile))
templateCache = jinja2.FileSystemBytecodeCache(cacheDir)
for expansionRule in expansionRules:
ProcessExpansionRule(sourceFiles, templateFiles, templateCache, outputDir, projectDir, expansionRule, dryrun, verbose, dataInputSet, outputFiles)
if not dryrun:
elapsedTime = time.time() - startTime
millis = int(round(elapsedTime * 10))
m, s = divmod(elapsedTime, 60)
h, m = divmod(m, 60)
print('Total Time %d:%02d:%02d.%02d' % (h, m, s, millis))
# Return true on success
return errorCount == 0
# Main Function
if __name__ == '__main__':
# setup our command syntax
parser = argparse.ArgumentParser()
parser.add_argument("cacheDir", help="location to store jinja template cache files")
parser.add_argument("outputDir", help="location to output generated files")
parser.add_argument("projectDir", help="location to build directory against")
parser.add_argument("inputFiles", help="set of files to run azcg expansion rules against")
parser.add_argument("expansionRules", help="set of azcg expansion rules for matching data files to template files")
parser.add_argument("-n", "--dryrun", action='store_true', help="does not execute autogen, only outputs the set of files that autogen would generate")
parser.add_argument("-v", "--verbose", action='store_true', help="output only the set of files that would be generated by an expansion run")
parser.add_argument("-p", "--pythonPaths", action='append', nargs='+', default=[""],
help="set of additional python paths to use for module imports")
args = parser.parse_args()
pythonPaths = args.pythonPaths
cacheDir = args.cacheDir
outputDir = args.outputDir
projectDir = args.projectDir
inputFiles = args.inputFiles.split(";")
expansionRules = args.expansionRules.split(";")
dryrun = args.dryrun
verbose = args.verbose
cacheDir = os.path.abspath(SanitizePath(cacheDir))
outputDir = os.path.abspath(SanitizePath(outputDir))
projectDir = os.path.abspath(SanitizePath(projectDir))
# Import 3rd party modules
for pythonPath in pythonPaths:
sys.path.append(pythonPath)
import jinja2
#from lxml import etree
import xml.etree.cElementTree as etree
import json
dataInputSet = {}
outputFiles = []
autoGenResult = ExecuteExpansionRules(cacheDir, outputDir, projectDir, inputFiles, expansionRules, dryrun, verbose, dataInputSet, outputFiles)
if dryrun:
print("%s" % ';'.join(outputFiles))
if autoGenResult:
sys.exit(0)
else:
sys.exit(1)
+19
View File
@@ -0,0 +1,19 @@
#
# 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.
#
cmake_minimum_required(VERSION 3.0)
ly_add_target(
NAME AzAutoGen HEADERONLY
NAMESPACE AZ
FILES_CMAKE
azautogen_files.cmake
)
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
AzAutoGen.py
)
+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;
};
}

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