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();
}
}