Merge branch 'development' into Atom/santorac/RemixableMaterialTypes3

There were lots of material system conflicts that had to be resolved. I expect the build is broken at this commit, and I'll fix it in followup commits.

Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
This commit is contained in:
santorac
2022-01-25 14:47:24 -08:00
9789 changed files with 757680 additions and 902265 deletions
@@ -89,6 +89,9 @@ namespace AZ
// Tracks the asset type used to create the instance.
AssetType m_assetType;
// Boolean to indicate if the instance has been orphaned from the instance database
bool m_isOrphaned = false;
};
/// @cond EXCLUDE_DOCS
@@ -17,6 +17,11 @@
#include <AzCore/Module/Environment.h>
#include <AzCore/std/parallel/shared_mutex.h>
namespace AZStd
{
class any;
}
namespace AZ
{
namespace Data
@@ -203,6 +208,16 @@ namespace AZ
//! Calls FindOrCreate using a random InstanceId
Data::Instance<Type> Create(const Asset<AssetData>& asset, const AZStd::any* param = nullptr);
/**
* Removes the instance data from the database. Does not release it.
* References to existing instances will remain valid, but new calls to Create/FindOrCreate will create a new instance
* This function is temporary, to provide functionality needed for Model hot-reloading, but will be removed
* once the Model class does not need it anymore.
*
* @param id The id of the instance to remove
*/
void TEMPOrphan(const InstanceId& id);
private:
InstanceDatabase(const AssetType& assetType);
~InstanceDatabase();
@@ -356,6 +371,20 @@ namespace AZ
return FindOrCreate(Data::InstanceId::CreateRandom(), asset, param);
}
template<typename Type>
void InstanceDatabase<Type>::TEMPOrphan(const InstanceId& id)
{
AZStd::scoped_lock<AZStd::recursive_mutex> lock(m_databaseMutex);
// Check if the instance is still in the database, in case it was orphaned twice
auto instanceItr = m_database.find(id);
if (instanceItr != m_database.end())
{
// Mark the instance as orphaned, and remove it from the database
instanceItr->second->m_isOrphaned = true;
m_database.erase(instanceItr);
}
}
template<typename Type>
void InstanceDatabase<Type>::ReleaseInstance(InstanceData* instance, const InstanceId& instanceId)
{
@@ -374,6 +403,12 @@ namespace AZ
m_database.erase(instance->GetId());
m_instanceHandler.m_deleteFunction(static_cast<Type*>(instance));
}
else if (instance->m_isOrphaned && instance->m_useCount.compare_exchange_strong(expectedRefCount, -1))
{
// If the instance was orphaned, it has already been removed from the database,
// but still needs to be deleted when the refcount drops to 0
m_instanceHandler.m_deleteFunction(static_cast<Type*>(instance));
}
}
template<typename Type>
@@ -181,7 +181,76 @@ namespace UnitTest
EXPECT_EQ(instance, instance3);
}
void ParallelInstanceCreateHelper(size_t threadCountMax, size_t assetIdCount, size_t durationSeconds)
TEST_F(InstanceDatabaseTest, InstanceOrphan)
{
auto& assetManager = AssetManager::Instance();
auto& instanceDatabase = InstanceDatabase<TestInstanceA>::Instance();
Asset<TestAssetType> someAsset = assetManager.CreateAsset<TestAssetType>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
Instance<TestInstanceA> orphanedInstance = instanceDatabase.FindOrCreate(s_instanceId0, someAsset);
EXPECT_NE(orphanedInstance, nullptr);
instanceDatabase.TEMPOrphan(s_instanceId0);
// After orphan, the instance should not be found in the database, but it should still be valid
EXPECT_EQ(instanceDatabase.Find(s_instanceId0), nullptr);
EXPECT_NE(orphanedInstance, nullptr);
instanceDatabase.TEMPOrphan(s_instanceId0);
// Orphaning twice should be a no-op
EXPECT_EQ(instanceDatabase.Find(s_instanceId0), nullptr);
EXPECT_NE(orphanedInstance, nullptr);
Instance<TestInstanceA> instance2 = instanceDatabase.FindOrCreate(s_instanceId0, someAsset);
// Creating another instance with the same id should return a different instance than the one that was orphaned
EXPECT_NE(orphanedInstance, instance2);
}
enum class ParallelInstanceTestCases
{
Create,
CreateAndDeferRemoval,
CreateAndOrphan,
CreateDeferRemovalAndOrphan
};
enum class ParralleInstanceCurrentAction
{
Create,
DeferredRemoval,
Orphan
};
ParralleInstanceCurrentAction ParallelInstanceGetCurrentAction(ParallelInstanceTestCases testCase)
{
switch (testCase)
{
case ParallelInstanceTestCases::CreateAndDeferRemoval:
switch (rand() % 2)
{
case 0: return ParralleInstanceCurrentAction::Create;
case 1: return ParralleInstanceCurrentAction::DeferredRemoval;
}
case ParallelInstanceTestCases::CreateAndOrphan:
switch (rand() % 2)
{
case 0: return ParralleInstanceCurrentAction::Create;
case 1: return ParralleInstanceCurrentAction::Orphan;
}
case ParallelInstanceTestCases::CreateDeferRemovalAndOrphan:
switch (rand() % 3)
{
case 0: return ParralleInstanceCurrentAction::Create;
case 1: return ParralleInstanceCurrentAction::DeferredRemoval;
case 2: return ParralleInstanceCurrentAction::Orphan;
}
case ParallelInstanceTestCases::Create:
default:
return ParralleInstanceCurrentAction::Create;
}
}
void ParallelInstanceCreateHelper(size_t threadCountMax, size_t assetIdCount, float durationSeconds, ParallelInstanceTestCases testCase)
{
printf("Testing threads=%zu assetIds=%zu ... ", threadCountMax, assetIdCount);
@@ -192,6 +261,7 @@ namespace UnitTest
auto& instanceManager = InstanceDatabase<TestInstanceA>::Instance();
AZStd::vector<Uuid> guids;
AZStd::vector<Data::Instance<Data::InstanceData>> instances;
AZStd::vector<Asset<TestAssetType>> assets;
for (size_t i = 0; i < assetIdCount; ++i)
@@ -199,6 +269,7 @@ namespace UnitTest
Uuid guid = Uuid::CreateRandom();
guids.emplace_back(guid);
instances.emplace_back(nullptr);
// 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));
@@ -206,6 +277,7 @@ namespace UnitTest
AZStd::vector<AZStd::thread> threads;
AZStd::mutex mutex;
AZStd::mutex referenceTableMutex;
AZStd::atomic<int> threadCount((int)threadCountMax);
AZStd::condition_variable cv;
AZStd::atomic_bool keepDispatching(true);
@@ -225,11 +297,15 @@ namespace UnitTest
for (size_t i = 0; i < threadCountMax; ++i)
{
threads.emplace_back(
[&instanceManager, &threadCount, &cv, &guids, &assets, &durationSeconds]()
[&instanceManager, &threadCount, &cv, &guids, &instances, &assets, &durationSeconds, &testCase, &referenceTableMutex]()
{
AZ::Debug::Timer timer;
timer.Stamp();
bool deferRemoval = testCase == ParallelInstanceTestCases::CreateAndDeferRemoval ||
testCase == ParallelInstanceTestCases::CreateDeferRemovalAndOrphan
? true : false;
while (timer.GetDeltaTimeInSeconds() < durationSeconds)
{
const size_t index = rand() % guids.size();
@@ -237,11 +313,36 @@ namespace UnitTest
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]);
ParralleInstanceCurrentAction currentAction = ParallelInstanceGetCurrentAction(testCase);
if (currentAction == ParralleInstanceCurrentAction::Orphan)
{
// Orphan the instance, but don't decrease its refcount
instanceManager.TEMPOrphan(instanceId);
}
else if (currentAction == ParralleInstanceCurrentAction::DeferredRemoval)
{
// Drop the refcount to zero so the instance will be released
referenceTableMutex.lock();
instances[index] = nullptr;
referenceTableMutex.unlock();
}
else
{
// Otherwise, add a new instance
Instance<TestInstanceA> instance = instanceManager.FindOrCreate(instanceId, assets[index]);
EXPECT_NE(instance, nullptr);
EXPECT_EQ(instance->GetId(), instanceId);
EXPECT_EQ(instance->m_asset, assets[index]);
if (deferRemoval)
{
// Keep a reference to the instance alive so it can be removed later
referenceTableMutex.lock();
instances[index] = instance;
referenceTableMutex.unlock();
}
}
}
threadCount--;
@@ -254,10 +355,12 @@ namespace UnitTest
// Used to detect a deadlock. If we wait for more than 10 seconds, it's likely a deadlock has occurred
while (threadCount > 0 && !timedOut)
{
size_t durationSecondsRoundedUp = static_cast<size_t>(std::ceil(durationSeconds));
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)));
cv.wait_until(lock, AZStd::chrono::system_clock::now() + AZStd::chrono::seconds(durationSecondsRoundedUp * 2)));
}
EXPECT_TRUE(threadCount == 0) << "One or more threads appear to be deadlocked at " << timer.GetDeltaTimeInSeconds() << " seconds";
@@ -273,11 +376,11 @@ namespace UnitTest
printf("Took %f seconds\n", timer.GetDeltaTimeInSeconds());
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreate)
void ParallelCreateTest(ParallelInstanceTestCases testCase)
{
// This is the original test scenario from when InstanceDatabase was first implemented
// threads, AssetIds, seconds
ParallelInstanceCreateHelper(8, 100, 5);
ParallelInstanceCreateHelper(8, 100, 5, testCase);
// 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;
@@ -289,11 +392,11 @@ namespace UnitTest
// 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;
const float duration = 2.0f;
// threads, AssetIds, seconds
ParallelInstanceCreateHelper(2, 1, duration);
ParallelInstanceCreateHelper(4, 1, duration);
ParallelInstanceCreateHelper(8, 1, duration);
ParallelInstanceCreateHelper(2, 1, duration, testCase);
ParallelInstanceCreateHelper(4, 1, duration, testCase);
ParallelInstanceCreateHelper(8, 1, duration, testCase);
}
for (size_t i = 0; i < attempts; ++i)
@@ -301,19 +404,39 @@ namespace UnitTest
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;
const float duration = 2.0f;
// 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);
ParallelInstanceCreateHelper(2, 1, duration, testCase);
ParallelInstanceCreateHelper(4, 1, duration, testCase);
ParallelInstanceCreateHelper(4, 2, duration, testCase);
ParallelInstanceCreateHelper(4, 4, duration, testCase);
ParallelInstanceCreateHelper(8, 1, duration, testCase);
ParallelInstanceCreateHelper(8, 2, duration, testCase);
ParallelInstanceCreateHelper(8, 3, duration, testCase);
ParallelInstanceCreateHelper(8, 4, duration, testCase);
}
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreate)
{
ParallelCreateTest(ParallelInstanceTestCases::Create);
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreateAndDeferRemoval)
{
ParallelCreateTest(ParallelInstanceTestCases::CreateAndDeferRemoval);
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreateAndOrphan)
{
ParallelCreateTest(ParallelInstanceTestCases::CreateAndOrphan);
}
TEST_F(InstanceDatabaseTest, ParallelInstanceCreateDeferRemovalAndOrphan)
{
ParallelCreateTest(ParallelInstanceTestCases::CreateDeferRemovalAndOrphan);
}
TEST_F(InstanceDatabaseTest, InstanceCreateNoDatabase)
{
bool m_deleted = false;
+2 -3
View File
@@ -7,7 +7,6 @@
*/
#include <AzCore/Debug/Timer.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/std/typetraits/typetraits.h>
#include <AzCore/UnitTest/TestTypes.h>
@@ -38,7 +37,7 @@ namespace AZ
using namespace AZ;
// Handle asserts
class TraceDrillerHook
class TestEnvironmentHook
: public AZ::Test::ITestEnvironment
, public UnitTest::TraceBusRedirector
{
@@ -58,5 +57,5 @@ public:
}
};
AZ_UNIT_TEST_HOOK(new TraceDrillerHook());
AZ_UNIT_TEST_HOOK(new TestEnvironmentHook());
@@ -64,7 +64,7 @@ public class LumberyardActivity extends NativeActivity
////////////////////////////////////////////////////////////////
// called from the native to get the application package name
// e.g. com.lumberyard.samples for SamplesProject
// e.g. org.o3de.samples for SamplesProject
public String GetPackageName()
{
return getApplicationContext().getPackageName();
@@ -116,7 +116,7 @@ namespace AZ
const char* GetObbStoragePath() const { return m_obbStoragePath.c_str(); }
//! Get the dot separated package name for the current application.
//! e.g. com.lumberyard.samples for SamplesProject
//! e.g. org.o3de.samples for SamplesProject
const char* GetPackageName() const { return m_packageName.c_str(); }
//! Get the app version code (android:versionCode in the manifest).
+1 -1
View File
@@ -52,7 +52,7 @@ namespace AZ
const char* GetObbStoragePath();
//! Get the dot separated package name for the current application.
//! e.g. com.o3de.samples for SamplesProject
//! e.g. org.o3de.samples for SamplesProject
const char* GetPackageName();
//! Get the app version code (android:versionCode in the manifest).
+414 -417
View File
@@ -14,453 +14,450 @@
#include <AzCore/std/parallel/lock.h>
#include <AzCore/std/string/conversions.h>
namespace AZ
namespace AZ::Data
{
namespace Data
AssetFilterInfo::AssetFilterInfo(const AssetId& id, const AssetType& assetType, AssetLoadBehavior loadBehavior)
: m_assetId(id)
, m_assetType(assetType)
, m_loadBehavior(loadBehavior)
{
AssetFilterInfo::AssetFilterInfo(const AssetId& id, const AssetType& assetType, AssetLoadBehavior loadBehavior)
: m_assetId(id)
, m_assetType(assetType)
, m_loadBehavior(loadBehavior)
}
AssetFilterInfo::AssetFilterInfo(const Asset<AssetData>& asset)
: m_assetId(asset.GetId())
, m_assetType(asset.GetType())
, m_loadBehavior(asset.GetAutoLoadBehavior())
{
}
AssetId AssetId::CreateString(AZStd::string_view input)
{
size_t separatorIdx = input.find(':');
if (separatorIdx == AZStd::string_view::npos)
{
return AssetId();
}
AssetFilterInfo::AssetFilterInfo(const Asset<AssetData>& asset)
: m_assetId(asset.GetId())
, m_assetType(asset.GetType())
, m_loadBehavior(asset.GetAutoLoadBehavior())
AssetId assetId;
assetId.m_guid = Uuid::CreateString(input.data(), separatorIdx);
if (assetId.m_guid.IsNull())
{
return AssetId();
}
assetId.m_subId = strtoul(&input[separatorIdx + 1], nullptr, 16);
AssetId AssetId::CreateString(AZStd::string_view input)
return assetId;
}
void AssetId::Reflect(AZ::ReflectContext* context)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
size_t separatorIdx = input.find(':');
if (separatorIdx == AZStd::string_view::npos)
{
return AssetId();
}
AssetId assetId;
assetId.m_guid = Uuid::CreateString(input.data(), separatorIdx);
if (assetId.m_guid.IsNull())
{
return AssetId();
}
assetId.m_subId = strtoul(&input[separatorIdx + 1], nullptr, 16);
return assetId;
serializeContext->Class<Data::AssetId>()
->Version(1)
->Field("guid", &Data::AssetId::m_guid)
->Field("subId", &Data::AssetId::m_subId)
;
}
void AssetId::Reflect(AZ::ReflectContext* context)
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<Data::AssetId>()
->Version(1)
->Field("guid", &Data::AssetId::m_guid)
->Field("subId", &Data::AssetId::m_subId)
;
}
behaviorContext->Class<Data::AssetId>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Constructor()
->Constructor<const Uuid&, u32>()
->Method("CreateString", &Data::AssetId::CreateString)
->Method("IsValid", &Data::AssetId::IsValid)
->Attribute(AZ::Script::Attributes::Alias, "is_valid")
->Method("ToString", [](const Data::AssetId* self) { return self->ToString<AZStd::string>(); })
->Attribute(AZ::Script::Attributes::Alias, "to_string")
->Method("IsEqual", [](const Data::AssetId& self, const Data::AssetId& other) { return self == other; })
->Attribute(AZ::Script::Attributes::Alias, "is_equal")
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)
;
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->Class<Data::AssetId>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Constructor()
->Constructor<const Uuid&, u32>()
->Method("CreateString", &Data::AssetId::CreateString)
->Method("IsValid", &Data::AssetId::IsValid)
->Attribute(AZ::Script::Attributes::Alias, "is_valid")
->Method("ToString", [](const Data::AssetId* self) { return self->ToString<AZStd::string>(); })
->Attribute(AZ::Script::Attributes::Alias, "to_string")
->Method("IsEqual", [](const Data::AssetId& self, const Data::AssetId& other) { return self == other; })
->Attribute(AZ::Script::Attributes::Alias, "is_equal")
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)
;
behaviorContext->Class<Data::AssetInfo>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Property("assetId", BehaviorValueGetter(&Data::AssetInfo::m_assetId), nullptr)
->Property("assetType", BehaviorValueGetter(&Data::AssetInfo::m_assetType), nullptr)
->Property("sizeBytes", BehaviorValueGetter(&Data::AssetInfo::m_sizeBytes), nullptr)
->Property("relativePath", BehaviorValueGetter(&Data::AssetInfo::m_relativePath), nullptr)
;
}
}
behaviorContext->Class<Data::AssetInfo>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Property("assetId", BehaviorValueGetter(&Data::AssetInfo::m_assetId), nullptr)
->Property("assetType", BehaviorValueGetter(&Data::AssetInfo::m_assetType), nullptr)
->Property("sizeBytes", BehaviorValueGetter(&Data::AssetInfo::m_sizeBytes), nullptr)
->Property("relativePath", BehaviorValueGetter(&Data::AssetInfo::m_relativePath), nullptr)
;
}
namespace AssetInternal
{
Asset<AssetData> FindOrCreateAsset(const AssetId& id, const AssetType& type, AssetLoadBehavior assetReferenceLoadBehavior)
{
return AssetManager::Instance().FindOrCreateAsset(id, type, assetReferenceLoadBehavior);
}
namespace AssetInternal
Asset<AssetData> GetAsset(const AssetId& id, const AssetType& type, AssetLoadBehavior assetReferenceLoadBehavior,
const AssetLoadParameters& loadParams)
{
Asset<AssetData> FindOrCreateAsset(const AssetId& id, const AssetType& type, AssetLoadBehavior assetReferenceLoadBehavior)
{
return AssetManager::Instance().FindOrCreateAsset(id, type, assetReferenceLoadBehavior);
}
Asset<AssetData> GetAsset(const AssetId& id, const AssetType& type, AssetLoadBehavior assetReferenceLoadBehavior,
const AssetLoadParameters& loadParams)
{
return AssetManager::Instance().GetAsset(id, type, assetReferenceLoadBehavior, loadParams);
}
AssetData::AssetStatus BlockUntilLoadComplete(const Asset<AssetData>& asset)
{
return AssetManager::Instance().BlockUntilLoadComplete(asset);
}
void UpdateAssetInfo(AssetId& id, AZStd::string& assetHint)
{
// it is possible that the assetID given is legacy / old and we have a new assetId we can use instead for it.
// in that case, upgrade the AssetID to the new one, so that future saves are in the new format.
// this function should only be invoked if the feature is turned on in the asset manager as it can be (slightly) expensive
if ((!AssetManager::IsReady()) || (!AssetManager::Instance().GetAssetInfoUpgradingEnabled()))
{
return;
}
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, id);
if (assetInfo.m_assetId.IsValid())
{
id = assetInfo.m_assetId;
if (!assetInfo.m_relativePath.empty())
{
assetHint = assetInfo.m_relativePath;
}
}
}
bool ReloadAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior)
{
AssetManager::Instance().ReloadAsset(assetData->GetId(), assetReferenceLoadBehavior);
return true;
}
bool SaveAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior)
{
AssetManager::Instance().SaveAsset({ assetData, assetReferenceLoadBehavior });
return true;
}
Asset<AssetData> GetAssetData(const AssetId& id, AssetLoadBehavior assetReferenceLoadBehavior)
{
if (AssetManager::IsReady())
{
AZStd::lock_guard<AZStd::recursive_mutex> assetLock(AssetManager::Instance().m_assetMutex);
auto it = AssetManager::Instance().m_assets.find(id);
if (it != AssetManager::Instance().m_assets.end())
{
return { it->second, assetReferenceLoadBehavior };
}
}
return {};
}
AssetId ResolveAssetId(const AssetId& id)
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, id);
if (assetInfo.m_assetId.IsValid())
{
return assetInfo.m_assetId;
}
else
{
return id;
}
}
return AssetManager::Instance().GetAsset(id, type, assetReferenceLoadBehavior, loadParams);
}
AssetData::~AssetData()
AssetData::AssetStatus BlockUntilLoadComplete(const Asset<AssetData>& asset)
{
UnregisterWithHandler();
return AssetManager::Instance().BlockUntilLoadComplete(asset);
}
void AssetData::Reflect(AZ::ReflectContext* context)
void UpdateAssetInfo(AssetId& id, AZStd::string& assetHint)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
// it is possible that the assetID given is legacy / old and we have a new assetId we can use instead for it.
// in that case, upgrade the AssetID to the new one, so that future saves are in the new format.
// this function should only be invoked if the feature is turned on in the asset manager as it can be (slightly) expensive
if ((!AssetManager::IsReady()) || (!AssetManager::Instance().GetAssetInfoUpgradingEnabled()))
{
serializeContext->Class<AZ::Data::AssetData>()
->Version(1)
;
}
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->Class<AssetData>("AssetData")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Method("IsReady", &AssetData::IsReady)
->Attribute(AZ::Script::Attributes::Alias, "is_ready")
->Method("IsError", &AssetData::IsError)
->Attribute(AZ::Script::Attributes::Alias, "is_error")
->Method("IsLoading", &AssetData::IsLoading)
->Attribute(AZ::Script::Attributes::Alias, "is_loading")
->Method("GetId", &AssetData::GetId)
->Attribute(AZ::Script::Attributes::Alias, "get_id")
->Method("GetUseCount", &AssetData::GetUseCount)
->Attribute(AZ::Script::Attributes::Alias, "get_use_count")
;
}
}
void AssetData::Acquire()
{
AZ_Assert(m_useCount >= 0, "AssetData has been deleted");
AcquireWeak();
++m_useCount;
}
void AssetData::Release()
{
AZ_Assert(m_useCount > 0, "Usecount is already 0!");
if (m_useCount.fetch_sub(1) == 1)
{
if (AssetManager::IsReady())
{
AssetManager::Instance().OnAssetUnused(this);
}
else
{
AZ_Assert(false, "Attempting to release asset after AssetManager has been destroyed!");
}
}
ReleaseWeak();
}
void AssetData::AcquireWeak()
{
AZ_Assert(m_useCount >= 0, "AssetData has been deleted");
++m_weakUseCount;
}
void AssetData::ReleaseWeak()
{
AZ_Assert(m_weakUseCount > 0, "WeakUseCount is already 0");
AssetId assetId = m_assetId;
int creationToken = m_creationToken;
AssetType assetType = GetType();
bool removeFromHash = IsRegisterReadonlyAndShareable();
// default creation token implies that the asset was not created by the asset manager and therefore it cannot be in the asset map.
removeFromHash = creationToken == s_defaultCreationToken ? false : removeFromHash;
if (m_weakUseCount.fetch_sub(1) == 1)
{
if (AssetManager::IsReady())
{
AssetManager::Instance().ReleaseAsset(this, assetId, assetType, removeFromHash, creationToken);
}
else
{
AZ_Assert(false, "Attempting to release asset after AssetManager has been destroyed!");
}
}
}
bool AssetData::IsLoading(bool includeQueued) const
{
auto curStatus = GetStatus();
return(curStatus == AssetStatus::Loading || curStatus == AssetStatus::LoadedPreReady || curStatus==AssetStatus::StreamReady ||
(includeQueued && curStatus == AssetStatus::Queued));
}
void AssetData::RegisterWithHandler(AssetHandler* handler)
{
if (!handler)
{
AZ_Error("AssetData", false, "No handler to register with");
return;
}
m_registeredHandler = handler;
}
void AssetData::UnregisterWithHandler()
{
if (m_registeredHandler)
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, id);
if (assetInfo.m_assetId.IsValid())
{
m_registeredHandler = nullptr;
}
}
bool AssetData::GetFlag(const AssetDataFlags& checkFlag) const
{
return m_flags[aznumeric_cast<AZStd::size_t>(checkFlag)];
}
void AssetData::SetFlag(const AssetDataFlags& checkFlag, bool setValue)
{
m_flags.set(aznumeric_cast<AZStd::size_t>(checkFlag), setValue);
}
bool AssetData::GetRequeue() const
{
return GetFlag(AssetDataFlags::Requeue);
}
void AssetData::SetRequeue(bool requeue)
{
SetFlag(AssetDataFlags::Requeue, requeue);
}
void AssetBusCallbacks::SetCallbacks(const AssetReadyCB& readyCB, const AssetMovedCB& movedCB, const AssetReloadedCB& reloadedCB,
const AssetSavedCB& savedCB, const AssetUnloadedCB& unloadedCB, const AssetErrorCB& errorCB, const AssetCanceledCB& cancelCB)
{
m_onAssetReadyCB = readyCB;
m_onAssetMovedCB = movedCB;
m_onAssetReloadedCB = reloadedCB;
m_onAssetSavedCB = savedCB;
m_onAssetUnloadedCB = unloadedCB;
m_onAssetErrorCB = errorCB;
m_onAssetCanceledCB = cancelCB;
}
void AssetBusCallbacks::ClearCallbacks()
{
SetCallbacks(AssetBusCallbacks::AssetReadyCB(),
AssetBusCallbacks::AssetMovedCB(),
AssetBusCallbacks::AssetReloadedCB(),
AssetBusCallbacks::AssetSavedCB(),
AssetBusCallbacks::AssetUnloadedCB(),
AssetBusCallbacks::AssetErrorCB(),
AssetBusCallbacks::AssetCanceledCB());
}
void AssetBusCallbacks::SetOnAssetReadyCallback(const AssetReadyCB& readyCB)
{
m_onAssetReadyCB = readyCB;
}
void AssetBusCallbacks::SetOnAssetMovedCallback(const AssetMovedCB& movedCB)
{
m_onAssetMovedCB = movedCB;
}
void AssetBusCallbacks::SetOnAssetReloadedCallback(const AssetReloadedCB& reloadedCB)
{
m_onAssetReloadedCB = reloadedCB;
}
void AssetBusCallbacks::SetOnAssetSavedCallback(const AssetSavedCB& savedCB)
{
m_onAssetSavedCB = savedCB;
}
void AssetBusCallbacks::SetOnAssetUnloadedCallback(const AssetUnloadedCB& unloadedCB)
{
m_onAssetUnloadedCB = unloadedCB;
}
void AssetBusCallbacks::SetOnAssetErrorCallback(const AssetErrorCB& errorCB)
{
m_onAssetErrorCB = errorCB;
}
void AssetBusCallbacks::SetOnAssetCanceledCallback(const AssetCanceledCB& cancelCB)
{
m_onAssetCanceledCB = cancelCB;
}
void AssetBusCallbacks::OnAssetReady(Asset<AssetData> asset)
{
if (m_onAssetReadyCB)
{
m_onAssetReadyCB(asset, *this);
}
}
void AssetBusCallbacks::OnAssetMoved(Asset<AssetData> asset, void* oldDataPointer)
{
if (m_onAssetMovedCB)
{
m_onAssetMovedCB(asset, oldDataPointer, *this);
}
}
void AssetBusCallbacks::OnAssetReloaded(Asset<AssetData> asset)
{
if (m_onAssetReloadedCB)
{
m_onAssetReloadedCB(asset, *this);
}
}
void AssetBusCallbacks::OnAssetSaved(Asset<AssetData> asset, bool isSuccessful)
{
if (m_onAssetSavedCB)
{
m_onAssetSavedCB(asset, isSuccessful, *this);
}
}
void AssetBusCallbacks::OnAssetUnloaded(const AssetId assetId, const AssetType assetType)
{
if (m_onAssetUnloadedCB)
{
m_onAssetUnloadedCB(assetId, assetType, *this);
}
}
void AssetBusCallbacks::OnAssetError(Asset<AssetData> asset)
{
if (m_onAssetErrorCB)
{
m_onAssetErrorCB(asset, *this);
}
}
void AssetBusCallbacks::OnAssetCanceled(const AssetId assetId)
{
if (m_onAssetCanceledCB)
{
m_onAssetCanceledCB(assetId, *this);
}
}
/*static*/ bool AssetFilterNoAssetLoading([[maybe_unused]] const AssetFilterInfo& filterInfo)
{
return false;
}
namespace ProductDependencyInfo
{
AZ::Data::AssetLoadBehavior LoadBehaviorFromFlags(const ProductDependencyFlags& dependencyFlags)
{
AZ::u8 loadBehaviorValue = 0;
for (AZ::u8 thisFlag = aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorLow);
thisFlag <= aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorHigh); ++thisFlag)
id = assetInfo.m_assetId;
if (!assetInfo.m_relativePath.empty())
{
if (dependencyFlags[thisFlag])
{
loadBehaviorValue |= (1 << thisFlag);
}
assetHint = assetInfo.m_relativePath;
}
return static_cast<AZ::Data::AssetLoadBehavior>(loadBehaviorValue);
}
ProductDependencyFlags CreateFlags(AZ::Data::AssetLoadBehavior autoLoadBehavior)
{
AZ::Data::ProductDependencyInfo::ProductDependencyFlags returnFlags;
AZ::u8 loadBehavior = aznumeric_caster(autoLoadBehavior);
for (AZ::u8 thisFlag = aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorLow);
thisFlag <= aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorHigh); ++thisFlag)
{
if (loadBehavior & (1 << thisFlag))
{
returnFlags[thisFlag] = true;
}
}
return returnFlags;
}
}
} // namespace Data
} // namespace AZ
bool ReloadAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior)
{
AssetManager::Instance().ReloadAsset(assetData->GetId(), assetReferenceLoadBehavior);
return true;
}
bool SaveAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior)
{
AssetManager::Instance().SaveAsset({ assetData, assetReferenceLoadBehavior });
return true;
}
Asset<AssetData> GetAssetData(const AssetId& id, AssetLoadBehavior assetReferenceLoadBehavior)
{
if (AssetManager::IsReady())
{
AZStd::lock_guard<AZStd::recursive_mutex> assetLock(AssetManager::Instance().m_assetMutex);
auto it = AssetManager::Instance().m_assets.find(id);
if (it != AssetManager::Instance().m_assets.end())
{
return { it->second, assetReferenceLoadBehavior };
}
}
return {};
}
AssetId ResolveAssetId(const AssetId& id)
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, id);
if (assetInfo.m_assetId.IsValid())
{
return assetInfo.m_assetId;
}
else
{
return id;
}
}
}
AssetData::~AssetData()
{
UnregisterWithHandler();
}
void AssetData::Reflect(AZ::ReflectContext* context)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<AZ::Data::AssetData>()
->Version(1)
;
}
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->Class<AssetData>("AssetData")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Asset")
->Attribute(AZ::Script::Attributes::Module, "asset")
->Method("IsReady", &AssetData::IsReady)
->Attribute(AZ::Script::Attributes::Alias, "is_ready")
->Method("IsError", &AssetData::IsError)
->Attribute(AZ::Script::Attributes::Alias, "is_error")
->Method("IsLoading", &AssetData::IsLoading)
->Attribute(AZ::Script::Attributes::Alias, "is_loading")
->Method("GetId", &AssetData::GetId)
->Attribute(AZ::Script::Attributes::Alias, "get_id")
->Method("GetUseCount", &AssetData::GetUseCount)
->Attribute(AZ::Script::Attributes::Alias, "get_use_count")
;
}
}
void AssetData::Acquire()
{
AZ_Assert(m_useCount >= 0, "AssetData has been deleted");
AcquireWeak();
++m_useCount;
}
void AssetData::Release()
{
AZ_Assert(m_useCount > 0, "Usecount is already 0!");
if (m_useCount.fetch_sub(1) == 1)
{
if (AssetManager::IsReady())
{
AssetManager::Instance().OnAssetUnused(this);
}
else
{
AZ_Assert(false, "Attempting to release asset after AssetManager has been destroyed!");
}
}
ReleaseWeak();
}
void AssetData::AcquireWeak()
{
AZ_Assert(m_useCount >= 0, "AssetData has been deleted");
++m_weakUseCount;
}
void AssetData::ReleaseWeak()
{
AZ_Assert(m_weakUseCount > 0, "WeakUseCount is already 0");
AssetId assetId = m_assetId;
int creationToken = m_creationToken;
AssetType assetType = GetType();
bool removeFromHash = IsRegisterReadonlyAndShareable();
// default creation token implies that the asset was not created by the asset manager and therefore it cannot be in the asset map.
removeFromHash = creationToken == s_defaultCreationToken ? false : removeFromHash;
if (m_weakUseCount.fetch_sub(1) == 1)
{
if (AssetManager::IsReady())
{
AssetManager::Instance().ReleaseAsset(this, assetId, assetType, removeFromHash, creationToken);
}
else
{
AZ_Assert(false, "Attempting to release asset after AssetManager has been destroyed!");
}
}
}
bool AssetData::IsLoading(bool includeQueued) const
{
auto curStatus = GetStatus();
return(curStatus == AssetStatus::Loading || curStatus == AssetStatus::LoadedPreReady || curStatus==AssetStatus::StreamReady ||
(includeQueued && curStatus == AssetStatus::Queued));
}
void AssetData::RegisterWithHandler(AssetHandler* handler)
{
if (!handler)
{
AZ_Error("AssetData", false, "No handler to register with");
return;
}
m_registeredHandler = handler;
}
void AssetData::UnregisterWithHandler()
{
if (m_registeredHandler)
{
m_registeredHandler = nullptr;
}
}
bool AssetData::GetFlag(const AssetDataFlags& checkFlag) const
{
return m_flags[aznumeric_cast<AZStd::size_t>(checkFlag)];
}
void AssetData::SetFlag(const AssetDataFlags& checkFlag, bool setValue)
{
m_flags.set(aznumeric_cast<AZStd::size_t>(checkFlag), setValue);
}
bool AssetData::GetRequeue() const
{
return GetFlag(AssetDataFlags::Requeue);
}
void AssetData::SetRequeue(bool requeue)
{
SetFlag(AssetDataFlags::Requeue, requeue);
}
void AssetBusCallbacks::SetCallbacks(const AssetReadyCB& readyCB, const AssetMovedCB& movedCB, const AssetReloadedCB& reloadedCB,
const AssetSavedCB& savedCB, const AssetUnloadedCB& unloadedCB, const AssetErrorCB& errorCB, const AssetCanceledCB& cancelCB)
{
m_onAssetReadyCB = readyCB;
m_onAssetMovedCB = movedCB;
m_onAssetReloadedCB = reloadedCB;
m_onAssetSavedCB = savedCB;
m_onAssetUnloadedCB = unloadedCB;
m_onAssetErrorCB = errorCB;
m_onAssetCanceledCB = cancelCB;
}
void AssetBusCallbacks::ClearCallbacks()
{
SetCallbacks(AssetBusCallbacks::AssetReadyCB(),
AssetBusCallbacks::AssetMovedCB(),
AssetBusCallbacks::AssetReloadedCB(),
AssetBusCallbacks::AssetSavedCB(),
AssetBusCallbacks::AssetUnloadedCB(),
AssetBusCallbacks::AssetErrorCB(),
AssetBusCallbacks::AssetCanceledCB());
}
void AssetBusCallbacks::SetOnAssetReadyCallback(const AssetReadyCB& readyCB)
{
m_onAssetReadyCB = readyCB;
}
void AssetBusCallbacks::SetOnAssetMovedCallback(const AssetMovedCB& movedCB)
{
m_onAssetMovedCB = movedCB;
}
void AssetBusCallbacks::SetOnAssetReloadedCallback(const AssetReloadedCB& reloadedCB)
{
m_onAssetReloadedCB = reloadedCB;
}
void AssetBusCallbacks::SetOnAssetSavedCallback(const AssetSavedCB& savedCB)
{
m_onAssetSavedCB = savedCB;
}
void AssetBusCallbacks::SetOnAssetUnloadedCallback(const AssetUnloadedCB& unloadedCB)
{
m_onAssetUnloadedCB = unloadedCB;
}
void AssetBusCallbacks::SetOnAssetErrorCallback(const AssetErrorCB& errorCB)
{
m_onAssetErrorCB = errorCB;
}
void AssetBusCallbacks::SetOnAssetCanceledCallback(const AssetCanceledCB& cancelCB)
{
m_onAssetCanceledCB = cancelCB;
}
void AssetBusCallbacks::OnAssetReady(Asset<AssetData> asset)
{
if (m_onAssetReadyCB)
{
m_onAssetReadyCB(asset, *this);
}
}
void AssetBusCallbacks::OnAssetMoved(Asset<AssetData> asset, void* oldDataPointer)
{
if (m_onAssetMovedCB)
{
m_onAssetMovedCB(asset, oldDataPointer, *this);
}
}
void AssetBusCallbacks::OnAssetReloaded(Asset<AssetData> asset)
{
if (m_onAssetReloadedCB)
{
m_onAssetReloadedCB(asset, *this);
}
}
void AssetBusCallbacks::OnAssetSaved(Asset<AssetData> asset, bool isSuccessful)
{
if (m_onAssetSavedCB)
{
m_onAssetSavedCB(asset, isSuccessful, *this);
}
}
void AssetBusCallbacks::OnAssetUnloaded(const AssetId assetId, const AssetType assetType)
{
if (m_onAssetUnloadedCB)
{
m_onAssetUnloadedCB(assetId, assetType, *this);
}
}
void AssetBusCallbacks::OnAssetError(Asset<AssetData> asset)
{
if (m_onAssetErrorCB)
{
m_onAssetErrorCB(asset, *this);
}
}
void AssetBusCallbacks::OnAssetCanceled(const AssetId assetId)
{
if (m_onAssetCanceledCB)
{
m_onAssetCanceledCB(assetId, *this);
}
}
/*static*/ bool AssetFilterNoAssetLoading([[maybe_unused]] const AssetFilterInfo& filterInfo)
{
return false;
}
namespace ProductDependencyInfo
{
AZ::Data::AssetLoadBehavior LoadBehaviorFromFlags(const ProductDependencyFlags& dependencyFlags)
{
AZ::u8 loadBehaviorValue = 0;
for (AZ::u8 thisFlag = aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorLow);
thisFlag <= aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorHigh); ++thisFlag)
{
if (dependencyFlags[thisFlag])
{
loadBehaviorValue |= (1 << thisFlag);
}
}
return static_cast<AZ::Data::AssetLoadBehavior>(loadBehaviorValue);
}
ProductDependencyFlags CreateFlags(AZ::Data::AssetLoadBehavior autoLoadBehavior)
{
AZ::Data::ProductDependencyInfo::ProductDependencyFlags returnFlags;
AZ::u8 loadBehavior = aznumeric_caster(autoLoadBehavior);
for (AZ::u8 thisFlag = aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorLow);
thisFlag <= aznumeric_cast<AZ::u8>(ProductDependencyFlagBits::LoadBehaviorHigh); ++thisFlag)
{
if (loadBehavior & (1 << thisFlag))
{
returnFlags[thisFlag] = true;
}
}
return returnFlags;
}
}
} // namespace AZ::Data
@@ -19,8 +19,7 @@
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/string_view.h>
#include <AzCore/std/typetraits/is_base_of.h>
#include <AzCore/Debug/AssetTracking.h>
#include <AzCore/IO/Streamer/FileRequest.h>
#include <AzCore/IO/IStreamerTypes.h>
namespace AZ
{
@@ -169,7 +168,7 @@ namespace AZ
virtual bool IsRegisterReadonlyAndShareable() { return true; }
/**
* Override this function to control automatic reload behavior.
* Override this function to control automatic reload behavior.
* By default, the asset will reload automatically.
* Return false to disable automatic reload. Potential use cases include:
* 1, If an asset is dependent on a parent asset(i.e.both assets need to be reloaded as a group) the parent asset can explicitly reload the child.
@@ -201,10 +200,10 @@ namespace AZ
AssetHandler* m_registeredHandler{ nullptr };
// This is used to identify a unique asset and should only be set by the asset manager
// This is used to identify a unique asset and should only be set by the asset manager
// and therefore does not need to be atomic.
// All shared copy of an asset should have the same identifier and therefore
// should not be modified while making copy of an existing asset.
// should not be modified while making copy of an existing asset.
int m_creationToken = s_defaultCreationToken;
// General purpose flags that should only be accessed within the asset mutex
AZStd::bitset<32> m_flags;
@@ -325,13 +324,13 @@ namespace AZ
T& operator*() const
{
AZ_Assert(m_assetData, "Asset is not loaded");
AZ_Assert(m_assetData, "Asset %s (%s) is not loaded", m_assetId.ToString<AZStd::string>().c_str(), m_assetHint.c_str());
return *Get();
}
T* operator->() const
{
AZ_Assert(m_assetData, "Asset is not loaded");
AZ_Assert(m_assetData, "Asset %s (%s) is not loaded", m_assetId.ToString<AZStd::string>().c_str(), m_assetHint.c_str());
return Get();
}
@@ -431,7 +430,7 @@ namespace AZ
*/
void UpgradeAssetInfo();
/**
/**
* for debugging purposes - creates a string that represents the assets id, subid, hint, and name.
* You should use this function for any time you want to show the full details of an asset in a log message
* as it will always produce a consistent output string. By convention, don't surround the output of this call
@@ -556,16 +555,24 @@ namespace AZ
Asset<AssetData> assetData(AssetInternal::GetAssetData(actualId, AZ::Data::AssetLoadBehavior::Default));
if (assetData)
{
auto curStatus = assetData->GetStatus();
auto isReady = assetData->GetStatus() == AssetData::AssetStatus::Ready;
bool isError = assetData->IsError();
connectLock.unlock();
if (curStatus == AssetData::AssetStatus::Ready)
if (isReady || isError)
{
handler->OnAssetReady(assetData);
}
else if (isError)
{
handler->OnAssetError(assetData);
connectLock.unlock();
if (isReady)
{
handler->OnAssetReady(assetData);
}
else if (isError)
{
handler->OnAssetError(assetData);
}
// Lock the mutex again since some destructors will be modifying the context afterwards
connectLock.lock();
}
}
}
@@ -573,33 +580,32 @@ namespace AZ
template<typename Bus>
using ConnectionPolicy = AssetConnectionPolicy<Bus>;
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
//////////////////////////////////////////////////////////////////////////
virtual ~AssetEvents() {}
/// Called when an asset is loaded, patched and ready to be used.
virtual void OnAssetReady(Asset<AssetData> asset) { (void)asset; }
/// Called when an asset has been moved (usually due to de-fragmentation/compaction), if possible the only data pointer is provided otherwise NULL.
virtual void OnAssetMoved(Asset<AssetData> asset, void* oldDataPointer) { (void)asset; (void)oldDataPointer; }
/// Called before an asset reload has started.
virtual void OnAssetPreReload(Asset<AssetData> asset) { (void)asset; }
/// Called when an asset has been reloaded (usually in tool mode and loose more). It should not be called in final build.
virtual void OnAssetReloaded(Asset<AssetData> asset) { (void)asset; }
/// Called when an asset failed to reload.
virtual void OnAssetReloadError(Asset<AssetData> asset) { (void)asset; }
/// Called when an asset has been saved. In general most assets can't be saved (in a game) so make sure you check the flag.
virtual void OnAssetSaved(Asset<AssetData> asset, bool isSuccessful) { (void)asset; (void)isSuccessful; }
/// Called when an asset is unloaded.
virtual void OnAssetUnloaded(const AssetId assetId, const AssetType assetType) { (void)assetId; (void)assetType; }
/**
/**
* Called when an error happened with an asset. When this message is received the asset should be considered broken by default.
* Note that this can happen when the asset errors during load, but also happens when the asset is missing (not in catalog etc.)
* in the case of an asset that is completely missing, the Asset<T> passed in here will have no hint or other information about
@@ -1088,7 +1094,7 @@ namespace AZ
// if we are a different asset (or being swapped with a empty) then we just swap as usual.
AZStd::swap(m_assetHint, rhs.m_assetHint);
}
}
//=========================================================================
@@ -1212,7 +1218,7 @@ namespace AZ
/// Indiscriminately skips all asset references.
bool AssetFilterNoAssetLoading(const AssetFilterInfo& filterInfo);
// Shared ProductDependency concepts between AP and LY
// Shared ProductDependency concepts between AP and LY
namespace ProductDependencyInfo
{
//! Corresponds to all ProductDependencyFlags, not just LoadBehaviors
File diff suppressed because it is too large Load Diff
@@ -24,8 +24,8 @@ namespace AZ
// AssetContainer loads an asset and all of its dependencies as a collection which is parallellized as much as possible.
// With the container, the data will all load in parallel. Dependent asset loads will still obey the expected rules
// where PreLoad assets will emit OnAssetReady before the parent does, and QueueLoad assets will emit OnAssetReady in
// no guaranteed order. However, the OnAssetContainerReady signals will not emit until all PreLoad and QueueLoad assets
// where PreLoad assets will emit OnAssetReady before the parent does, and QueueLoad assets will emit OnAssetReady in
// no guaranteed order. However, the OnAssetContainerReady signals will not emit until all PreLoad and QueueLoad assets
// are ready. NoLoad dependencies are not loaded by default but can be loaded along with their dependencies using the
// same rules as above by using the LoadAll dependency rule.
class AssetContainer :
@@ -36,7 +36,7 @@ namespace AZ
AZ_CLASS_ALLOCATOR(AssetContainer, SystemAllocator, 0);
AssetContainer() = default;
AssetContainer(Asset<AssetData> asset, const AssetLoadParameters& loadParams);
~AssetContainer();
@@ -81,6 +81,10 @@ namespace AZ
// AssetLoadBus
void OnAssetDataLoaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
protected:
virtual AZStd::vector<AZStd::pair<AssetInfo, Asset<AssetData>>> CreateAndQueueDependentAssets(
const AZStd::vector<AssetInfo>& dependencyInfoList, const AssetLoadParameters& loadParamsCopyWithNoLoadingFilter);
// Waiting assets are those which have not yet signalled ready. In the case of PreLoad dependencies the data may have completed the load cycle but
// the Assets aren't considered "Ready" yet if there are PreLoad dependencies still loading and will still be in the list until the point that asset and
// All of its preload dependencies have been loaded, when it signals OnAssetReady
@@ -97,7 +101,7 @@ namespace AZ
void AddDependency(Asset<AssetData>&& addDependency);
// Add a "graph section" to our list of dependencies. This checks the catalog for all Pre and Queue load assets which are dependents of the requested asset and kicks off loads
// NoLoads which are encounted are placed in another list and can be loaded on demand with the LoadDependency call.
// NoLoads which are encounted are placed in another list and can be loaded on demand with the LoadDependency call.
void AddDependentAssets(Asset<AssetData> rootAsset, const AssetLoadParameters& loadParams);
// If "PreLoad" assets are found in the graph these are cached and tracked with both OnAssetReady and OnAssetDataLoaded messages.
@@ -117,7 +121,7 @@ namespace AZ
// duringInit if we're coming from the checkReady method - containers that start ready don't need to signal
void HandleReadyAsset(AZ::Data::Asset<AZ::Data::AssetData> asset);
// Optimization to save the lookup in the dependencies map
// Optimization to save the lookup in the dependencies map
AssetInternal::WeakAsset<AssetData> m_rootAsset;
// The root asset id is stored here semi-redundantly on initialization so that we can still refer to it even if the
@@ -136,7 +140,7 @@ namespace AZ
AZStd::atomic_bool m_finalNotificationSent{false};
mutable AZStd::recursive_mutex m_preloadMutex;
// AssetId -> List of assets it is still waiting on
// AssetId -> List of assets it is still waiting on
PreloadAssetListType m_preloadList;
// AssetId -> List of assets waiting on it
@@ -7,11 +7,62 @@
*/
#include <AzCore/Asset/AssetDataStream.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/IO/IStreamer.h>
#include <AzCore/IO/Streamer/FileRequest.h>
#include <AzCore/std/parallel/condition_variable.h>
namespace AZ::Data
{
namespace DataStreamInternal
{
struct AssetDataStreamPrivate
{
//! Optional data buffer that's been directly passed in through Open(), instead of reading data from a file.
AZStd::vector<AZ::u8> m_preloadedData;
//! The current active streamer read request - tracked in case we need to cancel it prematurely
AZ::IO::FileRequestPtr m_curReadRequest{ nullptr };
//! Synchronization for the read request, so that it's possible to block until completion.
AZStd::mutex m_readRequestMutex;
AZStd::condition_variable m_readRequestActive;
void SetReadRequest(AZ::IO::FileRequestPtr&& req)
{
AZStd::scoped_lock<AZStd::mutex> lock(m_readRequestMutex);
// The read request finished, so stop tracking it.
m_curReadRequest = AZStd::move(req);
}
void BlockUntilReadComplete()
{
AZStd::unique_lock lock(m_readRequestMutex);
m_readRequestActive.wait(
lock,
[this]
{
return m_curReadRequest == nullptr;
});
lock.unlock();
}
void CancelRequest()
{
AZStd::scoped_lock<AZStd::mutex> lock(m_readRequestMutex);
if (m_curReadRequest)
{
auto streamer = Interface<IO::IStreamer>::Get();
m_curReadRequest = streamer->Cancel(m_curReadRequest);
}
}
};
} // namespace Internal
AssetDataStream::AssetDataStream(AZ::IO::IStreamerTypes::RequestMemoryAllocator* bufferAllocator)
: m_bufferAllocator(bufferAllocator ? bufferAllocator : &m_defaultAllocator)
: m_privateData(AZStd::make_unique<DataStreamInternal::AssetDataStreamPrivate>())
, m_bufferAllocator(bufferAllocator ? bufferAllocator : &m_defaultAllocator)
{
ClearInternalStateData();
}
@@ -53,9 +104,9 @@ namespace AZ::Data
OpenInternal(data.size(), "(mem buffer)");
// Directly take ownership of the provided buffer
m_preloadedData = AZStd::move(data);
m_buffer = m_preloadedData.data();
m_loadedSize = m_preloadedData.size();
m_privateData->m_preloadedData = AZStd::move(data);
m_buffer = m_privateData->m_preloadedData.data();
m_loadedSize = m_privateData->m_preloadedData.size();
}
void AssetDataStream::Open(const AZStd::string& filePath, size_t fileOffset, size_t assetSize,
@@ -65,7 +116,7 @@ namespace AZ::Data
AZ_PROFILE_FUNCTION(AzCore);
AZ_Assert(!m_isOpen, "Attempting to open the stream when it is already open.");
AZ_Assert(!m_curReadRequest, "Queueing an asset stream load while one is still in progress.");
AZ_Assert(!m_privateData->m_curReadRequest, "Queueing an asset stream load while one is still in progress.");
AZ_Assert(!filePath.empty(), "AssetDataStream::Open called without a valid file name.");
// Initialize the state variables and start tracking the overall load timings
@@ -83,7 +134,7 @@ namespace AZ::Data
AZ_PROFILE_SCOPE(AzCore, "AZ::Data::LoadAssetDataStreamCallback %s",
m_filePath.c_str());
// Get the results
// Get the results
auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
AZ::u64 bytesRead = 0;
streamer->GetReadRequestResult(fileHandle, m_buffer, bytesRead,
@@ -97,11 +148,8 @@ namespace AZ::Data
"Buffer for %s was expected to be %zu bytes, but is %zu bytes.",
m_filePath.c_str(), m_requestedAssetSize, m_loadedSize);
{
AZStd::scoped_lock<AZStd::mutex> lock(m_readRequestMutex);
// The read request finished, so stop tracking it.
m_curReadRequest = nullptr;
}
// The read request finished, so stop tracking it.
m_privateData->SetReadRequest(nullptr);
// Call the load callback to start processing the loaded data.
if (loadCallback)
@@ -115,21 +163,22 @@ namespace AZ::Data
}
// Notify that the load is complete, in case anyone is using BlockUntilLoadComplete to block.
m_readRequestActive.notify_one();
m_privateData->m_readRequestActive.notify_one();
};
// Queue the raw file load with the file streamer.
auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
m_curReadRequest = streamer->Read(
m_privateData->m_curReadRequest =
streamer->Read(
m_filePath,
*m_bufferAllocator,
m_requestedAssetSize,
deadline, priority, m_fileOffset);
m_curDeadline = deadline;
m_curPriority = priority;
streamer->SetRequestCompleteCallback(m_curReadRequest, streamerCallback);
streamer->SetRequestCompleteCallback(m_privateData->m_curReadRequest, streamerCallback);
streamer->QueueRequest(m_curReadRequest);
streamer->QueueRequest(m_privateData->m_curReadRequest);
}
else
{
@@ -139,19 +188,19 @@ namespace AZ::Data
loadCallback(AZ::IO::IStreamerTypes::RequestStatus::Completed);
}
m_readRequestActive.notify_one();
m_privateData->m_readRequestActive.notify_one();
}
}
void AssetDataStream::Reschedule(AZStd::chrono::milliseconds newDeadline, AZ::IO::IStreamerTypes::Priority newPriority)
{
if (m_curReadRequest && (newDeadline < m_curDeadline || newPriority > m_curPriority))
if (m_privateData->m_curReadRequest && (newDeadline < m_curDeadline || newPriority > m_curPriority))
{
auto deadline = AZStd::GetMin(m_curDeadline, newDeadline);
auto priority = AZStd::GetMax(m_curPriority, newPriority);
auto streamer = Interface<IO::IStreamer>::Get();
m_curReadRequest = streamer->RescheduleRequest(m_curReadRequest, deadline, priority);
m_privateData->m_curReadRequest = streamer->RescheduleRequest(m_privateData->m_curReadRequest, deadline, priority);
m_curDeadline = deadline;
m_curPriority = priority;
}
@@ -159,15 +208,13 @@ namespace AZ::Data
void AssetDataStream::BlockUntilLoadComplete()
{
AZStd::unique_lock<AZStd::mutex> lock(m_readRequestMutex);
m_readRequestActive.wait(lock, [this] { return m_curReadRequest == nullptr; });
lock.unlock();
m_privateData->BlockUntilReadComplete();
}
void AssetDataStream::ClearInternalStateData()
{
// Clear all our internal state data.
m_preloadedData.resize(0);
m_privateData->m_preloadedData.resize(0);
m_buffer = nullptr;
m_loadedSize = 0;
m_requestedAssetSize = 0;
@@ -204,10 +251,10 @@ namespace AZ::Data
void AssetDataStream::Close()
{
AZ_Assert(m_isOpen, "Attempting to close a stream that hasn't been opened.");
AZ_Assert(m_curReadRequest == nullptr, "Attempting to close a stream with a read request in flight.");
AZ_Assert(m_privateData->m_curReadRequest == nullptr, "Attempting to close a stream with a read request in flight.");
// Destroy the asset buffer and unlock the allocator, so the allocator itself knows that it is no longer needed.
if (m_buffer != m_preloadedData.data())
if (m_buffer != m_privateData->m_preloadedData.data())
{
m_bufferAllocator->Release(m_buffer);
}
@@ -221,12 +268,7 @@ namespace AZ::Data
void AssetDataStream::RequestCancel()
{
AZStd::scoped_lock<AZStd::mutex> lock(m_readRequestMutex);
if (m_curReadRequest)
{
auto streamer = Interface<IO::IStreamer>::Get();
m_curReadRequest = streamer->Cancel(m_curReadRequest);
}
m_privateData->CancelRequest();
}
void AssetDataStream::Seek(AZ::IO::OffsetType bytes, AZ::IO::GenericStream::SeekMode mode)
@@ -9,17 +9,26 @@
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/IStreamerTypes.h>
#include <AzCore/IO/IStreamer.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/parallel/condition_variable.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZStd
{
template<class T, class Allocator>
class vector;
}
namespace AZ::Data
{
namespace DataStreamInternal
{
struct AssetDataStreamPrivate;
}
class AssetDataStream : public AZ::IO::GenericStream
{
public:
using VectorDataSource = AZStd::vector<AZ::u8, AZStd::allocator>;
// The default Generic Stream APIs in this class will only allow for a single sequential pass
// through the data, no seeking. Reads will block when pages aren't available yet, and
// pages will be marked for recycling once reading has progressed beyond them.
@@ -29,10 +38,10 @@ namespace AZ::Data
~AssetDataStream() override;
// Open the AssetDataStream and make a copy of the provided memory buffer.
void Open(const AZStd::vector<AZ::u8>& data);
void Open(const VectorDataSource& data);
// Open the AssetDataStream and directly take ownership of a pre-populated memory buffer.
void Open(AZStd::vector<AZ::u8>&& data);
void Open(VectorDataSource&& data);
// Open the AssetDataStream and load it via file streaming
using OnCompleteCallback = AZStd::function<void(AZ::IO::IStreamerTypes::RequestStatus)>;
@@ -70,6 +79,9 @@ namespace AZ::Data
const char* GetFilename() const override { return m_filePath.c_str(); }
AZStd::chrono::milliseconds GetStreamingDeadline() const { return m_curDeadline; }
AZ::IO::IStreamerTypes::Priority GetStreamingPriority() const { return m_curPriority; }
// AssetDataStream specific APIs
//! Whether or not all data has been loaded.
@@ -88,6 +100,8 @@ namespace AZ::Data
void ClearInternalStateData();
AZStd::unique_ptr<DataStreamInternal::AssetDataStreamPrivate> m_privateData;
//! The allocator to use for allocating / deallocating asset buffers
AZ::IO::IStreamerTypes::RequestMemoryAllocator* m_bufferAllocator{ nullptr };
@@ -97,15 +111,12 @@ namespace AZ::Data
//! The path and file name of the asset being loaded
AZStd::string m_filePath;
//! The offset into the file to start loading at.
//! The offset into the file to start loading at.
size_t m_fileOffset{ 0 };
//! The amount of data that's expected to be loaded.
size_t m_requestedAssetSize{ 0 };
//! Optional data buffer that's been directly passed in through Open(), instead of reading data from a file.
AZStd::vector<AZ::u8> m_preloadedData;
//! The buffer that will hold the raw data after it's loaded from the file.
void* m_buffer{ nullptr };
@@ -116,19 +127,12 @@ namespace AZ::Data
//! The current offset representing how far we've read into the buffer.
size_t m_curOffset{ 0 };
//! The current active streamer read request - tracked in case we need to cancel it prematurely
AZ::IO::FileRequestPtr m_curReadRequest{ nullptr };
//! The current request deadline. Used to avoid requesting a reschedule to the same (current) deadline.
AZStd::chrono::milliseconds m_curDeadline{ AZ::IO::IStreamerTypes::s_noDeadline };
//! The current request priority. Used to avoid requesting a reschedule to the same (current) priority.
AZ::IO::IStreamerTypes::Priority m_curPriority{ AZ::IO::IStreamerTypes::s_priorityMedium };
//! Synchronization for the read request, so that it's possible to block until completion.
AZStd::mutex m_readRequestMutex;
AZStd::condition_variable m_readRequestActive;
//! Track whether or not the stream is currently open
bool m_isOpen{ false };
@@ -12,192 +12,204 @@
#include <AzCore/Serialization/Json/StackedString.h>
#include <AzCore/Memory/SystemAllocator.h>
namespace AZ
namespace AZ::Data
{
namespace Data
AZ_CLASS_ALLOCATOR_IMPL(AssetJsonSerializer, SystemAllocator, 0);
JsonSerializationResult::Result AssetJsonSerializer::Load(void* outputValue, const Uuid& /*outputValueTypeId*/,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
AZ_CLASS_ALLOCATOR_IMPL(AssetJsonSerializer, SystemAllocator, 0);
namespace JSR = JsonSerializationResult;
JsonSerializationResult::Result AssetJsonSerializer::Load(void* outputValue, const Uuid& /*outputValueTypeId*/,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
switch (inputValue.GetType())
{
namespace JSR = JsonSerializationResult;
case rapidjson::kObjectType:
return LoadAsset(outputValue, inputValue, context);
case rapidjson::kArrayType: // fall through
case rapidjson::kNullType: // fall through
case rapidjson::kStringType: // fall through
case rapidjson::kFalseType: // fall through
case rapidjson::kTrueType: // fall through
case rapidjson::kNumberType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. Asset<T> can only be read from an object.");
switch (inputValue.GetType())
default:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unknown, "Unknown json type encountered for Asset<T>.");
}
}
JsonSerializationResult::Result AssetJsonSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
const void* defaultValue, const Uuid& /*valueTypeId*/, JsonSerializerContext& context)
{
namespace JSR = JsonSerializationResult;
const Asset<AssetData>* instance = reinterpret_cast<const Asset<AssetData>*>(inputValue);
const Asset<AssetData>* defaultInstance = reinterpret_cast<const Asset<AssetData>*>(defaultValue);
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
ScopedContextPath subPathId(context, "m_assetId");
const auto* id = &instance->GetId();
const auto* defaultId = defaultInstance ? &defaultInstance->GetId() : nullptr;
rapidjson::Value assetIdValue;
result = ContinueStoring(assetIdValue, id, defaultId, azrtti_typeid<AssetId>(), context);
if (result.GetOutcome() == JSR::Outcomes::Success || result.GetOutcome() == JSR::Outcomes::PartialDefaults)
{
case rapidjson::kObjectType:
return LoadAsset(outputValue, inputValue, context);
case rapidjson::kArrayType: // fall through
case rapidjson::kNullType: // fall through
case rapidjson::kStringType: // fall through
case rapidjson::kFalseType: // fall through
case rapidjson::kTrueType: // fall through
case rapidjson::kNumberType:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. Asset<T> can only be read from an object.");
default:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unknown, "Unknown json type encountered for Asset<T>.");
if (!outputValue.IsObject())
{
outputValue.SetObject();
}
outputValue.AddMember(rapidjson::StringRef("assetId"), AZStd::move(assetIdValue), context.GetJsonAllocator());
}
}
JsonSerializationResult::Result AssetJsonSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
const void* defaultValue, const Uuid& /*valueTypeId*/, JsonSerializerContext& context)
{
namespace JSR = JsonSerializationResult;
const AZ::Data::AssetLoadBehavior autoLoadBehavior = instance->GetAutoLoadBehavior();
const AZ::Data::AssetLoadBehavior defaultAutoLoadBehavior = defaultInstance ?
defaultInstance->GetAutoLoadBehavior() : AZ::Data::AssetLoadBehavior::Default;
const Asset<AssetData>* instance = reinterpret_cast<const Asset<AssetData>*>(inputValue);
const Asset<AssetData>* defaultInstance = reinterpret_cast<const Asset<AssetData>*>(defaultValue);
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
ScopedContextPath subPathId(context, "m_assetId");
const auto* id = &instance->GetId();
const auto* defaultId = defaultInstance ? &defaultInstance->GetId() : nullptr;
rapidjson::Value assetIdValue;
result = ContinueStoring(assetIdValue, id, defaultId, azrtti_typeid<AssetId>(), context);
if (result.GetOutcome() == JSR::Outcomes::Success || result.GetOutcome() == JSR::Outcomes::PartialDefaults)
{
if (!outputValue.IsObject())
{
outputValue.SetObject();
}
outputValue.AddMember(rapidjson::StringRef("assetId"), AZStd::move(assetIdValue), context.GetJsonAllocator());
}
}
{
const AZ::Data::AssetLoadBehavior autoLoadBehavior = instance->GetAutoLoadBehavior();
const AZ::Data::AssetLoadBehavior defaultAutoLoadBehavior = defaultInstance ?
defaultInstance->GetAutoLoadBehavior() : AZ::Data::AssetLoadBehavior::Default;
result.Combine(
ContinueStoringToJsonObjectField(outputValue, "loadBehavior",
&autoLoadBehavior, &defaultAutoLoadBehavior,
azrtti_typeid<Data::AssetLoadBehavior>(), context));
}
{
ScopedContextPath subPathHint(context, "m_assetHint");
const AZStd::string* hint = &instance->GetHint();
const AZStd::string defaultHint;
rapidjson::Value assetHintValue;
JSR::ResultCode resultHint = ContinueStoring(assetHintValue, hint, &defaultHint, azrtti_typeid<AZStd::string>(), context);
if (resultHint.GetOutcome() == JSR::Outcomes::Success || resultHint.GetOutcome() == JSR::Outcomes::PartialDefaults)
{
if (!outputValue.IsObject())
{
outputValue.SetObject();
}
outputValue.AddMember(rapidjson::StringRef("assetHint"), AZStd::move(assetHintValue), context.GetJsonAllocator());
}
result.Combine(resultHint);
}
return context.Report(result,
result.GetProcessing() == JSR::Processing::Completed ? "Successfully stored Asset<T>." : "Failed to store Asset<T>.");
result.Combine(
ContinueStoringToJsonObjectField(outputValue, "loadBehavior",
&autoLoadBehavior, &defaultAutoLoadBehavior,
azrtti_typeid<Data::AssetLoadBehavior>(), context));
}
JsonSerializationResult::Result AssetJsonSerializer::LoadAsset(void* outputValue, const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult;
Asset<AssetData>* instance = reinterpret_cast<Asset<AssetData>*>(outputValue);
AssetId id;
JSR::ResultCode result(JSR::Tasks::ReadField);
SerializedAssetTracker* assetTracker =
context.GetMetadata().Find<SerializedAssetTracker>();
ScopedContextPath subPathHint(context, "m_assetHint");
const AZStd::string* hint = &instance->GetHint();
const AZStd::string defaultHint;
rapidjson::Value assetHintValue;
JSR::ResultCode resultHint = ContinueStoring(assetHintValue, hint, &defaultHint, azrtti_typeid<AZStd::string>(), context);
if (resultHint.GetOutcome() == JSR::Outcomes::Success || resultHint.GetOutcome() == JSR::Outcomes::PartialDefaults)
{
Data::AssetLoadBehavior loadBehavior = instance->GetAutoLoadBehavior();
result =
ContinueLoadingFromJsonObjectField(&loadBehavior,
azrtti_typeid<Data::AssetLoadBehavior>(),
inputValue, "loadBehavior", context);
instance->SetAutoLoadBehavior(loadBehavior);
if (!outputValue.IsObject())
{
outputValue.SetObject();
}
outputValue.AddMember(rapidjson::StringRef("assetHint"), AZStd::move(assetHintValue), context.GetJsonAllocator());
}
result.Combine(resultHint);
}
auto it = inputValue.FindMember("assetId");
if (it != inputValue.MemberEnd())
return context.Report(result,
result.GetProcessing() == JSR::Processing::Completed ? "Successfully stored Asset<T>." : "Failed to store Asset<T>.");
}
JsonSerializationResult::Result AssetJsonSerializer::LoadAsset(void* outputValue, const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult;
Asset<AssetData>* instance = reinterpret_cast<Asset<AssetData>*>(outputValue);
AssetId id;
JSR::ResultCode result(JSR::Tasks::ReadField);
SerializedAssetTracker* assetTracker =
context.GetMetadata().Find<SerializedAssetTracker>();
{
Data::AssetLoadBehavior loadBehavior = instance->GetAutoLoadBehavior();
result =
ContinueLoadingFromJsonObjectField(&loadBehavior,
azrtti_typeid<Data::AssetLoadBehavior>(),
inputValue, "loadBehavior", context);
instance->SetAutoLoadBehavior(loadBehavior);
}
auto it = inputValue.FindMember("assetId");
if (it != inputValue.MemberEnd())
{
ScopedContextPath subPath(context, "assetId");
result.Combine(ContinueLoading(&id, azrtti_typeid<AssetId>(), it->value, context));
if (!id.m_guid.IsNull())
{
ScopedContextPath subPath(context, "assetId");
result.Combine(ContinueLoading(&id, azrtti_typeid<AssetId>(), it->value, context));
if (!id.m_guid.IsNull())
*instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), instance->GetAutoLoadBehavior());
if (!instance->GetId().IsValid())
{
*instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), instance->GetAutoLoadBehavior());
if (!instance->GetId().IsValid())
{
// If the asset failed to be created, FindOrCreateAsset returns an asset instance with a null
// id. To preserve the asset id in the source json, reset the asset to an empty one, but with
// the right id.
const auto loadBehavior = instance->GetAutoLoadBehavior();
*instance = Asset<AssetData>(id, instance->GetType());
instance->SetAutoLoadBehavior(loadBehavior);
}
// If the asset failed to be created, FindOrCreateAsset returns an asset instance with a null
// id. To preserve the asset id in the source json, reset the asset to an empty one, but with
// the right id.
const auto loadBehavior = instance->GetAutoLoadBehavior();
*instance = Asset<AssetData>(id, instance->GetType());
instance->SetAutoLoadBehavior(loadBehavior);
}
result.Combine(context.Report(result, "Successfully created Asset<T> with id."));
}
else if (result.GetProcessing() == JSR::Processing::Completed)
{
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
"Null Asset<T> created."));
}
else
{
result.Combine(context.Report(result, "Failed to retrieve asset id for Asset<T>."));
}
result.Combine(context.Report(result, "Successfully created Asset<T> with id."));
}
else if (result.GetProcessing() == JSR::Processing::Completed)
{
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
"Null Asset<T> created."));
}
else
{
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
"The asset id is missing, so there's not enough information to create an Asset<T>."));
result.Combine(context.Report(result, "Failed to retrieve asset id for Asset<T>."));
}
it = inputValue.FindMember("assetHint");
if (it != inputValue.MemberEnd())
{
ScopedContextPath subPath(context, "assetHint");
AZStd::string hint;
result.Combine(ContinueLoading(&hint, azrtti_typeid<AZStd::string>(), it->value, context));
instance->SetHint(AZStd::move(hint));
}
else
{
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
"The asset hint is missing for Asset<T>, so it will be left empty."));
}
if (assetTracker)
{
assetTracker->AddAsset(*instance);
}
bool success = result.GetOutcome() <= JSR::Outcomes::PartialSkip;
bool defaulted = result.GetOutcome() == JSR::Outcomes::DefaultsUsed || result.GetOutcome() == JSR::Outcomes::PartialDefaults;
AZStd::string_view message =
success ? "Successfully loaded information and created instance of Asset<T>." :
defaulted ? "A default id was provided for Asset<T>, so no instance could be created." :
"Not enough information was available to create an instance of Asset<T> or data was corrupted.";
return context.Report(result, message);
}
void SerializedAssetTracker::AddAsset(Asset<AssetData>& asset)
else
{
m_serializedAssets.emplace_back(asset);
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
"The asset id is missing, so there's not enough information to create an Asset<T>."));
}
const AZStd::vector<Asset<AssetData>>& SerializedAssetTracker::GetTrackedAssets() const
it = inputValue.FindMember("assetHint");
if (it != inputValue.MemberEnd())
{
return m_serializedAssets;
ScopedContextPath subPath(context, "assetHint");
AZStd::string hint;
result.Combine(ContinueLoading(&hint, azrtti_typeid<AZStd::string>(), it->value, context));
instance->SetHint(AZStd::move(hint));
}
else
{
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed,
"The asset hint is missing for Asset<T>, so it will be left empty."));
}
AZStd::vector<Asset<AssetData>>& SerializedAssetTracker::GetTrackedAssets()
if (assetTracker)
{
return m_serializedAssets;
assetTracker->FixUpAsset(*instance);
assetTracker->AddAsset(*instance);
}
} // namespace Data
} // namespace AZ
bool success = result.GetOutcome() <= JSR::Outcomes::PartialSkip;
bool defaulted = result.GetOutcome() == JSR::Outcomes::DefaultsUsed || result.GetOutcome() == JSR::Outcomes::PartialDefaults;
AZStd::string_view message =
success ? "Successfully loaded information and created instance of Asset<T>." :
defaulted ? "A default id was provided for Asset<T>, so no instance could be created." :
"Not enough information was available to create an instance of Asset<T> or data was corrupted.";
return context.Report(result, message);
}
void SerializedAssetTracker::SetAssetFixUp(AssetFixUp assetFixUpCallback)
{
m_assetFixUpCallback = AZStd::move(assetFixUpCallback);
}
void SerializedAssetTracker::FixUpAsset(Asset<AssetData>& asset)
{
if (m_assetFixUpCallback)
{
m_assetFixUpCallback(asset);
}
}
void SerializedAssetTracker::AddAsset(Asset<AssetData> asset)
{
m_serializedAssets.emplace_back(asset);
}
const AZStd::vector<Asset<AssetData>>& SerializedAssetTracker::GetTrackedAssets() const
{
return m_serializedAssets;
}
AZStd::vector<Asset<AssetData>>& SerializedAssetTracker::GetTrackedAssets()
{
return m_serializedAssets;
}
} // namespace AZ::Data
@@ -39,13 +39,18 @@ namespace AZ
{
public:
AZ_RTTI(SerializedAssetTracker, "{1E067091-8C0A-44B1-A455-6E97663F6963}");
using AssetFixUp = AZStd::function<void(Asset<AssetData>& asset)>;
void AddAsset(Asset<AssetData>& asset);
void SetAssetFixUp(AssetFixUp assetFixUpCallback);
void FixUpAsset(Asset<AssetData>& asset);
void AddAsset(Asset<AssetData> asset);
AZStd::vector<Asset<AssetData>>& GetTrackedAssets();
const AZStd::vector<Asset<AssetData>>& GetTrackedAssets() const;
private:
AZStd::vector<Asset<AssetData>> m_serializedAssets;
AssetFixUp m_assetFixUpCallback;
};
} // namespace Data
} // namespace AZ
File diff suppressed because it is too large Load Diff
@@ -12,7 +12,6 @@
#include <AzCore/Asset/AssetContainer.h>
#include <AzCore/Asset/AssetDataStream.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/IO/Streamer/FileRequest.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h> // used as allocator for most components
#include <AzCore/std/parallel/mutex.h>
@@ -169,14 +168,14 @@ namespace AZ
/// Register handler with the system for a particular asset type.
/// A handler should be registered for each asset type it handles.
/// Please note that all the handlers are registered just once during app startup from the main thread
/// and therefore this is not a thread safe method and should not be invoked from different threads.
/// and therefore this is not a thread safe method and should not be invoked from different threads.
void RegisterHandler(AssetHandler* handler, const AssetType& assetType);
/// Unregister handler from the asset system.
/// Please note that all the handlers are unregistered just once during app shutdown from the main thread
/// and therefore this is not a thread safe method and should not be invoked from different threads.
void UnregisterHandler(AssetHandler* handler);
// @}
// @{ Asset catalog management
/// Register a catalog with the system for a particular asset type.
/// A catalog should be registered for each asset type it is responsible for.
@@ -295,7 +294,7 @@ namespace AZ
/**
* Old 'legacy' assetIds and asset hints can be automatically replaced with new ones during deserialize / assignment.
* This operation can be somewhat costly, and its only useful if the program subsequently re-saves the files its loading so that
* the asset hints and assetIds actually persist. Thus, it can be disabled in situations where you know you are not going to be
* the asset hints and assetIds actually persist. Thus, it can be disabled in situations where you know you are not going to be
* saving over or creating new source files (for example builders/background apps)
* By default, it is enabled.
*/
@@ -316,7 +315,7 @@ namespace AZ
* This method must be invoked before you start unregistering handlers manually and shutting down the asset manager.
* This method ensures that all jobs in flight are either canceled or completed.
* This method is automatically called in the destructor but if you are unregistering handlers manually,
* you must invoke it yourself.
* you must invoke it yourself.
*/
void PrepareShutDown();
@@ -366,7 +365,7 @@ namespace AZ
/**
* Creates a new shared AssetContainer with an optional loadFilter
* **/
AZStd::shared_ptr<AssetContainer> CreateAssetContainer(Asset<AssetData> asset, const AssetLoadParameters& loadParams = AssetLoadParameters{}) const;
virtual AZStd::shared_ptr<AssetContainer> CreateAssetContainer(Asset<AssetData> asset, const AssetLoadParameters& loadParams = AssetLoadParameters{}) const;
/**
@@ -452,7 +451,7 @@ namespace AZ
// Variant of RegisterAssetLoading used for jobs which have been queued and need to verify the status of the asset
// before loading in order to prevent cases where a load is queued, then a blocking load goes through, then the queued
// before loading in order to prevent cases where a load is queued, then a blocking load goes through, then the queued
// load is processed. This validation step leaves the loaded (And potentially modified) data as is in that case.
bool ValidateAndRegisterAssetLoading(const Asset<AssetData>& asset);
@@ -482,7 +481,7 @@ namespace AZ
* the blocking. That will result in a single thread deadlock.
*
* If you need to queue work, the logic needs to be similar to this:
*
*
AssetHandler::LoadResult MyAssetHandler::LoadAssetData(const Asset<AssetData>& asset, AZStd::shared_ptr<AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB)
{
@@ -496,13 +495,13 @@ namespace AZ
}
else
{
// queue job to load asset in thread identified by m_loadingThreadId
// queue job to load asset in thread identified by m_loadingThreadId
auto* queuedJob = QueueLoadingOnOtherThread(...);
// block waiting for queued job to complete
queuedJob->BlockUntilComplete();
}
.
.
.
@@ -525,7 +524,7 @@ namespace AZ
//! Result from LoadAssetData - it either finished loading, didn't finish and is waiting for more data, or had an error.
enum class LoadResult : u8
{
Error, // The provided data failed to load correctly
MoreDataRequired, // The provided data loaded correctly, but more data is required to finish the asset load
LoadComplete // The provided data loaded correctly, and the asset has been created
@@ -10,6 +10,7 @@
#include <AzCore/EBus/EBus.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/bitset.h>
#include <AzCore/Outcome/Outcome.h>
@@ -65,8 +66,35 @@ namespace AZ
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides - Application is a singleton
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
using MutexType = AZStd::recursive_mutex;
static constexpr bool EnableEventQueue = true;
using EventQueueMutexType = AZStd::mutex;
struct PostThreadDispatchInvoker
{
~PostThreadDispatchInvoker();
};
template <typename DispatchMutex>
struct ThreadDispatchLockGuard
{
ThreadDispatchLockGuard(DispatchMutex& contextMutex)
: m_lock{ contextMutex }
{}
ThreadDispatchLockGuard(DispatchMutex& contextMutex, AZStd::adopt_lock_t adopt_lock)
: m_lock{ contextMutex, adopt_lock }
{}
ThreadDispatchLockGuard(const ThreadDispatchLockGuard&) = delete;
ThreadDispatchLockGuard& operator=(const ThreadDispatchLockGuard&) = delete;
private:
PostThreadDispatchInvoker m_threadPolicyInvoker;
using LockType = AZStd::conditional_t<LocklessDispatch, AZ::Internal::NullLockGuard<DispatchMutex>, AZStd::scoped_lock<DispatchMutex>>;
LockType m_lock;
};
template <typename DispatchMutex, bool>
using DispatchLockGuard = ThreadDispatchLockGuard<DispatchMutex>;
//////////////////////////////////////////////////////////////////////////
virtual ~AssetCatalogRequests() = default;
@@ -102,7 +130,8 @@ namespace AZ
/// Remove a catalog from our delta list and rebuild the catalog from remaining items
virtual bool RemoveDeltaCatalog(AZStd::shared_ptr<AzFramework::AssetRegistry> /*deltaCatalog*/) { return true; }
/// Creates a manifest with the given DeltaCatalog name
virtual bool CreateBundleManifest(const AZStd::string& /*deltaCatalogPath*/, const AZStd::vector<AZStd::string>& /*dependentBundleNames*/, const AZStd::string& /*fileDirectory*/, int /*bundleVersion*/, const AZStd::vector<AZStd::string>& /*levelDirs*/) { return false; }
virtual bool CreateBundleManifest(const AZStd::string& /*deltaCatalogPath*/, const AZStd::vector<AZStd::string>& /*dependentBundleNames*/,
const AZStd::string& /*fileDirectory*/, int /*bundleVersion*/, const AZStd::vector<AZ::IO::Path>& /*levelDirs*/) { return false; }
/// Creates an instance of a registry containing info for just the specified files, and writes it out to a file at the specified path
virtual bool CreateDeltaCatalog(const AZStd::vector<AZStd::string>& /*files*/, const AZStd::string& /*filePath*/) { return false; }
@@ -200,6 +229,17 @@ namespace AZ
using AssetCatalogRequestBus = AZ::EBus<AssetCatalogRequests>;
inline AssetCatalogRequests::PostThreadDispatchInvoker::~PostThreadDispatchInvoker()
{
if (!AssetCatalogRequestBus::IsInDispatchThisThread())
{
if (AssetCatalogRequestBus::QueuedEventCount())
{
AssetCatalogRequestBus::ExecuteQueuedEvents();
}
}
}
/*
* Events that AssetManager listens for
*/
@@ -13,6 +13,7 @@
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Slice/SliceAssetHandler.h>
#include <AzCore/Slice/SliceComponent.h>
@@ -8,6 +8,7 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/IO/SystemFile.h>
namespace AZ {
+12 -3
View File
@@ -19,9 +19,10 @@
#include <AzCore/Slice/SliceSystemComponent.h>
#include <AzCore/Slice/SliceMetadataInfoComponent.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Time/TimeSystemComponent.h>
#include <AzCore/Console/LoggerSystemComponent.h>
#include <AzCore/EBus/EventSchedulerSystemComponent.h>
#include <AzCore/Task/TaskGraphSystemComponent.h>
#include <AzCore/Statistics/StatisticalProfilerProxySystemComponent.h>
namespace AZ
{
@@ -38,9 +39,13 @@ namespace AZ
SliceComponent::CreateDescriptor(),
SliceSystemComponent::CreateDescriptor(),
SliceMetadataInfoComponent::CreateDescriptor(),
TimeSystemComponent::CreateDescriptor(),
LoggerSystemComponent::CreateDescriptor(),
EventSchedulerSystemComponent::CreateDescriptor(),
TaskGraphSystemComponent::CreateDescriptor(),
#if !defined(_RELEASE)
Statistics::StatisticalProfilerProxySystemComponent::CreateDescriptor(),
#endif
#if !defined(AZCORE_EXCLUDE_LUA)
ScriptSystemComponent::CreateDescriptor(),
@@ -52,9 +57,13 @@ namespace AZ
{
return AZ::ComponentTypeList
{
azrtti_typeid<TimeSystemComponent>(),
azrtti_typeid<LoggerSystemComponent>(),
azrtti_typeid<EventSchedulerSystemComponent>(),
azrtti_typeid<TaskGraphSystemComponent>(),
#if !defined(_RELEASE)
azrtti_typeid<Statistics::StatisticalProfilerProxySystemComponent>(),
#endif
};
}
}
@@ -22,6 +22,7 @@
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h> // Used as the allocator for most components.
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/containers/unordered_set.h>
namespace AZ
{
@@ -14,6 +14,7 @@
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/ComponentApplicationLifecycle.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Debug/LocalFileEventLogger.h>
@@ -44,14 +45,9 @@
#include <AzCore/Module/Module.h>
#include <AzCore/Module/ModuleManager.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/IO/Path/PathReflect.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Driller/Driller.h>
#include <AzCore/Memory/MemoryDriller.h>
#include <AzCore/Debug/TraceMessagesDriller.h>
#include <AzCore/Debug/EventTraceDriller.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Script/ScriptSystemBus.h>
@@ -73,8 +69,7 @@
#include <AzCore/Module/Environment.h>
#include <AzCore/std/string/conversions.h>
AZ_CVAR(float, g_simulation_tick_rate, 0, nullptr, AZ::ConsoleFunctorFlags::Null, "The rate at which the game simulation tick loop runs, or 0 for as fast as possible");
#include <AzCore/Time/TimeSystem.h>
static void PrintEntityName(const AZ::ConsoleCommandContainer& arguments)
{
@@ -157,7 +152,6 @@ namespace AZ
m_reservedDebug = 0;
m_recordingMode = Debug::AllocationRecords::RECORD_STACK_IF_NO_FILE_LINE;
m_stackRecordLevels = 5;
m_enableDrilling = false;
m_useOverrunDetection = false;
m_useMalloc = false;
}
@@ -194,11 +188,80 @@ namespace AZ
};
//! SettingsRegistry notifier handler which is responsible for loading
//! the project.json file at the new project path
//! if an update to '<BootstrapSettingsRootKey>/project_path' key occurs.
struct ProjectPathChangedEventHandler
{
ProjectPathChangedEventHandler(AZ::SettingsRegistryInterface& registry)
: m_registry{ registry }
{
}
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
// Update the project settings when the project path is set
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
AZ::IO::FixedMaxPath newProjectPath;
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectPathKey, path)
&& m_registry.Get(newProjectPath.Native(), projectPathKey) && newProjectPath != m_oldProjectPath)
{
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
m_oldProjectPath = newProjectPath;
// Update all the runtime file paths based on the new "project_path" value.
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
}
}
private:
AZ::IO::FixedMaxPath m_oldProjectPath;
AZ::SettingsRegistryInterface& m_registry;
};
//! SettingsRegistry notifier handler which adds the project name as a specialization tag
//! to the registry
//! if an update to '<ProjectSettingsRootKey>/project_name' key occurs.
struct ProjectNameChangedEventHandler
{
ProjectNameChangedEventHandler(AZ::SettingsRegistryInterface& registry)
: m_registry{ registry }
{
}
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
// Update the project specialization when the project name is set
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name";
FixedValueString newProjectName;
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectNameKey, path)
&& m_registry.Get(newProjectName, projectNameKey) && newProjectName != m_oldProjectName)
{
// Add the project_name as a specialization for loading the build system dependency .setreg files
auto newProjectNameSpecialization = FixedValueString::format("%s/%.*s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
aznumeric_cast<int>(newProjectName.size()), newProjectName.data());
auto oldProjectNameSpecialization = FixedValueString::format("%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
m_oldProjectName.c_str());
m_registry.Remove(oldProjectNameSpecialization);
m_oldProjectName = newProjectName;
m_registry.Set(newProjectNameSpecialization, true);
}
}
private:
AZ::SettingsRegistryInterface::FixedValueString m_oldProjectName;
AZ::SettingsRegistryInterface& m_registry;
};
//! SettingsRegistry notifier handler which updates relevant registry settings based
//! on an update to '/Amazon/AzCore/Bootstrap/project_path' key.
struct UpdateProjectSettingsEventHandler
struct UpdateCommandLineEventHandler
{
UpdateProjectSettingsEventHandler(AZ::SettingsRegistryInterface& registry, AZ::CommandLine& commandLine)
UpdateCommandLineEventHandler(AZ::SettingsRegistryInterface& registry, AZ::CommandLine& commandLine)
: m_registry{ registry }
, m_commandLine{ commandLine }
{
@@ -206,70 +269,14 @@ namespace AZ
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
// #1 Update the project settings when the project path is set
const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
AZ::IO::FixedMaxPath newProjectPath;
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectPathKey, path)
&& m_registry.Get(newProjectPath.Native(), projectPathKey) && newProjectPath != m_oldProjectPath)
{
UpdateProjectSettingsFromProjectPath(AZ::IO::PathView(newProjectPath));
}
// #2 Update the project specialization when the project name is set
const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name";
FixedValueString newProjectName;
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectNameKey, path)
&& m_registry.Get(newProjectName, projectNameKey) && newProjectName != m_oldProjectName)
{
UpdateProjectSpecializationFromProjectName(newProjectName);
}
// #3 Update the ComponentApplication CommandLine instance when the command line settings are merged into the Settings Registry
// Update the ComponentApplication CommandLine instance when the command line settings are merged into the Settings Registry
if (path == AZ::SettingsRegistryMergeUtils::CommandLineValueChangedKey)
{
UpdateCommandLine();
AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(m_registry, m_commandLine);
}
}
//! Add the project name as a specialization underneath the /Amazon/AzCore/Settings/Specializations path
//! and remove the current project name specialization if one exists.
void UpdateProjectSpecializationFromProjectName(AZStd::string_view newProjectName)
{
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
// Add the project_name as a specialization for loading the build system dependency .setreg files
auto newProjectNameSpecialization = FixedValueString::format("%s/%.*s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
aznumeric_cast<int>(newProjectName.size()), newProjectName.data());
auto oldProjectNameSpecialization = FixedValueString::format("%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
m_oldProjectName.c_str());
m_registry.Remove(oldProjectNameSpecialization);
m_oldProjectName = newProjectName;
m_registry.Set(newProjectNameSpecialization, true);
}
void UpdateProjectSettingsFromProjectPath(AZ::IO::PathView newProjectPath)
{
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
m_oldProjectPath = newProjectPath;
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath };
projectMetadataFile /= "project.json";
m_registry.MergeSettingsFile(projectMetadataFile.Native(),
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
// Update all the runtime file paths based on the new "project_path" value.
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
}
void UpdateCommandLine()
{
AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(m_registry, m_commandLine);
}
private:
AZ::IO::FixedMaxPath m_oldProjectPath;
AZ::SettingsRegistryInterface::FixedValueString m_oldProjectName;
AZ::SettingsRegistryInterface& m_registry;
AZ::CommandLine& m_commandLine;
};
@@ -316,7 +323,6 @@ namespace AZ
->Field("blockSize", &Descriptor::m_memoryBlocksByteSize)
->Field("reservedOS", &Descriptor::m_reservedOS)
->Field("reservedDebug", &Descriptor::m_reservedDebug)
->Field("enableDrilling", &Descriptor::m_enableDrilling)
->Field("useOverrunDetection", &Descriptor::m_useOverrunDetection)
->Field("useMalloc", &Descriptor::m_useMalloc)
->Field("allocatorRemappings", &Descriptor::m_allocatorRemappings)
@@ -355,7 +361,6 @@ namespace AZ
->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize)
->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedOS, "OS reserved memory", "System memory reserved for OS (used only when 'Allocate all memory at startup' is true)")
->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedDebug, "Memory reserved for debugger", "System memory reserved for Debug allocator, like memory tracking (used only when 'Allocate all memory at startup' is true)")
->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_enableDrilling, "Enable Driller", "Enable Drilling support for the application (ignored in Release builds)")
->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_useOverrunDetection, "Use Overrun Detection", "Use the overrun detection memory manager (only available on some platforms, ignored in Release builds)")
->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_useMalloc, "Use Malloc", "Use malloc for memory allocations (for memory debugging only, ignored in Release builds)")
;
@@ -406,6 +411,7 @@ namespace AZ
ComponentApplication::ComponentApplication(int argC, char** argV)
: m_eventLogger{}
, m_timeSystem(AZStd::make_unique<TimeSystem>())
{
if (Interface<ComponentApplicationRequests>::Get() == nullptr)
{
@@ -464,26 +470,33 @@ namespace AZ
// 1. The 'project_path' key changes
// 2. The project specialization when the 'project-name' key changes
// 3. The ComponentApplication command line when the command line is stored to the registry
m_projectChangedHandler = m_settingsRegistry->RegisterNotifier(UpdateProjectSettingsEventHandler{ *m_settingsRegistry, m_commandLine });
m_projectPathChangedHandler = m_settingsRegistry->RegisterNotifier(ProjectPathChangedEventHandler{
*m_settingsRegistry });
m_projectNameChangedHandler = m_settingsRegistry->RegisterNotifier(ProjectNameChangedEventHandler{
*m_settingsRegistry });
m_commandLineUpdatedHandler = m_settingsRegistry->RegisterNotifier(UpdateCommandLineEventHandler{
*m_settingsRegistry, m_commandLine });
// Merge Command Line arguments
constexpr bool executeRegDumpCommands = false;
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
// Query for the Executable Path using OS specific functions
CalculateExecutablePath();
// Determine the path to the engine
CalculateEngineRoot();
// If the current platform returns an engaged optional from Utils::GetDefaultAppRootPath(), that is used
// for the application root.
CalculateAppRoot();
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
// Skip over merging the User Registry in non-debug and profile configurations
SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(*m_settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});
#endif
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry);
// The /O3DE/Application/LifecycleEvents array contains a valid set of lifecycle events
// Those lifecycle events are normally read from the <engine-root>/Registry
// which isn't merged until ComponentApplication::Create invokes MergeSettingsToRegistry
// So pre-populate the valid lifecycle even entries
ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "SystemAllocatorCreated");
ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "SettingsRegistryAvailable");
ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "ConsoleAvailable");
ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SystemAllocatorCreated", R"({})");
ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SettingsRegistryAvailable", R"({})");
// Create the Module Manager
m_moduleManager = AZStd::make_unique<ModuleManager>();
@@ -498,6 +511,7 @@ namespace AZ
m_ownsConsole = true;
m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead());
m_settingsRegistryConsoleFunctors = AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_settingsRegistry, *m_console);
ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ConsoleAvailable", R"({})");
}
}
@@ -517,17 +531,19 @@ namespace AZ
Destroy();
}
// The m_projectChangedHandler stores an AZStd::function internally
// which allocates using the AZ SystemAllocator
// m_projectChangedHandler is being default value initialized
// to clear out the AZStd::function
m_projectChangedHandler = {};
// The SettingsRegistry Notify handlers stores an AZStd::function internally
// which may allocates using the AZ SystemAllocator(if the functor > 16 bytes)
// The handlers are being default value initialized to clear out the AZStd::function
m_commandLineUpdatedHandler = {};
m_projectNameChangedHandler = {};
m_projectPathChangedHandler = {};
// Delete the AZ::IConsole if it was created by this application instance
if (m_ownsConsole)
{
AZ::Interface<AZ::IConsole>::Unregister(m_console);
delete m_console;
ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ConsoleUnavailable", R"({})");
}
m_moduleManager.reset();
@@ -535,6 +551,8 @@ namespace AZ
if (AZ::SettingsRegistry::Get() == m_settingsRegistry.get())
{
SettingsRegistry::Unregister(m_settingsRegistry.get());
ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SettingsRegistryUnavailable", R"({})");
ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SystemAllocatorPendingDestruction", R"({})");
}
m_settingsRegistry.reset();
@@ -547,14 +565,9 @@ namespace AZ
m_entityActivatedEvent.DisconnectAllHandlers();
m_entityDeactivatedEvent.DisconnectAllHandlers();
#if !defined(_RELEASE)
m_budgetTracker.Reset();
#endif
DestroyAllocator();
}
void ReportBadEngineRoot()
{
AZStd::string errorMessage = {"Unable to determine a valid path to the engine.\n"
@@ -584,7 +597,8 @@ namespace AZ
{
AZ_Assert(!m_isStarted, "Component application already started!");
if (m_engineRoot.empty())
using Type = AZ::SettingsRegistryInterface::Type;
if (m_settingsRegistry->GetType(SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder) == Type::NoType)
{
ReportBadEngineRoot();
return nullptr;
@@ -649,12 +663,13 @@ namespace AZ
ReflectionEnvironment::GetReflectionManager()->Reflect(azrtti_typeid(this), [this](ReflectContext* context) {Reflect(context); });
RegisterCoreComponents();
ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ReflectionManagerAvailable", R"({})");
TickBus::AllowFunctionQueuing(true);
SystemTickBus::AllowFunctionQueuing(true);
ComponentApplicationBus::Handler::BusConnect();
m_currentTime = AZStd::chrono::system_clock::now();
TickRequestBus::Handler::BusConnect();
#if defined(AZ_ENABLE_DEBUG_TOOLS)
@@ -668,6 +683,7 @@ namespace AZ
// Load the actual modules
LoadModules();
ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "GemsLoaded", R"({})");
// Execute user.cfg after modules have been loaded but before processing any command-line overrides
AZ::IO::FixedMaxPath platformCachePath;
@@ -733,12 +749,20 @@ namespace AZ
m_entities.rehash(0); // force free all memory
DestroyReflectionManager();
ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ReflectionManagerUnavailable", R"({})");
static_cast<SettingsRegistryImpl*>(m_settingsRegistry.get())->ClearNotifiers();
static_cast<SettingsRegistryImpl*>(m_settingsRegistry.get())->ClearMergeEvents();
#if !defined(_RELEASE)
// the budget tracker must be cleaned up prior to module unloading to ensure
// budgets initialized cross boundary are freed properly
m_budgetTracker.Reset();
#endif
// Uninit and unload any dynamic modules.
m_moduleManager->UnloadModules();
ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "GemsUnloaded", R"({})");
NameDictionary::Destroy();
@@ -1145,6 +1169,24 @@ namespace AZ
return ReflectionEnvironment::GetReflectionManager() ? ReflectionEnvironment::GetReflectionManager()->GetReflectContext<JsonRegistrationContext>() : nullptr;
}
/// Returns the path to the engine.
const char* ComponentApplication::GetEngineRoot() const
{
static IO::FixedMaxPathString engineRoot;
engineRoot.clear();
m_settingsRegistry->Get(engineRoot, SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
return engineRoot.c_str();
}
const char* ComponentApplication::GetExecutableFolder() const
{
static IO::FixedMaxPathString exeFolder;
exeFolder.clear();
m_settingsRegistry->Get(exeFolder, SettingsRegistryMergeUtils::FilePathKey_BinaryFolder);
return exeFolder.c_str();
}
//=========================================================================
// CreateReflectionManager
//=========================================================================
@@ -1369,56 +1411,25 @@ namespace AZ
#endif
}
//=========================================================================
// Tick
//=========================================================================
void ComponentApplication::Tick(float deltaOverride /*= -1.f*/)
void ComponentApplication::Tick()
{
AZ_PROFILE_SCOPE(System, "Component application simulation tick");
{
AZ_PROFILE_SCOPE(System, "Component application simulation tick");
AZStd::chrono::system_clock::time_point now = AZStd::chrono::system_clock::now();
m_deltaTime = 0.0f;
if (now >= m_currentTime)
{
AZStd::chrono::duration<float> delta = now - m_currentTime;
m_deltaTime = deltaOverride >= 0.f ? deltaOverride : delta.count();
}
{
AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:ExecuteQueuedEvents");
TickBus::ExecuteQueuedEvents();
}
m_currentTime = now;
{
AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:OnTick");
EBUS_EVENT(TickBus, OnTick, m_deltaTime, ScriptTimePoint(now));
}
// If tick rate limiting is on, ensure (1 / g_simulation_tick_rate) ms has elapsed since the last frame,
// sleeping if there's still time remaining.
if (g_simulation_tick_rate > 0.f)
{
now = AZStd::chrono::system_clock::now();
// Work in microsecond durations here as that's the native measurement time for time_point
constexpr float microsecondsPerSecond = 1000.f * 1000.f;
const AZStd::chrono::microseconds timeBudgetPerTick(static_cast<int>(microsecondsPerSecond / g_simulation_tick_rate));
AZStd::chrono::microseconds timeUntilNextTick = m_currentTime + timeBudgetPerTick - now;
if (timeUntilNextTick.count() > 0)
{
AZStd::this_thread::sleep_for(timeUntilNextTick);
}
}
AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:ExecuteQueuedEvents");
TickBus::ExecuteQueuedEvents();
}
{
AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:OnTick");
const AZ::TimeUs deltaTimeUs = m_timeSystem->AdvanceTickDeltaTimes();
const float deltaTimeSeconds = AZ::TimeUsToSeconds(deltaTimeUs);
AZ::TickBus::Broadcast(&TickEvents::OnTick, deltaTimeSeconds, GetTimeAtCurrentTick());
}
m_timeSystem->ApplyTickRateLimiterIfNeeded();
}
//=========================================================================
// Tick
//=========================================================================
void ComponentApplication::TickSystem()
{
AZ_PROFILE_SCOPE(System, "Component application tick");
@@ -1473,27 +1484,6 @@ namespace AZ
}
}
//=========================================================================
// CalculateExecutablePath
//=========================================================================
void ComponentApplication::CalculateExecutablePath()
{
m_exeDirectory = Utils::GetExecutableDirectory();
}
void ComponentApplication::CalculateAppRoot()
{
if (AZStd::optional<AZ::StringFunc::Path::FixedString> appRootPath = Utils::GetDefaultAppRootPath(); appRootPath)
{
m_appRoot = AZStd::move(*appRootPath);
}
}
void ComponentApplication::CalculateEngineRoot()
{
m_engineRoot = AZ::SettingsRegistryMergeUtils::FindEngineRoot(*m_settingsRegistry).Native();
}
void ComponentApplication::ResolveModulePath([[maybe_unused]] AZ::OSString& modulePath)
{
// No special parsing of the Module Path is done by the Component Application anymore
@@ -1519,13 +1509,10 @@ namespace AZ
appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Invalid;
}
//=========================================================================
// GetFrameTime
// [1/22/2016]
//=========================================================================
float ComponentApplication::GetTickDeltaTime()
{
return m_deltaTime;
const AZ::TimeUs gameTickTime = m_timeSystem->GetSimulationTickDeltaTimeUs();
return AZ::TimeUsToSeconds(gameTickTime);
}
//=========================================================================
@@ -1534,7 +1521,8 @@ namespace AZ
//=========================================================================
ScriptTimePoint ComponentApplication::GetTimeAtCurrentTick()
{
return ScriptTimePoint(m_currentTime);
const AZ::TimeUs lastGameTickTime = m_timeSystem->GetLastSimulationTickTime();
return ScriptTimePoint(AZ::TimeUsToChrono(lastGameTickTime));
}
//=========================================================================
@@ -1557,7 +1545,7 @@ namespace AZ
// reflect name dictionary.
Name::Reflect(context);
// reflect path
IO::PathReflection::Reflect(context);
IO::PathReflect(context);
// reflect the SettingsRegistryInterface, SettignsRegistryImpl and the global Settings Registry
// instance (AZ::SettingsRegistry::Get()) into the Behavior Context
@@ -1566,5 +1554,4 @@ namespace AZ
AZ::SettingsRegistryScriptUtils::ReflectSettingsRegistryToBehaviorContext(*behaviorContext);
}
}
} // namespace AZ
@@ -5,6 +5,7 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Component/ComponentApplicationBus.h>
@@ -29,16 +30,17 @@
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/string/osstring.h>
namespace AZ
{
class BehaviorContext;
class IConsole;
class Module;
class ModuleManager;
class TimeSystem;
}
namespace AZ::Debug
{
class DrillerManager;
class LocalFileEventLogger;
}
@@ -140,7 +142,6 @@ namespace AZ
AZ::u64 m_reservedDebug; //!< Reserved memory for Debugging (allocation,etc.). Used only when m_grabAllMemory is set to true. (default: 0)
Debug::AllocationRecords::Mode m_recordingMode; //!< When to record stack traces (default: AZ::Debug::AllocationRecords::RECORD_STACK_IF_NO_FILE_LINE)
AZ::u64 m_stackRecordLevels; //!< If stack recording is enabled, how many stack levels to record. (default: 5)
bool m_enableDrilling; //!< True to enabled drilling support for the application. RegisterDrillers will be called. Ignored in release. (default: true)
bool m_useOverrunDetection; //!< True to use the overrun detection memory management scheme. Only available on some platforms; greatly increases memory consumption.
bool m_useMalloc; //!< True to use malloc instead of the internal memory manager. Intended for debugging purposes only.
@@ -174,6 +175,8 @@ namespace AZ
bool m_loadDynamicModules = true;
//! Used by test fixtures to ensure reflection occurs to edit context.
bool m_createEditContext = false;
//! Indicates whether the AssetCatalog.xml should be loaded by default in Application::StartCommon
bool m_loadAssetCatalog = true;
};
ComponentApplication();
@@ -218,13 +221,10 @@ namespace AZ
BehaviorContext* GetBehaviorContext() override;
/// Returns the json registration context that has been registered with the app, if there is one.
JsonRegistrationContext* GetJsonRegistrationContext() override;
/// Returns the working root folder that has been registered with the app, if there is one.
/// It's expected that derived applications will implement an application root.
const char* GetAppRoot() const override { return m_appRoot.c_str(); }
/// Returns the path to the engine.
const char* GetEngineRoot() const override { return m_engineRoot.c_str(); }
const char* GetEngineRoot() const override;
/// Returns the path to the folder the executable is in.
const char* GetExecutableFolder() const override { return m_exeDirectory.c_str(); }
const char* GetExecutableFolder() const override;
//////////////////////////////////////////////////////////////////////////
/// TickRequestBus
@@ -237,7 +237,7 @@ namespace AZ
/**
* Ticks all components using the \ref AZ::TickBus during simulation time. May not tick if the application is not active (i.e. not in focus)
*/
virtual void Tick(float deltaOverride = -1.f);
virtual void Tick();
/**
* Ticks all using the \ref AZ::SystemTickBus at all times. Should always tick even if the application is not active.
@@ -349,15 +349,6 @@ namespace AZ
/// Adds system components requested by modules and the application to the system entity.
void AddRequiredSystemComponents(AZ::Entity* systemEntity);
/// Calculates the directory the application executable comes from.
void CalculateExecutablePath();
/// Calculates the root directory of the engine.
void CalculateEngineRoot();
/// Calculates the directory where the bootstrap.cfg file resides.
void CalculateAppRoot();
template<typename Iterator>
static void NormalizePath(Iterator begin, Iterator end, bool doLowercase = true)
{
@@ -368,8 +359,6 @@ namespace AZ
}
}
AZStd::chrono::system_clock::time_point m_currentTime{ AZStd::chrono::system_clock::time_point::max() };
float m_deltaTime{ 0.0f };
AZStd::unique_ptr<ModuleManager> m_moduleManager;
AZStd::unique_ptr<SettingsRegistryInterface> m_settingsRegistry;
EntityAddedEvent m_entityAddedEvent;
@@ -385,11 +374,12 @@ namespace AZ
void* m_fixedMemoryBlock{ nullptr }; //!< Pointer to the memory block allocator, so we can free it OnDestroy.
IAllocatorAllocate* m_osAllocator{ nullptr };
EntitySetType m_entities;
AZ::IO::FixedMaxPath m_exeDirectory;
AZ::IO::FixedMaxPath m_engineRoot;
AZ::IO::FixedMaxPath m_appRoot;
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectChangedHandler;
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectPathChangedHandler;
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectNameChangedHandler;
AZ::SettingsRegistryInterface::NotifyEventHandler m_commandLineUpdatedHandler;
AZStd::unique_ptr<AZ::TimeSystem> m_timeSystem;
// ConsoleFunctorHandle is responsible for unregistering the Settings Registry Console
// from the m_console member when it goes out of scope
@@ -37,11 +37,6 @@ namespace AZ
class ComponentFactoryInterface;
}
namespace Debug
{
class DrillerManager;
}
struct ApplicationTypeQuery
{
bool IsEditor() const;
@@ -175,10 +170,6 @@ namespace AZ
//! the serializers used by the best-effort json serialization.
virtual class JsonRegistrationContext* GetJsonRegistrationContext() = 0;
//! Gets the name of the working root folder that was registered with the app.
//! @return a pointer to the name of the app's root folder, if a root folder was registered.
virtual const char* GetAppRoot() const = 0;
//! Gets the path of the working engine folder that the app is a part of.
//! @return a pointer to the engine path.
virtual const char* GetEngineRoot() const = 0;
@@ -0,0 +1,93 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Component/ComponentApplicationLifecycle.h>
#include <AzCore/Settings/SettingsRegistryVisitorUtils.h>
namespace AZ::ComponentApplicationLifecycle
{
bool ValidateEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName)
{
using FixedValueString = SettingsRegistryInterface::FixedValueString;
using Type = SettingsRegistryInterface::Type;
FixedValueString eventRegistrationKey{ ApplicationLifecycleEventRegistrationKey };
eventRegistrationKey += '/';
eventRegistrationKey += eventName;
return settingsRegistry.GetType(eventRegistrationKey) == Type::Object;
}
bool SignalEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName, AZStd::string_view eventValue)
{
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
using Format = AZ::SettingsRegistryInterface::Format;
if (!ValidateEvent(settingsRegistry, eventName))
{
AZ_Warning("ComponentApplicationLifecycle", false, R"(Cannot signal event %.*s. Name does is not a field of object "%.*s".)"
R"( Please make sure the entry exists in the '<engine-root>/Registry/application_lifecycle_events.setreg")"
" or in *.setreg within the project", AZ_STRING_ARG(eventName), AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey));
return false;
}
auto eventRegistrationKey = FixedValueString::format("%.*s/%.*s", AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey),
AZ_STRING_ARG(eventName));
return settingsRegistry.MergeSettings(eventValue, Format::JsonMergePatch, eventRegistrationKey);
}
bool RegisterEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName)
{
using FixedValueString = SettingsRegistryInterface::FixedValueString;
using Format = AZ::SettingsRegistryInterface::Format;
if (!ValidateEvent(settingsRegistry, eventName))
{
FixedValueString eventRegistrationKey{ ApplicationLifecycleEventRegistrationKey };
eventRegistrationKey += '/';
eventRegistrationKey += eventName;
return settingsRegistry.MergeSettings(R"({})", Format::JsonMergePatch, eventRegistrationKey);
}
return true;
}
bool RegisterHandler(AZ::SettingsRegistryInterface& settingsRegistry, AZ::SettingsRegistryInterface::NotifyEventHandler& handler,
AZ::SettingsRegistryInterface::NotifyCallback callback, AZStd::string_view eventName, bool autoRegisterEvent)
{
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
using Type = AZ::SettingsRegistryInterface::Type;
using NotifyEventHandler = AZ::SettingsRegistryInterface::NotifyEventHandler;
// Some systems may attempt to register a handler before the settings registry has been loaded
// If so, this flag lets them automatically register an event if it hasn't yet been registered.
// RegisterEvent calls validate event.
if ((!autoRegisterEvent && !ValidateEvent(settingsRegistry, eventName)) ||
(autoRegisterEvent && !RegisterEvent(settingsRegistry, eventName)))
{
AZ_Warning(
"ComponentApplicationLifecycle", false,
R"(Cannot register event %.*s. Name is not a field of object "%.*s".)"
R"( Please make sure the entry exists in the '<engine-root>/Registry/application_lifecycle_events.setreg")"
" or in *.setreg within the project", AZ_STRING_ARG(eventName), AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey));
return false;
}
auto eventNameRegistrationKey = FixedValueString::format("%.*s/%.*s", AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey),
AZ_STRING_ARG(eventName));
auto lifecycleCallback = [callback = AZStd::move(callback), eventNameRegistrationKey](AZStd::string_view path, Type type)
{
if (path == eventNameRegistrationKey)
{
callback(path, type);
}
};
handler = NotifyEventHandler(AZStd::move(lifecycleCallback));
settingsRegistry.RegisterNotifier(handler);
return true;
}
}
@@ -0,0 +1,56 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/string/string_view.h>
namespace AZ::ComponentApplicationLifecycle
{
//! Root Key where lifecycle events should be registered under
inline constexpr AZStd::string_view ApplicationLifecycleEventRegistrationKey = "/O3DE/Application/LifecycleEvents";
//! Validates that the event @eventName is stored in the array at ApplicationLifecycleEventRegistrationKey
//! @param settingsRegistry registry where @eventName will be searched
//! @param eventName name of key that validated that exists as an element in the ApplicationLifecycleEventRegistrationKey array
//! @return true if the @eventName was found in the ApplicationLifecycleEventRegistrationKey array
bool ValidateEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName);
//! Wrapper around setting a value underneath the ApplicationLifecycleEventRegistrationKey
//! It validates if the @eventName is is part of the ApplicationLifecycleEventRegistrationKey array
//! It then appends the @eventName to the ApplicationLifecycleEventRegistrationKey merges the @eventValue into
//! the SettingsRegistry at that key
//! NOTE: This function should only be invoked from ComponentApplication and its derived classes
//! @param settingsRegistry registry where eventName should be set
//! @param eventName name of key underneath the ApplicationLifecycleEventRegistrationKey to signal
//! @param eventValue JSON Object that will be merged into the SettingsRegistry at <ApplicationLifecycleEventRootKey>/<eventName>
//! @return true if the eventValue was successfully merged at the <ApplicationLifecycleEventRootKey>/<eventName>
bool SignalEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName, AZStd::string_view eventValue);
//! Register that the event @eventName is stored in the array at ApplicationLifecycleEventRegistrationKey
//! @param settingsRegistry registry where @eventName will be searched
//! @param eventName name of key that will be stored in the ApplicationLifecycleEventRegistrationKey array
//! @return true if the event passed validation or the eventName was stored in the ApplicationLifecycleEventRegistrationKey array
bool RegisterEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName);
//! Wrapper around registering the NotifyEventHandler with the SettingsRegistry for the specified event
//! It validates if the @eventName is is part of the ApplicationLifecycleEventRegistrationKey array and if
//! so moves the @callback into @handler and then registers the handler with the SettingsRegistry NotifyEvent
//! @param settingsRegistry registry where handler will be registered
//! @param handler handler where callback will be moved into and then registered with the SettingsRegistry
//! if the specified @eventName passes validation
//! @param callback will be moved into the handler if the specified @eventName is valid
//! @param eventName name of key underneath the ApplicationLifecycleEventRegistrationKey to register
//! @param autoRegisterEvent automatically register this event if it hasn't been registered yet. This is useful
//! when registering a handler before the settings registry has been loaded.
//! @return true if the handler was registered with the SettingsRegistry NotifyEvent
bool RegisterHandler(AZ::SettingsRegistryInterface& settingsRegistry, AZ::SettingsRegistryInterface::NotifyEventHandler& handler,
AZ::SettingsRegistryInterface::NotifyCallback callback, AZStd::string_view eventName, bool autoRegisterEvent = false);
}
@@ -811,12 +811,12 @@ namespace AZ
if (behaviorContext)
{
behaviorContext->Class<EntityId>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "entity")
->Method("IsValid", &EntityId::IsValid)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("ToString", &EntityId::ToString)
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
@@ -10,292 +10,289 @@
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/std/containers/fixed_vector.h>
namespace AZ
namespace AZ::EntityUtils
{
namespace EntityUtils
//=========================================================================
// Reflect
//=========================================================================
void Reflect(ReflectContext* context)
{
//=========================================================================
// Reflect
//=========================================================================
void Reflect(ReflectContext* context)
if (auto serializeContext = azrtti_cast<SerializeContext*>(context))
{
if (auto serializeContext = azrtti_cast<SerializeContext*>(context))
serializeContext->Class<SerializableEntityContainer>()->
Version(1)->
Field("Entities", &SerializableEntityContainer::m_entities);
}
}
struct StackDataType
{
const SerializeContext::ClassData* m_classData;
const SerializeContext::ClassElement* m_elementData;
void* m_dataPtr;
bool m_isModifiedContainer;
};
//=========================================================================
// EnumerateEntityIds
//=========================================================================
void EnumerateEntityIds(const void* classPtr, const Uuid& classUuid, const EntityIdVisitor& visitor, SerializeContext* context)
{
AZ_PROFILE_FUNCTION(AzCore);
if (!context)
{
context = GetApplicationSerializeContext();
if (!context)
{
serializeContext->Class<SerializableEntityContainer>()->
Version(1)->
Field("Entities", &SerializableEntityContainer::m_entities);
AZ_Error("Serialization", false, "No serialize context provided! Failed to get component application default serialize context! ComponentApp is not started or input serialize context should not be null!");
return;
}
}
AZStd::vector<const SerializeContext::ClassData*> parentStack;
parentStack.reserve(30);
auto beginCB = [ &](void* ptr, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* elementData) -> bool
{
(void)elementData;
struct StackDataType
if (classData->m_typeId == SerializeTypeInfo<EntityId>::GetUuid())
{
// determine if this is entity ref or just entityId (please refer to the function documentation for more info)
bool isEntityId = false;
if (!parentStack.empty() && parentStack.back()->m_typeId == SerializeTypeInfo<Entity>::GetUuid())
{
// our parent in the entity (currently entity has only one EntityId member, but we can check the offset for future proof
AZ_Assert(elementData && strcmp(elementData->m_name, "Id") == 0, "class Entity, should have only ONE EntityId member, the actual entity id!");
isEntityId = true;
}
EntityId* entityIdPtr = (elementData->m_flags & SerializeContext::ClassElement::FLG_POINTER) ?
*reinterpret_cast<EntityId**>(ptr) : reinterpret_cast<EntityId*>(ptr);
visitor(*entityIdPtr, isEntityId, elementData);
}
parentStack.push_back(classData);
return true;
};
auto endCB = [ &]() -> bool
{
parentStack.pop_back();
return true;
};
SerializeContext::EnumerateInstanceCallContext callContext(
beginCB,
endCB,
context,
SerializeContext::ENUM_ACCESS_FOR_READ,
nullptr
);
context->EnumerateInstanceConst(
&callContext,
classPtr,
classUuid,
nullptr,
nullptr
);
}
//=========================================================================
// GetApplicationSerializeContext
//=========================================================================
SerializeContext* GetApplicationSerializeContext()
{
SerializeContext* context = nullptr;
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
return context;
}
//=========================================================================
// FindFirstDerivedComponent
//=========================================================================
Component* FindFirstDerivedComponent(const Entity* entity, const Uuid& typeId)
{
for (AZ::Component* component : entity->GetComponents())
{
const SerializeContext::ClassData* m_classData;
const SerializeContext::ClassElement* m_elementData;
void* m_dataPtr;
bool m_isModifiedContainer;
if (azrtti_istypeof(typeId, component))
{
return component;
}
}
return nullptr;
}
Component* FindFirstDerivedComponent(EntityId entityId, const Uuid& typeId)
{
Entity* entity{};
ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId);
return entity ? FindFirstDerivedComponent(entity, typeId) : nullptr;
}
//=========================================================================
// FindDerivedComponents
//=========================================================================
Entity::ComponentArrayType FindDerivedComponents(const Entity* entity, const Uuid& typeId)
{
Entity::ComponentArrayType result;
for (AZ::Component* component : entity->GetComponents())
{
if (azrtti_istypeof(typeId, component))
{
result.push_back(component);
}
}
return result;
}
Entity::ComponentArrayType FindDerivedComponents(EntityId entityId, const Uuid& typeId)
{
Entity* entity{};
ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId);
return entity ? FindDerivedComponents(entity, typeId) : Entity::ComponentArrayType();
}
bool EnumerateBaseRecursive(SerializeContext* context, const EnumerateBaseRecursiveVisitor& baseClassVisitor, const TypeId& typeToExamine)
{
AZ_Assert(context, "CheckDeclaresSerializeBaseClass called with no serialize context.");
if (!context)
{
return false;
}
AZStd::fixed_vector<TypeId, 64> knownBaseClasses = { typeToExamine }; // avoid allocating heap here if possible. 64 types are 64*sizeof(Uuid) which is only 1k.
bool foundBaseClass = false;
auto enumerateBaseVisitor = [&baseClassVisitor, &knownBaseClasses](const AZ::SerializeContext::ClassData* classData, const TypeId& examineTypeId)
{
if (!classData)
{
return false;
}
if (AZStd::find(knownBaseClasses.begin(), knownBaseClasses.end(), classData->m_typeId) == knownBaseClasses.end())
{
if (knownBaseClasses.size() == 64)
{
// this should be pretty unlikely since a single class would have to have many other classes in its heirarchy
// and it'd all have to be basically in one layer, as we are popping as we explore.
AZ_WarningOnce("EntityUtils", false, "While trying to find a base class, all available slots were consumed. consider increasing the size of knownBaseClasses.\n");
// we cannot continue any further, assume we did not find it.
return false;
}
knownBaseClasses.push_back(classData->m_typeId);
}
return baseClassVisitor(classData, examineTypeId);
};
//=========================================================================
// EnumerateEntityIds
//=========================================================================
void EnumerateEntityIds(const void* classPtr, const Uuid& classUuid, const EntityIdVisitor& visitor, SerializeContext* context)
while (!knownBaseClasses.empty() && !foundBaseClass)
{
AZ_PROFILE_FUNCTION(AzCore);
TypeId toExamine = knownBaseClasses.back();
knownBaseClasses.pop_back();
if (!context)
{
context = GetApplicationSerializeContext();
if (!context)
{
AZ_Error("Serialization", false, "No serialize context provided! Failed to get component application default serialize context! ComponentApp is not started or input serialize context should not be null!");
return;
}
}
AZStd::vector<const SerializeContext::ClassData*> parentStack;
parentStack.reserve(30);
auto beginCB = [ &](void* ptr, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* elementData) -> bool
{
(void)elementData;
if (classData->m_typeId == SerializeTypeInfo<EntityId>::GetUuid())
{
// determine if this is entity ref or just entityId (please refer to the function documentation for more info)
bool isEntityId = false;
if (!parentStack.empty() && parentStack.back()->m_typeId == SerializeTypeInfo<Entity>::GetUuid())
{
// our parent in the entity (currently entity has only one EntityId member, but we can check the offset for future proof
AZ_Assert(elementData && strcmp(elementData->m_name, "Id") == 0, "class Entity, should have only ONE EntityId member, the actual entity id!");
isEntityId = true;
}
EntityId* entityIdPtr = (elementData->m_flags & SerializeContext::ClassElement::FLG_POINTER) ?
*reinterpret_cast<EntityId**>(ptr) : reinterpret_cast<EntityId*>(ptr);
visitor(*entityIdPtr, isEntityId, elementData);
}
parentStack.push_back(classData);
return true;
};
auto endCB = [ &]() -> bool
{
parentStack.pop_back();
return true;
};
SerializeContext::EnumerateInstanceCallContext callContext(
beginCB,
endCB,
context,
SerializeContext::ENUM_ACCESS_FOR_READ,
nullptr
);
context->EnumerateInstanceConst(
&callContext,
classPtr,
classUuid,
nullptr,
nullptr
);
context->EnumerateBase(enumerateBaseVisitor, toExamine);
}
//=========================================================================
// GetApplicationSerializeContext
//=========================================================================
SerializeContext* GetApplicationSerializeContext()
{
SerializeContext* context = nullptr;
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
return context;
}
return foundBaseClass;
}
//=========================================================================
// FindFirstDerivedComponent
//=========================================================================
Component* FindFirstDerivedComponent(const Entity* entity, const Uuid& typeId)
bool CheckIfClassIsDeprecated(SerializeContext* context, const TypeId& typeToExamine)
{
bool isDeprecated = false;
auto classVisitorFn = [&isDeprecated](const AZ::SerializeContext::ClassData* classData, const TypeId& /*rttiBase*/)
{
for (AZ::Component* component : entity->GetComponents())
{
if (azrtti_istypeof(typeId, component))
{
return component;
}
}
return nullptr;
}
Component* FindFirstDerivedComponent(EntityId entityId, const Uuid& typeId)
{
Entity* entity{};
ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId);
return entity ? FindFirstDerivedComponent(entity, typeId) : nullptr;
}
//=========================================================================
// FindDerivedComponents
//=========================================================================
Entity::ComponentArrayType FindDerivedComponents(const Entity* entity, const Uuid& typeId)
{
Entity::ComponentArrayType result;
for (AZ::Component* component : entity->GetComponents())
{
if (azrtti_istypeof(typeId, component))
{
result.push_back(component);
}
}
return result;
}
Entity::ComponentArrayType FindDerivedComponents(EntityId entityId, const Uuid& typeId)
{
Entity* entity{};
ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId);
return entity ? FindDerivedComponents(entity, typeId) : Entity::ComponentArrayType();
}
bool EnumerateBaseRecursive(SerializeContext* context, const EnumerateBaseRecursiveVisitor& baseClassVisitor, const TypeId& typeToExamine)
{
AZ_Assert(context, "CheckDeclaresSerializeBaseClass called with no serialize context.");
if (!context)
// Stop iterating once we stop receiving SerializeContext::ClassData*.
if (!classData)
{
return false;
}
AZStd::fixed_vector<TypeId, 64> knownBaseClasses = { typeToExamine }; // avoid allocating heap here if possible. 64 types are 64*sizeof(Uuid) which is only 1k.
bool foundBaseClass = false;
auto enumerateBaseVisitor = [&baseClassVisitor, &knownBaseClasses](const AZ::SerializeContext::ClassData* classData, const TypeId& examineTypeId)
{
if (!classData)
{
return false;
}
if (AZStd::find(knownBaseClasses.begin(), knownBaseClasses.end(), classData->m_typeId) == knownBaseClasses.end())
{
if (knownBaseClasses.size() == 64)
{
// this should be pretty unlikely since a single class would have to have many other classes in its heirarchy
// and it'd all have to be basically in one layer, as we are popping as we explore.
AZ_WarningOnce("EntityUtils", false, "While trying to find a base class, all available slots were consumed. consider increasing the size of knownBaseClasses.\n");
// we cannot continue any further, assume we did not find it.
return false;
}
knownBaseClasses.push_back(classData->m_typeId);
}
return baseClassVisitor(classData, examineTypeId);
};
while (!knownBaseClasses.empty() && !foundBaseClass)
{
TypeId toExamine = knownBaseClasses.back();
knownBaseClasses.pop_back();
context->EnumerateBase(enumerateBaseVisitor, toExamine);
}
return foundBaseClass;
}
bool CheckIfClassIsDeprecated(SerializeContext* context, const TypeId& typeToExamine)
{
bool isDeprecated = false;
auto classVisitorFn = [&isDeprecated](const AZ::SerializeContext::ClassData* classData, const TypeId& /*rttiBase*/)
{
// Stop iterating once we stop receiving SerializeContext::ClassData*.
if (!classData)
{
return false;
}
// Stop iterating if we've found that the class is deprecated
if (classData->IsDeprecated())
{
isDeprecated = true;
return false;
}
return true; // keep iterating
};
// Check if the type is deprecated
const AZ::SerializeContext::ClassData* classData = context->FindClassData(typeToExamine);
// Stop iterating if we've found that the class is deprecated
if (classData->IsDeprecated())
{
return true;
}
// Check if any of its bases are deprecated
EnumerateBaseRecursive(context, classVisitorFn, typeToExamine);
return isDeprecated;
}
bool CheckDeclaresSerializeBaseClass(SerializeContext* context, const TypeId& typeToFind, const TypeId& typeToExamine)
{
AZ_Assert(context, "CheckDeclaresSerializeBaseClass called with no serialize context.");
if (!context)
{
isDeprecated = true;
return false;
}
bool foundBaseClass = false;
auto baseClassVisitorFn = [&typeToFind, &foundBaseClass](const AZ::SerializeContext::ClassData* reflectedBase, const TypeId& /*rttiBase*/)
{
if (!reflectedBase)
{
foundBaseClass = false;
return false; // stop iterating
}
return true; // keep iterating
};
foundBaseClass = (reflectedBase->m_typeId == typeToFind);
if (foundBaseClass)
{
return false; // we have a base, stop iterating
}
return true; // keep iterating
};
EnumerateBaseRecursive(context, baseClassVisitorFn, typeToExamine);
return foundBaseClass;
}
bool RemoveDuplicateServicesOfAndAfterIterator(
const ComponentDescriptor::DependencyArrayType::iterator& iterator,
ComponentDescriptor::DependencyArrayType& providedServiceArray,
const Entity* entity)
// Check if the type is deprecated
const AZ::SerializeContext::ClassData* classData = context->FindClassData(typeToExamine);
if (classData->IsDeprecated())
{
// Build types that strip out AZ_Warnings will complain that entity is unused without this.
(void)entity;
if (iterator == providedServiceArray.end())
{
return false;
}
bool duplicateFound = false;
for (ComponentDescriptor::DependencyArrayType::iterator duplicateCheckIter = AZStd::next(iterator);
duplicateCheckIter != providedServiceArray.end();)
{
if (*iterator == *duplicateCheckIter)
{
AZ_Warning("Entity", false, "Duplicate service %d found on entity %s [%s]",
*duplicateCheckIter,
entity ? entity->GetName().c_str() : "Entity not provided",
entity ? entity->GetId().ToString().c_str() : "");
duplicateCheckIter = providedServiceArray.erase(duplicateCheckIter);
duplicateFound = true;
}
else
{
++duplicateCheckIter;
}
}
return duplicateFound;
return true;
}
} // namespace EntityUtils
} // namespace AZ
// Check if any of its bases are deprecated
EnumerateBaseRecursive(context, classVisitorFn, typeToExamine);
return isDeprecated;
}
bool CheckDeclaresSerializeBaseClass(SerializeContext* context, const TypeId& typeToFind, const TypeId& typeToExamine)
{
AZ_Assert(context, "CheckDeclaresSerializeBaseClass called with no serialize context.");
if (!context)
{
return false;
}
bool foundBaseClass = false;
auto baseClassVisitorFn = [&typeToFind, &foundBaseClass](const AZ::SerializeContext::ClassData* reflectedBase, const TypeId& /*rttiBase*/)
{
if (!reflectedBase)
{
foundBaseClass = false;
return false; // stop iterating
}
foundBaseClass = (reflectedBase->m_typeId == typeToFind);
if (foundBaseClass)
{
return false; // we have a base, stop iterating
}
return true; // keep iterating
};
EnumerateBaseRecursive(context, baseClassVisitorFn, typeToExamine);
return foundBaseClass;
}
bool RemoveDuplicateServicesOfAndAfterIterator(
const ComponentDescriptor::DependencyArrayType::iterator& iterator,
ComponentDescriptor::DependencyArrayType& providedServiceArray,
const Entity* entity)
{
// Build types that strip out AZ_Warnings will complain that entity is unused without this.
(void)entity;
if (iterator == providedServiceArray.end())
{
return false;
}
bool duplicateFound = false;
for (ComponentDescriptor::DependencyArrayType::iterator duplicateCheckIter = AZStd::next(iterator);
duplicateCheckIter != providedServiceArray.end();)
{
if (*iterator == *duplicateCheckIter)
{
AZ_Warning("Entity", false, "Duplicate service %d found on entity %s [%s]",
*duplicateCheckIter,
entity ? entity->GetName().c_str() : "Entity not provided",
entity ? entity->GetId().ToString().c_str() : "");
duplicateCheckIter = providedServiceArray.erase(duplicateCheckIter);
duplicateFound = true;
}
else
{
++duplicateCheckIter;
}
}
return duplicateFound;
}
} // namespace AZ::EntityUtils
@@ -5,8 +5,7 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZCORE_ENTITY_UTILS_H
#define AZCORE_ENTITY_UTILS_H
#pragma once
#include <AzCore/Component/Entity.h>
#include <AzCore/Debug/Profiler.h>
@@ -217,6 +216,3 @@ namespace AZ
} // namespace EntityUtils
} // namespace AZ
#endif // AZCORE_ENTITY_UTILS_H
#pragma once
@@ -16,7 +16,6 @@
#define AZCORE_COMPONENT_TICK_BUS_H
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Debug/AssetTracking.h>
#include <AzCore/std/chrono/chrono.h>
#include <AzCore/std/parallel/mutex.h> // For TickBus thread events.
#include <AzCore/Script/ScriptTimePoint.h>
@@ -46,8 +45,6 @@ namespace AZ
TICK_PRE_RENDER = 750, ///< Suggested tick handler position to update render-related data.
TICK_RENDER = 800, ///< Suggested tick handler position for rendering.
TICK_DEFAULT = 1000, ///< Default tick handler position when the handler is constructed.
TICK_UI = 2000, ///< Suggested tick handler position for UI components.
@@ -114,10 +111,6 @@ namespace AZ
AZ_FORCE_INLINE bool operator()(TickEvents* left, TickEvents* right) const { return left->GetTickOrder() < right->GetTickOrder(); }
};
/**
* Enable tick bus to work with the AssetTracking
*/
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
//////////////////////////////////////////////////////////////////////////
/**
@@ -219,10 +212,6 @@ namespace AZ
*/
typedef AZStd::mutex EventQueueMutexType;
/**
* Enable tick bus to work with the AssetTracking
*/
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
//////////////////////////////////////////////////////////////////////////
/**
@@ -168,6 +168,11 @@ namespace AZ
//! Rotation modifiers
//! @{
//! Set the world rotation matrix using the composition of rotations around
//! the principle axes in the order of z-axis first and y-axis and then x-axis.
//! @param eulerRadianAngles A Vector3 denoting radian angles of the rotations around each principle axis.
virtual void SetWorldRotation([[maybe_unused]] const AZ::Vector3& eulerAnglesRadian) {}
//! Sets the entity's rotation in the world in quaternion notation.
//! The origin of the axes is the entity's position in world space.
//! @param quaternion A quaternion that represents the rotation to use for the entity.
@@ -57,6 +57,7 @@ void ZStd::StartCompressor(unsigned int compressionLevel)
ZSTD_customMem customAlloc;
customAlloc.customAlloc = reinterpret_cast<ZSTD_allocFunction>(&AllocateMem);
customAlloc.customFree = &FreeMem;
customAlloc.opaque = nullptr;
AZ_UNUSED(compressionLevel);
m_streamCompression = (ZSTD_createCStream_advanced(customAlloc));
@@ -225,8 +225,16 @@ namespace AZ
ConsoleCommandContainer commandSubset;
for (ConsoleFunctorBase* curr = m_head; curr != nullptr; curr = curr->m_next)
for (const auto& functor : m_commands)
{
if (functor.second.empty())
{
continue;
}
// Filter functors registered with the same name
const ConsoleFunctorBase* curr = functor.second.front();
if ((curr->GetFlags() & ConsoleFunctorFlags::IsInvisible) == ConsoleFunctorFlags::IsInvisible)
{
// Filter functors marked as invisible
@@ -235,8 +243,13 @@ namespace AZ
if (StringFunc::StartsWith(curr->m_name, command, false))
{
AZLOG_INFO("- %s : %s\n", curr->m_name, curr->m_desc);
commandSubset.push_back(curr->m_name);
AZLOG_INFO("- %s : %s", curr->m_name, curr->m_desc);
if (commandSubset.size() < MaxConsoleCommandPlusArgsLength)
{
commandSubset.push_back(curr->m_name);
}
if (matches)
{
matches->push_back(curr->m_name);
@@ -271,7 +284,10 @@ namespace AZ
{
for (auto& curr : m_commands)
{
visitor(curr.second.front());
if (!curr.second.empty())
{
visitor(curr.second.front());
}
}
}
@@ -336,6 +352,11 @@ namespace AZ
{
iter->second.erase(iter2);
}
if (iter->second.empty())
{
m_commands.erase(iter);
}
}
functor->Unlink(m_head);
functor->m_console = nullptr;
@@ -412,29 +433,29 @@ namespace AZ
{
if ((curr->GetFlags() & requiredSet) != requiredSet)
{
AZLOG_WARN("%s failed required set flag check\n", curr->m_name);
AZLOG_WARN("%s failed required set flag check", curr->m_name);
continue;
}
if ((curr->GetFlags() & requiredClear) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("%s failed required clear flag check\n", curr->m_name);
AZLOG_WARN("%s failed required clear flag check", curr->m_name);
continue;
}
if ((curr->GetFlags() & ConsoleFunctorFlags::IsCheat) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("%s is marked as a cheat\n", curr->m_name);
AZLOG_WARN("%s is marked as a cheat", curr->m_name);
}
if ((curr->GetFlags() & ConsoleFunctorFlags::IsDeprecated) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("%s is marked as deprecated\n", curr->m_name);
AZLOG_WARN("%s is marked as deprecated", curr->m_name);
}
if ((curr->GetFlags() & ConsoleFunctorFlags::NeedsReload) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("Changes to %s will only take effect after level reload\n", curr->m_name);
AZLOG_WARN("Changes to %s will only take effect after level reload", curr->m_name);
}
// Letting this intentionally fall-through, since in editor we can register common variables multiple times
@@ -447,7 +468,7 @@ namespace AZ
{
CVarFixedString value;
curr->GetValue(value);
AZLOG_INFO("> %s : %s\n", curr->GetName(), value.empty() ? "<empty>" : value.c_str());
AZLOG_INFO("> %s : %s", curr->GetName(), value.empty() ? "<empty>" : value.c_str());
}
flags = curr->GetFlags();
}
@@ -476,15 +497,16 @@ namespace AZ
// Responsible for using the Json Serialization Issue Callback system
// to determine when a JSON Patch or JSON Merge Patch modifies a value
// at a path underneath the IConsole::ConsoleRootCommandKey JSON pointer
// at a path underneath the IConsole::ConsoleRuntimeCommandKey JSON pointer
JsonSerializationResult::ResultCode operator()(AZStd::string_view message,
JsonSerializationResult::ResultCode result, AZStd::string_view path)
{
AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator };
constexpr AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRuntimeCommandKey, AZ::IO::PosixPathSeparator };
constexpr AZ::IO::PathView consoleAutoexecCommandKey{ IConsole::ConsoleAutoexecCommandKey, AZ::IO::PosixPathSeparator };
AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator };
if (result.GetTask() == JsonSerializationResult::Tasks::Merge
&& result.GetProcessing() == JsonSerializationResult::Processing::Completed
&& inputKey.IsRelativeTo(consoleRootCommandKey))
&& (inputKey.IsRelativeTo(consoleRootCommandKey) || inputKey.IsRelativeTo(consoleAutoexecCommandKey)))
{
if (auto type = m_settingsRegistry.GetType(path); type != SettingsRegistryInterface::Type::NoType)
{
@@ -510,12 +532,24 @@ namespace AZ
{
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator };
constexpr AZ::IO::PathView consoleRuntimeCommandKey{ IConsole::ConsoleRuntimeCommandKey, AZ::IO::PosixPathSeparator };
constexpr AZ::IO::PathView consoleAutoexecCommandKey{ IConsole::ConsoleAutoexecCommandKey, AZ::IO::PosixPathSeparator };
AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator };
// The ConsoleRootComamndKey is not a command itself so strictly children keys are being examined
if (inputKey.IsRelativeTo(consoleRootCommandKey) && inputKey != consoleRootCommandKey)
// Abuses the IsRelativeToFuncton function of the path class to extract the console
// command from the settings registry objects
FixedValueString command;
if (inputKey != consoleRuntimeCommandKey && inputKey.IsRelativeTo(consoleRuntimeCommandKey))
{
command = inputKey.LexicallyRelative(consoleRuntimeCommandKey).Native();
}
else if (inputKey != consoleAutoexecCommandKey && inputKey.IsRelativeTo(consoleAutoexecCommandKey))
{
command = inputKey.LexicallyRelative(consoleAutoexecCommandKey).Native();
}
if (!command.empty())
{
FixedValueString command = inputKey.LexicallyRelative(consoleRootCommandKey).Native();
ConsoleCommandContainer commandArgs;
// Argument string which stores the value from the Settings Registry long enough
// to pass into the PerformCommand. The ConsoleCommandContainer stores string_views
@@ -603,10 +637,12 @@ namespace AZ
void Console::RegisterCommandInvokerWithSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry)
{
// Make sure the there is a JSON object at the path of AZ::IConsole::ConsoleRootCommandKey
// Make sure the there is a JSON object at the ConsoleRuntimeCommandKey or ConsoleAutoexecKey
// So that JSON Patch is able to add values underneath that object (JSON Patch doesn't create intermediate objects)
settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } }}})",
SettingsRegistryInterface::Format::JsonMergePatch);
settingsRegistry.MergeSettings(R"({})", SettingsRegistryInterface::Format::JsonMergePatch,
IConsole::ConsoleRuntimeCommandKey);
settingsRegistry.MergeSettings(R"({})", SettingsRegistryInterface::Format::JsonMergePatch,
IConsole::ConsoleAutoexecCommandKey);
m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this });
JsonApplyPatchSettings applyPatchSettings;
@@ -32,7 +32,16 @@ namespace AZ
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
inline void ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator =(const BASE_TYPE& rhs)
{
const BASE_TYPE currentValue = this->m_value;
// Do the value assignment outside new value check.
// Client code can supply a type for m_value that overrides the operator= function and trigger side effects
// in the operator= function body. Doing the assignment outside the value change check avoids those side
// effects not being triggered because AzCore believes the value wouldn't change.
this->m_value = rhs;
if (currentValue != rhs)
{
InvokeCallback();
}
}
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
@@ -31,7 +31,8 @@ namespace AZ
using FunctorVisitor = AZStd::function<void(ConsoleFunctorBase*)>;
inline static constexpr AZStd::string_view ConsoleRootCommandKey = "/Amazon/AzCore/Runtime/ConsoleCommands";
inline static constexpr AZStd::string_view ConsoleRuntimeCommandKey = "/Amazon/AzCore/Runtime/ConsoleCommands";
inline static constexpr AZStd::string_view ConsoleAutoexecCommandKey = "/O3DE/Autoexec/ConsoleCommands";
IConsole() = default;
virtual ~IConsole() = default;
@@ -261,6 +262,6 @@ static constexpr AZ::ThreadSafety ConsoleThreadSafety<_TYPE, std::enable_if_t<st
//! @param _FLAGS a set of AzFramework::ConsoleFunctorFlags used to mutate behaviour
//! @param _DESC a description of the cvar
#define AZ_CONSOLEFREEFUNC_4(_NAME, _FUNCTION, _FLAGS, _DESC) \
inline AZ::ConsoleFunctor<void, false> Functor##_FUNCTION(#_FUNCTION, _DESC, _FLAGS | AZ::ConsoleFunctorFlags::DontDuplicate, AZ::TypeId::CreateNull(), &_FUNCTION)
inline AZ::ConsoleFunctor<void, false> Functor##_FUNCTION(_NAME, _DESC, _FLAGS | AZ::ConsoleFunctorFlags::DontDuplicate, AZ::TypeId::CreateNull(), &_FUNCTION)
#define AZ_CONSOLEFREEFUNC(...) AZ_MACRO_SPECIALIZE(AZ_CONSOLEFREEFUNC_, AZ_VA_NUM_ARGS(__VA_ARGS__), (__VA_ARGS__))
@@ -119,25 +119,21 @@ namespace AZ
void LoggerSystemComponent::LogInternalV(LogLevel level, const char* format, const char* file, const char* function, int32_t line, va_list args)
{
constexpr AZStd::size_t MaxLogBufferSize = 1000;
char buffer[MaxLogBufferSize];
auto buffer = AZStd::fixed_string<MaxLogBufferSize>::format_arg(format, args);
m_logEvent.Signal(level, buffer.c_str(), file, function, line);
buffer += '\n';
const AZStd::size_t length = azvsnprintf(buffer, MaxLogBufferSize, format, args);
buffer[AZStd::min<AZStd::size_t>(length + 1, MaxLogBufferSize - 1)] = '\0';
m_logEvent.Signal(level, buffer, file, function, line);
// Force a new-line before calling the AZ::Debug::Trace functions, as they assume a newline is present
buffer[AZStd::min<AZStd::size_t>(length + 1, MaxLogBufferSize - 2)] = '\n';
switch (level)
{
case LogLevel::Warn:
AZ_Warning("Logger", true, buffer);
AZ_Warning("Logger", true, buffer.c_str());
break;
case LogLevel::Error:
AZ_Error("Logger", true, buffer);
AZ_Error("Logger", true, buffer.c_str());
break;
default:
// Catch all else with trace
AZ::Debug::Trace::Output("Logger", buffer);
AZ::Debug::Trace::Output("Logger", buffer.c_str());
break;
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
#include <AzCore/DOM/DomBackend.h>
#include <AzCore/IO/ByteContainerStream.h>
namespace AZ::Dom
{
//! A DOM backend for serializing and deserializing JSON <=> UTF-8 text
//! \param ParseFlags Controls how deserialized JSON is parsed.
//! \param WriteFormat Controls how serialized JSON is formatted.
template<
Json::ParseFlags ParseFlags = Json::ParseFlags::ParseComments,
Json::OutputFormatting WriteFormat = Json::OutputFormatting::PrettyPrintedJson>
class JsonBackend final : public Backend
{
public:
Visitor::Result ReadFromBuffer(const char* buffer, size_t size, AZ::Dom::Lifetime lifetime, Visitor& visitor) override
{
return Json::VisitSerializedJson<ParseFlags>({ buffer, size }, lifetime, visitor);
}
Visitor::Result ReadFromBufferInPlace(char* buffer, [[maybe_unused]] AZStd::optional<size_t> size, Visitor& visitor) override
{
return Json::VisitSerializedJsonInPlace<ParseFlags>(buffer, visitor);
}
Visitor::Result WriteToBuffer(AZStd::string& buffer, WriteCallback callback)
{
AZ::IO::ByteContainerStream<AZStd::string> stream{ &buffer };
AZStd::unique_ptr<Visitor> visitor = Json::CreateJsonStreamWriter(stream, WriteFormat);
return callback(*visitor);
}
};
} // namespace AZ::Dom
@@ -0,0 +1,578 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/IO/TextStreamWriters.h>
#include <AzCore/JSON/filewritestream.h>
#include <AzCore/JSON/memorystream.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/JSON/rapidjson.h>
#include <AzCore/JSON/reader.h>
#include <AzCore/JSON/writer.h>
#include <AzCore/std/containers/stack.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/optional.h>
namespace AZ::Dom::Json
{
//
// class RapidJsonValueWriter
//
RapidJsonValueWriter::RapidJsonValueWriter(rapidjson::Value& outputValue, rapidjson::Value::AllocatorType& allocator)
: m_result(outputValue)
, m_allocator(allocator)
{
}
VisitorFlags RapidJsonValueWriter::GetVisitorFlags() const
{
return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects;
}
Visitor::Result RapidJsonValueWriter::Null()
{
CurrentValue().SetNull();
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Bool(bool value)
{
CurrentValue().SetBool(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Int64(AZ::s64 value)
{
CurrentValue().SetInt64(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Uint64(AZ::u64 value)
{
CurrentValue().SetUint64(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Double(double value)
{
CurrentValue().SetDouble(value);
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::String(AZStd::string_view value, Lifetime lifetime)
{
if (lifetime == Lifetime::Temporary)
{
CurrentValue().SetString(value.data(), aznumeric_cast<rapidjson::SizeType>(value.length()), m_allocator);
}
else
{
CurrentValue().SetString(value.data(), aznumeric_cast<rapidjson::SizeType>(value.length()));
}
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::StartObject()
{
CurrentValue().SetObject();
const bool isObject = true;
m_entryStack.emplace_front(isObject, CurrentValue());
return VisitorSuccess();
}
Visitor::Result RapidJsonValueWriter::EndObject(AZ::u64 attributeCount)
{
if (m_entryStack.empty())
{
return VisitorFailure(VisitorErrorCode::InternalError, "EndObject called without a matching BeginObject call");
}
const ValueInfo& frontEntry = m_entryStack.front();
if (!frontEntry.m_isObject)
{
return VisitorFailure(VisitorErrorCode::InternalError, "Expected EndArray and received EndObject instead");
}
if (frontEntry.m_entryCount != attributeCount)
{
return VisitorFailure(
VisitorErrorCode::InternalError,
AZStd::string::format(
"EndObject: Expected %llu attributes but received %llu attributes instead", attributeCount,
frontEntry.m_entryCount));
}
m_entryStack.pop_front();
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::Key(AZ::Name key)
{
return RawKey(key.GetStringView(), Lifetime::Persistent);
}
Visitor::Result RapidJsonValueWriter::RawKey(AZStd::string_view key, Lifetime lifetime)
{
AZ_Assert(!m_entryStack.empty(), "Attempmted to push a key with no object");
AZ_Assert(m_entryStack.front().m_isObject, "Attempted to push a key to an array");
if (lifetime == Lifetime::Persistent)
{
m_entryStack.front().m_key.SetString(key.data(), aznumeric_cast<rapidjson::SizeType>(key.size()));
}
else
{
m_entryStack.front().m_key.SetString(key.data(), aznumeric_cast<rapidjson::SizeType>(key.size()), m_allocator);
}
return VisitorSuccess();
}
Visitor::Result RapidJsonValueWriter::StartArray()
{
CurrentValue().SetArray();
const bool isObject = false;
m_entryStack.emplace_front(isObject, CurrentValue());
return VisitorSuccess();
}
Visitor::Result RapidJsonValueWriter::EndArray(AZ::u64 elementCount)
{
if (m_entryStack.empty())
{
return VisitorFailure(VisitorErrorCode::InternalError, "EndArray called without a matching BeginArray call");
}
const ValueInfo& frontEntry = m_entryStack.front();
if (frontEntry.m_isObject)
{
return VisitorFailure(VisitorErrorCode::InternalError, "Expected EndObject and received EndArray instead");
}
if (frontEntry.m_entryCount != elementCount)
{
return VisitorFailure(
VisitorErrorCode::InternalError,
AZStd::string::format(
"EndArray: Expected %llu elements but received %llu elements instead", elementCount, frontEntry.m_entryCount));
}
m_entryStack.pop_front();
return FinishWrite();
}
Visitor::Result RapidJsonValueWriter::FinishWrite()
{
if (m_entryStack.empty())
{
return VisitorSuccess();
}
// Retrieve the top value of the stack and replace it with a null value
rapidjson::Value value;
m_entryStack.front().m_value.Swap(value);
ValueInfo& newEntry = m_entryStack.front();
++newEntry.m_entryCount;
if (newEntry.m_key.IsString())
{
newEntry.m_container.AddMember(m_entryStack.front().m_key.Move(), AZStd::move(value), m_allocator);
newEntry.m_key.SetNull();
}
else
{
newEntry.m_container.PushBack(AZStd::move(value), m_allocator);
}
return VisitorSuccess();
}
rapidjson::Value& RapidJsonValueWriter::CurrentValue()
{
if (m_entryStack.empty())
{
return m_result;
}
return m_entryStack.front().m_value;
}
RapidJsonValueWriter::ValueInfo::ValueInfo(bool isObject, rapidjson::Value& container)
: m_isObject(isObject)
, m_container(container)
{
}
//
// class StreamWriter
//
// Visitor that writes to a rapidjson::Writer
template<class Writer>
class StreamWriter : public Visitor
{
public:
StreamWriter(AZ::IO::GenericStream* stream)
: m_streamWriter(stream)
, m_writer(Writer(m_streamWriter))
{
}
VisitorFlags GetVisitorFlags() const override
{
return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects;
}
Result Null() override
{
return CheckWrite(m_writer.Null());
}
Result Bool(bool value) override
{
return CheckWrite(m_writer.Bool(value));
}
Result Int64(AZ::s64 value) override
{
return CheckWrite(m_writer.Int64(value));
}
Result Uint64(AZ::u64 value) override
{
return CheckWrite(m_writer.Uint64(value));
}
Result Double(double value) override
{
return CheckWrite(m_writer.Double(value));
}
Result String(AZStd::string_view value, Lifetime lifetime) override
{
const bool shouldCopy = lifetime == Lifetime::Temporary;
return CheckWrite(m_writer.String(value.data(), aznumeric_cast<rapidjson::SizeType>(value.size()), shouldCopy));
}
Result StartObject() override
{
return CheckWrite(m_writer.StartObject());
}
Result EndObject(AZ::u64 attributeCount) override
{
return CheckWrite(m_writer.EndObject(aznumeric_cast<rapidjson::SizeType>(attributeCount)));
}
Result Key(AZ::Name key) override
{
return RawKey(key.GetStringView(), Lifetime::Persistent);
}
Result RawKey(AZStd::string_view key, Lifetime lifetime) override
{
const bool shouldCopy = lifetime == Lifetime::Temporary;
return CheckWrite(m_writer.Key(key.data(), aznumeric_cast<rapidjson::SizeType>(key.size()), shouldCopy));
}
Result StartArray() override
{
return CheckWrite(m_writer.StartArray());
}
Result EndArray(AZ::u64 elementCount) override
{
return CheckWrite(m_writer.EndArray(aznumeric_cast<rapidjson::SizeType>(elementCount)));
}
private:
Result CheckWrite(bool writeSucceeded)
{
if (writeSucceeded)
{
return VisitorSuccess();
}
else
{
return VisitorFailure(VisitorErrorCode::InternalError, "Failed to write JSON");
}
}
AZ::IO::RapidJSONStreamWriter m_streamWriter;
Writer m_writer;
};
//
// struct JsonReadHandler
//
RapidJsonReadHandler::RapidJsonReadHandler(Visitor* visitor, Lifetime stringLifetime)
: m_visitor(visitor)
, m_stringLifetime(stringLifetime)
, m_outcome(AZ::Success())
{
}
bool RapidJsonReadHandler::Null()
{
return CheckResult(m_visitor->Null());
}
bool RapidJsonReadHandler::Bool(bool b)
{
return CheckResult(m_visitor->Bool(b));
}
bool RapidJsonReadHandler::Int(int i)
{
return CheckResult(m_visitor->Int64(aznumeric_cast<AZ::s64>(i)));
}
bool RapidJsonReadHandler::Uint(unsigned i)
{
return CheckResult(m_visitor->Uint64(aznumeric_cast<AZ::u64>(i)));
}
bool RapidJsonReadHandler::Int64(int64_t i)
{
return CheckResult(m_visitor->Int64(i));
}
bool RapidJsonReadHandler::Uint64(uint64_t i)
{
return CheckResult(m_visitor->Uint64(i));
}
bool RapidJsonReadHandler::Double(double d)
{
return CheckResult(m_visitor->Double(d));
}
bool RapidJsonReadHandler::RawNumber(
[[maybe_unused]] const char* str, [[maybe_unused]] rapidjson::SizeType length, [[maybe_unused]] bool copy)
{
AZ_Assert(false, "Raw numbers are unsupported in the rapidjson DOM backend");
return false;
}
bool RapidJsonReadHandler::String(const char* str, rapidjson::SizeType length, bool copy)
{
const Lifetime lifetime = !copy ? m_stringLifetime : Lifetime::Temporary;
return CheckResult(m_visitor->String(AZStd::string_view(str, length), lifetime));
}
bool RapidJsonReadHandler::StartObject()
{
return CheckResult(m_visitor->StartObject());
}
bool RapidJsonReadHandler::Key(const char* str, rapidjson::SizeType length, [[maybe_unused]] bool copy)
{
AZStd::string_view key = AZStd::string_view(str, length);
const Lifetime lifetime = !copy ? m_stringLifetime : Lifetime::Temporary;
return CheckResult(m_visitor->RawKey(key, lifetime));
}
bool RapidJsonReadHandler::EndObject([[maybe_unused]] rapidjson::SizeType memberCount)
{
return CheckResult(m_visitor->EndObject(memberCount));
}
bool RapidJsonReadHandler::StartArray()
{
return CheckResult(m_visitor->StartArray());
}
bool RapidJsonReadHandler::EndArray([[maybe_unused]] rapidjson::SizeType elementCount)
{
return CheckResult(m_visitor->EndArray(elementCount));
}
Visitor::Result&& RapidJsonReadHandler::TakeOutcome()
{
return AZStd::move(m_outcome);
}
bool RapidJsonReadHandler::CheckResult(Visitor::Result result)
{
if (result.IsSuccess())
{
return true;
}
else
{
m_outcome = AZStd::move(result);
return false;
}
}
//
// Serialized JSON util functions
//
AZStd::unique_ptr<Visitor> CreateJsonStreamWriter(AZ::IO::GenericStream& stream, OutputFormatting format)
{
if (format == OutputFormatting::MinifiedJson)
{
using WriterType = rapidjson::Writer<AZ::IO::RapidJSONStreamWriter>;
return AZStd::make_unique<StreamWriter<WriterType>>(&stream);
}
else
{
using WriterType = rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter>;
return AZStd::make_unique<StreamWriter<WriterType>>(&stream);
}
}
//
// In-memory rapidjson util functions
//
AZ::Outcome<rapidjson::Document, AZStd::string> WriteToRapidJsonDocument(Backend::WriteCallback writeCallback)
{
rapidjson::Document document;
RapidJsonValueWriter writer(document, document.GetAllocator());
auto result = writeCallback(writer);
if (!result.IsSuccess())
{
return AZ::Failure(result.TakeError().FormatVisitorErrorMessage());
}
return AZ::Success(AZStd::move(document));
}
Visitor::Result WriteToRapidJsonValue(
rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator, Backend::WriteCallback writeCallback)
{
RapidJsonValueWriter writer(value, allocator);
return writeCallback(writer);
}
Visitor::Result VisitRapidJsonValue(const rapidjson::Value& value, Visitor& visitor, Lifetime lifetime)
{
struct EndArrayMarker
{
};
struct EndObjectMarker
{
};
// Processing stack consists of values comprised of one of a:
// - rapidjson::Value to process
// - EndArrayMarker or EndObjectMarker denoting the end of an array or object
// - string denoting a key at the beginning of a key/value pair
using Entry = AZStd::variant<const rapidjson::Value*, EndArrayMarker, EndObjectMarker, AZStd::string_view>;
AZStd::stack<Entry> entryStack;
AZStd::stack<u64> entryCountStack;
entryStack.push(&value);
while (!entryStack.empty())
{
const Entry currentEntry = entryStack.top();
entryStack.pop();
Visitor::Result result = AZ::Success();
AZStd::visit(
[&visitor, &entryStack, &entryCountStack, &result, lifetime](auto&& arg)
{
using Alternative = AZStd::decay_t<decltype(arg)>;
if constexpr (AZStd::is_same_v<Alternative, const rapidjson::Value*>)
{
const rapidjson::Value& currentValue = *arg;
if (!entryCountStack.empty())
{
++entryCountStack.top();
}
switch (currentValue.GetType())
{
case rapidjson::kNullType:
result = visitor.Null();
break;
case rapidjson::kFalseType:
result = visitor.Bool(false);
break;
case rapidjson::kTrueType:
result = visitor.Bool(true);
break;
case rapidjson::kObjectType:
entryStack.push(EndObjectMarker{});
entryCountStack.push(0);
result = visitor.StartObject();
for (auto it = currentValue.MemberEnd(); it != currentValue.MemberBegin(); --it)
{
auto entry = (it - 1);
const AZStd::string_view key(
entry->name.GetString(), aznumeric_cast<size_t>(entry->name.GetStringLength()));
entryStack.push(&entry->value);
entryStack.push(key);
}
break;
case rapidjson::kArrayType:
entryStack.push(EndArrayMarker{});
entryCountStack.push(0);
result = visitor.StartArray();
for (auto it = currentValue.End(); it != currentValue.Begin(); --it)
{
auto entry = (it - 1);
entryStack.push(entry);
}
break;
case rapidjson::kStringType:
result = visitor.String(
AZStd::string_view(currentValue.GetString(), aznumeric_cast<size_t>(currentValue.GetStringLength())),
lifetime);
break;
case rapidjson::kNumberType:
if (currentValue.IsFloat() || currentValue.IsDouble())
{
result = visitor.Double(currentValue.GetDouble());
}
else if (currentValue.IsInt64() || currentValue.IsInt())
{
result = visitor.Int64(currentValue.GetInt64());
}
else
{
result = visitor.Uint64(currentValue.GetUint64());
}
break;
default:
result = AZ::Failure(VisitorError(VisitorErrorCode::InvalidData, "Value with invalid type specified"));
}
}
else if constexpr (AZStd::is_same_v<Alternative, EndArrayMarker>)
{
result = visitor.EndArray(entryCountStack.top());
entryCountStack.pop();
}
else if constexpr (AZStd::is_same_v<Alternative, EndObjectMarker>)
{
result = visitor.EndObject(entryCountStack.top());
entryCountStack.pop();
}
else if constexpr (AZStd::is_same_v<Alternative, AZStd::string_view>)
{
if (visitor.SupportsRawKeys())
{
visitor.RawKey(arg, lifetime);
}
else
{
visitor.Key(AZ::Name(arg));
}
}
},
currentEntry);
if (!result.IsSuccess())
{
return result;
}
}
return AZ::Success();
}
} // namespace AZ::Dom::Json
@@ -0,0 +1,256 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/DOM/DomBackend.h>
#include <AzCore/DOM/DomVisitor.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/JSON/document.h>
#include <AzCore/std/containers/deque.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ::Dom::Json
{
//! Specifies how JSON should be formatted when serialized.
enum class OutputFormatting
{
MinifiedJson, //!< Formats JSON in compact minified form, focusing on minimizing output size.
PrettyPrintedJson, //!< Formats JSON in a pretty printed form, focusing on legibility to readers.
};
//! Specifies parsing behavior when deserializing JSON.
enum class ParseFlags : int
{
Null = 0,
StopWhenDone = rapidjson::kParseStopWhenDoneFlag,
FullFloatingPointPrecision = rapidjson::kParseFullPrecisionFlag,
ParseComments = rapidjson::kParseCommentsFlag,
ParseNumbersAsStrings = rapidjson::kParseNumbersAsStringsFlag,
ParseTrailingCommas = rapidjson::kParseTrailingCommasFlag,
ParseNanAndInfinity = rapidjson::kParseNanAndInfFlag,
ParseEscapedApostrophies = rapidjson::kParseEscapedApostropheFlag,
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(ParseFlags);
//! Visitor that feeds into a rapidjson::Value
class RapidJsonValueWriter final : public Visitor
{
public:
RapidJsonValueWriter(rapidjson::Value& outputValue, rapidjson::Value::AllocatorType& allocator);
VisitorFlags GetVisitorFlags() const override;
Result Null() override;
Result Bool(bool value) override;
Result Int64(AZ::s64 value) override;
Result Uint64(AZ::u64 value) override;
Result Double(double value) override;
Result String(AZStd::string_view value, Lifetime lifetime) override;
Result StartObject() override;
Result EndObject(AZ::u64 attributeCount) override;
Result Key(AZ::Name key) override;
Result RawKey(AZStd::string_view key, Lifetime lifetime) override;
Result StartArray() override;
Result EndArray(AZ::u64 elementCount) override;
private:
Result FinishWrite();
rapidjson::Value& CurrentValue();
struct ValueInfo
{
ValueInfo(bool isObject, rapidjson::Value& container);
rapidjson::Value m_key;
rapidjson::Value m_value;
rapidjson::Value& m_container;
AZ::u64 m_entryCount = 0;
bool m_isObject;
};
rapidjson::Value& m_result;
rapidjson::Value::AllocatorType& m_allocator;
AZStd::deque<ValueInfo> m_entryStack;
};
//! Handler for a rapidjson::Reader that translates reads into an AZ::Dom::Visitor
struct RapidJsonReadHandler
{
public:
RapidJsonReadHandler(Visitor* visitor, Lifetime stringLifetime);
bool Null();
bool Bool(bool b);
bool Int(int i);
bool Uint(unsigned i);
bool Int64(int64_t i);
bool Uint64(uint64_t i);
bool Double(double d);
bool RawNumber(const char* str, rapidjson::SizeType length, bool copy);
bool String(const char* str, rapidjson::SizeType length, bool copy);
bool StartObject();
bool Key(const char* str, rapidjson::SizeType length, bool copy);
bool EndObject(rapidjson::SizeType memberCount);
bool StartArray();
bool EndArray(rapidjson::SizeType elementCount);
Visitor::Result&& TakeOutcome();
private:
bool CheckResult(Visitor::Result result);
Visitor::Result m_outcome;
Visitor* m_visitor;
Lifetime m_stringLifetime;
};
//! rapidjson stream wrapper for AZStd::string suitable for in-situ parsing
//! Faster than rapidjson::MemoryStream for reading from AZStd::string / AZStd::string_view (because it requires a null terminator)
//! \note This needs to be inlined for performance reasons.
struct NullDelimitedStringStream
{
using Ch = char; //<! Denotes the string character storage type for rapidjson
AZ_FORCE_INLINE NullDelimitedStringStream(char* buffer)
{
m_cursor = buffer;
m_begin = m_cursor;
}
AZ_FORCE_INLINE NullDelimitedStringStream(AZStd::string_view buffer)
{
// rapidjson won't actually call PutBegin or Put unless kParseInSituFlag is set, so this is safe
m_cursor = const_cast<char*>(buffer.data());
m_begin = m_cursor;
}
AZ_FORCE_INLINE char Peek() const
{
return *m_cursor;
}
AZ_FORCE_INLINE char Take()
{
return *m_cursor++;
}
AZ_FORCE_INLINE size_t Tell() const
{
return static_cast<size_t>(m_cursor - m_begin);
}
AZ_FORCE_INLINE char* PutBegin()
{
m_write = m_cursor;
return m_cursor;
}
AZ_FORCE_INLINE void Put(char c)
{
(*m_write++) = c;
}
AZ_FORCE_INLINE void Flush()
{
}
AZ_FORCE_INLINE size_t PutEnd(char* begin)
{
return m_write - begin;
}
AZ_FORCE_INLINE const char* Peek4() const
{
AZ_Assert(false, "Not implemented, encoding is hard-coded to UTF-8");
return m_cursor;
}
char* m_cursor; //!< Current read position.
char* m_write; //!< Current write position.
const char* m_begin; //!< Head of string.
};
//! Creates a Visitor that will write serialized JSON to the specified stream.
//! \param stream The stream the visitor will write to.
//! \param format The format to write in.
//! \return A Visitor that will write to stream when visited.
AZStd::unique_ptr<Visitor> CreateJsonStreamWriter(
AZ::IO::GenericStream& stream, OutputFormatting format = OutputFormatting::PrettyPrintedJson);
//! Reads serialized JSON from a string and applies it to a visitor.
//! \param buffer The UTF-8 serialized JSON to read.
//! \param lifetime Specifies the lifetime of the specified buffer. If the string specified by buffer might be deallocated,
//! ensure Lifetime::Temporary is specified.
//! \param visitor The visitor to visit with the JSON buffer's contents.
//! \param parseFlags (template) Settings for adjusting parser behavior.
//! \return The aggregate result specifying whether the visitor operations were successful.
template<ParseFlags parseFlags = ParseFlags::ParseComments>
Visitor::Result VisitSerializedJson(AZStd::string_view buffer, Lifetime lifetime, Visitor& visitor);
//! Reads serialized JSON from a string in-place and applies it to a visitor.
//! \param buffer The UTF-8 serialized JSON to read. This buffer will be modified as part of the deserialization process to
//! apply null terminators.
//! \param visitor The visitor to visit with the JSON buffer's contents. The strings provided to the visitor will only
//! be valid for the lifetime of buffer.
//! \param parseFlags (template) Settings for adjusting parser behavior.
//! \return The aggregate result specifying whether the visitor operations were successful.
template<ParseFlags parseFlags = ParseFlags::ParseComments>
Visitor::Result VisitSerializedJsonInPlace(char* buffer, Visitor& visitor);
//! Takes a visitor specified by a callback and produces a rapidjson::Document.
//! \param writeCallback A callback specifying a visitor to accept to build the resulting document.
//! \return An outcome with either the rapidjson::Document or an error message.
AZ::Outcome<rapidjson::Document, AZStd::string> WriteToRapidJsonDocument(Backend::WriteCallback writeCallback);
//! Takes a visitor specified by a callback and reads them into a rapidjson::Value.
//! \param value The value to read into, its contents will be overridden.
//! \param allocator The allocator to use when performing rapidjson allocations (generally provded by the rapidjson::Document).
//! \param writeCallback A callback specifying a visitor to accept to build the resulting document.
//! \return An outcome with either the rapidjson::Document or an error message.
Visitor::Result WriteToRapidJsonValue(
rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator, Backend::WriteCallback writeCallback);
//! Accepts a visitor with the contents of a rapidjson::Value.
//! \param value The rapidjson::Value to apply to visitor.
//! \param visitor The visitor to receive the contents of value.
//! \param lifetime The lifetime to specify for visiting strings. If the rapidjson::Value might be destroyed or changed
//! before the visitor is finished using these values, Lifetime::Temporary should be specified.
//! \return The aggregate result specifying whether the visitor operations were successful.
Visitor::Result VisitRapidJsonValue(const rapidjson::Value& value, Visitor& visitor, Lifetime lifetime);
template<ParseFlags parseFlags>
Visitor::Result VisitSerializedJson(AZStd::string_view buffer, Lifetime lifetime, Visitor& visitor)
{
rapidjson::Reader reader;
RapidJsonReadHandler handler(&visitor, lifetime);
// If the string is null terminated, we can use the faster AzStringStream path - otherwise we fall back on rapidjson::MemoryStream
if (buffer.data()[buffer.size()] == '\0')
{
NullDelimitedStringStream stream(buffer);
reader.Parse<aznumeric_cast<unsigned>(parseFlags)>(stream, handler);
}
else
{
rapidjson::MemoryStream stream(buffer.data(), buffer.size());
reader.Parse<aznumeric_cast<unsigned>(parseFlags)>(stream, handler);
}
return handler.TakeOutcome();
}
template<ParseFlags parseFlags>
Visitor::Result VisitSerializedJsonInPlace(char* buffer, Visitor& visitor)
{
rapidjson::Reader reader;
NullDelimitedStringStream stream(buffer);
RapidJsonReadHandler handler(&visitor, Lifetime::Persistent);
reader.Parse<aznumeric_cast<unsigned>(parseFlags) | rapidjson::kParseInsituFlag>(stream, handler);
return handler.TakeOutcome();
}
} // namespace AZ::Dom::Json
@@ -0,0 +1,17 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/DomBackend.h>
namespace AZ::Dom
{
Visitor::Result Backend::ReadFromBufferInPlace(char* buffer, AZStd::optional<size_t> size, Visitor& visitor)
{
return ReadFromBuffer(buffer, size.value_or(strlen(buffer)), AZ::Dom::Lifetime::Persistent, visitor);
}
} // namespace AZ::Dom
@@ -0,0 +1,44 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/DOM/DomVisitor.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ::Dom
{
//! Backends are registered centrally and used to transition DOM formats to and from a textual format.
class Backend
{
public:
virtual ~Backend() = default;
//! Attempt to read this format from the given buffer into the target Visitor.
virtual Visitor::Result ReadFromBuffer(
const char* buffer, size_t size, AZ::Dom::Lifetime lifetime, Visitor& visitor) = 0;
//! Attempt to read this format from a mutable string into the target Visitor. This enables some backends to
//! parse without making additional string allocations.
//! This string must be null terminated.
//! This string may be modified and read in place without being copied, so when calling this please ensure that:
//! - The string won't be deallocated until the visitor no longer needs the values and
//! - The string is safe to modify in place.
//! The base implementation simply calls ReadFromBuffer.
virtual Visitor::Result ReadFromBufferInPlace(char* buffer, AZStd::optional<size_t> size, Visitor& visitor);
//! A callback that accepts a Visitor, making DOM calls to inform the serializer, and returns an
//! aggregate error code to indicate whether or not the operation succeeded.
using WriteCallback = AZStd::function<Visitor::Result(Visitor&)>;
//! Attempt to write a value to the specified string using a write callback.
virtual Visitor::Result WriteToBuffer(AZStd::string& buffer, WriteCallback callback) = 0;
};
} // namespace AZ::Dom
@@ -0,0 +1,478 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/DOM/DomPath.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/Console/ConsoleTypeHelpers.h>
namespace AZ::Dom
{
PathEntry::PathEntry(size_t value)
: m_value(value)
{
}
PathEntry::PathEntry(AZ::Name value)
: m_value(AZStd::move(value))
{
}
PathEntry::PathEntry(AZStd::string_view value)
: m_value(AZ::Name(value))
{
}
PathEntry& PathEntry::operator=(size_t value)
{
m_value = value;
return *this;
}
PathEntry& PathEntry::operator=(AZ::Name value)
{
m_value = AZStd::move(value);
return *this;
}
PathEntry& PathEntry::operator=(AZStd::string_view value)
{
m_value = AZ::Name(value);
return *this;
}
bool PathEntry::operator==(const PathEntry& other) const
{
return m_value == other.m_value;
}
bool PathEntry::operator==(size_t value) const
{
return IsIndex() && GetIndex() == value;
}
bool PathEntry::operator==(const AZ::Name& key) const
{
return IsKey() && GetKey() == key;
}
bool PathEntry::operator==(AZStd::string_view key) const
{
return IsKey() && GetKey() == AZ::Name(key);
}
bool PathEntry::operator!=(const PathEntry& other) const
{
return m_value != other.m_value;
}
bool PathEntry::operator!=(size_t value) const
{
return !IsIndex() || GetIndex() != value;
}
bool PathEntry::operator!=(const AZ::Name& key) const
{
return !IsKey() || GetKey() != key;
}
bool PathEntry::operator!=(AZStd::string_view key) const
{
return !IsKey() || GetKey() != AZ::Name(key);
}
void PathEntry::SetEndOfArray()
{
m_value = EndOfArrayIndex;
}
bool PathEntry::IsEndOfArray() const
{
const size_t* result = AZStd::get_if<size_t>(&m_value);
return result == nullptr ? false : ((*result) == EndOfArrayIndex);
}
bool PathEntry::IsIndex() const
{
const size_t* result = AZStd::get_if<size_t>(&m_value);
return result == nullptr ? false : ((*result) != EndOfArrayIndex);
}
bool PathEntry::IsKey() const
{
return AZStd::holds_alternative<AZ::Name>(m_value);
}
size_t PathEntry::GetIndex() const
{
AZ_Assert(IsIndex(), "GetIndex called on PathEntry that is not an index");
return AZStd::get<size_t>(m_value);
}
const AZ::Name& PathEntry::GetKey() const
{
AZ_Assert(IsKey(), "Key called on PathEntry that is not a key");
return AZStd::get<AZ::Name>(m_value);
}
Path::Path(AZStd::initializer_list<PathEntry> init)
: m_entries(init)
{
}
Path::Path(AZStd::string_view pathString)
{
FromString(pathString);
}
Path Path::operator/(const PathEntry& entry) const
{
Path newPath(*this);
newPath /= entry;
return newPath;
}
Path Path::operator/(size_t index) const
{
return *this / PathEntry(index);
}
Path Path::operator/(AZ::Name key) const
{
return *this / PathEntry(key);
}
Path Path::operator/(AZStd::string_view key) const
{
return *this / PathEntry(key);
}
Path Path::operator/(const Path& other) const
{
Path newPath(*this);
newPath /= other;
return newPath;
}
Path& Path::operator/=(const PathEntry& entry)
{
Push(entry);
return *this;
}
Path& Path::operator/=(size_t index)
{
return *this /= PathEntry(index);
}
Path& Path::operator/=(AZ::Name key)
{
return *this /= PathEntry(key);
}
Path& Path::operator/=(AZStd::string_view key)
{
return *this /= PathEntry(key);
}
Path& Path::operator/=(const Path& other)
{
for (const PathEntry& entry : other)
{
Push(entry);
}
return *this;
}
bool Path::operator==(const Path& other) const
{
return m_entries == other.m_entries;
}
const Path::ContainerType& Path::GetEntries() const
{
return m_entries;
}
void Path::Push(PathEntry entry)
{
m_entries.push_back(AZStd::move(entry));
}
void Path::Push(size_t entry)
{
Push(PathEntry(entry));
}
void Path::Push(AZ::Name entry)
{
Push(PathEntry(AZStd::move(entry)));
}
void Path::Push(AZStd::string_view entry)
{
Push(AZ::Name(entry));
}
void Path::Pop()
{
m_entries.pop_back();
}
void Path::Clear()
{
m_entries.clear();
}
PathEntry Path::At(size_t index) const
{
if (index < m_entries.size())
{
return m_entries[index];
}
return {};
}
size_t Path::Size() const
{
return m_entries.size();
}
PathEntry& Path::operator[](size_t index)
{
return m_entries[index];
}
const PathEntry& Path::operator[](size_t index) const
{
return m_entries[index];
}
Path::ContainerType::iterator Path::begin()
{
return m_entries.begin();
}
Path::ContainerType::iterator Path::end()
{
return m_entries.end();
}
Path::ContainerType::const_iterator Path::begin() const
{
return m_entries.cbegin();
}
Path::ContainerType::const_iterator Path::end() const
{
return m_entries.cend();
}
Path::ContainerType::const_iterator Path::cbegin() const
{
return m_entries.cbegin();
}
Path::ContainerType::const_iterator Path::cend() const
{
return m_entries.cend();
}
size_t Path::size() const
{
return m_entries.size();
}
size_t Path::GetStringLength() const
{
size_t size = 0;
for (const PathEntry& entry : m_entries)
{
++size;
if (entry.IsEndOfArray())
{
size += 1;
}
else if (entry.IsIndex())
{
const size_t index = entry.GetIndex();
const double digitCount = index > 0 ? log10(aznumeric_cast<double>(index + 1)) : 1.0;
size += aznumeric_cast<size_t>(ceil(digitCount));
}
else
{
const char* nameBuffer = entry.GetKey().GetCStr();
for (size_t i = 0; nameBuffer[i]; ++i)
{
if (nameBuffer[i] == EscapeCharacter || nameBuffer[i] == PathSeparator)
{
++size;
}
++size;
}
}
}
return size;
}
void Path::FormatString(char* stringBuffer, size_t bufferSize) const
{
size_t bufferIndex = 0;
auto putChar = [&](char c)
{
if (bufferIndex == bufferSize)
{
return;
}
stringBuffer[bufferIndex++] = c;
};
auto writeToBuffer = [&](const char* key)
{
for (size_t keyIndex = 0; key[keyIndex]; ++keyIndex)
{
const char c = key[keyIndex];
if (c == EscapeCharacter)
{
putChar(EscapeCharacter);
putChar(TildeSequence);
}
else if (c == PathSeparator)
{
putChar(EscapeCharacter);
putChar(ForwardSlashSequence);
}
else
{
putChar(c);
}
}
};
for (const PathEntry& entry : m_entries)
{
putChar(PathSeparator);
if (entry.IsEndOfArray())
{
putChar(EndOfArrayCharacter);
}
else if (entry.IsIndex())
{
bufferIndex += azsnprintf(&stringBuffer[bufferIndex], bufferSize - bufferIndex, "%zu", entry.GetIndex());
}
else
{
writeToBuffer(entry.GetKey().GetCStr());
}
}
putChar('\0');
}
AZStd::string Path::ToString() const
{
AZStd::string formattedString;
const size_t size = GetStringLength();
formattedString.resize_no_construct(size);
FormatString(formattedString.data(), size + 1);
return formattedString;
}
void Path::AppendToString(AZStd::string& output) const
{
const size_t startIndex = output.length();
const size_t stringLength = GetStringLength();
output.resize_no_construct(startIndex + stringLength);
FormatString(output.data() + startIndex, stringLength + 1);
}
void Path::FromString(AZStd::string_view pathString)
{
m_entries.clear();
if (pathString.empty())
{
return;
}
size_t pathEntryCount = 0;
for (size_t i = 1; i <= pathString.size(); ++i)
{
if (pathString[i] == PathSeparator)
{
++pathEntryCount;
}
}
m_entries.reserve(pathEntryCount);
// Ignore a preceeding path separator and start processing after it
size_t pathIndex = pathString[0] == PathSeparator ? 1 : 0;
bool isNumber = true;
AZStd::string convertedSection;
for (size_t i = pathIndex; i <= pathString.size(); ++i)
{
if (i == pathString.size() || pathString[i] == PathSeparator)
{
AZStd::string_view section = pathString.substr(pathIndex, i - pathIndex);
if (section.size() == 1 && section[0] == EndOfArrayCharacter)
{
PathEntry entry;
entry.SetEndOfArray();
m_entries.push_back(AZStd::move(entry));
}
else if (isNumber && !section.empty())
{
size_t index = 0;
ConsoleTypeHelpers::StringToValue(index, section);
m_entries.push_back(PathEntry{ index });
}
else
{
convertedSection.clear();
size_t lastPos = 0;
size_t posToEscape = section.find(EscapeCharacter);
while (posToEscape != AZStd::string_view::npos)
{
if (convertedSection.empty())
{
convertedSection.reserve(section.size() - 1);
}
convertedSection += section.substr(lastPos, posToEscape - lastPos);
if (section[posToEscape + 1] == ForwardSlashSequence)
{
convertedSection += '/';
}
else
{
convertedSection += '~';
}
lastPos = posToEscape + 2;
posToEscape = section.find(EscapeCharacter, posToEscape + 2);
}
if (!convertedSection.empty())
{
convertedSection += section.substr(lastPos);
m_entries.emplace_back(convertedSection);
}
else
{
m_entries.emplace_back(section);
}
}
pathIndex = i + 1;
isNumber = true;
continue;
}
const char c = pathString[i];
isNumber = isNumber && c >= '0' && c <= '9';
}
}
} // namespace AZ::Dom
+144
View File
@@ -0,0 +1,144 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Name/Name.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/containers/vector.h>
namespace AZ::Dom
{
//! Represents the path to a direct descendant of a Value.
//! PathEntry may be one of the following:
//! - Index, a numerical index for indexing within Arrays and Nodes
//! - Key, a name for indexing within Objects and Nodes
//! - EndOfArray, a special-case indicator for representing the end of an array
//! used by the patching system to represent push / pop back operations.
class PathEntry final
{
public:
static constexpr size_t EndOfArrayIndex = size_t(-1);
PathEntry() = default;
PathEntry(const PathEntry&) = default;
PathEntry(PathEntry&&) = default;
explicit PathEntry(size_t value);
explicit PathEntry(AZ::Name value);
explicit PathEntry(AZStd::string_view value);
PathEntry& operator=(const PathEntry&) = default;
PathEntry& operator=(PathEntry&&) = default;
PathEntry& operator=(size_t value);
PathEntry& operator=(AZ::Name value);
PathEntry& operator=(AZStd::string_view value);
bool operator==(const PathEntry& other) const;
bool operator==(size_t index) const;
bool operator==(const AZ::Name& key) const;
bool operator==(AZStd::string_view key) const;
bool operator!=(const PathEntry& other) const;
bool operator!=(size_t index) const;
bool operator!=(const AZ::Name& key) const;
bool operator!=(AZStd::string_view key) const;
void SetEndOfArray();
bool IsEndOfArray() const;
bool IsIndex() const;
bool IsKey() const;
size_t GetIndex() const;
const AZ::Name& GetKey() const;
private:
AZStd::variant<size_t, AZ::Name> m_value;
};
//! Represents a path, represented as a series of PathEntry values, to a position in a Value.
class Path final
{
public:
using ContainerType = AZStd::vector<PathEntry>;
static constexpr char PathSeparator = '/';
static constexpr char EscapeCharacter = '~';
static constexpr char TildeSequence = '0';
static constexpr char ForwardSlashSequence = '1';
static constexpr char EndOfArrayCharacter = '-';
Path() = default;
Path(const Path&) = default;
Path(Path&&) = default;
explicit Path(AZStd::initializer_list<PathEntry> init);
//! Creates a Path from a path string, a path string is formatted per the JSON pointer specification
//! and looks like "/path/to/value/0"
explicit Path(AZStd::string_view pathString);
template<class InputIterator>
explicit Path(InputIterator first, InputIterator last)
: m_entries(first, last)
{
}
Path& operator=(const Path&) = default;
Path& operator=(Path&&) = default;
Path operator/(const PathEntry&) const;
Path operator/(size_t) const;
Path operator/(AZ::Name) const;
Path operator/(AZStd::string_view) const;
Path operator/(const Path&) const;
Path& operator/=(const PathEntry&);
Path& operator/=(size_t);
Path& operator/=(AZ::Name);
Path& operator/=(AZStd::string_view);
Path& operator/=(const Path&);
bool operator==(const Path&) const;
const ContainerType& GetEntries() const;
void Push(PathEntry entry);
void Push(size_t entry);
void Push(AZ::Name entry);
void Push(AZStd::string_view key);
void Pop();
void Clear();
PathEntry At(size_t index) const;
size_t Size() const;
PathEntry& operator[](size_t index);
const PathEntry& operator[](size_t index) const;
ContainerType::iterator begin();
ContainerType::iterator end();
ContainerType::const_iterator begin() const;
ContainerType::const_iterator end() const;
ContainerType::const_iterator cbegin() const;
ContainerType::const_iterator cend() const;
size_t size() const;
//! Gets the length this path would require, if string-formatted.
//! The length includes the contents of the string but not a null terminator.
size_t GetStringLength() const;
//! Formats a JSON-pointer style path string into the target buffer.
//! This operation will fail if bufferSize < GetStringLength() + 1
void FormatString(char* stringBuffer, size_t bufferSize) const;
//! Returns a JSON-pointer style path string for this path.
AZStd::string ToString() const;
void AppendToString(AZStd::string& output) const;
//! Reads a JSON-pointer style path from pathString and replaces this path's contents.
//! Paths are accepted in the following forms:
//! "/path/to/foo/0"
//! "path/to/foo/0"
void FromString(AZStd::string_view pathString);
private:
ContainerType m_entries;
};
} // namespace AZ::Dom
@@ -0,0 +1,184 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/DomUtils.h>
#include <AzCore/IO/ByteContainerStream.h>
namespace AZ::Dom::Utils
{
Visitor::Result ReadFromString(Backend& backend, AZStd::string_view string, AZ::Dom::Lifetime lifetime, Visitor& visitor)
{
return backend.ReadFromBuffer(string.data(), string.length(), lifetime, visitor);
}
Visitor::Result ReadFromStringInPlace(Backend& backend, AZStd::string& string, Visitor& visitor)
{
return backend.ReadFromBufferInPlace(string.data(), string.size(), visitor);
}
AZ::Outcome<Value, AZStd::string> WriteToValue(const Backend::WriteCallback& writeCallback)
{
Value value;
AZStd::unique_ptr<Visitor> writer = value.GetWriteHandler();
Visitor::Result result = writeCallback(*writer);
if (!result.IsSuccess())
{
return AZ::Failure(result.GetError().FormatVisitorErrorMessage());
}
return AZ::Success(AZStd::move(value));
}
bool DeepCompareIsEqual(const Value& lhs, const Value& rhs)
{
const Value::ValueType& lhsValue = lhs.GetInternalValue();
const Value::ValueType& rhsValue = rhs.GetInternalValue();
if (lhs.IsString() && rhs.IsString())
{
// If we both hold the same ref counted string we don't need to do a full comparison
if (AZStd::holds_alternative<Value::SharedStringType>(lhsValue) && lhsValue == rhsValue)
{
return true;
}
return lhs.GetString() == rhs.GetString();
}
return AZStd::visit(
[&](auto&& ourValue) -> bool
{
using Alternative = AZStd::decay_t<decltype(ourValue)>;
if constexpr (AZStd::is_same_v<Alternative, ObjectPtr>)
{
if (!rhs.IsObject())
{
return false;
}
auto&& theirValue = AZStd::get<AZStd::remove_cvref_t<decltype(ourValue)>>(rhsValue);
if (ourValue == theirValue)
{
return true;
}
const Object::ContainerType& ourValues = ourValue->GetValues();
const Object::ContainerType& theirValues = theirValue->GetValues();
if (ourValues.size() != theirValues.size())
{
return false;
}
for (size_t i = 0; i < ourValues.size(); ++i)
{
const Object::EntryType& lhsChild = ourValues[i];
auto rhsIt = rhs.FindMember(lhsChild.first);
if (rhsIt == rhs.MemberEnd() || !DeepCompareIsEqual(lhsChild.second, rhsIt->second))
{
return false;
}
}
return true;
}
else if constexpr (AZStd::is_same_v<Alternative, ArrayPtr>)
{
if (!rhs.IsArray())
{
return false;
}
auto&& theirValue = AZStd::get<AZStd::remove_cvref_t<decltype(ourValue)>>(rhsValue);
if (ourValue == theirValue)
{
return true;
}
const Array::ContainerType& ourValues = ourValue->GetValues();
const Array::ContainerType& theirValues = theirValue->GetValues();
if (ourValues.size() != theirValues.size())
{
return false;
}
for (size_t i = 0; i < ourValues.size(); ++i)
{
const Value& lhsChild = ourValues[i];
const Value& rhsChild = theirValues[i];
if (!DeepCompareIsEqual(lhsChild, rhsChild))
{
return false;
}
}
return true;
}
else if constexpr (AZStd::is_same_v<Alternative, NodePtr>)
{
if (!rhs.IsNode())
{
return false;
}
auto&& theirValue = AZStd::get<AZStd::remove_cvref_t<decltype(ourValue)>>(rhsValue);
if (ourValue == theirValue)
{
return true;
}
const Node& ourNode = *ourValue;
const Node& theirNode = *theirValue;
const Object::ContainerType& ourProperties = ourNode.GetProperties();
const Object::ContainerType& theirProperties = theirNode.GetProperties();
if (ourProperties.size() != theirProperties.size())
{
return false;
}
for (size_t i = 0; i < ourProperties.size(); ++i)
{
const Object::EntryType& lhsChild = ourProperties[i];
auto rhsIt = rhs.FindMember(lhsChild.first);
if (rhsIt == rhs.MemberEnd() || !DeepCompareIsEqual(lhsChild.second, rhsIt->second))
{
return false;
}
}
const Array::ContainerType& ourChildren = ourNode.GetChildren();
const Array::ContainerType& theirChildren = theirNode.GetChildren();
for (size_t i = 0; i < ourChildren.size(); ++i)
{
const Value& lhsChild = ourChildren[i];
const Value& rhsChild = theirChildren[i];
if (!DeepCompareIsEqual(lhsChild, rhsChild))
{
return false;
}
}
return true;
}
else
{
return lhs == rhs;
}
},
lhsValue);
}
Value DeepCopy(const Value& value, bool copyStrings)
{
Value copiedValue;
AZStd::unique_ptr<Visitor> writer = copiedValue.GetWriteHandler();
value.Accept(*writer, copyStrings);
return copiedValue;
}
} // namespace AZ::Dom::Utils
@@ -0,0 +1,23 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/DOM/DomBackend.h>
#include <AzCore/DOM/DomValue.h>
namespace AZ::Dom::Utils
{
Visitor::Result ReadFromString(Backend& backend, AZStd::string_view string, AZ::Dom::Lifetime lifetime, Visitor& visitor);
Visitor::Result ReadFromStringInPlace(Backend& backend, AZStd::string& string, Visitor& visitor);
AZ::Outcome<Value, AZStd::string> WriteToValue(const Backend::WriteCallback& writeCallback);
bool DeepCompareIsEqual(const Value& lhs, const Value& rhs);
Value DeepCopy(const Value& value, bool copyStrings = true);
} // namespace AZ::Dom::Utils
File diff suppressed because it is too large Load Diff
+415
View File
@@ -0,0 +1,415 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/DOM/DomBackend.h>
#include <AzCore/DOM/DomVisitor.h>
#include <AzCore/Memory/HphaSchema.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/stack.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
namespace AZ::Dom
{
class PathEntry;
class Path;
using KeyType = AZ::Name;
//! The type of underlying value stored in a value. \see Value
enum class Type
{
Null,
Bool,
Object,
Array,
String,
Int64,
Uint64,
Double,
Node,
Opaque,
};
//! The allocator used by Value.
//! Value heap allocates shared_ptrs for its container storage (Array / Object / Node) alongside
class ValueAllocator final : public SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false, false>
{
public:
AZ_TYPE_INFO(ValueAllocator, "{5BC8B389-72C7-459E-B502-12E74D61869F}");
using Base = SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false, false>;
ValueAllocator()
: Base("DomValueAllocator", "Allocator for AZ::Dom::Value")
{
DisableOverriding();
}
};
using StdValueAllocator = AZStdAlloc<ValueAllocator>;
class Value;
//! Internal storage for a Value array: an ordered list of Values.
class Array
{
public:
using ContainerType = AZStd::vector<Value, StdValueAllocator>;
using Iterator = ContainerType::iterator;
using ConstIterator = ContainerType::const_iterator;
static constexpr const size_t ReserveIncrement = 4;
static_assert((ReserveIncrement & (ReserveIncrement - 1)) == 0, "ReserveIncremenet must be a power of 2");
const ContainerType& GetValues() const;
private:
ContainerType m_values;
friend class Value;
};
using ArrayPtr = AZStd::shared_ptr<Array>;
using ConstArrayPtr = AZStd::shared_ptr<const Array>;
//! Internal storage for a Value object: an ordered list of Name / Value pairs.
class Object
{
public:
using EntryType = AZStd::pair<KeyType, Value>;
using ContainerType = AZStd::vector<EntryType, StdValueAllocator>;
using Iterator = ContainerType::iterator;
using ConstIterator = ContainerType::const_iterator;
static constexpr const size_t ReserveIncrement = 8;
static_assert((ReserveIncrement & (ReserveIncrement - 1)) == 0, "ReserveIncremenet must be a power of 2");
const ContainerType& GetValues() const;
private:
ContainerType m_values;
friend class Value;
};
using ObjectPtr = AZStd::shared_ptr<Object>;
using ConstObjectPtr = AZStd::shared_ptr<const Object>;
//! Storage for a Value node: a named Value with both properties and children.
//! Properties are stored as an ordered list of Name / Value pairs.
//! Children are stored as an oredered list of Values.
class Node
{
public:
Node() = default;
Node(const Node&) = default;
Node(Node&&) = default;
explicit Node(AZ::Name name);
Node& operator=(const Node&) = default;
Node& operator=(Node&&) = default;
AZ::Name GetName() const;
void SetName(AZ::Name name);
Object::ContainerType& GetProperties();
const Object::ContainerType& GetProperties() const;
Array::ContainerType& GetChildren();
const Array::ContainerType& GetChildren() const;
private:
AZ::Name m_name;
Object::ContainerType m_properties;
Array::ContainerType m_children;
friend class Value;
};
using NodePtr = AZStd::shared_ptr<Node>;
using ConstNodePtr = AZStd::shared_ptr<Node>;
//! Value is a typed union of Dom types that can represent the types provdied by AZ::Dom::Visitor.
//! Value can be one of the following types:
//! - Null: a type with no value, this is the default type for Value
//! - Bool: a true or false boolean value
//! - Object: a container with an ordered list of Name/Value pairs, analagous to a JSON object
//! - Array: a container with an ordered list of Values, analagous to a JSON array
//! - String: a UTF-8 string
//! - Int64: a signed, 64-bit integer
//! - Uint64: an unsigned, 64-bit integer
//! - Double: a double precision floating point value
//! - Node: a container with a Name, an ordered list of Name/Values pairs (attributes), and an ordered list of Values (children),
//! analagous to an XML node
//! - Opaque: an arbitrary value stored in an AZStd::any. This is a non-serializable representation of an entry used only for in-memory
//! options. This is intended to be used as an intermediate value over the course of DOM transformation and as a proxy to pass through
//! types of which the DOM has no knowledge to other systems.
//! \note Value is a copy-on-write data structure and may be cheaply returned by value. Heap allocated data larger than the size of the
//! value itself (objects, arrays, and nodes) are copied by new Values only when their contents change, so care should be taken in
//! performance critical code to avoid mutation operations such as operator[] to avoid copies. It is recommended that an immutable Value
//! be explicitly be stored as a `const Value` to avoid accidental detach and copy operations.
class Value final
{
public:
// Determine the short string buffer size based on the size of our largest internal type (string_view)
// minus the size of the short string size field.
static constexpr const size_t ShortStringSize = sizeof(AZStd::string_view) - 2;
using ShortStringType = AZStd::fixed_string<ShortStringSize>;
using SharedStringContainer = AZStd::vector<char>;
using SharedStringType = AZStd::shared_ptr<const SharedStringContainer>;
using OpaqueStorageType = AZStd::shared_ptr<AZStd::any>;
//! The internal storage type for Value.
//! These types do not correspond one-to-one with the Value's external Type as there may be multiple storage classes
//! for the same type in some instances, such as string storage.
using ValueType = AZStd::variant<
// Null
AZStd::monostate,
// Int64
int64_t,
// Uint64
uint64_t,
// Double
double,
// Bool
bool,
// String
AZStd::string_view,
SharedStringType,
ShortStringType,
// Object
ObjectPtr,
// Array
ArrayPtr,
// Node
NodePtr,
// Opaque
OpaqueStorageType>;
// Constructors...
Value() = default;
Value(const Value&);
Value(Value&&) noexcept;
Value(AZStd::string_view stringView, bool copy);
explicit Value(const ValueType&);
explicit Value(ValueType&&);
explicit Value(SharedStringType sharedString);
explicit Value(int8_t value);
explicit Value(uint8_t value);
explicit Value(int16_t value);
explicit Value(uint16_t value);
explicit Value(int32_t value);
explicit Value(uint32_t value);
explicit Value(int64_t value);
explicit Value(uint64_t value);
explicit Value(float value);
explicit Value(double value);
explicit Value(bool value);
explicit Value(Type type);
// Disable accidental calls to Value(bool) with pointer types
template<class T>
explicit Value(T*) = delete;
static Value FromOpaqueValue(const AZStd::any& value);
// Equality / comparison / swap...
Value& operator=(const Value&);
Value& operator=(Value&&) noexcept;
//! Assignment operator to allow forwarding types constructible via Value(T) to be assigned
template<class T>
auto operator=(T&& arg)
-> AZStd::enable_if_t<!AZStd::is_same_v<AZStd::remove_cvref_t<T>, Value> && AZStd::is_constructible_v<Value, T>, Value&>
{
return operator=(Value(AZStd::forward<T>(arg)));
}
bool operator==(const Value& rhs) const;
bool operator!=(const Value& rhs) const;
void Swap(Value& other) noexcept;
// Type info...
Type GetType() const;
bool IsNull() const;
bool IsFalse() const;
bool IsTrue() const;
bool IsBool() const;
bool IsNode() const;
bool IsObject() const;
bool IsArray() const;
bool IsOpaqueValue() const;
bool IsNumber() const;
bool IsInt() const;
bool IsUint() const;
bool IsDouble() const;
bool IsString() const;
// Object API (also used by Node)...
Value& SetObject();
size_t MemberCount() const;
size_t MemberCapacity() const;
bool ObjectEmpty() const;
Value& operator[](KeyType name);
const Value& operator[](KeyType name) const;
Value& operator[](AZStd::string_view name);
const Value& operator[](AZStd::string_view name) const;
Object::ConstIterator MemberBegin() const;
Object::ConstIterator MemberEnd() const;
Object::Iterator MutableMemberBegin();
Object::Iterator MutableMemberEnd();
Object::Iterator FindMutableMember(KeyType name);
Object::Iterator FindMutableMember(AZStd::string_view name);
Object::ConstIterator FindMember(KeyType name) const;
Object::ConstIterator FindMember(AZStd::string_view name) const;
Value& MemberReserve(size_t newCapacity);
bool HasMember(KeyType name) const;
bool HasMember(AZStd::string_view name) const;
Value& AddMember(KeyType name, const Value& value);
Value& AddMember(AZStd::string_view name, const Value& value);
Value& AddMember(KeyType name, Value&& value);
Value& AddMember(AZStd::string_view name, Value&& value);
void RemoveAllMembers();
void RemoveMember(KeyType name);
void RemoveMember(AZStd::string_view name);
Object::Iterator RemoveMember(Object::Iterator pos);
Object::Iterator EraseMember(Object::Iterator pos);
Object::Iterator EraseMember(Object::Iterator first, Object::Iterator last);
Object::Iterator EraseMember(KeyType name);
Object::Iterator EraseMember(AZStd::string_view name);
Object::ContainerType& GetMutableObject();
const Object::ContainerType& GetObject() const;
// Array API (also used by Node)...
Value& SetArray();
size_t ArraySize() const;
size_t ArrayCapacity() const;
bool IsArrayEmpty() const;
void ClearArray();
Value& operator[](size_t index);
const Value& operator[](size_t index) const;
Value& MutableArrayAt(size_t index);
const Value& ArrayAt(size_t index) const;
Array::ConstIterator ArrayBegin() const;
Array::ConstIterator ArrayEnd() const;
Array::Iterator MutableArrayBegin();
Array::Iterator MutableArrayEnd();
Value& ArrayReserve(size_t newCapacity);
Value& ArrayPushBack(Value value);
Value& ArrayPopBack();
Array::Iterator ArrayErase(Array::Iterator pos);
Array::Iterator ArrayErase(Array::Iterator first, Array::Iterator last);
Array::ContainerType& GetMutableArray();
const Array::ContainerType& GetArray() const;
// Node API (supports both object + array API, plus a dedicated NodeName)...
void SetNode(AZ::Name name);
void SetNode(AZStd::string_view name);
AZ::Name GetNodeName() const;
void SetNodeName(AZ::Name name);
void SetNodeName(AZStd::string_view name);
//! Convenience method, sets the first non-node element of a Node.
void SetNodeValue(Value value);
//! Convenience method, gets the first non-node element of a Node.
Value GetNodeValue() const;
Node& GetMutableNode();
const Node& GetNode() const;
// int API...
int64_t GetInt64() const;
void SetInt64(int64_t);
// uint API...
uint64_t GetUint64() const;
void SetUint64(uint64_t);
// bool API...
bool GetBool() const;
void SetBool(bool);
// double API...
double GetDouble() const;
void SetDouble(double);
// String API...
AZStd::string_view GetString() const;
size_t GetStringLength() const;
void SetString(AZStd::string_view);
void SetString(SharedStringType sharedString);
void CopyFromString(AZStd::string_view);
// Opaque type API...
const AZStd::any& GetOpaqueValue() const;
//! This sets this Value to represent a value of an type that the DOM has
//! no formal knowledge of. Where possible, it should be preferred to
//! serialize an opaque type into a DOM value instead, as serializers
//! and other systems will have no means of dealing with fully arbitrary
//! values.
void SetOpaqueValue(AZStd::any);
// Null API...
void SetNull();
// Visitor API...
Visitor::Result Accept(Visitor& visitor, bool copyStrings) const;
AZStd::unique_ptr<Visitor> GetWriteHandler();
// Path API...
Value& operator[](const PathEntry& entry);
const Value& operator[](const PathEntry& entry) const;
Value& operator[](const Path& path);
const Value& operator[](const Path& path) const;
const Value* FindChild(const PathEntry& entry) const;
Value* FindMutableChild(const PathEntry& entry);
const Value* FindChild(const Path& path) const;
Value* FindMutableChild(const Path& path);
//! Gets the internal value of this Value. Note that this value's types may not correspond one-to-one with the Type enumeration,
//! as internally the same type might have different storage mechanisms. Where possible, prefer using the typed API.
const ValueType& GetInternalValue() const;
private:
const Node& GetNodeInternal() const;
Node& GetNodeInternal();
const Object::ContainerType& GetObjectInternal() const;
Object::ContainerType& GetObjectInternal();
const Array::ContainerType& GetArrayInternal() const;
Array::ContainerType& GetArrayInternal();
explicit Value(AZStd::any opaqueValue);
static_assert(
sizeof(ValueType) == sizeof(ShortStringType) + sizeof(size_t), "ValueType should have no members larger than ShortStringType");
ValueType m_value;
};
} // namespace AZ::Dom
@@ -0,0 +1,262 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/DomValueWriter.h>
namespace AZ::Dom
{
ValueWriter::ValueWriter(Value& outputValue)
: m_result(outputValue)
{
}
VisitorFlags ValueWriter::GetVisitorFlags() const
{
return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects | VisitorFlags::SupportsNodes;
}
ValueWriter::ValueInfo::ValueInfo(Value& container)
: m_container(container)
{
}
Visitor::Result ValueWriter::Null()
{
CurrentValue().SetNull();
return FinishWrite();
}
Visitor::Result ValueWriter::Bool(bool value)
{
CurrentValue().SetBool(value);
return FinishWrite();
}
Visitor::Result ValueWriter::Int64(AZ::s64 value)
{
CurrentValue().SetInt64(value);
return FinishWrite();
}
Visitor::Result ValueWriter::Uint64(AZ::u64 value)
{
CurrentValue().SetUint64(value);
return FinishWrite();
}
Visitor::Result ValueWriter::Double(double value)
{
CurrentValue().SetDouble(value);
return FinishWrite();
}
Visitor::Result ValueWriter::String(AZStd::string_view value, Lifetime lifetime)
{
if (lifetime == Lifetime::Persistent)
{
CurrentValue().SetString(value);
}
else
{
CurrentValue().CopyFromString(value);
}
return FinishWrite();
}
Visitor::Result ValueWriter::RefCountedString(AZStd::shared_ptr<const AZStd::vector<char>> value, [[maybe_unused]] Lifetime lifetime)
{
CurrentValue().SetString(AZStd::move(value));
return FinishWrite();
}
Visitor::Result ValueWriter::StartObject()
{
CurrentValue().SetObject();
m_entryStack.emplace(CurrentValue());
return VisitorSuccess();
}
template <class T, class A>
void MoveVectorMemory(AZStd::vector<T, A>& dest, AZStd::vector<T, A>& source)
{
dest.resize_no_construct(source.size());
const size_t size = sizeof(T) * source.size();
memcpy(dest.data(), source.data(), size);
memset(source.data(), 0, size);
source.resize(0);
}
Visitor::Result ValueWriter::EndContainer(Type containerType, AZ::u64 attributeCount, AZ::u64 elementCount)
{
const char* endMethodName;
switch (containerType)
{
case Type::Object:
endMethodName = "EndObject";
break;
case Type::Array:
endMethodName = "EndArray";
break;
case Type::Node:
endMethodName = "EndNode";
break;
default:
AZ_Assert(false, "Invalid container type specified");
return VisitorFailure(VisitorErrorCode::InternalError, "AZ::Dom::ValueWriter: EndContainer called with invalid container type");
}
if (m_entryStack.empty())
{
return VisitorFailure(
VisitorErrorCode::InternalError,
AZStd::string::format("AZ::Dom::ValueWriter: %s called without a matching call", endMethodName));
}
const ValueInfo& topEntry = m_entryStack.top();
Value& container = topEntry.m_container;
ValueBuffer& buffer = GetValueBuffer();
if (container.GetType() != containerType)
{
return VisitorFailure(
VisitorErrorCode::InternalError,
AZStd::string::format("AZ::Dom::ValueWriter: %s called from within a different container type", endMethodName));
}
if (aznumeric_cast<AZ::u64>(buffer.m_attributes.size()) != attributeCount)
{
return VisitorFailure(
VisitorErrorCode::InternalError,
AZStd::string::format(
"AZ::Dom::ValueWriter: %s expected %llu attributes but received %zu attributes instead", endMethodName, attributeCount,
buffer.m_attributes.size()));
}
if (aznumeric_cast<AZ::u64>(buffer.m_elements.size()) != elementCount)
{
return VisitorFailure(
VisitorErrorCode::InternalError,
AZStd::string::format(
"AZ::Dom::ValueWriter: %s expected %llu elements but received %zu elements instead", endMethodName, elementCount,
buffer.m_elements.size()));
}
if (buffer.m_attributes.size() > 0)
{
MoveVectorMemory(container.GetMutableObject(), buffer.m_attributes);
}
if(buffer.m_elements.size() > 0)
{
MoveVectorMemory(container.GetMutableArray(), buffer.m_elements);
}
m_entryStack.pop();
return FinishWrite();
}
ValueWriter::ValueBuffer& ValueWriter::GetValueBuffer()
{
if (m_entryStack.size() <= m_valueBuffers.size())
{
return m_valueBuffers[m_entryStack.size() - 1];
}
m_valueBuffers.resize(m_entryStack.size());
return m_valueBuffers[m_entryStack.size() - 1];
}
Visitor::Result ValueWriter::EndObject(AZ::u64 attributeCount)
{
return EndContainer(Type::Object, attributeCount, 0);
}
Visitor::Result ValueWriter::Key(AZ::Name key)
{
AZ_Assert(!m_entryStack.empty(), "Attempmted to push a key with no object");
AZ_Assert(!m_entryStack.top().m_container.IsArray(), "Attempted to push a key to an array");
m_entryStack.top().m_key = AZStd::move(key);
return VisitorSuccess();
}
Visitor::Result ValueWriter::RawKey(AZStd::string_view key, [[maybe_unused]] Lifetime lifetime)
{
return Key(AZ::Name(key));
}
Visitor::Result ValueWriter::StartArray()
{
CurrentValue().SetArray();
m_entryStack.emplace(CurrentValue());
return VisitorSuccess();
}
Visitor::Result ValueWriter::EndArray(AZ::u64 elementCount)
{
return EndContainer(Type::Array, 0, elementCount);
}
Visitor::Result ValueWriter::StartNode(AZ::Name name)
{
CurrentValue().SetNode(name);
m_entryStack.emplace(CurrentValue());
return VisitorSuccess();
}
Visitor::Result ValueWriter::RawStartNode(AZStd::string_view name, [[maybe_unused]] Lifetime lifetime)
{
return StartNode(AZ::Name(name));
}
Visitor::Result ValueWriter::EndNode(AZ::u64 attributeCount, AZ::u64 elementCount)
{
return EndContainer(Type::Node, attributeCount, elementCount);
}
Visitor::Result ValueWriter::OpaqueValue(OpaqueType& value)
{
CurrentValue().SetOpaqueValue(value);
return FinishWrite();
}
Visitor::Result ValueWriter::FinishWrite()
{
if (m_entryStack.empty())
{
return VisitorSuccess();
}
Value value;
m_entryStack.top().m_value.Swap(value);
ValueInfo& newEntry = m_entryStack.top();
if (!newEntry.m_key.IsEmpty())
{
GetValueBuffer().m_attributes.emplace_back(AZStd::move(newEntry.m_key), AZStd::move(value));
newEntry.m_key = AZ::Name();
}
else
{
GetValueBuffer().m_elements.emplace_back(AZStd::move(value));
}
return VisitorSuccess();
}
Value& ValueWriter::CurrentValue()
{
if (m_entryStack.empty())
{
return m_result;
}
return m_entryStack.top().m_value;
}
} // namespace AZ::Dom
@@ -0,0 +1,72 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/DOM/DomValue.h>
#include <AzCore/std/containers/stack.h>
namespace AZ::Dom
{
//! Visitor that writes to a Value.
//! Supports all Visitor operations.
class ValueWriter : public Visitor
{
public:
ValueWriter(Value& outputValue);
VisitorFlags GetVisitorFlags() const override;
Result Null() override;
Result Bool(bool value) override;
Result Int64(AZ::s64 value) override;
Result Uint64(AZ::u64 value) override;
Result Double(double value) override;
Result String(AZStd::string_view value, Lifetime lifetime) override;
Result RefCountedString(AZStd::shared_ptr<const AZStd::vector<char>> value, Lifetime lifetime) override;
Result StartObject() override;
Result EndObject(AZ::u64 attributeCount) override;
Result Key(AZ::Name key) override;
Result RawKey(AZStd::string_view key, Lifetime lifetime) override;
Result StartArray() override;
Result EndArray(AZ::u64 elementCount) override;
Result StartNode(AZ::Name name) override;
Result RawStartNode(AZStd::string_view name, Lifetime lifetime) override;
Result EndNode(AZ::u64 attributeCount, AZ::u64 elementCount) override;
Result OpaqueValue(OpaqueType& value) override;
private:
Result FinishWrite();
Value& CurrentValue();
Visitor::Result EndContainer(Type containerType, AZ::u64 attributeCount, AZ::u64 elementCount);
struct ValueInfo
{
ValueInfo(Value& container);
KeyType m_key;
Value m_value;
Value& m_container;
};
struct ValueBuffer
{
Array::ContainerType m_elements;
Object::ContainerType m_attributes;
};
ValueBuffer& GetValueBuffer();
Value& m_result;
// Stores info about the current value being processed
AZStd::stack<ValueInfo, AZStd::deque<ValueInfo, AZStdAlloc<ValueAllocator>>> m_entryStack;
// Provides temporary storage for elements and attributes to prevent extra heap allocations
// These buffers persist to be reused even as the entry stack changes
AZStd::vector<ValueBuffer, AZStdAlloc<ValueAllocator>> m_valueBuffers;
};
} // namespace AZ::Dom
@@ -0,0 +1,244 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/DomVisitor.h>
namespace AZ::Dom
{
const char* VisitorError::CodeToString(VisitorErrorCode code)
{
switch (code)
{
case VisitorErrorCode::UnsupportedOperation:
return "operation not supported";
case VisitorErrorCode::InvalidData:
return "invalid data specified";
case VisitorErrorCode::InternalError:
return "internal error";
default:
return "unknown error";
}
}
VisitorError::VisitorError(VisitorErrorCode code)
: m_code(code)
{
}
VisitorError::VisitorError(VisitorErrorCode code, AZStd::string additionalInfo)
: m_code(code)
, m_additionalInfo(AZStd::move(additionalInfo))
{
}
VisitorErrorCode VisitorError::GetCode() const
{
return m_code;
}
const AZStd::string& VisitorError::GetAdditionalInfo() const
{
return m_additionalInfo;
}
AZStd::string VisitorError::FormatVisitorErrorMessage() const
{
if (m_additionalInfo.empty())
{
return AZStd::string::format("VisitorError: %s.", CodeToString(m_code));
}
return AZStd::string::format("VisitorError: %s. %s.", CodeToString(m_code), m_additionalInfo.c_str());
}
Visitor::Result Visitor::VisitorFailure(VisitorErrorCode code)
{
return AZ::Failure(VisitorError(code));
}
Visitor::Result Visitor::VisitorFailure(VisitorErrorCode code, AZStd::string additionalInfo)
{
return AZ::Failure(VisitorError(code, AZStd::move(additionalInfo)));
}
Visitor::Result Visitor::VisitorFailure(VisitorError error)
{
return AZ::Failure(error);
}
Visitor::Result Visitor::VisitorSuccess()
{
return AZ::Success();
}
Visitor::Result Visitor::Null()
{
return VisitorSuccess();
}
Visitor::Result Visitor::Bool([[maybe_unused]] bool value)
{
return VisitorSuccess();
}
Visitor::Result Visitor::Int64([[maybe_unused]] AZ::s64 value)
{
return VisitorSuccess();
}
Visitor::Result Visitor::Uint64([[maybe_unused]] AZ::u64 value)
{
return VisitorSuccess();
}
Visitor::Result Visitor::Double([[maybe_unused]] double value)
{
return VisitorSuccess();
}
Visitor::Result Visitor::String([[maybe_unused]] AZStd::string_view value, [[maybe_unused]] Lifetime lifetime)
{
return VisitorSuccess();
}
Visitor::Result Visitor::RefCountedString(AZStd::shared_ptr<const AZStd::vector<char>> value, Lifetime lifetime)
{
return String({ value->data(), value->size() }, lifetime);
}
Visitor::Result Visitor::OpaqueValue([[maybe_unused]] OpaqueType& value)
{
if (!SupportsOpaqueValues())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Opaque values are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::RawValue([[maybe_unused]] AZStd::string_view value, [[maybe_unused]] Lifetime lifetime)
{
if (!SupportsRawValues())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Raw values are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::StartObject()
{
if (!SupportsObjects())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Objects are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::EndObject([[maybe_unused]] AZ::u64 attributeCount)
{
if (!SupportsObjects())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Objects are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::Key([[maybe_unused]] AZ::Name key)
{
if (!SupportsObjects() && !SupportsNodes())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Keys are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::RawKey(AZStd::string_view key, [[maybe_unused]] Lifetime lifetime)
{
if (!SupportsRawKeys())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Raw keys are not supported by this visitor");
}
return Key(AZ::Name(key));
}
Visitor::Result Visitor::StartArray()
{
if (!SupportsArrays())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Arrays are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::EndArray([[maybe_unused]] AZ::u64 elementCount)
{
if (!SupportsArrays())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Arrays are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::StartNode([[maybe_unused]] AZ::Name name)
{
if (!SupportsNodes())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Nodes are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::RawStartNode(AZStd::string_view name, [[maybe_unused]] Lifetime lifetime)
{
return StartNode(AZ::Name(name));
}
Visitor::Result Visitor::EndNode([[maybe_unused]] AZ::u64 attributeCount, [[maybe_unused]] AZ::u64 elementCount)
{
if (!SupportsNodes())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Nodes are not supported by this visitor");
}
return VisitorSuccess();
}
VisitorFlags Visitor::GetVisitorFlags() const
{
// By default support raw keys (promoting them to AZ::Name) and support Array / Object / Node
// We leave Opaque type support and Raw Values to more specialized, implementation-specific cases
return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects | VisitorFlags::SupportsNodes;
}
bool Visitor::SupportsRawValues() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsRawValues) != VisitorFlags::Null;
}
bool Visitor::SupportsRawKeys() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsRawKeys) != VisitorFlags::Null;
}
bool Visitor::SupportsObjects() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsObjects) != VisitorFlags::Null;
}
bool Visitor::SupportsArrays() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsArrays) != VisitorFlags::Null;
}
bool Visitor::SupportsNodes() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsNodes) != VisitorFlags::Null;
}
bool Visitor::SupportsOpaqueValues() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsOpaqueValues) != VisitorFlags::Null;
}
} // namespace AZ::Dom
@@ -0,0 +1,245 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Name/Name.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/any.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/string.h>
namespace AZ::Dom
{
//
// Lifetime enum
//
//! Specifies the period in which a reference value will still be alive and safe to read.
enum class Lifetime
{
//! Specifies that the value is safe to read and will remain so indefinitely.
//! This implies that the value will not be mutated for the duration of this storage.
Persistent,
//! Specifies that the value may change or be deallocated, and must be copied to be safely stored.
Temporary,
};
//
// VisitorErrorCode enum
//
//! Error code specifying the reason a Visitor operation failed.
enum class VisitorErrorCode
{
//! Set when a Visitor doesn't have an implementation for a given attribute type.
//! A pure-JSON serializer might reject a Node attribute, for example, and serialization visitors
//! can forbid non-serializable Opaque types.
UnsupportedOperation,
//! Set when a Visitor has received malformed or invalid data.
//! Potential sources include mismatching Begin/End call pairs or invalid attribute or element counts
//! being sent to End methods.
InvalidData,
//! The Visitor failed for some other reason not caused by invalid input.
//! If returning a custom error with this code, it's preferrable to also provide supplemental info
//! in the form of an explanatory string.
InternalError
};
//
// VisitorError class
//
//! Details of the reason for failure within a VisitorInterface operation.
class VisitorError final
{
public:
explicit VisitorError(VisitorErrorCode code);
VisitorError(VisitorErrorCode code, AZStd::string additionalInfo);
//! Gets the error code associated with this error.
VisitorErrorCode GetCode() const;
//! Gets a supplemental error info string from the error.
//! Returns an empty string if no additional information was provided to the error.
const AZStd::string& GetAdditionalInfo() const;
//! Provides a formatted, human-readable error description that can be used for logging purposes.
AZStd::string FormatVisitorErrorMessage() const;
//! Helper method, translates a VisitorErrorCode to a human readable string.
static const char* CodeToString(VisitorErrorCode code);
private:
VisitorErrorCode m_code;
AZStd::string m_additionalInfo;
};
//! A type alias for opaque DOM types that aren't meant to be serializable.
//! \see VisitorInterface::OpaqueValue
using OpaqueType = AZStd::any;
//
// VisitorFlags enum
//
//! Flags representning capabilities of a \ref Visitor.
enum class VisitorFlags : AZ::u16
{
//! No flags are set. This can be used in conjunction with bitwise operators to check a flag.
Null = 0,
//! If set, this Visitor interface supports raw strings in place of specific value types.
//! Visitors with this flag accept RawValue calls in lieu of more specific value calls such as Int64 or String.
SupportsRawValues = (1 << 1),
//! If set, this Visitor interface supports raw strings in place of Name types for keys and Node names.
//! Visitors with this flag accept RawKey and RawStartNode in lieu of Key and StartNode calls.
SupportsRawKeys = (1 << 2),
//! If set, this Visitor interface supports Object types described via BeginObject and EndObject.
SupportsObjects = (1 << 3),
//! If set, this Visitor interface supports Array types described via BeginArray and EndArray.
SupportsArrays = (1 << 4),
//! If set, this Visitor interface supports Node types described BeginNode and EndNode.
SupportsNodes = (1 << 4),
//! If set, this Visitor interface supports opaque values described via OpaqueValue.
SupportsOpaqueValues = (1 << 5),
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(VisitorFlags);
//
// Visitor class
//
//! An interface for performing operations on elements of a generic DOM (Document Object Model).
//! A Document Object Model is defined here as a tree structure comprised of one of the following values:
//! - Primitives: plain data types, including
//! - \ref Int64: 64 bit signed integer
//! - \ref Uint64: 64 bit unsigned integer
//! - \ref Bool: boolean value
//! - \ref Double: 64 bit double precision float
//! - \ref Null: sentinel "empty" type with no value representation
//! - \ref String: UTF8 encoded string
//! - \ref Object: an ordered container of key/value pairs where keys are \ref AZ::Name and values may be any DOM type
//! (including Object)
//! - \ref Array: an ordered container of values, in which values are any DOM value type (including Array)
//! - \ref Node: a container
//! - \ref OpaqueValue: An arbitrary value stored in an AZStd::any. This is a non-serializable representation of an
//! entry useful for in-memory options. This is intended to be used as an intermediate value over the course of DOM
//! transformation and as a proxy to pass through types of which the DOM has no knowledge to other systems.
//!
//! Opaque values are rejected by the default VisitorInterface implementation.
//!
//! Care should be ensured that DOMs representing opaque types are only visited by consumers that understand them.
class Visitor
{
public:
virtual ~Visitor() = default;
//! The result of a Visitor operation.
//! A failure indicates a non-recoverable issue and signals that no further visit calls may be made in the
//! current state.
using Result = AZ::Outcome<void, VisitorError>;
//! Returns a set of flags representing the operations this Visitor supports.
//! The base implementation supports raw keys (\see VisitorFlags::SupportsRawKeys) and
//! arrays (\see VisitorFlags::SupportsArrays), objects (\see VisitorFlags::SupportsObjects), and
//! nodes (\see VisitorFlags::SupportsNodes).
//! Raw (\see VisitorFlags::SupportsRawValues) and opaque values (\see VisitorFlags::SupportsOpaqueValues)
//! are disallowed by default, as their handling is intended to be implementation-specific.
virtual VisitorFlags GetVisitorFlags() const;
//! \see VisitorFlags::SupportsRawValues
bool SupportsRawValues() const;
//! \see VisitorFlags::SupportsRawKeys
bool SupportsRawKeys() const;
//! \see VisitorFlags::SupportsObjects
bool SupportsObjects() const;
//! \see VisitorFlags::SupportsArrays
bool SupportsArrays() const;
//! \see VisitorFlags::SupportsNodes
bool SupportsNodes() const;
//! \see VisitorFlags::SupportsOpaqueValues
bool SupportsOpaqueValues() const;
//! Operates on an empty null value.
virtual Result Null();
//! Operates on a bool value.
virtual Result Bool(bool value);
//! Operates on a signed, 64 bit integer value.
virtual Result Int64(AZ::s64 value);
//! Operates on an unsigned, 64 bit integer value.
virtual Result Uint64(AZ::u64 value);
//! Operates on a double precision, 64 bit floating point value.
virtual Result Double(double value);
//! Operates on a string value. As strings are a reference type,
//! storage semantics are provided to indicate where the value may be stored persistently or requires a copy.
//! \param lifetime Specifies the lifetime of this string - if the string has a temporary lifetime, it cannot
//! safely be stored as a reference.
virtual Result String(AZStd::string_view value, Lifetime lifetime);
//! Operates on a ref-counted string value. S
//! \param lifetime Specifies the lifetime of this string. If the string has a temporary lifetime, it may not
//! be safely stored as a reference, but may still be safely stored as a ref-counted shared_ptr.
virtual Result RefCountedString(AZStd::shared_ptr<const AZStd::vector<char>> value, Lifetime lifetime);
//! Operates on an opaque value. As opaque values are a reference type, storage semantics are provided to
//! indicate where the value may be stored persistently or requires a copy.
//! The base implementation of OpaqueValue rejects the operation, as opaque values are meant for special
//! cases with specific implementations, not generic usage.
virtual Result OpaqueValue(OpaqueType& value);
//! Operates on a raw value encoded as a UTF-8 string that hasn't had its type deduced.
//! Visitors that support raw values (\see VisitorFlags::SupportsRawValues) may parse the raw value and
//! forward it to the corresponding value call or calls of their choice.
//! The base implementation of RawValue rejects the operation, as raw values are meant to be handled on
//! a per-implementation basis.
virtual Result RawValue(AZStd::string_view value, Lifetime lifetime);
//! Operates on an Object.
//! Callers may make any number of Key calls, followed by calls representing a value (including a nested
//! StartObject call) and then must call EndObject.
virtual Result StartObject();
//! Finishes operating on an Object.
//! Callers must provide the number of attributes that were provided to the object, i.e. the number of key
//! and value calls made within the direct context of this object (but not any nested objects / nodes).
virtual Result EndObject(AZ::u64 attributeCount);
//! Specifies a key for a key/value pair.
//! Key must be called subsequent to a call to \ref StartObject or \ref StartNode and immediately followed by
//! calls representing the key's associated value.
virtual Result Key(AZ::Name key);
//! Specifies a key for a key/value pair using a raw string instead of \ref AZ::Name.
//! \see Key
virtual Result RawKey(AZStd::string_view key, Lifetime lifetime);
//! Operates on an Array.
//! Callers may make any number of subsequent value calls to represent the elements of the array, and then must
//! call EndArray.
virtual Result StartArray();
//! Finishes operating on an Array.
//! Callers must provide the number of elements that were provided to the array, i.e. the number of value calls
//! made within the direct context of this array (but not any nested arrays / nodes).
virtual Result EndArray(AZ::u64 elementCount);
//! Operates on a Node.
//! Callers may make any number of Key calls followed by value calls or value calls not prefixed with a Key
//! call, and then must call EndNode. See \ref StartObject and \ref StartArray as Node types combine the
//! functionality of both structures into a named Node structure.
virtual Result StartNode(AZ::Name name);
//! Operates on a Node using a raw string instead of \ref AZ::Name.
//! \see StartNode
virtual Result RawStartNode(AZStd::string_view name, Lifetime lifetime);
//! Finishes operating on a Node.
//! Callers must provide both the number of attributes the were provided and the number of elements that were
//! provided to the node, attributes being values prefaced by a call to Key.
virtual Result EndNode(AZ::u64 attributeCount, AZ::u64 elementCount);
protected:
Visitor() = default;
//! Helper method, constructs a failure \ref Result with the specified code.
static Result VisitorFailure(VisitorErrorCode code);
//! Helper method, constructs a failure \ref Result with the specified code and supplemental info.
static Result VisitorFailure(VisitorErrorCode code, AZStd::string additionalInfo);
//! Helper method, constructs a failure \ref Result with the specified error.
static Result VisitorFailure(VisitorError error);
//! Helper method, constructs a success \ref Result.
static Result VisitorSuccess();
};
} // namespace AZ::Dom
@@ -1,336 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "AssetTracking.h"
#include <AzCore/Debug/AssetTrackingTypes.h>
#include <AzCore/Memory/AllocatorManager.h>
#include <AzCore/Memory/HphaSchema.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/smart_ptr/make_shared.h>
namespace AZ
{
namespace Debug
{
namespace
{
struct AssetTreeNode;
// Per-thread data that needs to be stored.
struct ThreadData
{
AZStd::vector<AssetTreeNodeBase*, AZStdAssetTrackingAllocator> m_currentAssetStack;
};
// Access thread data through a virtual function to ensure that the same thread-local data is being shared across DLLs.
// Otherwise, the thread_local variables are replicated across DLLs that link the AzCore library, and you'll get a
// different version in each module.
class ThreadDataProvider
{
public:
virtual ThreadData& GetThreadData() = 0;
};
}
class AssetTrackingImpl final :
public ThreadDataProvider
{
public:
AZ_TYPE_INFO(AssetTrackingImpl, "{01E2A099-3523-40BE-80E0-E0ADD861BEE1}");
AZ_CLASS_ALLOCATOR(AssetTrackingImpl, OSAllocator, 0);
AssetTrackingImpl(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable);
~AssetTrackingImpl();
void AssetBegin(const char* id, const char* file, int line);
void AssetAttach(void* otherAllocation, const char* file, int line);
void AssetEnd();
ThreadData& GetThreadData() override;
private:
static EnvironmentVariable<AssetTrackingImpl*>& GetEnvironmentVariable();
static AssetTrackingImpl* GetSharedInstance();
static ThreadData& GetSharedThreadData();
using PrimaryAssets = AZStd::unordered_map<AssetTrackingId, AssetPrimaryInfo, AZStd::hash<AssetTrackingId>, AZStd::equal_to<AssetTrackingId>, AZStdAssetTrackingAllocator>;
using ThreadData = ThreadData;
using mutex_type = AZStd::mutex;
using lock_type = AZStd::lock_guard<mutex_type>;
mutex_type m_mutex;
PrimaryAssets m_primaryAssets;
AssetTreeNodeBase* m_assetRoot = nullptr;
AssetAllocationTableBase* m_allocationTable = nullptr;
bool m_performingAnalysis = false;
friend class AssetTracking;
friend class AssetTracking::Scope;
};
}
}
///////////////////////////////////////////////////////////////////////////////
// AssetTrackingImpl methods
///////////////////////////////////////////////////////////////////////////////
namespace AZ
{
namespace Debug
{
AssetTrackingImpl::AssetTrackingImpl(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable) :
m_assetRoot(&assetTree->GetRoot()),
m_allocationTable(allocationTable)
{
AZ_Assert(!GetSharedInstance(), "Only one AssetTrackingImpl can exist!");
GetEnvironmentVariable().Set(this);
AllocatorManager::Instance().EnterProfilingMode();
}
AssetTrackingImpl::~AssetTrackingImpl()
{
AllocatorManager::Instance().ExitProfilingMode();
GetEnvironmentVariable().Reset();
}
void AssetTrackingImpl::AssetBegin(const char* id, const char* file, int line)
{
// In the future it may be desirable to organize assets based on where in code the asset was entered into.
// For now these are ignored.
AZ_UNUSED(file);
AZ_UNUSED(line);
using namespace Internal;
AssetTrackingId assetId(id);
auto& threadData = GetSharedThreadData();
AssetTreeNodeBase* parentAsset = threadData.m_currentAssetStack.empty() ? nullptr : threadData.m_currentAssetStack.back();
AssetTreeNodeBase* childAsset;
AssetPrimaryInfo* assetPrimaryInfo;
if (!parentAsset)
{
parentAsset = m_assetRoot;
}
{
lock_type lock(m_mutex);
// Locate or create the primary record for this asset
auto primaryItr = m_primaryAssets.find(assetId);
if (primaryItr != m_primaryAssets.end())
{
assetPrimaryInfo = &primaryItr->second;
}
else
{
auto insertResult = m_primaryAssets.emplace(assetId, AssetPrimaryInfo());
assetPrimaryInfo = &insertResult.first->second;
assetPrimaryInfo->m_id = &insertResult.first->first;
}
// Add this asset to the stack for this thread's context
childAsset = parentAsset->FindOrAddChild(assetId, assetPrimaryInfo);
}
threadData.m_currentAssetStack.push_back(childAsset);
}
void AssetTrackingImpl::AssetAttach(void* otherAllocation, const char* file, int line)
{
AZ_UNUSED(file);
AZ_UNUSED(line);
using namespace Internal;
AssetTreeNodeBase* assetInfo = m_allocationTable->FindAllocation(otherAllocation);
// We will push back a nullptr if there is no asset, this is necessary to balance the call to AssetEnd()
GetSharedThreadData().m_currentAssetStack.push_back(assetInfo);
}
void AssetTrackingImpl::AssetEnd()
{
AZ_Assert(!GetSharedThreadData().m_currentAssetStack.empty(), "AssetEnd() called without matching AssetBegin() or AssetAttach. Use the AZ_ASSET_NAMED_SCOPE and AZ_ASSET_ATTACH_TO_SCOPE macros to avoid this!");
GetSharedThreadData().m_currentAssetStack.pop_back();
}
AssetTrackingImpl* AssetTrackingImpl::GetSharedInstance()
{
auto environmentVariable = GetEnvironmentVariable();
if(environmentVariable)
{
return *environmentVariable;
}
return nullptr;
}
ThreadData& AssetTrackingImpl::GetSharedThreadData()
{
// Cast to the base type so our virtual call doesn't get optimized away. We require GetThreadData() to be executed in the same DLL every time.
return static_cast<ThreadDataProvider*>(GetSharedInstance())->GetThreadData();
}
AssetTrackingImpl::ThreadData& AssetTrackingImpl::GetThreadData()
{
static thread_local ThreadData* data = nullptr;
static thread_local typename AZStd::aligned_storage_t<sizeof(ThreadData), alignof(ThreadData)> storage;
if (!data)
{
data = new (&storage) ThreadData;
}
return *data;
}
EnvironmentVariable<AssetTrackingImpl*>& AssetTrackingImpl::GetEnvironmentVariable()
{
static EnvironmentVariable<AssetTrackingImpl*> assetTrackingImpl = Environment::CreateVariable<AssetTrackingImpl*>(AzTypeInfo<AssetTrackingImpl*>::Name());
return assetTrackingImpl;
}
///////////////////////////////////////////////////////////////////////////////
// AssetTracking::Scope functions
///////////////////////////////////////////////////////////////////////////////
AssetTracking::Scope AssetTracking::Scope::ScopeFromAssetId(const char* file, int line, const char* fmt, ...)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
static const int BUFFER_SIZE = 1024;
char buffer[BUFFER_SIZE];
va_list args;
va_start(args, fmt);
azvsnprintf(buffer, BUFFER_SIZE, fmt, args);
va_end(args);
impl->AssetBegin(buffer, file, line);
}
return Scope();
}
AssetTracking::Scope AssetTracking::Scope::ScopeFromAttachment(void* attachTo, const char* file, int line)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetAttach(attachTo, file, line);
}
return Scope();
}
AssetTracking::Scope::~Scope()
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetEnd();
}
}
AssetTracking::Scope::Scope()
{
}
///////////////////////////////////////////////////////////////////////////////
// AssetTracking functions
///////////////////////////////////////////////////////////////////////////////
void AssetTracking::EnterScopeByAssetId(const char* file, int line, const char* fmt, ...)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
static const int BUFFER_SIZE = 1024;
char buffer[BUFFER_SIZE];
va_list args;
va_start(args, fmt);
azvsnprintf(buffer, BUFFER_SIZE, fmt, args);
va_end(args);
impl->AssetBegin(buffer, file, line);
}
}
void AssetTracking::EnterScopeByAttachment(void* attachTo, const char* file, int line)
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetAttach(attachTo, file, line);
}
}
void AssetTracking::ExitScope()
{
if (auto impl = AssetTrackingImpl::GetSharedInstance())
{
impl->AssetEnd();
}
}
const char* AssetTracking::GetDebugScope()
{
// Output debug information about the current asset scope in the current thread.
// Do not use in production code.
#ifndef RELEASE
static const int BUFFER_SIZE = 1024;
static char buffer[BUFFER_SIZE];
const auto& assetStack = AssetTrackingImpl::GetSharedInstance()->GetThreadData().m_currentAssetStack;
if (assetStack.empty())
{
azsnprintf(buffer, BUFFER_SIZE, "<none>");
}
else
{
char* pos = buffer;
for (auto itr = assetStack.rbegin(); itr != assetStack.rend(); ++itr)
{
pos += azsnprintf(pos, BUFFER_SIZE - (pos - buffer), "%s\n", (*itr)->GetAssetPrimaryInfo()->m_id->m_id.c_str());
if (pos >= buffer + BUFFER_SIZE)
{
break;
}
}
}
return buffer;
#else
return "";
#endif
}
AssetTracking::AssetTracking(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable)
{
m_impl.reset(aznew AssetTrackingImpl(assetTree, allocationTable));
}
AssetTracking::~AssetTracking()
{
}
AssetTreeNodeBase* AssetTracking::GetCurrentThreadAsset() const
{
const auto& assetStack = m_impl->GetThreadData().m_currentAssetStack;
AssetTreeNodeBase* result = assetStack.empty() ? nullptr : assetStack.back();
return result;
}
}
} // namespace AzFramework
@@ -1,130 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/EBus/Policies.h>
#ifndef AZ_TRACK_ASSET_SCOPES
// You may manually uncomment this to enable asset tracking.
//# define AZ_TRACK_ASSET_SCOPES
#endif
#if !defined(AZ_TRACK_ASSET_SCOPES)
// Default to enabling asset tracking when memory tracking is enabled
# define AZ_TRACK_ASSET_SCOPES
#endif
#ifdef AZ_TRACK_ASSET_SCOPES
#define AZ_ASSET_SCOPE_VARIABLE_NAME(line) AZ_JOIN(_az_assettracking_scope_, line)
///////////////////////////////////////////////////////////////////////////////
// Preferred macros to use at the top of a scope you want to to track asset memory for.
///////////////////////////////////////////////////////////////////////////////
// Creates a new scope with a name, usually the name of an asset being loaded. (This may be a format-string, e.g. "Foo: %s", bar.c_str())
# define AZ_ASSET_NAMED_SCOPE(...) AZ::Debug::AssetTracking::Scope AZ_ASSET_SCOPE_VARIABLE_NAME(__LINE__) (AZ::Debug::AssetTracking::Scope::ScopeFromAssetId(__FILE__, __LINE__, __VA_ARGS__))
// Attempts to enter an existing scope that already owns some other allocation.
# define AZ_ASSET_ATTACH_TO_SCOPE(other) AZ::Debug::AssetTracking::Scope AZ_ASSET_SCOPE_VARIABLE_NAME(__LINE__) (AZ::Debug::AssetTracking::Scope::ScopeFromAttachment((other), __FILE__, __LINE__))
///////////////////////////////////////////////////////////////////////////////
// Optional macros to manually enter and exit a scope.
// It is the responsibility of the user to make sure every call to AZ_ASSET_ENTER_SCOPE_* is matched by a corresponding call to AZ_ASSET_EXIT_SCOPE.
///////////////////////////////////////////////////////////////////////////////
# define AZ_ASSET_ENTER_SCOPE_BY_ASSET_ID(...) AZ::Debug::AssetTracking::EnterScopeByAssetId(__FILE__, __LINE__, __VA_ARGS__)
# define AZ_ASSET_ENTER_SCOPE_BY_ATTACHMENT(other) AZ::Debug::AssetTracking::EnterScopeByAttachment((other), __FILE__, __LINE__)
# define AZ_ASSET_EXIT_SCOPE AZ::Debug::AssetTracking::ExitScope()
#else
# define AZ_ASSET_NAMED_SCOPE(...) (void)0
# define AZ_ASSET_ATTACH_TO_SCOPE(other) (void)0
# define AZ_ASSET_ENTER_SCOPE_BY_ASSET_ID(...) (void)0
# define AZ_ASSET_ENTER_SCOPE_BY_ATTACHMENT(other) (void)0
# define AZ_ASSET_EXIT_SCOPE (void)0
#endif
namespace AZ
{
class ReflectContext;
namespace Debug
{
class AssetTrackingImpl;
class AssetTreeBase;
class AssetTreeNodeBase;
class AssetAllocationTableBase;
class AssetTracking
{
public:
AZ_TYPE_INFO(AssetTracking, "{D4335180-09A2-415A-8B50-9B734E7CE1E6}");
AZ_CLASS_ALLOCATOR(AssetTracking, OSAllocator, 0);
// Provide RAII method for entering and exiting scopes.
// Generally you will want to use the macros at the top of this file rather than instantiating this object directly.
class Scope
{
public:
static Scope ScopeFromAssetId(const char* file, int line, const char* fmt, ...);
static Scope ScopeFromAttachment(void* attachTo, const char* file, int line);
Scope(Scope&&) = default;
~Scope();
private:
Scope();
};
// Generally you will want to use the macros at the top of this file rather than calling these functions directly.
static void EnterScopeByAssetId(const char* file, int line, const char* fmt, ...);
static void EnterScopeByAttachment(void* attachTo, const char* file, int line);
static void ExitScope();
static const char* GetDebugScope();
AssetTracking(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable);
~AssetTracking();
AssetTreeNodeBase* GetCurrentThreadAsset() const;
private:
AZStd::unique_ptr<AssetTrackingImpl> m_impl;
};
// An EBus processing policy that attempts to attach to an existing scope before calling a handler.
//
// Use this on EBuses where you want the callees to track asset memory during their event handlers.
// This will work so long as the callees were themselves allocated inside an existing asset scope.
//
// May be added to an existing EBus with the following code:
// using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy;
//
template<typename Parent = EBusEventProcessingPolicy>
struct AssetTrackingEventProcessingPolicy
{
template<class Results, class Function, class Interface, class... InputArgs>
static void CallResult(Results& results, Function&& func, Interface&& iface, InputArgs&&... args)
{
AZ_ASSET_ATTACH_TO_SCOPE(iface);
Parent::CallResult(results, AZStd::forward<Function>(func), AZStd::forward<Interface>(iface), AZStd::forward<InputArgs>(args)...);
}
template<class Function, class Interface, class... InputArgs>
static void Call(Function&& func, Interface&& iface, InputArgs&&... args)
{
AZ_ASSET_ATTACH_TO_SCOPE(iface);
Parent::Call(AZStd::forward<Function>(func), AZStd::forward<Interface>(iface), AZStd::forward<InputArgs>(args)...);
}
};
}
} // namespace AzFramework
@@ -1,120 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Memory/HphaSchema.h>
#include <AzCore/Memory/SimpleSchemaAllocator.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
namespace Debug
{
struct AssetTrackingId;
}
}
namespace AZStd
{
// Declare hash specializations for types that need them; implementations will have to come after the classes are fully defined
template<>
struct hash<AZ::Debug::AssetTrackingId>
{
size_t operator()(const AZ::Debug::AssetTrackingId& id) const;
};
}
namespace AZ
{
namespace Debug
{
class AssetTrackingImpl;
// Custom allocator for the Analyzer that doesn't go through profiling tools and cannot be overridden
class AssetTrackingAllocator : public AZ::SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false>
{
public:
AZ_TYPE_INFO(AssetTrackingAllocator, "{F6C08E92-559C-4153-9620-6A8491F78F10}");
using Base = AZ::SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false>;
using Descriptor = Base::Descriptor;
AssetTrackingAllocator()
: Base("AssetTrackingAllocator", "Allocator for the AssetTracking")
{
DisableOverriding();
}
};
using AZStdAssetTrackingAllocator = AZ::AZStdAlloc<AssetTrackingAllocator>;
using AssetTrackingString = AZStd::basic_string<char, AZStd::char_traits<char>, AZStdAssetTrackingAllocator>;
template<typename Key, typename MappedType>
using AssetTrackingMap = AZStd::unordered_map<Key, MappedType, AZStd::hash<Key>, AZStd::equal_to<Key>, AZStdAssetTrackingAllocator>;
// ID for an asset that is hashable.
// Currently only contains one string identifier, but we may want to store a more sophisticated ID in the future.
struct AssetTrackingId
{
AssetTrackingId(const char* id) : m_id(id)
{
}
bool operator==(const AssetTrackingId& other) const
{
return m_id == other.m_id;
}
AssetTrackingString m_id;
};
// Primary information about an asset.
// Currently just contains the ID of the asset, but in the future may carry additional information about that asset (such as where in code it was initialized).
struct AssetPrimaryInfo
{
const AssetTrackingId* m_id;
};
// Base class for a node in the asset tree. Implemented by the template AssetTreeNode<>.
class AssetTreeNodeBase
{
public:
virtual ~AssetTreeNodeBase() = default;
virtual const AssetPrimaryInfo* GetAssetPrimaryInfo() const = 0;
virtual AssetTreeNodeBase* FindOrAddChild(const AssetTrackingId& id, const AssetPrimaryInfo* info) = 0;
};
// Base class for an asset tree. Implemented by the template AssetTree<>.
class AssetTreeBase
{
public:
virtual ~AssetTreeBase() = default;
virtual AssetTreeNodeBase& GetRoot() = 0;
};
// Base class for an asset allocation table. Implemented by the template AssetAllocationTable<>.
class AssetAllocationTableBase
{
public:
virtual ~AssetAllocationTableBase() = default;
virtual AssetTreeNodeBase* FindAllocation(void* ptr) const = 0;
};
}
}
///////////////////////////////////////////////////////////////////////////////
// Hash functions for map support
///////////////////////////////////////////////////////////////////////////////
inline size_t AZStd::hash<AZ::Debug::AssetTrackingId>::operator()(const AZ::Debug::AssetTrackingId& info) const
{
return AZStd::hash<AZ::Debug::AssetTrackingString>()(info.m_id);
}
@@ -1,174 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Debug/AssetTrackingTypes.h>
#include <AzCore/std/containers/map.h>
namespace AZ
{
namespace Debug
{
// A node in the current asset state tree.
// Each thread maintains a stack of currently in-scope assets. As this stack changes the asset tree forms.
// The same asset may appear in multiple places in the tree, e.g. if asset A is a common asset loaded by both asset B and asset C, the tree may look like:
// Root -> B -> A
// \--> C -> A
template<typename AssetDataT>
class AssetTreeNode : public AssetTreeNodeBase
{
public:
AssetTreeNode(const AssetPrimaryInfo* primaryInfo = nullptr, AssetTreeNode* parent = nullptr) :
m_primaryinfo(primaryInfo),
m_parent(parent)
{
}
~AssetTreeNode() override = default;
const AssetPrimaryInfo* GetAssetPrimaryInfo() const override
{
return m_primaryinfo;
}
AssetTreeNodeBase* FindOrAddChild(const AssetTrackingId& id, const AssetPrimaryInfo* info) override
{
AssetTreeNodeBase* result = nullptr;
auto childItr = m_children.find(id);
if (childItr != m_children.end())
{
result = &childItr->second;
}
else
{
auto childResult = m_children.emplace(id, AssetTreeNode(info, this));
result = &childResult.first->second;
}
return result;
}
using AssetMap = AssetTrackingMap<AssetTrackingId, AssetTreeNode>;
const AssetPrimaryInfo* m_primaryinfo;
AssetTreeNode* m_parent;
AssetMap m_children;
AssetDataT m_data;
};
template<typename AssetDataT>
class AssetTree : public AssetTreeBase
{
public:
~AssetTree() override = default;
AssetTreeNodeBase& GetRoot() override
{
return m_rootAssets;
}
using NodeType = AssetTreeNode<AssetDataT>;
NodeType m_rootAssets;
};
template<typename AllocationDataT>
struct AllocationRecord
{
AssetTreeNodeBase* m_asset;
uint32_t m_size;
AllocationDataT m_data;
};
template<typename AllocationDataT>
class AllocationTable : public AssetAllocationTableBase
{
public:
using RecordType = AllocationRecord<AllocationDataT>;
using AllocationReverseMap = AZStd::map<void*, RecordType, AZStd::greater<void*>, AZStdAssetTrackingAllocator>;
using mutex_type = AZStd::mutex;
using lock_type = AZStd::lock_guard<mutex_type>;
AllocationTable(mutex_type& mutex) : m_mutex(mutex)
{
}
~AllocationTable() override = default;
AssetTreeNodeBase* FindAllocation(void* ptr) const override
{
// Note that ptr is not guaranteed to have an exact entry in the map. For instance, ptr may point to a member of the original object that was allocated, or
// ptr may be a different "this" pointer in the case of multiple inheritance.
//
// To solve this, we use lower_bound() and check to see if ptr falls in the range of the nearest allocation. Our map uses AZStd::greater instead of
// AZStd::less as its sorting function, and thus sorts largest-to-smallest instead of smallest-to-largest. This causes lower_bound() to return the first
// iterator that is not greater than otherAllocation, i.e. less than or equal to ptr.
lock_type lock(m_mutex);
auto itr = m_allocationTable.lower_bound(ptr);
AssetTreeNodeBase* result = nullptr;
if (itr != m_allocationTable.end())
{
// Check if otherAllocation is within the size range of the allocation we found
if (reinterpret_cast<uintptr_t>(ptr) <= reinterpret_cast<uintptr_t>(itr->first) + itr->second.m_size)
{
result = itr->second.m_asset;
}
}
return result;
}
void ReallocateAllocation(void* prevAddress, void* newAddress, size_t newByteSize)
{
lock_type lock(m_mutex);
auto itr = m_allocationTable.find(prevAddress);
if (itr != m_allocationTable.end())
{
RecordType newAllocation = itr->second;
newAllocation.m_size = (uint32_t)newByteSize;
m_allocationTable.erase(itr);
m_allocationTable.emplace(newAddress, AZStd::move(newAllocation));
}
}
void ResizeAllocation(void* address, size_t newSize)
{
// Resize an existing allocation if we can find it
lock_type lock(m_mutex);
auto itr = m_allocationTable.find(address);
if (itr != m_allocationTable.end())
{
itr->second.m_size = (uint32_t)newSize;
}
}
AllocationReverseMap& Get()
{
return m_allocationTable;
}
const AllocationReverseMap& Get() const
{
return m_allocationTable;
}
private:
AllocationReverseMap m_allocationTable;
mutex_type& m_mutex;
};
}
}
@@ -11,6 +11,7 @@
#include <AzCore/Module/Environment.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Statistics/StatisticalProfilerProxy.h>
AZ_DEFINE_BUDGET(Animation);
AZ_DEFINE_BUDGET(Audio);
@@ -30,8 +31,7 @@ namespace AZ::Debug
};
Budget::Budget(const char* name)
: m_name{ name }
, m_crc{ Crc32(name) }
: Budget( name, Crc32(name) )
{
}
@@ -40,6 +40,10 @@ namespace AZ::Debug
, m_crc{ crc }
{
m_impl = aznew BudgetImpl;
if (auto statsProfiler = Interface<Statistics::StatisticalProfilerProxy>::Get(); statsProfiler)
{
statsProfiler->RegisterProfilerId(m_crc);
}
}
Budget::~Budget()
+10 -6
View File
@@ -62,12 +62,16 @@ namespace AZ::Debug
//
// Anywhere the budget is used, the budget must be declared (either in a header or in the source file itself)
// AZ_DECLARE_BUDGET(AzCore);
#define AZ_DEFINE_BUDGET(name) \
::AZ::Debug::Budget* AZ_BUDGET_GETTER(name)() \
{ \
constexpr static uint32_t crc = AZ_CRC_CE(#name); \
static ::AZ::Debug::Budget* budget = ::AZ::Debug::BudgetTracker::GetBudgetFromEnvironment(#name, crc); \
return budget; \
#define AZ_DEFINE_BUDGET(name) \
::AZ::Debug::Budget* AZ_BUDGET_GETTER(name)() \
{ \
static ::AZ::Debug::Budget* budget = nullptr; \
if (budget == nullptr) \
{ \
constexpr static uint32_t crc = AZ_CRC_CE(#name); \
::AZ::Debug::BudgetTracker::GetBudgetFromEnvironment(budget, #name, crc); \
} \
return budget; \
}
#endif
@@ -13,23 +13,24 @@
#include <AzCore/Interface/Interface.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/parallel/scoped_lock.h>
namespace AZ::Debug
{
struct BudgetTracker::BudgetTrackerImpl
{
AZStd::unordered_map<const char*, Budget> m_budgets;
AZStd::unordered_map<AZStd::string_view, Budget> m_budgets;
AZStd::unordered_set<Budget**> m_externalBudgetRefs;
};
Budget* BudgetTracker::GetBudgetFromEnvironment(const char* budgetName, uint32_t crc)
void BudgetTracker::GetBudgetFromEnvironment(Budget*& extBudgetRef, const char* budgetName, uint32_t crc)
{
BudgetTracker* tracker = Interface<BudgetTracker>::Get();
if (tracker)
{
return &tracker->GetBudget(budgetName, crc);
tracker->GetBudget(extBudgetRef, budgetName, crc);
}
return nullptr;
}
BudgetTracker::~BudgetTracker()
@@ -54,17 +55,24 @@ namespace AZ::Debug
if (m_impl)
{
Interface<BudgetTracker>::Unregister(this);
for (auto budgetRef : m_impl->m_externalBudgetRefs)
{
*budgetRef = nullptr;
}
delete m_impl;
m_impl = nullptr;
}
}
Budget& BudgetTracker::GetBudget(const char* budgetName, uint32_t crc)
void BudgetTracker::GetBudget(Budget*& extBudgetRef, const char* budgetName, uint32_t crc)
{
AZStd::scoped_lock lock{ m_mutex };
auto it = m_impl->m_budgets.try_emplace(budgetName, budgetName, crc).first;
m_impl->m_externalBudgetRefs.insert(&extBudgetRef);
return it->second;
auto iter = m_impl->m_budgets.try_emplace(budgetName, budgetName, crc).first;
extBudgetRef = &iter->second;
}
} // namespace AZ::Debug
@@ -20,7 +20,7 @@ namespace AZ::Debug
{
public:
AZ_TYPE_INFO(BudgetTracker, "{E14A746D-BFFE-4C02-90FB-4699B79864A5}");
static Budget* GetBudgetFromEnvironment(const char* budgetName, uint32_t crc);
static void GetBudgetFromEnvironment(Budget*& extBudgetRef, const char* budgetName, uint32_t crc);
~BudgetTracker();
@@ -28,7 +28,7 @@ namespace AZ::Debug
bool Init();
void Reset();
Budget& GetBudget(const char* budgetName, uint32_t crc);
void GetBudget(Budget*& extBudgetRef, const char* budgetName, uint32_t crc);
private:
struct BudgetTrackerImpl;
@@ -1,29 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Debug/EventTraceDrillerBus.h>
#include <AzCore/std/time.h>
#include <AzCore/std/parallel/thread.h>
namespace AZ
{
namespace Debug
{
EventTrace::ScopedSlice::ScopedSlice(const char* name, const char* category)
: m_Name(name)
, m_Category(category)
, m_Time(AZStd::GetTimeNowMicroSecond())
{}
EventTrace::ScopedSlice::~ScopedSlice()
{
EventTraceDrillerBus::TryQueueBroadcast(&EventTraceDrillerInterface::RecordSlice, m_Name, m_Category, AZStd::this_thread::get_id(), m_Time, (uint32_t)(AZStd::GetTimeNowMicroSecond() - m_Time));
}
}
}
@@ -1,43 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/PlatformDef.h>
#include <AzCore/Debug/Profiler.h>
namespace AZStd
{
struct thread_id;
}
namespace AZ
{
namespace Debug
{
namespace EventTrace
{
class ScopedSlice
{
public:
ScopedSlice(const char* name, const char* category);
~ScopedSlice();
private:
const char* m_Name;
const char* m_Category;
u64 m_Time;
};
}
}
}
#define AZ_TRACE_METHOD_NAME_CATEGORY(name, category)
#define AZ_TRACE_METHOD_NAME(name) AZ_TRACE_METHOD_NAME_CATEGORY(name, "")
#define AZ_TRACE_METHOD() AZ_TRACE_METHOD_NAME(AZ_FUNCTION_SIGNATURE)
@@ -1,161 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Debug/EventTraceDriller.h>
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/std/containers/array.h>
#include <algorithm>
namespace AZ
{
namespace Debug
{
namespace Crc
{
const u32 EventTraceDriller = AZ_CRC("EventTraceDriller", 0xf7aeae55);
const u32 Slice = AZ_CRC("Slice", 0x3dae78a5);
const u32 ThreadInfo = AZ_CRC("ThreadInfo", 0x89bf78be);
const u32 Name = AZ_CRC("Name", 0x5e237e06);
const u32 Category = AZ_CRC("Category", 0x064c19c1);
const u32 ThreadId = AZ_CRC("ThreadId", 0xd0fd9043);
const u32 Timestamp = AZ_CRC("Timestamp", 0xa5d6e63e);
const u32 Duration = AZ_CRC("Duration", 0x865f80c0);
const u32 Instant = AZ_CRC("Instant", 0x0e9047ad);
}
EventTraceDriller::EventTraceDriller()
{
EventTraceDrillerSetupBus::Handler::BusConnect();
AZStd::ThreadDrillerEventBus::Handler::BusConnect();
}
EventTraceDriller::~EventTraceDriller()
{
AZStd::ThreadDrillerEventBus::Handler::BusDisconnect();
EventTraceDrillerSetupBus::Handler::BusDisconnect();
}
void EventTraceDriller::Start(const Param* params, int numParams)
{
(void)params;
(void)numParams;
EventTraceDrillerBus::Handler::BusConnect();
TickBus::Handler::BusConnect();
EventTraceDrillerBus::AllowFunctionQueuing(true);
}
void EventTraceDriller::Stop()
{
EventTraceDrillerBus::AllowFunctionQueuing(false);
EventTraceDrillerBus::ClearQueuedEvents();
EventTraceDrillerBus::Handler::BusDisconnect();
TickBus::Handler::BusDisconnect();
}
void EventTraceDriller::OnTick(float deltaTime, ScriptTimePoint time)
{
(void)deltaTime;
(void)time;
AZ_TRACE_METHOD();
RecordThreads();
EventTraceDrillerBus::ExecuteQueuedEvents();
}
void EventTraceDriller::SetThreadName(const AZStd::thread_id& id, const char* name)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_ThreadMutex);
m_Threads[(size_t)id.m_id] = ThreadData{ name };
}
void EventTraceDriller::OnThreadEnter(const AZStd::thread::id& id, const AZStd::thread_desc* desc)
{
if (desc && desc->m_name)
{
SetThreadName(id, desc->m_name);
}
}
void EventTraceDriller::OnThreadExit(const AZStd::thread::id& id)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_ThreadMutex);
m_Threads.erase((size_t)id.m_id);
}
void EventTraceDriller::RecordThreads()
{
if (m_output && m_Threads.size())
{
// Main bus mutex guards m_output.
auto& context = EventTraceDrillerBus::GetOrCreateContext();
AZStd::scoped_lock<decltype(context.m_contextMutex), decltype(m_ThreadMutex)> lock(context.m_contextMutex, m_ThreadMutex);
for (const auto& keyValue : m_Threads)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::ThreadInfo);
m_output->Write(Crc::ThreadId, keyValue.first);
m_output->Write(Crc::Name, keyValue.second.name);
m_output->EndTag(Crc::ThreadInfo);
m_output->EndTag(Crc::EventTraceDriller);
}
}
}
void EventTraceDriller::RecordSlice(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp,
AZ::u32 duration)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::Slice);
m_output->Write(Crc::Name, name);
m_output->Write(Crc::Category, category);
m_output->Write(Crc::ThreadId, (size_t)threadId.m_id);
m_output->Write(Crc::Timestamp, timestamp);
m_output->Write(Crc::Duration, std::max(duration, 1u));
m_output->EndTag(Crc::Slice);
m_output->EndTag(Crc::EventTraceDriller);
}
void EventTraceDriller::RecordInstantGlobal(
const char* name,
const char* category,
AZ::u64 timestamp)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::Instant);
m_output->Write(Crc::Name, name);
m_output->Write(Crc::Category, category);
m_output->Write(Crc::Timestamp, timestamp);
m_output->EndTag(Crc::Instant);
m_output->EndTag(Crc::EventTraceDriller);
}
void EventTraceDriller::RecordInstantThread(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp)
{
m_output->BeginTag(Crc::EventTraceDriller);
m_output->BeginTag(Crc::Instant);
m_output->Write(Crc::Name, name);
m_output->Write(Crc::Category, category);
m_output->Write(Crc::ThreadId, (size_t)threadId.m_id);
m_output->Write(Crc::Timestamp, timestamp);
m_output->EndTag(Crc::Instant);
m_output->EndTag(Crc::EventTraceDriller);
}
}
}
@@ -1,87 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Driller/Driller.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/parallel/threadbus.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/Debug/EventTraceDrillerBus.h>
namespace AZ
{
namespace Debug
{
class EventTraceDriller
: public Driller
, public EventTraceDrillerBus::Handler
, public EventTraceDrillerSetupBus::Handler
, public AZStd::ThreadDrillerEventBus::Handler
, public AZ::TickBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(EventTraceDriller, OSAllocator, 0)
EventTraceDriller();
virtual ~EventTraceDriller();
private:
// Driller
//////////////////////////////////////////////////////////////////////////
const char* GroupName() const override { return "SystemDrillers"; }
const char* GetName() const override { return "EventTraceDriller"; }
const char* GetDescription() const override { return "Handles timed events for a Chrome Tracing."; }
void Start(const Param* params = NULL, int numParams = 0) override;
void Stop() override;
// ThreadBus
//////////////////////////////////////////////////////////////////////////
void OnThreadEnter(const AZStd::thread::id& id, const AZStd::thread_desc* desc) override;
void OnThreadExit(const AZStd::thread::id& id) override;
// TickBus
//////////////////////////////////////////////////////////////////////////
void OnTick(float deltaTime, ScriptTimePoint time) override;
// EventTraceDrillerSetupBus
//////////////////////////////////////////////////////////////////////////
void SetThreadName(const AZStd::thread_id& threadId, const char* name) override;
// EventTraceDrillerBus
//////////////////////////////////////////////////////////////////////////
void RecordSlice(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp,
AZ::u32 duration) override;
void RecordInstantGlobal(
const char* name,
const char* category,
AZ::u64 timestamp) override;
void RecordInstantThread(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp) override;
void RecordThreads();
struct ThreadData
{
AZStd::string name;
};
AZStd::recursive_mutex m_ThreadMutex;
AZStd::unordered_map<size_t, ThreadData, AZStd::hash<size_t>, AZStd::equal_to<size_t>, OSStdAllocator> m_Threads;
};
}
} // namespace AZ
@@ -1,81 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Driller/DrillerBus.h>
#include <AzCore/std/time.h>
#include <AzCore/std/string/string.h>
namespace AZStd
{
struct thread_id;
}
namespace AZ
{
namespace Debug
{
class EventTraceDrillerInterface
: public DrillerEBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const bool EnableEventQueue = true;
static const bool EventQueueingActiveByDefault = false;
//////////////////////////////////////////////////////////////////////////
virtual ~EventTraceDrillerInterface() {}
virtual void RecordSlice(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp,
AZ::u32 duration) = 0;
virtual void RecordInstantThread(
const char* name,
const char* category,
const AZStd::thread_id threadId,
AZ::u64 timestamp) = 0;
virtual void RecordInstantGlobal(
const char* name,
const char* category,
AZ::u64 timestamp) = 0;
};
typedef AZ::EBus<EventTraceDrillerInterface> EventTraceDrillerBus;
class EventTraceDrillerSetupInterface
: public DrillerEBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//////////////////////////////////////////////////////////////////////////
virtual ~EventTraceDrillerSetupInterface() {}
virtual void SetThreadName(const AZStd::thread_id& threadId, const char* name) = 0;
};
typedef AZ::EBus<EventTraceDrillerSetupInterface> EventTraceDrillerSetupBus;
}
}
#define AZ_TRACE_INSTANT_GLOBAL_CATEGORY(name, category) \
EBUS_QUEUE_EVENT(AZ::Debug::EventTraceDrillerBus, RecordInstantGlobal, name, category, AZStd::GetTimeNowMicroSecond())
#define AZ_TRACE_INSTANT_GLOBAL(name) AZ_TRACE_INSTANT_GLOBAL_CATEGORY(name, "")
#define AZ_TRACE_INSTANT_THREAD_CATEGORY(name, category) \
EBUS_QUEUE_EVENT(AZ::Debug::EventTraceDrillerBus, RecordInstantThread, name, category, AZStd::this_thread::get_id(), AZStd::GetTimeNowMicroSecond())
#define AZ_TRACE_INSTANT_THREAD(name) AZ_TRACE_INSTANT_THREAD_CATEGORY(name, "")
@@ -8,7 +8,7 @@
#pragma once
#ifndef AZ_PROFILE_MEMORY_ALLOC
// No other profiler has defined the performance markers AZ_PROFILE_MEMORY_ALLOC (and friends), fall back to a Driller implementation (currently empty)
// No other profiler has defined the performance markers AZ_PROFILE_MEMORY_ALLOC (and friends), fall back to current implementation (empty)
# define AZ_PROFILE_MEMORY_ALLOC(category, address, size, context)
# define AZ_PROFILE_MEMORY_ALLOC_EX(category, filename, lineNumber, address, size, context)
# define AZ_PROFILE_MEMORY_FREE(category, address)
@@ -7,4 +7,63 @@
*/
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Debug/ProfilerBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Settings/SettingsRegistry.h>
namespace AZ::Debug
{
AZStd::string GenerateOutputFile(const char* nameHint)
{
AZ::IO::FixedMaxPathString captureOutput = GetProfilerCaptureLocation();
return AZStd::string::format("%s/capture_%s_%lld.json", captureOutput.c_str(), nameHint, AZStd::GetTimeNowSecond());
}
void ProfilerCaptureFrame([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
if (auto profilerSystem = ProfilerSystemInterface::Get(); profilerSystem)
{
AZStd::string captureFile = GenerateOutputFile("single");
AZLOG_INFO("Setting capture file to %s", captureFile.c_str());
profilerSystem->CaptureFrame(captureFile);
}
}
AZ_CONSOLEFREEFUNC(ProfilerCaptureFrame, AZ::ConsoleFunctorFlags::DontReplicate, "Capture a single frame of profiling data");
void ProfilerStartCapture([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
if (auto profilerSystem = ProfilerSystemInterface::Get(); profilerSystem)
{
AZStd::string captureFile = GenerateOutputFile("multi");
AZLOG_INFO("Setting capture file to %s", captureFile.c_str());
profilerSystem->StartCapture(AZStd::move(captureFile));
}
}
AZ_CONSOLEFREEFUNC(ProfilerStartCapture, AZ::ConsoleFunctorFlags::DontReplicate, "Start a multi-frame capture of profiling data");
void ProfilerEndCapture([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
if (auto profilerSystem = ProfilerSystemInterface::Get(); profilerSystem)
{
profilerSystem->EndCapture();
}
}
AZ_CONSOLEFREEFUNC(ProfilerEndCapture, AZ::ConsoleFunctorFlags::DontReplicate, "End and dump an in-progress continuous capture");
AZ::IO::FixedMaxPathString GetProfilerCaptureLocation()
{
AZ::IO::FixedMaxPathString captureOutput;
if (AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry)
{
settingsRegistry->Get(captureOutput, RegistryKey_ProfilerCaptureLocation);
}
if (captureOutput.empty())
{
captureOutput = ProfilerCaptureLocationFallback;
}
return captureOutput;
}
} // namespace AZ::Debug
@@ -8,11 +8,7 @@
#pragma once
#include <AzCore/Debug/Budget.h>
#ifdef USE_PIX
#include <AzCore/PlatformIncl.h>
#include <WinPixEventRuntime/pix3.h>
#endif
#include <AzCore/Statistics/StatisticalProfilerProxy.h>
#if defined(AZ_PROFILER_MACRO_DISABLE) // by default we never disable the profiler registers as their overhead should be minimal, you can
// still do that for your code though.
@@ -44,7 +40,10 @@
#define AZ_PROFILE_INTERVAL_START(...)
#define AZ_PROFILE_INTERVAL_START_COLORED(...)
#define AZ_PROFILE_INTERVAL_END(...)
#define AZ_PROFILE_INTERVAL_SCOPED(...)
#define AZ_PROFILE_INTERVAL_SCOPED(budget, scopeNameId, ...) \
static constexpr AZ::Crc32 AZ_JOIN(blockId, __LINE__)(scopeNameId); \
AZ::Statistics::StatisticalProfilerProxy::TimedScope AZ_JOIN(scope, __LINE__)(AZ_CRC_CE(#budget), AZ_JOIN(blockId, __LINE__));
#endif
#ifndef AZ_PROFILE_DATAPOINT
+31 -25
View File
@@ -10,44 +10,48 @@
namespace AZ::Debug
{
namespace Platform
{
template<typename... T>
void BeginProfileRegion(Budget* budget, const char* eventName, T const&... args);
void BeginProfileRegion(Budget* budget, const char* eventName);
void EndProfileRegion(Budget* budget);
} // namespace Platform
template<typename... T>
void ProfileScope::BeginRegion(
[[maybe_unused]] Budget* budget, [[maybe_unused]] const char* eventName, [[maybe_unused]] T const&... args)
{
if (!budget)
#if !defined(_RELEASE)
if (budget)
{
return;
}
#if !defined(_RELEASE)
// TODO: Verification that the supplied system name corresponds to a known budget
#if defined(USE_PIX)
PIXBeginEvent(PIX_COLOR_INDEX(budget->Crc() & 0xff), eventName, args...);
#endif
budget->BeginProfileRegion();
Platform::BeginProfileRegion(budget, eventName, args...);
if (auto profiler = AZ::Interface<Profiler>::Get(); profiler)
{
profiler->BeginRegion(budget, eventName);
budget->BeginProfileRegion();
if (auto profiler = AZ::Interface<Profiler>::Get(); profiler)
{
profiler->BeginRegion(budget, eventName);
}
}
#endif
#endif // #if !defined(_RELEASE)
}
inline void ProfileScope::EndRegion([[maybe_unused]] Budget* budget)
{
if (!budget)
#if !defined(_RELEASE)
if (budget)
{
return;
budget->EndProfileRegion();
if (auto profiler = AZ::Interface<Profiler>::Get(); profiler)
{
profiler->EndRegion(budget);
}
Platform::EndProfileRegion(budget);
}
#if !defined(_RELEASE)
budget->EndProfileRegion();
#if defined(USE_PIX)
PIXEndEvent();
#endif
if (auto profiler = AZ::Interface<Profiler>::Get(); profiler)
{
profiler->EndRegion(budget);
}
#endif
#endif // !defined(_RELEASE)
}
template<typename... T>
@@ -63,3 +67,5 @@ namespace AZ::Debug
}
} // namespace AZ::Debug
#include <AzCore/Debug/Profiler_Platform.inl>
@@ -9,11 +9,20 @@
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
namespace Debug
{
//! settings registry entry for specifying where to output profiler captures
static constexpr const char* RegistryKey_ProfilerCaptureLocation = "/O3DE/AzCore/Debug/Profiler/CaptureLocation";
//! fallback value in the event the settings registry isn't ready or doesn't contain the key
static constexpr const char* ProfilerCaptureLocationFallback = "@user@/Profiler";
/**
* ProfilerNotifications provides a profiler event interface that can be used to update listeners on profiler status
*/
@@ -23,32 +32,38 @@ namespace AZ
public:
virtual ~ProfilerNotifications() = default;
virtual void OnProfileSystemInitialized() = 0;
//! Notify when the current profiler capture is finished
//! @param result Set to true if it's finished successfully
//! @param info The output file path or error information which depends on the return.
virtual void OnCaptureFinished(bool result, const AZStd::string& info) = 0;
};
using ProfilerNotificationBus = AZ::EBus<ProfilerNotifications>;
enum class ProfileFrameAdvanceType
{
Game,
Render,
Default = Game
};
/**
* ProfilerRequests provides an interface for making profiling system requests
*/
class ProfilerRequests
: public AZ::EBusTraits
{
public:
// Allow multiple threads to concurrently make requests
using MutexType = AZStd::mutex;
AZ_RTTI(ProfilerRequests, "{90AEC117-14C1-4BAE-9704-F916E49EF13F}");
virtual ~ProfilerRequests() = default;
virtual bool IsActive() = 0;
virtual void FrameAdvance(ProfileFrameAdvanceType type) = 0;
//! Getter/setter for the profiler active state
virtual bool IsActive() const = 0;
virtual void SetActive(bool active) = 0;
//! Capture a single frame of profiling data
virtual bool CaptureFrame(const AZStd::string& outputFilePath) = 0;
//! Starting/ending a multi-frame capture of profiling data
virtual bool StartCapture(AZStd::string outputFilePath) = 0;
virtual bool EndCapture() = 0;
};
using ProfilerRequestBus = AZ::EBus<ProfilerRequests>;
}
}
using ProfilerSystemInterface = AZ::Interface<ProfilerRequests>;
//! helper function for getting the profiler capture location from the settings registry that
//! includes fallback handing in the event the registry value can't be determined
AZ::IO::FixedMaxPathString GetProfilerCaptureLocation();
} // namespace Debug
} // namespace AZ
@@ -0,0 +1,92 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Debug/ProfilerBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/RTTI/BehaviorInterfaceProxy.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
namespace AZ::Debug
{
static constexpr const char* ProfilerScriptCategory = "Profiler";
static constexpr const char* ProfilerScriptModule = "debug";
static constexpr AZ::Script::Attributes::ScopeFlags ProfilerScriptScope = AZ::Script::Attributes::ScopeFlags::Automation;
class ProfilerNotificationBusHandler final
: public ProfilerNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(ProfilerNotificationBusHandler, "{44161459-B816-4876-95A4-BA16DEC767D6}", AZ::SystemAllocator,
OnCaptureFinished
);
void OnCaptureFinished(bool result, const AZStd::string& info) override
{
Call(FN_OnCaptureFinished, result, info);
}
static void Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<ProfilerNotificationBus>("ProfilerNotificationBus")
->Attribute(AZ::Script::Attributes::Category, ProfilerScriptCategory)
->Attribute(AZ::Script::Attributes::Module, ProfilerScriptModule)
->Attribute(AZ::Script::Attributes::Scope, ProfilerScriptScope)
->Handler<ProfilerNotificationBusHandler>();
}
}
};
class ProfilerSystemScriptProxy
: public BehaviorInterfaceProxy<ProfilerRequests>
{
public:
AZ_RTTI(ProfilerSystemScriptProxy, "{D671FB70-8B09-4C3A-96CD-06A339F3138E}", BehaviorInterfaceProxy<ProfilerRequests>);
AZ_BEHAVIOR_INTERFACE(ProfilerSystemScriptProxy, ProfilerRequests);
};
void ProfilerReflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->ConstantProperty("g_ProfilerSystem", ProfilerSystemScriptProxy::GetProxy)
->Attribute(AZ::Script::Attributes::Category, ProfilerScriptCategory)
->Attribute(AZ::Script::Attributes::Module, ProfilerScriptModule)
->Attribute(AZ::Script::Attributes::Scope, ProfilerScriptScope);
behaviorContext->Class<ProfilerSystemScriptProxy>("ProfilerSystemInterface")
->Attribute(AZ::Script::Attributes::Category, ProfilerScriptCategory)
->Attribute(AZ::Script::Attributes::Module, ProfilerScriptModule)
->Attribute(AZ::Script::Attributes::Scope, ProfilerScriptScope)
->Method("IsValid", &ProfilerSystemScriptProxy::IsValid)
->Method("GetCaptureLocation",
[](ProfilerSystemScriptProxy*) -> AZStd::string
{
AZ::IO::FixedMaxPathString captureOutput = GetProfilerCaptureLocation();
return AZStd::string(captureOutput.c_str(), captureOutput.length());
})
->Method("IsActive", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::IsActive>())
->Method("SetActive", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::SetActive>())
->Method("CaptureFrame", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::CaptureFrame>())
->Method("StartCapture", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::StartCapture>())
->Method("EndCapture", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::EndCapture>());
}
ProfilerNotificationBusHandler::Reflect(context);
}
} // namespace AZ::Debug
@@ -0,0 +1,19 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
namespace AZ
{
class ReflectContext;
namespace Debug
{
//! Reflects the profiler bus script bindings
void ProfilerReflect(AZ::ReflectContext* context);
} // namespace Debug
} // namespace AZ
@@ -40,6 +40,12 @@ namespace AZ
static unsigned int Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount = 0, void* nativeThread = 0);
};
class StackConverter
{
public:
static unsigned int FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext);
};
class SymbolStorage
{
public:
@@ -46,5 +46,23 @@ namespace AZ
private:
AZStd::sys_time_t m_timeStamp;
};
//! Utility type that updates the given variable with the lifetime of the object in cycles.
//! Useful for quick scope based timing.
struct ScopedTimer
{
explicit ScopedTimer(AZStd::sys_time_t& variable)
: m_variable(variable)
{
m_timer.Stamp();
}
~ScopedTimer()
{
m_variable = m_timer.GetDeltaTimeInTicks();
}
AZStd::sys_time_t& m_variable;
Timer m_timer;
};
}
}
+59 -38
View File
@@ -12,7 +12,6 @@
#include <AzCore/Debug/StackTracer.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/Debug/TraceMessagesDrillerBus.h>
#include <AzCore/Debug/IEventLogger.h>
#include <AzCore/Interface/Interface.h>
@@ -27,23 +26,20 @@
#include <AzCore/Console/IConsole.h>
#include <AzCore/std/chrono/chrono.h>
namespace AZ
namespace AZ::Debug
{
namespace Debug
{
namespace Platform
{
#if defined(AZ_ENABLE_DEBUG_TOOLS)
bool AttachDebugger();
bool IsDebuggerPresent();
void HandleExceptions(bool isEnabled);
void DebugBreak();
#endif
void Terminate(int exitCode);
}
}
struct StackFrame;
using namespace AZ::Debug;
namespace Platform
{
#if defined(AZ_ENABLE_DEBUG_TOOLS)
bool AttachDebugger();
bool IsDebuggerPresent();
void HandleExceptions(bool isEnabled);
void DebugBreak();
#endif
void Terminate(int exitCode);
}
namespace DebugInternal
{
@@ -58,7 +54,7 @@ namespace AZ
// Globals
const int g_maxMessageLength = 4096;
static const char* g_dbgSystemWnd = "System";
Trace Debug::g_tracer;
Trace g_tracer;
void* g_exceptionInfo = nullptr;
// Environment var needed to track ignored asserts across systems and disable native UI under certain conditions
@@ -81,6 +77,7 @@ namespace AZ
constexpr LogLevel DefaultLogLevel = LogLevel::Info;
AZ_CVAR_SCOPED(int, bg_traceLogLevel, DefaultLogLevel, nullptr, ConsoleFunctorFlags::Null, "Enable trace message logging in release mode. 0=disabled, 1=errors, 2=warnings, 3=info.");
AZ_CVAR_SCOPED(bool, bg_alwaysShowCallstack, false, nullptr, ConsoleFunctorFlags::Null, "Force stack trace output without allowing ebus interception.");
/**
* If any listener returns true, store the result so we don't outputs detailed information.
@@ -224,6 +221,8 @@ namespace AZ
void Debug::Trace::Terminate(int exitCode)
{
AZ_TracePrintf("Exit", "Called Terminate() with exit code: 0x%x", exitCode);
AZ::Debug::Trace::PrintCallstack("Exit");
Platform::Terminate(exitCode);
}
@@ -276,10 +275,15 @@ namespace AZ
logger->Flush(); // Flush as an assert may indicate a crash is imminent.
}
EBUS_EVENT(TraceMessageDrillerBus, OnPreAssert, fileName, line, funcName, message);
TraceMessageResult result;
EBUS_EVENT_RESULT(result, TraceMessageBus, OnPreAssert, fileName, line, funcName, message);
if (bg_alwaysShowCallstack)
{
// If we're always showing the callstack, print it now before there's any chance of an ebus handler interrupting
PrintCallstack(g_dbgSystemWnd, 1);
}
if (result.m_value)
{
g_alreadyHandlingAssertOrFatal = false;
@@ -295,7 +299,6 @@ namespace AZ
azstrcat(message, g_maxMessageLength, "\n");
Output(g_dbgSystemWnd, message);
EBUS_EVENT(TraceMessageDrillerBus, OnAssert, message);
EBUS_EVENT_RESULT(result, TraceMessageBus, OnAssert, message);
if (result.m_value)
{
@@ -305,7 +308,10 @@ namespace AZ
}
Output(g_dbgSystemWnd, "------------------------------------------------\n");
PrintCallstack(g_dbgSystemWnd, 1);
if (!bg_alwaysShowCallstack)
{
PrintCallstack(g_dbgSystemWnd, 1);
}
Output(g_dbgSystemWnd, "==================================================================\n");
char dialogBoxText[g_maxMessageLength];
@@ -395,8 +401,6 @@ namespace AZ
logger->RecordStringEvent(ErrorEventId, message);
}
EBUS_EVENT(TraceMessageDrillerBus, OnPreError, window, fileName, line, funcName, message);
TraceMessageResult result;
EBUS_EVENT_RESULT(result, TraceMessageBus, OnPreError, window, fileName, line, funcName, message);
if (result.m_value)
@@ -411,7 +415,6 @@ namespace AZ
azstrcat(message, g_maxMessageLength, "\n");
Output(window, message);
EBUS_EVENT(TraceMessageDrillerBus, OnError, window, message);
EBUS_EVENT_RESULT(result, TraceMessageBus, OnError, window, message);
Output(window, "==================================================================\n");
if (result.m_value)
@@ -447,8 +450,6 @@ namespace AZ
logger->RecordStringEvent(WarningEventId, message);
}
EBUS_EVENT(TraceMessageDrillerBus, OnPreWarning, window, fileName, line, funcName, message);
TraceMessageResult result;
EBUS_EVENT_RESULT(result, TraceMessageBus, OnPreWarning, window, fileName, line, funcName, message);
if (result.m_value)
@@ -462,7 +463,6 @@ namespace AZ
azstrcat(message, g_maxMessageLength, "\n");
Output(window, message);
EBUS_EVENT(TraceMessageDrillerBus, OnWarning, window, message);
EBUS_EVENT_RESULT(result, TraceMessageBus, OnWarning, window, message);
Output(window, "==================================================================\n");
}
@@ -491,8 +491,6 @@ namespace AZ
logger->RecordStringEvent(PrintfEventId, message);
}
EBUS_EVENT(TraceMessageDrillerBus, OnPrintf, window, message);
TraceMessageResult result;
EBUS_EVENT_RESULT(result, TraceMessageBus, OnPrintf, window, message);
if (result.m_value)
@@ -521,7 +519,6 @@ namespace AZ
// only call into Ebusses if we are not in a recursive-exception situation as that
// would likely just lead to even more exceptions.
EBUS_EVENT(TraceMessageDrillerBus, OnOutput, window, message);
TraceMessageResult result;
EBUS_EVENT_RESULT(result, TraceMessageBus, OnOutput, window, message);
if (result.m_value)
@@ -530,6 +527,16 @@ namespace AZ
}
}
RawOutput(window, message);
}
void Trace::RawOutput(const char* window, const char* message)
{
if (!window)
{
window = g_dbgSystemWnd;
}
// printf on Windows platforms seem to have a buffer length limit of 4096 characters
// Therefore fwrite is used directly to write the window and message to stdout
AZStd::string_view windowView{ window };
@@ -549,17 +556,19 @@ namespace AZ
{
StackFrame frames[25];
// Without StackFrame explicit alignment frames array is aligned to 4 bytes
// which causes the stack tracing to fail.
//size_t bla = AZStd::alignment_of<StackFrame>::value;
//printf("Alignment value %d address 0x%08x : 0x%08x\n",bla,frames);
SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)];
unsigned int numFrames = 0;
if (!nativeContext)
{
suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrinCallstack).
suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrintCallstack).
numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount);
}
unsigned int numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount, nativeContext);
else
{
numFrames = StackConverter::FromNative(frames, AZ_ARRAY_SIZE(frames), nativeContext);
}
if (numFrames)
{
SymbolStorage::DecodeFrames(frames, numFrames, lines);
@@ -571,7 +580,19 @@ namespace AZ
}
azstrcat(lines[i], AZ_ARRAY_SIZE(lines[i]), "\n");
AZ_Printf(window, "%s", lines[i]); // feed back into the trace system so that listeners can get it.
// Use Output instead of AZ_Printf to be consistent with the exception output code and avoid
// this accidentally being suppressed as a normal message
if (bg_alwaysShowCallstack)
{
// Use Raw Output as this cannot be suppressed
RawOutput(window, lines[i]);
}
else
{
Output(window, lines[i]);
}
}
}
}
@@ -608,4 +629,4 @@ namespace AZ
val.Set(level);
}
}
} // namspace AZ
} // namspace AZ::Debug
+13 -10
View File
@@ -73,6 +73,9 @@ namespace AZ
static void Output(const char* window, const char* message);
/// Called by output to handle the actual output, does not interact with ebus or allow interception
static void RawOutput(const char* window, const char* message);
static void PrintCallstack(const char* window, unsigned int suppressCount = 0, void* nativeContext = 0);
/// PEXCEPTION_POINTERS on Windows, always NULL on other platforms
@@ -262,17 +265,17 @@ namespace AZ
#else // !AZ_ENABLE_TRACING
#define AZ_Assert(...) AZ_UNUSED(__VA_ARGS__);
#define AZ_Error(...) AZ_UNUSED(__VA_ARGS__);
#define AZ_ErrorOnce(...) AZ_UNUSED(__VA_ARGS__);
#define AZ_Warning(...) AZ_UNUSED(__VA_ARGS__);
#define AZ_WarningOnce(...) AZ_UNUSED(__VA_ARGS__);
#define AZ_TracePrintf(...) AZ_UNUSED(__VA_ARGS__);
#define AZ_TracePrintfOnce(...) AZ_UNUSED(__VA_ARGS__);
#define AZ_Assert(...)
#define AZ_Error(...)
#define AZ_ErrorOnce(...)
#define AZ_Warning(...)
#define AZ_WarningOnce(...)
#define AZ_TracePrintf(...)
#define AZ_TracePrintfOnce(...)
#define AZ_Verify(...) AZ_UNUSED(__VA_ARGS__);
#define AZ_VerifyError(...) AZ_UNUSED(__VA_ARGS__);
#define AZ_VerifyWarning(...) AZ_UNUSED(__VA_ARGS__);
#define AZ_Verify(expression, ...) AZ_UNUSED(expression)
#define AZ_VerifyError(window, expression, ...) AZ_UNUSED(expression)
#define AZ_VerifyWarning(window, expression, ...) AZ_UNUSED(expression)
#endif // AZ_ENABLE_TRACING
@@ -1,102 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Debug/TraceMessagesDriller.h>
#include <AzCore/Math/Crc.h>
namespace AZ
{
namespace Debug
{
//=========================================================================
// Start
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::Start(const Param* params, int numParams)
{
(void)params;
(void)numParams;
BusConnect();
}
//=========================================================================
// Stop
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::Stop()
{
BusDisconnect();
}
//=========================================================================
// OnAssert
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::OnAssert(const char* message)
{
// Not sure if we can really capture assert since the code will stop executing very soon.
m_output->BeginTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00));
m_output->Write(AZ_CRC("OnAssert", 0xb74db4ce), message);
m_output->EndTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00));
}
//=========================================================================
// OnException
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::OnException(const char* message)
{
// Not sure if we can really capture exception since the code will stop executing very soon.
m_output->BeginTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00));
m_output->Write(AZ_CRC("OnException", 0xfe457d12), message);
m_output->EndTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00));
}
//=========================================================================
// OnError
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::OnError(const char* window, const char* message)
{
m_output->BeginTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00));
m_output->BeginTag(AZ_CRC("OnError", 0x4993c634));
m_output->Write(AZ_CRC("Window", 0x8be4f9dd), window);
m_output->Write(AZ_CRC("Message", 0xb6bd307f), message);
m_output->EndTag(AZ_CRC("OnError", 0x4993c634));
m_output->EndTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00));
}
//=========================================================================
// OnWarning
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::OnWarning(const char* window, const char* message)
{
m_output->BeginTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00));
m_output->BeginTag(AZ_CRC("OnWarning", 0x7d90abea));
m_output->Write(AZ_CRC("Window", 0x8be4f9dd), window);
m_output->Write(AZ_CRC("Message", 0xb6bd307f), message);
m_output->EndTag(AZ_CRC("OnWarning", 0x7d90abea));
m_output->EndTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00));
}
//=========================================================================
// OnPrintf
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::OnPrintf(const char* window, const char* message)
{
m_output->BeginTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00));
m_output->BeginTag(AZ_CRC("OnPrintf", 0xd4b5c294));
m_output->Write(AZ_CRC("Window", 0x8be4f9dd), window);
m_output->Write(AZ_CRC("Message", 0xb6bd307f), message);
m_output->EndTag(AZ_CRC("OnPrintf", 0xd4b5c294));
m_output->EndTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00));
}
} // namespace Debug
} // namespace AZ
@@ -1,49 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Driller/Driller.h>
#include <AzCore/Debug/TraceMessagesDrillerBus.h>
namespace AZ
{
namespace Debug
{
/**
* Trace messages driller class
*/
class TraceMessagesDriller
: public Driller
, public TraceMessageDrillerBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(TraceMessagesDriller, OSAllocator, 0)
protected:
//////////////////////////////////////////////////////////////////////////
// Driller
const char* GroupName() const override { return "SystemDrillers"; }
const char* GetName() const override { return "TraceMessagesDriller"; }
const char* GetDescription() const override { return "Handles all system messages like Assert, Exception, Error, Warning, Printf, etc."; }
void Start(const Param* params = NULL, int numParams = 0) override;
void Stop() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// TraceMessagesDrillerBus
/// Triggered when a AZ_Assert failed. This is terminating event! (the code will break, crash).
void OnAssert(const char* message) override;
void OnException(const char* message) override;
void OnError(const char* window, const char* message) override;
void OnWarning(const char* window, const char* message) override;
void OnPrintf(const char* window, const char* message) override;
//////////////////////////////////////////////////////////////////////////
};
} // namespace Debug
} // namespace AZ
@@ -1,53 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Driller/DrillerBus.h>
namespace AZ
{
namespace Debug
{
/**
* Trace messages event handle.
* All messages are optional (they have default implementation) and you can handle only one at a time.
* Driller messages are similar to TraceMessages, but do not provide a return value,
* as we only care about collecting driller messages, not operating on them.
*
* We use a driller bus so all messages are sending in exclusive matter no other driller messages
* can be triggered at that moment, so we already preserve the calling order. You can assume
* all access code in the driller framework in guarded. You can manually lock the driller mutex are you
* use by using \ref AZ::Debug::DrillerEBusMutex.
*/
class TraceMessageDrillerEvents
: public DrillerEBusTraits
{
public:
virtual ~TraceMessageDrillerEvents() {}
/// Triggered when a AZ_Assert failed. This is terminating event! (the code will break, crash).
virtual void OnPreAssert(const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) {}
virtual void OnAssert(const char* /*message*/) {}
virtual void OnException(const char* /*message*/) {}
virtual void OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) {}
virtual void OnError(const char* /*window*/, const char* /*message*/) {}
virtual void OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) {}
virtual void OnWarning(const char* /*window*/, const char* /*message*/) {}
virtual void OnPrintf(const char* /*window*/, const char* /*message*/) {}
/**
* All trace functions you output to anything. So if you want to handle all the output this is the place.
* You are not given the choice to disable the system output as if you listen at that level you can't make
* that decision. Otherwise we can trigger an assert without even one line of message send to the console/debugger.
*/
virtual void OnOutput(const char* /*window*/, const char* /*message*/) {}
};
typedef AZ::EBus<TraceMessageDrillerEvents> TraceMessageDrillerBus;
} // namespace Debug
} // namespace AZ
@@ -12,283 +12,280 @@
#include <AzCore/Script/ScriptContext.h>
#include <AzCore/Component/TickBus.h>
namespace AZ
namespace AZ::Debug
{
namespace Debug
//! Trace Message Event Handler for Automation.
//! Since TraceMessageBus will be called from multiple threads and
//! python interpreter is single threaded, all the bus calls are
//! queued into a list and called at the end of the frame in the main thread.
//! @note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER
//! macro as the signature needs to be changed to connect to Tick bus.
class TraceMessageBusHandler
: public AZ::Debug::TraceMessageBus::Handler
, public AZ::BehaviorEBusHandler
, public AZ::TickBus::Handler
{
//! Trace Message Event Handler for Automation.
//! Since TraceMessageBus will be called from multiple threads and
//! python interpreter is single threaded, all the bus calls are
//! queued into a list and called at the end of the frame in the main thread.
//! @note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER
//! macro as the signature needs to be changed to connect to Tick bus.
class TraceMessageBusHandler
: public AZ::Debug::TraceMessageBus::Handler
, public AZ::BehaviorEBusHandler
, public AZ::TickBus::Handler
public:
AZ_CLASS_ALLOCATOR(TraceMessageBusHandler, AZ::SystemAllocator, 0);
AZ_RTTI(TraceMessageBusHandler, "{5CDBAF09-5EB0-48AC-B327-2AF8601BB550}", AZ::BehaviorEBusHandler);
TraceMessageBusHandler();
using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence<
decltype(&TraceMessageBusHandler::OnPreAssert),
decltype(&TraceMessageBusHandler::OnPreError),
decltype(&TraceMessageBusHandler::OnPreWarning),
decltype(&TraceMessageBusHandler::OnAssert),
decltype(&TraceMessageBusHandler::OnError),
decltype(&TraceMessageBusHandler::OnWarning),
decltype(&TraceMessageBusHandler::OnException),
decltype(&TraceMessageBusHandler::OnPrintf),
decltype(&TraceMessageBusHandler::OnOutput)
>;
enum
{
public:
AZ_CLASS_ALLOCATOR(TraceMessageBusHandler, AZ::SystemAllocator, 0);
AZ_RTTI(TraceMessageBusHandler, "{5CDBAF09-5EB0-48AC-B327-2AF8601BB550}", AZ::BehaviorEBusHandler);
TraceMessageBusHandler();
using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence<
decltype(&TraceMessageBusHandler::OnPreAssert),
decltype(&TraceMessageBusHandler::OnPreError),
decltype(&TraceMessageBusHandler::OnPreWarning),
decltype(&TraceMessageBusHandler::OnAssert),
decltype(&TraceMessageBusHandler::OnError),
decltype(&TraceMessageBusHandler::OnWarning),
decltype(&TraceMessageBusHandler::OnException),
decltype(&TraceMessageBusHandler::OnPrintf),
decltype(&TraceMessageBusHandler::OnOutput)
>;
enum
{
FN_OnPreAssert = 0,
FN_OnPreError,
FN_OnPreWarning,
FN_OnAssert,
FN_OnError,
FN_OnWarning,
FN_OnException,
FN_OnPrintf,
FN_OnOutput,
FN_MAX
};
static inline constexpr const char* m_functionNames[FN_MAX] =
{
"OnPreAssert",
"OnPreError",
"OnPreWarning",
"OnAssert",
"OnError",
"OnWarning",
"OnException",
"OnPrintf",
"OnOutput"
};
// AZ::BehaviorEBusHandler overrides...
int GetFunctionIndex(const char* functionName) const override;
void Disconnect() override;
bool Connect(AZ::BehaviorValueParameter* id = nullptr) override;
bool IsConnected() override;
bool IsConnectedId(AZ::BehaviorValueParameter* id) override;
// TraceMessageBus
/*
* Note: Since at editor runtime there is already have a handler, for automation (OnPreAssert, OnPreWarning, OnPreWarning)
* must be used instead of (OnAssert, OnWarning, OnError)
*/
bool OnPreAssert(const char* fileName, int line, const char* func, const char* message) override;
bool OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message) override;
bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override;
bool OnAssert(const char* message) override;
bool OnError(const char* window, const char* message) override;
bool OnWarning(const char* window, const char* message) override;
bool OnException(const char* message) override;
bool OnPrintf(const char* window, const char* message) override;
bool OnOutput(const char* window, const char* message) override;
// AZ::TickBus::Handler overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
private:
void QueueMessageCall(AZStd::function<void()> messageCall);
void FlushMessageCalls();
AZStd::list<AZStd::function<void()>> m_messageCalls;
AZStd::mutex m_messageCallsLock;
FN_OnPreAssert = 0,
FN_OnPreError,
FN_OnPreWarning,
FN_OnAssert,
FN_OnError,
FN_OnWarning,
FN_OnException,
FN_OnPrintf,
FN_OnOutput,
FN_MAX
};
TraceMessageBusHandler::TraceMessageBusHandler()
static inline constexpr const char* m_functionNames[FN_MAX] =
{
m_events.resize(FN_MAX);
"OnPreAssert",
"OnPreError",
"OnPreWarning",
"OnAssert",
"OnError",
"OnWarning",
"OnException",
"OnPrintf",
"OnOutput"
};
SetEvent(&TraceMessageBusHandler::OnPreAssert, m_functionNames[FN_OnPreAssert]);
SetEvent(&TraceMessageBusHandler::OnPreError, m_functionNames[FN_OnPreError]);
SetEvent(&TraceMessageBusHandler::OnPreWarning, m_functionNames[FN_OnPreWarning]);
SetEvent(&TraceMessageBusHandler::OnAssert, m_functionNames[FN_OnAssert]);
SetEvent(&TraceMessageBusHandler::OnError, m_functionNames[FN_OnError]);
SetEvent(&TraceMessageBusHandler::OnWarning, m_functionNames[FN_OnWarning]);
SetEvent(&TraceMessageBusHandler::OnException, m_functionNames[FN_OnException]);
SetEvent(&TraceMessageBusHandler::OnPrintf, m_functionNames[FN_OnPrintf]);
SetEvent(&TraceMessageBusHandler::OnOutput, m_functionNames[FN_OnOutput]);
}
// AZ::BehaviorEBusHandler overrides...
int GetFunctionIndex(const char* functionName) const override;
void Disconnect() override;
bool Connect(AZ::BehaviorValueParameter* id = nullptr) override;
bool IsConnected() override;
bool IsConnectedId(AZ::BehaviorValueParameter* id) override;
int TraceMessageBusHandler::GetFunctionIndex(const char* functionName) const
// TraceMessageBus
/*
* Note: Since at editor runtime there is already have a handler, for automation (OnPreAssert, OnPreWarning, OnPreWarning)
* must be used instead of (OnAssert, OnWarning, OnError)
*/
bool OnPreAssert(const char* fileName, int line, const char* func, const char* message) override;
bool OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message) override;
bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override;
bool OnAssert(const char* message) override;
bool OnError(const char* window, const char* message) override;
bool OnWarning(const char* window, const char* message) override;
bool OnException(const char* message) override;
bool OnPrintf(const char* window, const char* message) override;
bool OnOutput(const char* window, const char* message) override;
// AZ::TickBus::Handler overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
private:
void QueueMessageCall(AZStd::function<void()> messageCall);
void FlushMessageCalls();
AZStd::list<AZStd::function<void()>> m_messageCalls;
AZStd::mutex m_messageCallsLock;
};
TraceMessageBusHandler::TraceMessageBusHandler()
{
m_events.resize(FN_MAX);
SetEvent(&TraceMessageBusHandler::OnPreAssert, m_functionNames[FN_OnPreAssert]);
SetEvent(&TraceMessageBusHandler::OnPreError, m_functionNames[FN_OnPreError]);
SetEvent(&TraceMessageBusHandler::OnPreWarning, m_functionNames[FN_OnPreWarning]);
SetEvent(&TraceMessageBusHandler::OnAssert, m_functionNames[FN_OnAssert]);
SetEvent(&TraceMessageBusHandler::OnError, m_functionNames[FN_OnError]);
SetEvent(&TraceMessageBusHandler::OnWarning, m_functionNames[FN_OnWarning]);
SetEvent(&TraceMessageBusHandler::OnException, m_functionNames[FN_OnException]);
SetEvent(&TraceMessageBusHandler::OnPrintf, m_functionNames[FN_OnPrintf]);
SetEvent(&TraceMessageBusHandler::OnOutput, m_functionNames[FN_OnOutput]);
}
int TraceMessageBusHandler::GetFunctionIndex(const char* functionName) const
{
for (int i = 0; i < FN_MAX; ++i)
{
for (int i = 0; i < FN_MAX; ++i)
if (azstricmp(functionName, m_functionNames[i]) == 0)
{
if (azstricmp(functionName, m_functionNames[i]) == 0)
{
return i;
}
return i;
}
return -1;
}
return -1;
}
void TraceMessageBusHandler::Disconnect()
void TraceMessageBusHandler::Disconnect()
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
}
bool TraceMessageBusHandler::Connect(AZ::BehaviorValueParameter* id)
{
AZ::TickBus::Handler::BusConnect();
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::Connect(this, id);
}
bool TraceMessageBusHandler::IsConnected()
{
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::IsConnected(this);
}
bool TraceMessageBusHandler::IsConnectedId(AZ::BehaviorValueParameter* id)
{
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::IsConnectedId(this, id);
}
//////////////////////////////////////////////////////////////////////////
// TraceMessageBusHandler Implementation
inline bool TraceMessageBusHandler::OnPreAssert(const char* fileName, int line, const char* func, const char* message)
{
QueueMessageCall(
[this, fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
}
Call(FN_OnPreAssert, fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
});
return false;
}
bool TraceMessageBusHandler::Connect(AZ::BehaviorValueParameter* id)
inline bool TraceMessageBusHandler::OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message)
{
QueueMessageCall(
[this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
{
AZ::TickBus::Handler::BusConnect();
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::Connect(this, id);
}
Call(FN_OnPreError, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
});
return false;
}
bool TraceMessageBusHandler::IsConnected()
inline bool TraceMessageBusHandler::OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message)
{
QueueMessageCall(
[this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
{
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::IsConnected(this);
}
return Call(FN_OnPreWarning, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
});
return false;
}
bool TraceMessageBusHandler::IsConnectedId(AZ::BehaviorValueParameter* id)
inline bool TraceMessageBusHandler::OnAssert(const char* message)
{
QueueMessageCall(
[this, messageString = AZStd::string(message)]()
{
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::IsConnectedId(this, id);
}
return Call(FN_OnAssert, messageString.c_str());
});
return false;
}
//////////////////////////////////////////////////////////////////////////
// TraceMessageBusHandler Implementation
inline bool TraceMessageBusHandler::OnPreAssert(const char* fileName, int line, const char* func, const char* message)
inline bool TraceMessageBusHandler::OnError(const char* window, const char* message)
{
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
QueueMessageCall(
[this, fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
{
Call(FN_OnPreAssert, fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
});
return false;
}
return Call(FN_OnError, windowString.c_str(), messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message)
inline bool TraceMessageBusHandler::OnWarning(const char* window, const char* message)
{
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
QueueMessageCall(
[this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
{
Call(FN_OnPreError, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
});
return false;
}
return Call(FN_OnWarning, windowString.c_str(), messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message)
inline bool TraceMessageBusHandler::OnException(const char* message)
{
QueueMessageCall(
[this, messageString = AZStd::string(message)]()
{
QueueMessageCall(
[this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
{
return Call(FN_OnPreWarning, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
});
return false;
}
return Call(FN_OnException, messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnAssert(const char* message)
inline bool TraceMessageBusHandler::OnPrintf(const char* window, const char* message)
{
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
QueueMessageCall(
[this, messageString = AZStd::string(message)]()
{
return Call(FN_OnAssert, messageString.c_str());
});
return false;
}
return Call(FN_OnPrintf, windowString.c_str(), messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnError(const char* window, const char* message)
inline bool TraceMessageBusHandler::OnOutput(const char* window, const char* message)
{
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
return Call(FN_OnError, windowString.c_str(), messageString.c_str());
});
return false;
}
return Call(FN_OnOutput, windowString.c_str(), messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnWarning(const char* window, const char* message)
{
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
return Call(FN_OnWarning, windowString.c_str(), messageString.c_str());
});
return false;
}
void TraceMessageBusHandler::OnTick(
[[maybe_unused]] float deltaTime,
[[maybe_unused]] AZ::ScriptTimePoint time)
{
FlushMessageCalls();
}
inline bool TraceMessageBusHandler::OnException(const char* message)
{
QueueMessageCall(
[this, messageString = AZStd::string(message)]()
{
return Call(FN_OnException, messageString.c_str());
});
return false;
}
int TraceMessageBusHandler::GetTickOrder()
{
return AZ::TICK_LAST;
}
inline bool TraceMessageBusHandler::OnPrintf(const char* window, const char* message)
{
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
return Call(FN_OnPrintf, windowString.c_str(), messageString.c_str());
});
return false;
}
void TraceMessageBusHandler::QueueMessageCall(AZStd::function<void()> messageCall)
{
AZStd::lock_guard<decltype(m_messageCallsLock)> lock(m_messageCallsLock);
m_messageCalls.emplace_back(messageCall);
}
inline bool TraceMessageBusHandler::OnOutput(const char* window, const char* message)
{
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
return Call(FN_OnOutput, windowString.c_str(), messageString.c_str());
});
return false;
}
void TraceMessageBusHandler::OnTick(
[[maybe_unused]] float deltaTime,
[[maybe_unused]] AZ::ScriptTimePoint time)
{
FlushMessageCalls();
}
int TraceMessageBusHandler::GetTickOrder()
{
return AZ::TICK_LAST;
}
void TraceMessageBusHandler::QueueMessageCall(AZStd::function<void()> messageCall)
void TraceMessageBusHandler::FlushMessageCalls()
{
AZStd::list<AZStd::function<void()>> messageCalls;
{
AZStd::lock_guard<decltype(m_messageCallsLock)> lock(m_messageCallsLock);
m_messageCalls.push_back(messageCall);
m_messageCalls.swap(messageCalls); // Move calls to a new list to release the lock as soon as possible
}
void TraceMessageBusHandler::FlushMessageCalls()
for (auto& messageCall : messageCalls)
{
AZStd::list<AZStd::function<void()>> messageCalls;
{
AZStd::lock_guard<decltype(m_messageCallsLock)> lock(m_messageCallsLock);
m_messageCalls.swap(messageCalls); // Move calls to a new list to release the lock as soon as possible
}
for (auto& messageCall : messageCalls)
{
messageCall();
}
}
void TraceReflect(ReflectContext* context)
{
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->EBus<AZ::Debug::TraceMessageBus>("TraceMessageBus")
->Attribute(AZ::Script::Attributes::Module, "debug")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Handler<TraceMessageBusHandler>()
;
}
messageCall();
}
}
}
void TraceReflect(ReflectContext* context)
{
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->EBus<AZ::Debug::TraceMessageBus>("TraceMessageBus")
->Attribute(AZ::Script::Attributes::Module, "debug")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Handler<TraceMessageBusHandler>()
;
}
}
} // namespace AZ::Debug
@@ -1,125 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZCORE_DRILLER_DEFAULT_STRING_POOL_H
#define AZCORE_DRILLER_DEFAULT_STRING_POOL_H
#include <AzCore/Driller/Stream.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/unordered_set.h>
namespace AZ
{
namespace Debug
{
template<class Key, class Mapped>
struct unordered_map
{
typedef AZStd::unordered_map<Key, Mapped, AZStd::hash<Key>, AZStd::equal_to<Key>, OSStdAllocator> type;
};
template<class Key>
struct unordered_set
{
typedef AZStd::unordered_set<Key, AZStd::hash<Key>, AZStd::equal_to<Key>, OSStdAllocator> type;
};
/**
* Default implementation of a string pool.
*/
class DrillerDefaultStringPool
: public DrillerStringPool
{
public:
virtual ~DrillerDefaultStringPool()
{
Reset();
}
typedef unordered_map<AZ::u32, const char*>::type CrcToStringMapType;
typedef unordered_set<const char*>::type OwnedStringsMapType;
/**
* Add a copy of the string to the pool. If we return true the string was added otherwise it was already in the bool.
* In both cases the crc32 of the string and the pointer to the shared copy is returned (optional for poolStringAddress)!
*/
virtual bool InsertCopy(const char* string, unsigned int length, AZ::u32& crc32, const char** poolStringAddress = nullptr)
{
crc32 = AZ::Crc32(string, length);
CrcToStringMapType::pair_iter_bool insertIt = m_crcToStringMap.insert_key(crc32);
if (insertIt.second)
{
char* newString = reinterpret_cast<char*>(azmalloc(length + 1, 1, AZ::OSAllocator));
memcpy(newString, string, length);
newString[length] = '\0'; // terminate
m_ownedStrings.insert(newString);
insertIt.first->second = newString;
}
if (poolStringAddress)
{
*poolStringAddress = insertIt.first->second;
}
return insertIt.second;
}
/**
* Same as the InsertCopy above without actually coping the string into the pool. The pool assumes that
* none of the strings added to the pool will be deleted.
*/
virtual bool Insert(const char* string, unsigned int length, AZ::u32& crc32)
{
crc32 = AZ::Crc32(string, length);
return m_crcToStringMap.insert(AZStd::make_pair(crc32, string)).second;
}
/// Finds a string in the pool by crc32.
virtual const char* Find(AZ::u32 crc32)
{
CrcToStringMapType::iterator it = m_crcToStringMap.find(crc32);
if (it != m_crcToStringMap.end())
{
return it->second;
}
return NULL;
}
virtual void Erase(AZ::u32 crc32)
{
CrcToStringMapType::iterator it = m_crcToStringMap.find(crc32);
if (it != m_crcToStringMap.end())
{
OwnedStringsMapType::iterator ownerIt = m_ownedStrings.find(it->second);
if (ownerIt != m_ownedStrings.end())
{
azfree(const_cast<char*>(it->second), AZ::OSAllocator);
m_ownedStrings.erase(ownerIt);
}
m_crcToStringMap.erase(it);
}
}
virtual void Reset()
{
for (OwnedStringsMapType::iterator it = m_ownedStrings.begin(); it != m_ownedStrings.end(); ++it)
{
azfree(const_cast<char*>(*it), AZ::OSAllocator);
}
m_crcToStringMap.clear();
m_ownedStrings.clear();
}
protected:
CrcToStringMapType m_crcToStringMap;
OwnedStringsMapType m_ownedStrings;
};
} // namespace Debug
} // namespace AZ
#endif // AZCORE_DRILLER_DEFAULT_STRING_POOL_H
#pragma once
@@ -1,304 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Driller/Driller.h>
#include <AzCore/Driller/DrillerBus.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/Math/Crc.h>
namespace AZ
{
namespace Debug
{
class DrillerManagerImpl
: public DrillerManager
{
public:
AZ_CLASS_ALLOCATOR(DrillerManagerImpl, OSAllocator, 0);
typedef forward_list<DrillerSession>::type SessionListType;
SessionListType m_sessions;
typedef vector<Driller*>::type DrillerArrayType;
DrillerArrayType m_drillers;
~DrillerManagerImpl() override;
void Register(Driller* factory) override;
void Unregister(Driller* factory) override;
void FrameUpdate() override;
DrillerSession* Start(DrillerOutputStream& output, const DrillerListType& drillerList, int numFrames = -1) override;
void Stop(DrillerSession* session) override;
int GetNumDrillers() const override { return static_cast<int>(m_drillers.size()); }
Driller* GetDriller(int index) override { return m_drillers[index]; }
};
//////////////////////////////////////////////////////////////////////////
// Driller
//=========================================================================
// Register
// [3/17/2011]
//=========================================================================
AZ::u32 Driller::GetId() const
{
return AZ::Crc32(GetName());
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Driller Manager
//=========================================================================
// Register
// [3/17/2011]
//=========================================================================
DrillerManager* DrillerManager::Create(/*const Descriptor& desc*/)
{
const bool createAllocator = !AZ::AllocatorInstance<OSAllocator>::IsReady();
if (createAllocator)
{
AZ::AllocatorInstance<OSAllocator>::Create();
}
DrillerManagerImpl* impl = aznew DrillerManagerImpl;
impl->m_ownsOSAllocator = createAllocator;
return impl;
}
//=========================================================================
// Register
// [3/17/2011]
//=========================================================================
void DrillerManager::Destroy(DrillerManager* manager)
{
const bool allocatorCreated = manager->m_ownsOSAllocator;
delete manager;
if (allocatorCreated)
{
AZ::AllocatorInstance<OSAllocator>::Destroy();
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// DrillerManagerImpl
//=========================================================================
// ~DrillerManagerImpl
// [3/17/2011]
//=========================================================================
DrillerManagerImpl::~DrillerManagerImpl()
{
while (!m_sessions.empty())
{
Stop(&m_sessions.front());
}
while (!m_drillers.empty())
{
Driller* driller = m_drillers[0];
Unregister(driller);
delete driller;
}
}
//=========================================================================
// Register
// [3/17/2011]
//=========================================================================
void
DrillerManagerImpl::Register(Driller* driller)
{
AZ_Assert(driller, "You must provide a valid factory!");
for (size_t i = 0; i < m_drillers.size(); ++i)
{
if (m_drillers[i]->GetId() == driller->GetId())
{
AZ_Error("Debug", false, "Driller with id %08x has already been registered! You can't have two factory instances for the same driller type", driller->GetId());
return;
}
}
m_drillers.push_back(driller);
}
//=========================================================================
// Unregister
// [3/17/2011]
//=========================================================================
void
DrillerManagerImpl::Unregister(Driller* driller)
{
AZ_Assert(driller, "You must provide a valid factory!");
for (DrillerArrayType::iterator iter = m_drillers.begin(); iter != m_drillers.end(); ++iter)
{
if ((*iter)->GetId() == driller->GetId())
{
m_drillers.erase(iter);
return;
}
}
AZ_Error("Debug", false, "Failed to find driller factory with id %08x", driller->GetId());
}
//=========================================================================
// FrameUpdate
// [3/17/2011]
//=========================================================================
void
DrillerManagerImpl::FrameUpdate()
{
if (m_sessions.empty())
{
return;
}
AZStd::lock_guard<DrillerEBusMutex::MutexType> lock(DrillerEBusMutex::GetMutex()); ///< Make sure no driller is writing to the stream
for (SessionListType::iterator sessionIter = m_sessions.begin(); sessionIter != m_sessions.end(); )
{
DrillerSession& s = *sessionIter;
// tick the drillers directly if they care.
for (size_t i = 0; i < s.drillers.size(); ++i)
{
s.drillers[i]->Update();
}
s.output->EndTag(AZ_CRC("Frame", 0xb5f83ccd));
s.output->OnEndOfFrame();
s.curFrame++;
if (s.numFrames != -1)
{
if (s.curFrame == s.numFrames)
{
Stop(&s);
continue;
}
}
s.output->BeginTag(AZ_CRC("Frame", 0xb5f83ccd));
s.output->Write(AZ_CRC("FrameNum", 0x85a1a919), s.curFrame);
++sessionIter;
}
}
//=========================================================================
// Start
// [3/17/2011]
//=========================================================================
DrillerSession*
DrillerManagerImpl::Start(DrillerOutputStream& output, const DrillerListType& drillerList, int numFrames)
{
if (drillerList.empty())
{
return nullptr;
}
m_sessions.push_back();
DrillerSession& s = m_sessions.back();
s.curFrame = 0;
s.numFrames = numFrames;
s.output = &output;
s.output->WriteHeader(); // first write the header in the stream
s.output->BeginTag(AZ_CRC("StartData", 0xecf3f53f));
s.output->Write(AZ_CRC("Platform", 0x3952d0cb), (unsigned int)g_currentPlatform);
for (DrillerListType::const_iterator iDriller = drillerList.begin(); iDriller != drillerList.end(); ++iDriller)
{
const DrillerInfo& di = *iDriller;
s.output->BeginTag(AZ_CRC("Driller", 0xa6e1fb73));
s.output->Write(AZ_CRC("Name", 0x5e237e06), di.id);
for (int iParam = 0; iParam < (int)di.params.size(); ++iParam)
{
s.output->BeginTag(AZ_CRC("Param", 0xa4fa7c89));
s.output->Write(AZ_CRC("Name", 0x5e237e06), di.params[iParam].name);
s.output->Write(AZ_CRC("Description", 0x6de44026), di.params[iParam].desc);
s.output->Write(AZ_CRC("Type", 0x8cde5729), di.params[iParam].type);
s.output->Write(AZ_CRC("Value", 0x1d775834), di.params[iParam].value);
s.output->EndTag(AZ_CRC("Param", 0xa4fa7c89));
}
s.output->EndTag(AZ_CRC("Driller", 0xa6e1fb73));
}
s.output->EndTag(AZ_CRC("StartData", 0xecf3f53f));
s.output->BeginTag(AZ_CRC("Frame", 0xb5f83ccd));
s.output->Write(AZ_CRC("FrameNum", 0x85a1a919), s.curFrame);
{
AZStd::lock_guard<DrillerEBusMutex::MutexType> lock(DrillerEBusMutex::GetMutex()); ///< Make sure no driller is writing to the stream
for (DrillerListType::const_iterator iDriller = drillerList.begin(); iDriller != drillerList.end(); ++iDriller)
{
Driller* driller = nullptr;
const DrillerInfo& di = *iDriller;
for (size_t iDesc = 0; iDesc < m_drillers.size(); ++iDesc)
{
if (m_drillers[iDesc]->GetId() == di.id)
{
driller = m_drillers[iDesc];
AZ_Assert(driller->m_output == nullptr, "Driller with id %08x is already have an output stream %p (currently we support only 1 at a time)", di.id, driller->m_output);
driller->m_output = &output;
driller->Start(di.params.data(), static_cast<unsigned int>(di.params.size()));
s.drillers.push_back(driller);
break;
}
}
AZ_Warning("Driller", driller != nullptr, "We can't start a driller with id %d!", di.id);
}
}
return &s;
}
//=========================================================================
// Stop
// [3/17/2011]
//=========================================================================
void
DrillerManagerImpl::Stop(DrillerSession* session)
{
SessionListType::iterator iter;
for (iter = m_sessions.begin(); iter != m_sessions.end(); ++iter)
{
if (&*iter == session)
{
break;
}
}
AZ_Assert(iter != m_sessions.end(), "We did not find session ID 0x%08x in the list!", session);
if (iter != m_sessions.end())
{
DrillerSession& s = *session;
{
AZStd::lock_guard<DrillerEBusMutex::MutexType> lock(DrillerEBusMutex::GetMutex());
for (size_t i = 0; i < s.drillers.size(); ++i)
{
s.drillers[i]->Stop();
s.drillers[i]->m_output = nullptr;
}
}
s.output->EndTag(AZ_CRC("Frame", 0xb5f83ccd));
m_sessions.erase(iter);
}
}
} // namespace Debug
} // namespace AZ
@@ -1,141 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZCORE_DRILLER_H
#define AZCORE_DRILLER_H
#include <AzCore/Driller/Stream.h>
namespace AZStd
{
class mutex;
}
namespace AZ
{
namespace Debug
{
class DrillerOutputStream;
/**
* Driller base class. Every driller should inherit from this class.
* When a driller is need to start outputting data
* the DrillerManager will call Driller::Start() so the driller
* can output the initial state for all reported entities.
* The same applies for the Stop.
* Depending on the type of your driller you might choose to collect state
* even before the driller has started. This of course should be a fast as
* possible, as we don't want to burden engine systems and it's highly recommended
* that you use configuration parameters to change that behavior as not all drillers
* are used on a daily basis.
* All drillers should use DebugAllocators (AZ_CLASS_ALLOCATOR(Driller,OSAllocator,0))
* and they should use 'aznew' to create one, as by default if you don't unregister a
* a driller, the manager will use "delete" to delete them.
*
* IMPORTANT: Driller systems works OUTSIDE engine systems, you should NOT use SystemAllocator or any other engine systems
* as they might be drilled or not available at the moment.
*/
class Driller
{
friend class DrillerManagerImpl;
public:
struct Param
{
enum Type
{
PT_BOOL,
PT_INT,
PT_FLOAT
};
const char* desc;
u32 name;
int type;
int value;
};
Driller()
: m_output(NULL) {}
virtual ~Driller() {}
/// Returns the driller ID Crc32 of the name (Crc32(GetName())
AZ::u32 GetId() const;
/// Driller group name, used only for organizational purpose
virtual const char* GroupName() const = 0;
/// Unique name of the Driller, driller ID is the Crc of the name
virtual const char* GetName() const = 0;
virtual const char* GetDescription() const = 0;
// @{ Managing the list of supported driller parameters.
virtual int GetNumParams() const { return 0; }
virtual const Param* GetParam(int index) const { (void)index; return NULL; }
protected:
Driller& operator=(const Driller&);
/// Called by DrillerManager
virtual void Start(const Param* params = NULL, int numParams = 0) { (void)params; (void)numParams; }
/// Called by DrillerManager
virtual void Stop() {}
/// Called every frame by DrillerManger (while the driller is started)
virtual void Update() {}
DrillerOutputStream* m_output; ///< Session output stream.
};
/**
* Stores the information while an active
* driller(s) session is running.
*/
struct DrillerSession
{
int numFrames;
int curFrame;
typedef vector<Driller*>::type DrillerArrayType;
DrillerArrayType drillers;
DrillerOutputStream* output;
};
/**
* Driller manager will manage all active driller sessions and driller factories. Generally you will never
* need more than one driller manger.
* IMPORTANT: Driller systems works OUTSIDE engine systems, you should NOT use SystemAllocator or any other engine systems
* as they might be drilled or not available at the moment.
*/
class DrillerManager
{
friend class DrillerRemoteServer;
public:
struct DrillerInfo
{
AZ::u32 id;
vector<Driller::Param>::type params;
};
typedef forward_list<DrillerInfo>::type DrillerListType;
virtual ~DrillerManager() {}
static DrillerManager* Create(/*const Descriptor& desc*/);
static void Destroy(DrillerManager* manager);
virtual void Register(Driller* driller) = 0;
virtual void Unregister(Driller* driller) = 0;
virtual void FrameUpdate() = 0;
virtual DrillerSession* Start(DrillerOutputStream& output, const DrillerListType& drillerList, int numFrames = -1) = 0;
virtual void Stop(DrillerSession* session) = 0;
virtual int GetNumDrillers() const = 0;
virtual Driller* GetDriller(int index) = 0;
private:
// If the manager created the allocator, it should destroy it when it gets destroyed
bool m_ownsOSAllocator = false;
};
}
}
#endif // AZCORE_DRILLER_H
#pragma once
@@ -1,68 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Driller/DrillerBus.h>
#include <AzCore/Driller/Driller.h>
#include <AzCore/Module/Environment.h>
#include <AzCore/std/parallel/mutex.h>
namespace AZ
{
namespace Debug
{
//////////////////////////////////////////////////////////////////////////
// Globals
// We need to synchronize all driller evens, so we have proper order, and access to the data
// We use a global mutex which should be used for all driller operations.
// The mutex is held in an environment variable so it works across DLLs.
EnvironmentVariable<AZStd::recursive_mutex> s_drillerGlobalMutex;
//////////////////////////////////////////////////////////////////////////
//=========================================================================
// lock
// [4/11/2011]
//=========================================================================
void DrillerEBusMutex::lock()
{
GetMutex().lock();
}
//=========================================================================
// try_lock
// [4/11/2011]
//=========================================================================
bool DrillerEBusMutex::try_lock()
{
return GetMutex().try_lock();
}
//=========================================================================
// unlock
// [4/11/2011]
//=========================================================================
void DrillerEBusMutex::unlock()
{
GetMutex().unlock();
}
//=========================================================================
// unlock
// [4/11/2011]
//=========================================================================
AZStd::recursive_mutex& DrillerEBusMutex::GetMutex()
{
if (!s_drillerGlobalMutex)
{
s_drillerGlobalMutex = Environment::CreateVariable<AZStd::recursive_mutex>(AZ_FUNCTION_SIGNATURE);
}
return *s_drillerGlobalMutex;
}
} // namespace Debug
} // namespace AZ
@@ -1,52 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZCORE_DRILLER_BUS_H
#define AZCORE_DRILLER_BUS_H
#include <AzCore/base.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Memory/OSAllocator.h>
namespace AZStd
{
class mutex;
}
namespace AZ
{
namespace Debug
{
class DrillerEBusMutex
{
public:
typedef AZStd::recursive_mutex MutexType;
static MutexType& GetMutex();
void lock();
bool try_lock();
void unlock();
};
/**
* Specialization of the EBusTraits for a driller bus. We make sure
* all allocation are made using DebugAllocation (so no engine systems are involved).
* In addition we make sure all driller buses use the same Mutex to synchronize data across
* threads (so all events came in order all the time), they are still executed in the context of
* the thread.
*/
struct DrillerEBusTraits
: public AZ::EBusTraits
{
typedef DrillerEBusMutex MutexType;
typedef OSStdAllocator AllocatorType;
};
}
}
#endif // AZCORE_DRILLER_BUS_H
#pragma once
@@ -1,170 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZCORE_DRILLER_ROOT_HANDLER_H
#define AZCORE_DRILLER_ROOT_HANDLER_H
#include <AzCore/Driller/Stream.h>
#include <AzCore/Math/Crc.h>
namespace AZ
{
namespace Debug
{
// Please check DrillerRootHandler class... this is the one for direct use.
/**
* Handler for the <Frame><StartData><Driller></Driller></StartData></Frame> tag.
*/
class DrillerDrillerdataHandler
: public DrillerHandlerParser
{
public:
class ParamHandler
: public DrillerHandlerParser
{
public:
virtual void OnData(const DrillerSAXParser::Data& dataNode)
{
if (dataNode.m_name == AZ_CRC("Name", 0x5e237e06))
{
dataNode.Read(m_param->name);
}
else if (dataNode.m_name == AZ_CRC("Description", 0x6de44026))
{
m_param->desc = NULL; // ignored
}
else if (dataNode.m_name == AZ_CRC("Type", 0x8cde5729))
{
dataNode.Read(m_param->type);
}
else if (dataNode.m_name == AZ_CRC("Value", 0x1d775834))
{
dataNode.Read(m_param->value);
}
}
Driller::Param* m_param;
};
virtual DrillerHandlerParser* OnEnterTag(u32 tagName)
{
if (tagName == AZ_CRC("Param", 0xa4fa7c89))
{
m_drillerInfo->params.push_back();
m_paramHandler.m_param = &m_drillerInfo->params.back();
return &m_paramHandler;
}
return NULL;
}
virtual void OnData(const DrillerSAXParser::Data& dataNode)
{
if (dataNode.m_name == AZ_CRC("Name", 0x5e237e06))
{
dataNode.Read(m_drillerInfo->id);
}
}
DrillerManager::DrillerInfo* m_drillerInfo;
ParamHandler m_paramHandler;
};
/**
* Handler for the <Frame><StartData></StartData></Frame> tag
*/
class DrillerStartdataHandler
: public DrillerHandlerParser
{
public:
virtual DrillerHandlerParser* OnEnterTag(u32 tagName)
{
if (tagName == AZ_CRC("Driller", 0xa6e1fb73))
{
m_drillers.push_back();
m_drillerDataHandler.m_drillerInfo = &m_drillers.back();
return &m_drillerDataHandler;
}
return NULL;
}
virtual void OnData(const DrillerSAXParser::Data& dataNode)
{
if (dataNode.m_name == AZ_CRC("Platform", 0x3952d0cb))
{
dataNode.Read(m_platform);
}
}
unsigned int m_platform;
DrillerManager::DrillerListType m_drillers;
DrillerDrillerdataHandler m_drillerDataHandler;
};
/**
* Handler for the <Frame></Frame> tag
*/
template<class DrillerContainer>
class FrameHandler
: public DrillerHandlerParser
{
public:
FrameHandler()
: DrillerHandlerParser(DrillerContainer::s_isWarnOnMissingDrillers)
, m_currentFrame(-1) {}
virtual DrillerHandlerParser* OnEnterTag(u32 tagName) { return m_drillersContainer.FindDrillerHandler(tagName); }
virtual void OnData(const DrillerSAXParser::Data& dataNode)
{
if (dataNode.m_name == AZ_CRC("FrameNum", 0x85a1a919))
{
dataNode.Read(m_currentFrame);
}
}
DrillerContainer m_drillersContainer;
int m_currentFrame;
};
/**
* Use this class a input parameter to DrillerSAXParserHandler::DrillerSAXParserHandler(). It will handle all root level
* tags for a standard driller input stream stream.
*
* DrillerContainer should comply to the following requirements:
* - default constructible
* - has a static const bool s_isWarnOnMissingDrillers member to indicate if you want to
* trigger a warning when a driller is not found in the class.
* - implement a function DrillerHandlerParser* DrillerContainer::FindDrillerHandler(u32 drillerName)
*
*/
template<class DrillerContainer>
class DrillerRootHandler
: public DrillerHandlerParser
{
public:
DrillerContainer* GetDrillerContainer() { return m_frameHandler.m_drillersContainer; }
virtual DrillerHandlerParser* OnEnterTag(u32 tagName)
{
if (tagName == AZ_CRC("StartData", 0xecf3f53f))
{
return &m_drillerSessionInfo;
}
if (tagName == AZ_CRC("Frame", 0xb5f83ccd))
{
return &m_frameHandler;
}
return NULL;
}
DrillerStartdataHandler m_drillerSessionInfo;
FrameHandler<DrillerContainer> m_frameHandler;
};
} // namespace Debug
} // namespace AZ
#endif // AZCORE_DRILLER_ROOT_HANDLER_H
#pragma once
@@ -1,896 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Driller/Stream.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Obb.h>
#include <AzCore/Math/Plane.h>
#include <AzCore/std/time.h>
#if !defined(AZCORE_EXCLUDE_ZLIB)
# define AZ_FILE_STREAM_COMPRESSION
#endif // AZCORE_EXCLUDE_ZLIB
#if defined(AZ_FILE_STREAM_COMPRESSION)
# include <AzCore/Compression/Compression.h>
#endif // AZ_FILE_STREAM_COMPRESSION
namespace AZ
{
namespace Debug
{
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Driller output stream
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void DrillerOutputStream::Write(u32 name, const AZ::Vector3& v)
{
float data[4];
unsigned int dataSize = 3 * sizeof(float);
v.StoreToFloat4(data);
StreamEntry de;
de.name = name;
de.sizeAndFlags = dataSize;
WriteBinary(de);
WriteBinary(data, dataSize);
}
void DrillerOutputStream::Write(u32 name, const AZ::Vector4& v)
{
float data[4];
unsigned int dataSize = 4 * sizeof(float);
v.StoreToFloat4(data);
StreamEntry de;
de.name = name;
de.sizeAndFlags = dataSize;
WriteBinary(de);
WriteBinary(data, dataSize);
}
void DrillerOutputStream::Write(u32 name, const AZ::Aabb& aabb)
{
float data[7];
unsigned int dataSize = 6 * sizeof(float);
aabb.GetMin().StoreToFloat4(data);
aabb.GetMax().StoreToFloat4(&data[3]);
StreamEntry de;
de.name = name;
de.sizeAndFlags = dataSize;
WriteBinary(de);
WriteBinary(data, dataSize);
}
void DrillerOutputStream::Write(u32 name, const AZ::Obb& obb)
{
float data[10];
unsigned int dataSize = 10 * sizeof(float); // position (Vector3), rotation (Quaternion) and halfLengths (Vector3)
obb.GetPosition().StoreToFloat3(data);
obb.GetRotation().StoreToFloat4(&data[3]);
obb.GetHalfLengths().StoreToFloat3(&data[7]);
StreamEntry de;
de.name = name;
de.sizeAndFlags = dataSize;
WriteBinary(de);
WriteBinary(data, dataSize);
}
void DrillerOutputStream::Write(u32 name, const AZ::Transform& tm)
{
float data[12];
unsigned int dataSize = 12 * sizeof(float);
const Matrix3x4 matrix3x4 = Matrix3x4::CreateFromTransform(tm);
matrix3x4.StoreToRowMajorFloat12(data);
StreamEntry de;
de.name = name;
de.sizeAndFlags = dataSize;
WriteBinary(de);
WriteBinary(data, dataSize);
}
void DrillerOutputStream::Write(u32 name, const AZ::Matrix3x3& tm)
{
float data[9];
unsigned int dataSize = 9 * sizeof(float);
tm.StoreToRowMajorFloat9(data);
StreamEntry de;
de.name = name;
de.sizeAndFlags = dataSize;
WriteBinary(de);
WriteBinary(data, dataSize);
}
void DrillerOutputStream::Write(u32 name, const AZ::Matrix4x4& tm)
{
float data[16];
unsigned int dataSize = 16 * sizeof(float);
tm.StoreToRowMajorFloat16(data);
StreamEntry de;
de.name = name;
de.sizeAndFlags = dataSize;
WriteBinary(de);
WriteBinary(data, dataSize);
}
void DrillerOutputStream::Write(u32 name, const AZ::Quaternion& tm)
{
float data[4];
unsigned int dataSize = 4 * sizeof(float);
tm.StoreToFloat4(data);
StreamEntry de;
de.name = name;
de.sizeAndFlags = dataSize;
WriteBinary(de);
WriteBinary(data, dataSize);
}
void DrillerOutputStream::Write(u32 name, const AZ::Plane& plane)
{
Write(name, plane.GetPlaneEquationCoefficients());
}
void DrillerOutputStream::WriteHeader()
{
StreamHeader sh; // StreamHeader should be endianess independent.
WriteBinary(&sh, sizeof(sh));
}
void DrillerOutputStream::WriteTimeUTC(u32 name)
{
AZStd::sys_time_t now = AZStd::GetTimeUTCMilliSecond();
Write(name, now);
}
void DrillerOutputStream::WriteTimeMicrosecond(u32 name)
{
AZStd::sys_time_t now = AZStd::GetTimeNowMicroSecond();
Write(name, now);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Driller Input Stream
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
bool DrillerInputStream::ReadHeader()
{
DrillerOutputStream::StreamHeader sh; // StreamHeader should be endianess independent.
unsigned int numRead = ReadBinary(&sh, sizeof(sh));
(void)numRead;
AZ_Error("IO", numRead == sizeof(sh), "We should have atleast %d bytes in the stream to read the header!", sizeof(sh));
if (numRead != sizeof(sh))
{
return false;
}
m_isEndianSwap = AZ::IsBigEndian(static_cast<AZ::PlatformID>(sh.platform)) != AZ::IsBigEndian(AZ::g_currentPlatform);
return true;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Driller file stream
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//=========================================================================
// DrillerOutputFileStream::DrillerOutputFileStream
// [3/23/2011]
//=========================================================================
DrillerOutputFileStream::DrillerOutputFileStream()
{
#if defined(AZ_FILE_STREAM_COMPRESSION)
m_zlib = azcreate(ZLib, (&AllocatorInstance<OSAllocator>::GetAllocator()), OSAllocator);
m_zlib->StartCompressor(2);
#endif
}
//=========================================================================
// DrillerOutputFileStream::~DrillerOutputFileStream
// [3/23/2011]
//=========================================================================
DrillerOutputFileStream::~DrillerOutputFileStream()
{
#if defined(AZ_FILE_STREAM_COMPRESSION)
azdestroy(m_zlib, OSAllocator);
#endif
}
//=========================================================================
// DrillerOutputFileStream::Open
// [3/23/2011]
//=========================================================================
bool DrillerOutputFileStream::Open(const char* fileName, int mode, int platformFlags)
{
if (IO::SystemFile::Open(fileName, mode, platformFlags))
{
m_dataBuffer.reserve(100 * 1024);
#if defined(AZ_FILE_STREAM_COMPRESSION)
// // Enable optional: encode the file in the same format as the streamer so they are interchangeable
// IO::CompressorHeader ch;
// ch.SetAZCS();
// ch.m_compressorId = IO::CompressorZLib::TypeId();
// ch.m_uncompressedSize = 0; // will be updated later
// AZStd::endian_swap(ch.m_compressorId);
// AZStd::endian_swap(ch.m_uncompressedSize);
// IO::SystemFile::Write(&ch,sizeof(ch));
// IO::CompressorZLibHeader zlibHdr;
// zlibHdr.m_numSeekPoints = 0;
// IO::SystemFile::Write(&zlibHdr,sizeof(zlibHdr));
#endif
return true;
}
return false;
}
//=========================================================================
// DrillerOutputFileStream::Close
// [3/23/2011]
//=========================================================================
void DrillerOutputFileStream::Close()
{
unsigned int dataSizeInBuffer = static_cast<unsigned int>(m_dataBuffer.size());
{
#if defined(AZ_FILE_STREAM_COMPRESSION)
unsigned int minCompressBufferSize = m_zlib->GetMinCompressedBufferSize(dataSizeInBuffer);
if (m_compressionBuffer.size() < minCompressBufferSize) // grow compression buffer if needed
{
m_compressionBuffer.clear();
m_compressionBuffer.resize(minCompressBufferSize);
}
unsigned int compressedSize;
do
{
compressedSize = m_zlib->Compress(m_dataBuffer.data(), dataSizeInBuffer, m_compressionBuffer.data(), (unsigned)m_compressionBuffer.size(), ZLib::FT_FINISH);
if (compressedSize)
{
IO::SystemFile::Write(m_compressionBuffer.data(), compressedSize);
}
} while (compressedSize > 0);
m_zlib->ResetCompressor();
#else
if (dataSizeInBuffer)
{
IO::SystemFile::Write(m_dataBuffer.data(), m_dataBuffer.size());
}
#endif
m_dataBuffer.clear();
}
IO::SystemFile::Close();
}
//=========================================================================
// DrillerOutputFileStream::WriteBinary
// [3/23/2011]
//=========================================================================
void DrillerOutputFileStream::WriteBinary(const void* data, unsigned int dataSize)
{
size_t dataSizeInBuffer = m_dataBuffer.size();
if (dataSizeInBuffer + dataSize > m_dataBuffer.capacity())
{
if (dataSizeInBuffer > 0)
{
#if defined(AZ_FILE_STREAM_COMPRESSION)
// we need to flush the data
unsigned int dataToCompress = static_cast<unsigned int>(dataSizeInBuffer);
unsigned int minCompressBufferSize = m_zlib->GetMinCompressedBufferSize(dataToCompress);
if (m_compressionBuffer.size() < minCompressBufferSize) // grow compression buffer if needed
{
m_compressionBuffer.clear();
m_compressionBuffer.resize(minCompressBufferSize);
}
while (dataToCompress > 0)
{
unsigned int compressedSize = m_zlib->Compress(m_dataBuffer.data(), dataToCompress, m_compressionBuffer.data(), (unsigned)m_compressionBuffer.size());
if (compressedSize)
{
IO::SystemFile::Write(m_compressionBuffer.data(), compressedSize);
}
}
#else
IO::SystemFile::Write(m_dataBuffer.data(), m_dataBuffer.size());
#endif
m_dataBuffer.clear();
}
}
m_dataBuffer.insert(m_dataBuffer.end(), reinterpret_cast<const unsigned char*>(data), reinterpret_cast<const unsigned char*>(data) + dataSize);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Driller file input stream
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//=========================================================================
// DrillerInputFileStream::DrillerInputFileStream
// [3/23/2011]
//=========================================================================
DrillerInputFileStream::DrillerInputFileStream()
{
#if defined(AZ_FILE_STREAM_COMPRESSION)
m_zlib = azcreate(ZLib, (&AllocatorInstance<OSAllocator>::GetAllocator()), OSAllocator);
m_zlib->StartDecompressor();
#endif
}
//=========================================================================
// DrillerInputFileStream::DrillerInputFileStream
// [3/23/2011]
//=========================================================================
DrillerInputFileStream::~DrillerInputFileStream()
{
#if defined(AZ_FILE_STREAM_COMPRESSION)
azdestroy(m_zlib, OSAllocator);
#endif
}
//=========================================================================
// DrillerInputFileStream::Open
// [3/23/2011]
//=========================================================================
bool DrillerInputFileStream::Open(const char* fileName, int mode, int platformFlags)
{
if (IO::SystemFile::Open(fileName, mode, platformFlags))
{
DrillerOutputStream::StreamHeader sh;
#if defined(AZ_FILE_STREAM_COMPRESSION)
// TODO: optional encode the file in the same format as the streamer so they are interchangeable
#endif
// first read the header of the stream file.
return ReadHeader();
}
return false;
}
//=========================================================================
// DrillerInputFileStream::ReadBinary
// [3/23/2011]
//=========================================================================
unsigned int DrillerInputFileStream::ReadBinary(void* data, unsigned int maxDataSize)
{
// make sure the compressed buffer if full enough...
size_t dataToLoad = maxDataSize * 2;
m_compressedData.reserve(dataToLoad);
while (m_compressedData.size() < dataToLoad)
{
unsigned char buffer[10 * 1024];
IO::SystemFile::SizeType bytesRead = Read(AZ_ARRAY_SIZE(buffer), buffer);
if (bytesRead > 0)
{
m_compressedData.insert(m_compressedData.end(), (unsigned char*)buffer, buffer + bytesRead);
}
if (bytesRead < AZ_ARRAY_SIZE(buffer))
{
break;
}
}
#if defined(AZ_FILE_STREAM_COMPRESSION)
unsigned int dataSize = maxDataSize;
unsigned int bytesProcessed = m_zlib->Decompress(m_compressedData.data(), (unsigned)m_compressedData.size(), data, dataSize);
unsigned int readSize = maxDataSize - dataSize; // Zlib::Decompress decrements the dataSize parameter by the amount uncompressed
#else
unsigned int bytesProcessed = AZStd::GetMin((unsigned int)m_compressedData.size(), maxDataSize);
unsigned int readSize = bytesProcessed;
memcpy(data, m_compressedData.data(), readSize);
#endif
m_compressedData.erase(m_compressedData.begin(), m_compressedData.begin() + bytesProcessed);
return readSize;
}
//=========================================================================
// DrillerInputFileStream::Close
// [3/23/2011]
//=========================================================================
void DrillerInputFileStream::Close()
{
#if defined(AZ_FILE_STREAM_COMPRESSION)
if (m_zlib)
{
m_zlib->ResetDecompressor();
}
#endif // AZ_FILE_STREAM_COMPRESSION
AZ::IO::SystemFile::Close();
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// DrillerSAXParser
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//=========================================================================
// DrillerSAXParser
// [3/23/2011]
//=========================================================================
DrillerSAXParser::DrillerSAXParser(const TagCallbackType& tcb, const DataCallbackType& dcb)
: m_tagCallback(tcb)
, m_dataCallback(dcb)
{
}
//=========================================================================
// ProcessStream
// [3/23/2011]
//=========================================================================
void
DrillerSAXParser::ProcessStream(DrillerInputStream& stream)
{
static const int processChunkSize = 15 * 1024;
char buffer[processChunkSize];
unsigned int dataSize;
bool isEndianSwap = stream.IsEndianSwap();
while ((dataSize = stream.ReadBinary(buffer, processChunkSize)) > 0)
{
char* dataStart = buffer;
char* dataEnd = dataStart + dataSize;
bool dataInBuffer = false;
if (!m_buffer.empty())
{
m_buffer.insert(m_buffer.end(), dataStart, dataEnd);
dataStart = m_buffer.data();
dataEnd = dataStart + m_buffer.size();
dataInBuffer = true;
}
const int entrySize = sizeof(DrillerOutputStream::StreamEntry);
while (dataStart != dataEnd)
{
if ((dataEnd - dataStart) < entrySize) // we need at least one entry to proceed
{
// not enough data to process, buffer it.
if (!dataInBuffer)
{
m_buffer.insert(m_buffer.end(), dataStart, dataEnd);
}
break;
}
DrillerOutputStream::StreamEntry* se = reinterpret_cast<DrillerOutputStream::StreamEntry*>(dataStart);
if (isEndianSwap)
{
// endian swap
AZStd::endian_swap(se->name);
AZStd::endian_swap(se->sizeAndFlags);
}
u32 dataType = (se->sizeAndFlags & DrillerOutputStream::StreamEntry::dataInternalMask) >> DrillerOutputStream::StreamEntry::dataInternalShift;
u32 value = se->sizeAndFlags & DrillerOutputStream::StreamEntry::dataSizeMask;
Data de;
de.m_name = se->name;
de.m_stringPool = stream.GetStringPool();
de.m_isPooledString = false;
de.m_isPooledStringCrc32 = false;
switch (dataType)
{
case DrillerOutputStream::StreamEntry::INT_TAG:
{
bool isStart = (value != 0);
m_tagCallback(se->name, isStart);
dataStart += entrySize;
} break;
case DrillerOutputStream::StreamEntry::INT_DATA_U8:
{
u8 value8 = static_cast<u8>(value);
de.m_data = &value8;
de.m_dataSize = 1;
de.m_isEndianSwap = false;
m_dataCallback(de);
dataStart += entrySize;
} break;
case DrillerOutputStream::StreamEntry::INT_DATA_U16:
{
u16 value16 = static_cast<u16>(value);
de.m_data = &value16;
de.m_dataSize = 2;
de.m_isEndianSwap = false;
m_dataCallback(de);
dataStart += entrySize;
} break;
case DrillerOutputStream::StreamEntry::INT_DATA_U29:
{
de.m_data = &value;
de.m_dataSize = 4;
de.m_isEndianSwap = false;
m_dataCallback(de);
dataStart += entrySize;
} break;
case DrillerOutputStream::StreamEntry::INT_POOLED_STRING:
{
unsigned int userDataSize = value;
if ((userDataSize + entrySize) <= (unsigned)(dataEnd - dataStart))
{
// Add string to the pool
AZ_Assert(de.m_stringPool != nullptr, "We require a string pool to parse this stream");
AZ::u32 crc32;
const char* stringPtr;
dataStart += entrySize;
de.m_stringPool->InsertCopy(reinterpret_cast<const char*>(dataStart), userDataSize, crc32, &stringPtr);
de.m_dataSize = userDataSize;
de.m_isEndianSwap = isEndianSwap;
de.m_isPooledString = true;
de.m_data = const_cast<void*>(static_cast<const void*>(stringPtr));
m_dataCallback(de);
dataStart += userDataSize;
}
else
{
// we can't process data right now add it to the buffer (if we have not done that already)
if (!dataInBuffer)
{
m_buffer.insert(m_buffer.end(), dataStart, dataEnd);
}
dataEnd = dataStart; // exit the loop
}
} break;
case DrillerOutputStream::StreamEntry::INT_POOLED_STRING_CRC32:
{
de.m_isPooledStringCrc32 = true;
AZ_Assert(value == 4, "The data size for a pooled string crc32 should be 4 bytes!");
} // continue to INT_SIZE
case DrillerOutputStream::StreamEntry::INT_SIZE:
{
unsigned int userDataSize = value;
if ((userDataSize + entrySize) <= (unsigned)(dataEnd - dataStart)) // do we have all the date we need to process...
{
dataStart += entrySize;
de.m_data = dataStart;
de.m_dataSize = userDataSize;
de.m_isEndianSwap = isEndianSwap;
m_dataCallback(de);
dataStart += userDataSize;
}
else
{
// we can't process data right now add it to the buffer (if we have not done that already)
if (!dataInBuffer)
{
m_buffer.insert(m_buffer.end(), dataStart, dataEnd);
}
dataEnd = dataStart; // exit the loop
}
} break;
default:
{
AZ_Error("DrillerSAXParser",false,"Encounted unknown symbol (%i) while processing stream (%s). Aborting stream.\n",dataType, stream.GetIdentifier());
// If we can't process anything, we want to just escape the loop, to avoid spinning infinitely
dataEnd = dataStart;
} break;
}
}
if (dataInBuffer) // if the data was in the buffer remove the processed data!
{
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + (dataStart - m_buffer.data()));
}
}
}
void DrillerSAXParser::Data::Read(AZ::Vector3& v) const
{
AZ_Assert(m_dataSize == sizeof(float) * 3, "We are expecting 3 floats for Vector3 element 0x%08x with size %d bytes", m_name, m_dataSize);
float* data = reinterpret_cast<float*>(m_data);
if (m_isEndianSwap)
{
AZStd::endian_swap(data, data + 3);
m_isEndianSwap = false;
}
v = Vector3::CreateFromFloat3(data);
}
void DrillerSAXParser::Data::Read(AZ::Vector4& v) const
{
AZ_Assert(m_dataSize == sizeof(float) * 4, "We are expecting 4 floats for Vector4 element 0x%08x with size %d bytes", m_name, m_dataSize);
float* data = reinterpret_cast<float*>(m_data);
if (m_isEndianSwap)
{
AZStd::endian_swap(data, data + 4);
m_isEndianSwap = false;
}
v = Vector4::CreateFromFloat4(data);
}
void DrillerSAXParser::Data::Read(AZ::Aabb& aabb) const
{
AZ_Assert(m_dataSize == sizeof(float) * 6, "We are expecting 6 floats for Aabb element 0x%08x with size %d bytes", m_name, m_dataSize);
float* data = reinterpret_cast<float*>(m_data);
if (m_isEndianSwap)
{
AZStd::endian_swap(data, data + 6);
m_isEndianSwap = false;
}
Vector3 min = Vector3::CreateFromFloat3(data);
Vector3 max = Vector3::CreateFromFloat3(&data[3]);
aabb = Aabb::CreateFromMinMax(min, max);
}
void DrillerSAXParser::Data::Read(AZ::Obb& obb) const
{
AZ_Assert(m_dataSize == sizeof(float) * 10, "We are expecting 10 floats for Obb element 0x%08x with size %d bytes", m_name, m_dataSize);
float* data = reinterpret_cast<float*>(m_data);
if (m_isEndianSwap)
{
AZStd::endian_swap(data, data + 10);
m_isEndianSwap = false;
}
Vector3 position = Vector3::CreateFromFloat3(data);
Quaternion rotation = Quaternion::CreateFromFloat4(&data[3]);
Vector3 halfLengths = Vector3::CreateFromFloat3(&data[7]);
obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths);
}
void DrillerSAXParser::Data::Read(AZ::Transform& tm) const
{
AZ_Assert(m_dataSize == sizeof(float) * 12, "We are expecting 12 floats for Transform element 0x%08x with size %d bytes", m_name, m_dataSize);
float* data = reinterpret_cast<float*>(m_data);
if (m_isEndianSwap)
{
AZStd::endian_swap(data, data + 12);
m_isEndianSwap = false;
}
const Matrix3x4 matrix3x4 = Matrix3x4::CreateFromRowMajorFloat12(data);
tm = Transform::CreateFromMatrix3x4(matrix3x4);
}
void DrillerSAXParser::Data::Read(AZ::Matrix3x3& tm) const
{
AZ_Assert(m_dataSize == sizeof(float) * 9, "We are expecting 9 floats for Matrix3x3 element 0x%08x with size %d bytes", m_name, m_dataSize);
float* data = reinterpret_cast<float*>(m_data);
if (m_isEndianSwap)
{
AZStd::endian_swap(data, data + 9);
m_isEndianSwap = false;
}
tm = Matrix3x3::CreateFromRowMajorFloat9(data);
}
void DrillerSAXParser::Data::Read(AZ::Matrix4x4& tm) const
{
AZ_Assert(m_dataSize == sizeof(float) * 16, "We are expecting 16 floats for Matrix4x4 element 0x%08x with size %d bytes", m_name, m_dataSize);
float* data = reinterpret_cast<float*>(m_data);
if (m_isEndianSwap)
{
AZStd::endian_swap(data, data + 16);
m_isEndianSwap = false;
}
tm = Matrix4x4::CreateFromRowMajorFloat16(data);
}
void DrillerSAXParser::Data::Read(AZ::Quaternion& tm) const
{
AZ_Assert(m_dataSize == sizeof(float) * 4, "We are expecting 4 floats for Quaternion element 0x%08x with size %d bytes", m_name, m_dataSize);
float* data = reinterpret_cast<float*>(m_data);
if (m_isEndianSwap)
{
AZStd::endian_swap(data, data + 4);
m_isEndianSwap = false;
}
tm = Quaternion::CreateFromFloat4(data);
}
void DrillerSAXParser::Data::Read(AZ::Plane& plane) const
{
AZ::Vector4 coeff;
Read(coeff);
plane = Plane::CreateFromCoefficients(coeff.GetX(), coeff.GetY(), coeff.GetZ(), coeff.GetW());
}
const char* DrillerSAXParser::Data::PrepareString(unsigned int& stringLength) const
{
const char* srcData = reinterpret_cast<const char*>(m_data);
stringLength = m_dataSize;
if (m_stringPool)
{
AZ::u32 crc32;
const char* stringPtr;
if (m_isPooledStringCrc32)
{
crc32 = *reinterpret_cast<AZ::u32*>(m_data);
if (m_isEndianSwap)
{
AZStd::endian_swap(crc32);
}
stringPtr = m_stringPool->Find(crc32);
AZ_Assert(stringPtr != nullptr, "Failed to find string with id 0x%08x in the string pool, proper stream read is impossible!", crc32);
stringLength = static_cast<unsigned int>(strlen(stringPtr));
}
else if (m_isPooledString)
{
stringPtr = srcData; // already stored in the pool just transfer the pointer
}
else
{
// Store copy of the string in the pool to save memory (keep only one reference of the string).
m_stringPool->InsertCopy(reinterpret_cast<const char*>(srcData), stringLength, crc32, &stringPtr);
}
srcData = stringPtr;
}
else
{
AZ_Assert(m_isPooledString == false && m_isPooledStringCrc32 == false, "This stream requires using of a string pool as the string is send only once and afterwards only the Crc32 is used!");
}
return srcData;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// DrillerDOMParser
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//=========================================================================
// Node::GetTag
// [1/23/2013]
//=========================================================================
const DrillerDOMParser::Node* DrillerDOMParser::Node::GetTag(u32 tagName) const
{
const Node* tagNode = nullptr;
for (Node::NodeListType::const_iterator i = m_tags.begin(); i != m_tags.end(); ++i)
{
if ((*i).m_name == tagName)
{
tagNode = &*i;
break;
}
}
return tagNode;
}
//=========================================================================
// Node::GetData
// [3/23/2011]
//=========================================================================
const DrillerDOMParser::Data* DrillerDOMParser::Node::GetData(u32 dataName) const
{
const Data* dataNode = nullptr;
for (Node::DataListType::const_iterator i = m_data.begin(); i != m_data.end(); ++i)
{
if (i->m_name == dataName)
{
dataNode = &*i;
break;
}
}
return dataNode;
}
//=========================================================================
// DrillerDOMParser
// [3/23/2011]
//=========================================================================
DrillerDOMParser::DrillerDOMParser(bool isPersistentInputData)
: DrillerSAXParser(TagCallbackType(this, &DrillerDOMParser::OnTag), DataCallbackType(this, &DrillerDOMParser::OnData))
, m_isPersistentInputData(isPersistentInputData)
{
m_root.m_name = 0;
m_root.m_parent = nullptr;
m_topNode = &m_root;
}
static int g_numFree = 0;
//=========================================================================
// ~DrillerDOMParser
// [3/23/2011]
//=========================================================================
DrillerDOMParser::~DrillerDOMParser()
{
DeleteNode(m_root);
}
//=========================================================================
// OnTag
// [3/23/2011]
//=========================================================================
void
DrillerDOMParser::OnTag(AZ::u32 name, bool isOpen)
{
if (isOpen)
{
m_topNode->m_tags.push_back();
Node& node = m_topNode->m_tags.back();
node.m_name = name;
node.m_parent = m_topNode;
m_topNode = &node;
}
else
{
AZ_Assert(m_topNode->m_name == name, "We have opened tag with name 0x%08x and closing with name 0x%08x", m_topNode->m_name, name);
m_topNode = m_topNode->m_parent;
}
}
//=========================================================================
// OnData
// [3/23/2011]
//=========================================================================
void
DrillerDOMParser::OnData(const Data& data)
{
Data de = data;
if (!m_isPersistentInputData)
{
de.m_data = azmalloc(data.m_dataSize, 1, OSAllocator);
memcpy(const_cast<void*>(de.m_data), data.m_data, data.m_dataSize);
}
m_topNode->m_data.push_back(de);
}
//=========================================================================
// DeleteNode
// [3/23/2011]
//=========================================================================
void
DrillerDOMParser::DeleteNode(Node& node)
{
if (!m_isPersistentInputData)
{
for (Node::DataListType::iterator iter = node.m_data.begin(); iter != node.m_data.end(); ++iter)
{
azfree(iter->m_data, OSAllocator, iter->m_dataSize);
++g_numFree;
}
node.m_data.clear();
}
for (Node::NodeListType::iterator iter = node.m_tags.begin(); iter != node.m_tags.end(); ++iter)
{
DeleteNode(*iter);
}
node.m_tags.clear();
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// DrillerSAXParserHandler
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//=========================================================================
// DrillerSAXParserHandler
// [3/14/2013]
//=========================================================================
DrillerSAXParserHandler::DrillerSAXParserHandler(DrillerHandlerParser* rootHandler)
: DrillerSAXParser(TagCallbackType(this, &DrillerSAXParserHandler::OnTag), DataCallbackType(this, &DrillerSAXParserHandler::OnData))
{
// Push the root element
m_stack.push_back(rootHandler);
}
//=========================================================================
// OnTag
// [3/14/2013]
//=========================================================================
void DrillerSAXParserHandler::OnTag(u32 name, bool isOpen)
{
if (m_stack.size() == 0)
{
return;
}
DrillerHandlerParser* childHandler = nullptr;
DrillerHandlerParser* currentHandler = m_stack.back();
if (isOpen)
{
if (currentHandler != nullptr)
{
childHandler = currentHandler->OnEnterTag(name);
AZ_Warning("Driller", !currentHandler->IsWarnOnUnsupportedTags() || childHandler != nullptr, "Could not find handler for tag 0x%08x", name);
}
m_stack.push_back(childHandler);
}
else
{
m_stack.pop_back();
if (m_stack.size() > 0)
{
DrillerHandlerParser* parentHandler = m_stack.back();
if (parentHandler)
{
parentHandler->OnExitTag(currentHandler, name);
}
}
}
}
//=========================================================================
// OnData
// [3/14/2013]
//=========================================================================
void DrillerSAXParserHandler::OnData(const DrillerSAXParser::Data& data)
{
if (m_stack.size() == 0)
{
return;
}
DrillerHandlerParser* currentHandler = m_stack.back();
if (currentHandler)
{
currentHandler->OnData(data);
}
}
} // namespace Debug
} // namespace AZ
@@ -1,848 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZCORE_DRILLER_STREAM_H
#define AZCORE_DRILLER_STREAM_H
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/std/delegate/delegate.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/forward_list.h>
#include <AzCore/std/typetraits/is_signed.h>
#include <AzCore/std/typetraits/is_pod.h>
#include <AzCore/IO/SystemFile.h> // for the Driller direct file stream
#include <AzCore/PlatformId/PlatformId.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
class Vector3;
class Vector4;
class Aabb;
class Obb;
class Transform;
class Matrix3x3;
class Matrix4x4;
class Quaternion;
class Plane;
class ZLib;
namespace IO
{
class Stream;
}
namespace Debug
{
template<class T>
struct vector
{
typedef AZStd::vector<T, OSStdAllocator> type;
};
template<class T>
struct forward_list
{
typedef AZStd::forward_list<T, OSStdAllocator> type;
};
/**
* Interface for a string pool which can be used by input/output streams to avoid storing multiple copies of the same
* string in the stream. Of course this comes at the bookkeeping cost of the table.
*/
class DrillerStringPool
{
public:
virtual ~DrillerStringPool() {}
/**
* Add a copy of the string to the pool. If we return true the string was added otherwise it was already in the bool.
* In both cases the crc32 of the string and the pointer to the shared copy is returned (optional for poolStringAddress)!
*/
virtual bool InsertCopy(const char* string, unsigned int length, AZ::u32& crc32, const char** poolStringAddress = NULL) = 0;
/**
* Same as the InsertCopy above without actually coping the string into the pool. The pool assumes that
* none of the strings added to the pool will be deleted.
*/
virtual bool Insert(const char* string, unsigned int length, AZ::u32& crc32) = 0;
/// Finds a string in the pool by crc32.
virtual const char* Find(AZ::u32 crc32) = 0;
virtual void Erase(AZ::u32 crc32) = 0;
/// Clears all the strings in the pool, make sure you don't reference any strings before you call that function.
virtual void Reset() = 0;
};
/**
*
*/
class DrillerOutputStream
{
protected:
friend class DrillerManagerImpl;
friend class DrillerSAXParser;
struct StreamEntry
{
enum InternalDataSize // max 8 values as we use 3 bit to store them
{
INT_SIZE = 0, ///< No internal data, we store the data size. IMPORTANT: INT_SIZE should be 0 the code makes assumptions based on that
INT_TAG, ///< True if this entry is tag
INT_DATA_U8, ///< Internal data u8 stored (1 byte)
INT_DATA_U16, ///< Internal data u16 stored (2 bytes)
INT_DATA_U29, ///< Internal data u32 stored (4 bytes) for which we use only the first 29 bits.
INT_POOLED_STRING_CRC32, ///< Data size should be 4 bytes crc32 that a string CRC and it require string pool.
INT_POOLED_STRING, ///< This data contains a string which should be inserted in the string pool.
};
static const u32 dataSizeMask = 0x1fffffff;
static const u32 dataInternalMask = 0xE0000000;
static const u32 dataInternalShift = 29;
u32 name; ///< data or tag name
u32 sizeAndFlags; ///<
};
template<class T, size_t Size, bool isIntegralType>
struct IntergralType;
template<class T>
struct IntergralType<T, 1, true>
{
static void Write(DrillerOutputStream& stream, u32 name, const T& data)
{
StreamEntry de;
de.name = name;
de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U8) << StreamEntry::dataInternalShift;
de.sizeAndFlags |= *reinterpret_cast<const u8*>(&data);
stream.WriteBinary(de);
}
};
template<class T>
struct IntergralType<T, 2, true>
{
static void Write(DrillerOutputStream& stream, u32 name, const T& data)
{
StreamEntry de;
de.name = name;
de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U16) << StreamEntry::dataInternalShift;
de.sizeAndFlags |= *reinterpret_cast<const u16*>(&data);
stream.WriteBinary(de);
}
};
template<class T>
struct IntergralType<T, 4, true>
{
static void Write(DrillerOutputStream& stream, u32 name, const T& data)
{
StreamEntry de;
de.name = name;
const u32* uintData = reinterpret_cast<const u32*>(&data);
if (((*uintData) & StreamEntry::dataSizeMask) == *uintData) // check if we can store it internally
{
de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U29) << StreamEntry::dataInternalShift;
de.sizeAndFlags |= *uintData;
stream.WriteBinary(de);
}
else
{
de.sizeAndFlags = 4;
stream.WriteBinary(de);
stream.WriteBinary(&data, de.sizeAndFlags);
}
}
};
template<class T>
struct IntergralType<T, 8, true>
{
static void Write(DrillerOutputStream& stream, u32 name, const T& data)
{
StreamEntry de;
de.name = name;
const u64* uintData = reinterpret_cast<const u64*>(&data);
if (((*uintData) & static_cast<u64>(StreamEntry::dataSizeMask)) == *uintData) // check if we can store it internally
{
de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U29) << StreamEntry::dataInternalShift;
de.sizeAndFlags |= static_cast<u32>(*uintData);
stream.WriteBinary(de);
}
else
{
de.sizeAndFlags = 8;
stream.WriteBinary(de);
stream.WriteBinary(&data, de.sizeAndFlags);
}
}
};
template<class T>
struct IntergralType<T*, sizeof(void*), false>
{
static void Write(DrillerOutputStream& stream, u32 name, const T* pointer)
{
size_t id = reinterpret_cast<size_t>(pointer);
IntergralType<size_t, sizeof(id), true>::Write(stream, name, id);
}
};
public:
/**
* Each stream with start with this header, before anything else.
*/
struct StreamHeader
{
StreamHeader()
: platform((u8)g_currentPlatform) {}
u8 platform;
};
DrillerOutputStream(DrillerStringPool* stringPool = NULL)
: m_stringPool(stringPool) { }
virtual ~DrillerOutputStream() {}
//////////////////////////////////////////////////////////////////////////
// Write
inline void BeginTag(u32 name)
{
StreamEntry de;
de.name = name;
de.sizeAndFlags = (u32)(StreamEntry::INT_TAG) << StreamEntry::dataInternalShift;
de.sizeAndFlags |= 1; // true - open tag
WriteBinary(de);
}
inline void EndTag(u32 name)
{
StreamEntry de;
de.name = name;
de.sizeAndFlags = (u32)(StreamEntry::INT_TAG) << StreamEntry::dataInternalShift;
WriteBinary(de);
}
//////////////////////////////////////////////////////////////////////////
// Generic
template<class T>
inline void Write(u32 name, const T& data)
{
// User should handle non specialized non integral types.
IntergralType<T, sizeof(T), AZStd::is_integral<T>::value || AZStd::is_enum<T>::value>::Write(*this, name, data);
}
//////////////////////////////////////////////////////////////////////////
// Binary and strings
inline void Write(u32 name, const void* data, unsigned int dataSize)
{
StreamEntry de;
de.name = name;
de.sizeAndFlags = dataSize;
WriteBinary(de);
WriteBinary(data, dataSize);
}
inline void Write(u32 name, const char* string, bool isCopyString = true)
{
StreamEntry de;
de.name = name;
de.sizeAndFlags = static_cast<unsigned int>(strlen(string));
AZ_Assert(de.sizeAndFlags <= StreamEntry::dataSizeMask, "Invalid string length! String is too long, length is limited to %u bytes!", StreamEntry::dataSizeMask);
;
if (m_stringPool)
{
AZ::u32 crc;
bool isInserted = isCopyString ? m_stringPool->InsertCopy(string, de.sizeAndFlags, crc) : m_stringPool->Insert(string, de.sizeAndFlags, crc);
if (!isInserted) // if already inserted, it means it's in the stream, so store the crc only.
{
de.sizeAndFlags = (u32)(StreamEntry::INT_POOLED_STRING_CRC32) << StreamEntry::dataInternalShift;
de.sizeAndFlags |= sizeof(crc);
WriteBinary(de);
WriteBinary(&crc, sizeof(crc));
}
else
{
AZ::u32 stringSize = de.sizeAndFlags;
de.sizeAndFlags |= (u32)(StreamEntry::INT_POOLED_STRING) << StreamEntry::dataInternalShift;
WriteBinary(de);
WriteBinary(string, stringSize);
}
}
else
{
WriteBinary(de);
WriteBinary(string, de.sizeAndFlags);
}
}
template<class Allocator>
inline void Write(u32 name, const AZStd::basic_string<AZStd::string::value_type, AZStd::string::traits_type, Allocator>& str, bool isCopyString = true)
{
StreamEntry de;
de.name = name;
de.sizeAndFlags = static_cast<AZ::u32>(str.size());
AZ_Assert(de.sizeAndFlags <= StreamEntry::dataSizeMask, "Invalid string length! String is too long, length is limited to %u bytes!", StreamEntry::dataSizeMask);
if (m_stringPool)
{
AZ::u32 crc;
bool isInserted = isCopyString ? m_stringPool->InsertCopy(str.c_str(), de.sizeAndFlags, crc) : m_stringPool->Insert(str.c_str(), de.sizeAndFlags, crc);
if (!isInserted) // if already inserted, it means it's in the stream, so store the crc only.
{
de.sizeAndFlags = (u32)(StreamEntry::INT_POOLED_STRING_CRC32) << StreamEntry::dataInternalShift;
de.sizeAndFlags |= sizeof(crc);
WriteBinary(de);
WriteBinary(&crc, sizeof(crc));
}
else
{
AZ::u32 stringSize = de.sizeAndFlags;
de.sizeAndFlags |= (u32)(StreamEntry::INT_POOLED_STRING) << StreamEntry::dataInternalShift;
WriteBinary(de);
WriteBinary(str.data(), stringSize);
}
}
else
{
WriteBinary(de);
WriteBinary(str.data(), de.sizeAndFlags);
}
}
template<class Allocator>
inline void Write(u32 name, const AZStd::basic_string<AZStd::wstring::value_type, AZStd::wstring::traits_type, Allocator>& str)
{
StreamEntry de;
de.name = name;
de.sizeAndFlags = static_cast<AZ::u32>(str.size());
AZ_Assert(de.sizeAndFlags <= StreamEntry::dataSizeMask, "Invalid string length! String is too long, length is limited to %u bytes!", StreamEntry::dataSizeMask);
WriteBinary(de);
WriteBinary(str.data(), de.sizeAndFlags * sizeof(AZStd::wstring::value_type));
}
//////////////////////////////////////////////////////////////////////////
// math types
inline void Write(u32 name, float f)
{
Write(name, &f, static_cast<unsigned int>(sizeof(float)));
}
inline void Write(u32 name, double d)
{
Write(name, &d, static_cast<unsigned int>(sizeof(double)));
}
void Write(u32 name, const AZ::Vector3& v);
void Write(u32 name, const AZ::Vector4& v);
void Write(u32 name, const AZ::Aabb& aabb);
void Write(u32 name, const AZ::Obb& obb);
void Write(u32 name, const AZ::Transform& tm);
void Write(u32 name, const AZ::Matrix3x3& tm);
void Write(u32 name, const AZ::Matrix4x4& tm);
void Write(u32 name, const AZ::Quaternion& tm);
void Write(u32 name, const AZ::Plane& plane);
//////////////////////////////////////////////////////////////////////////
// containers
template<class InputIterator>
inline void Write(u32 name, InputIterator first, InputIterator last)
{
// we can specialize for contiguous_iterator_tag so have only 1 write for all elements
size_t numElements = AZStd::distance(first, last);
size_t elementSize = sizeof(typename AZStd::iterator_traits<InputIterator>::value_type);
unsigned int dataSize = static_cast<unsigned int>(numElements * elementSize);
StreamEntry de;
de.name = name;
de.sizeAndFlags = dataSize;
AZ_Assert(dataSize < StreamEntry::dataSizeMask, "Invalid data size, size is limited to %d bytes!", StreamEntry::dataSizeMask - 1);
WriteBinary(de);
//WriteBinary(data,dataSize); for contiguous_iterator_tag
for (; first != last; ++first)
{
WriteBinary(&*first, static_cast<unsigned int>(elementSize));
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Raw data to the output
template<class T>
inline void WriteBinary(const T& data)
{
WriteBinary(&data, sizeof(T));
}
virtual void WriteBinary(const void* data, unsigned int dataSize) = 0;
//////////////////////////////////////////////////////////////////////////
/**
* Write a time stamp (AZStd::sys_time_t) in millisecond since 1970/01/01 00:00:00 UTC.
* On older windows this function can have ~15 ms resolution, in such cases use \ref GetTimeNowMicroSecond
*/
void WriteTimeUTC(u32 name);
/**
* Write a time stamp (AZStd::sys_time_t) in micriseconds. This function is inaccurate for long periods but it has ms resolution.
* For long periods use \ref WriteTimeUTC.
*/
void WriteTimeMicrosecond(u32 name);
/// Called when the driller is moving on the next frame, so you can flush you current buffer to network/disk.
virtual void OnEndOfFrame() {}
/// Sets the string pool used for this stream. To disable the pool just set it to NULL.
void SetStringPool(DrillerStringPool* stringPool) { m_stringPool = stringPool; }
protected:
/// Write the Stream header structure (should be endianess independent).
void WriteHeader();
DrillerStringPool* m_stringPool; ///< Optional pointer to a string pool.
};
/**
* For efficiency all data read functions are placed with the parsers.
*/
class DrillerInputStream
{
public:
DrillerInputStream(DrillerStringPool* stringPool = NULL)
: m_isEndianSwap(false)
, m_stringPool(stringPool) {}
virtual ~DrillerInputStream() {}
bool IsEndianSwap() const { return m_isEndianSwap; }
/// Reads binary data from a stream to to maxDataSize. Returns 0 if no more data.
virtual unsigned int ReadBinary(void* data, unsigned int maxDataSize) = 0;
/// Sets the string pool used for this stream. To disable the pool just set it to NULL.
void SetStringPool(DrillerStringPool* stringPool) { m_stringPool = stringPool; }
DrillerStringPool* GetStringPool() const { return m_stringPool; }
void SetIdentifier(const char* identifier) { m_streamIdentifier = identifier; }
const char* GetIdentifier() const { return m_streamIdentifier.c_str(); }
protected:
/// Read the Stream header structure
bool ReadHeader();
bool m_isEndianSwap;
DrillerStringPool* m_stringPool; ///< Optional pointer to a string pool.
AZStd::string m_streamIdentifier;
};
/**
* Outputs all stream data into a memory buffer. It will grow automatically.
*/
class DrillerOutputMemoryStream
: public DrillerOutputStream
{
protected:
vector<unsigned char>::type m_data;
public:
AZ_CLASS_ALLOCATOR(DrillerOutputMemoryStream, OSAllocator, 0)
DrillerOutputMemoryStream(size_t memorySize = 2048) { m_data.reserve(memorySize); }
const unsigned char* GetData() const { return m_data.data(); }
unsigned int GetDataSize() const { return static_cast<unsigned int>(m_data.size()); }
inline void Reset() { m_data.clear(); }
void WriteBinary(const void* data, unsigned int dataSize) override
{
m_data.insert(m_data.end(), reinterpret_cast<const unsigned char*>(data), reinterpret_cast<const unsigned char*>(data) + dataSize);
}
};
/**
* Reads data from a memory stream. Data is NOT copied and must be persistent while we are using it.
*/
class DrillerInputMemoryStream
: public DrillerInputStream
{
const unsigned char* m_data;
const unsigned char* m_dataEnd;
public:
AZ_CLASS_ALLOCATOR(DrillerInputMemoryStream, OSAllocator, 0)
DrillerInputMemoryStream(const char* streamIdentifier = "", const void* data = nullptr, unsigned int dataSize = 0)
: DrillerInputStream()
, m_data(nullptr)
, m_dataEnd(nullptr)
{
if (data != nullptr)
{
SetData(streamIdentifier, data, dataSize);
}
}
void SetData(const char* streamIdentifier, const void* data, unsigned int dataSize)
{
SetIdentifier(streamIdentifier);
AZ_Assert(data != nullptr && dataSize > 0, "We must have a valid pointer %p and data size %d !", data, dataSize);
if (m_data == nullptr) // this is the first data chuck, read the platform
{
m_data = reinterpret_cast<const unsigned char*>(data);
m_dataEnd = m_data + dataSize;
ReadHeader();
}
else
{
m_data = reinterpret_cast<const unsigned char*>(data);
m_dataEnd = m_data + dataSize;
}
}
unsigned int GetDataLeft() const { return static_cast<unsigned int>(m_dataEnd - m_data); }
unsigned int ReadBinary(void* data, unsigned int maxDataSize) override
{
AZ_Assert(m_data != nullptr, "You must call SetData function, before you can read data!");
AZ_Assert(data != nullptr && maxDataSize > 0, "We must have a valid pointer and max data size!");
unsigned int dataToCopy = AZStd::GetMin(static_cast<unsigned int>(m_dataEnd - m_data), maxDataSize);
if (dataToCopy)
{
memcpy(data, m_data, dataToCopy);
}
m_data += dataToCopy;
return dataToCopy;
}
};
/**
* Outputs driller data to a file (buffered)
* IMPORTANT: We provide direct IO classes (instead trough Streamer), because the driller
* framework should NOT use engine systems (for example imagine we are drilling the Streamer, using it to
* write the drilled data will invalidate all the results as the streamer is unaware which data is driller data and which not)
*/
class DrillerOutputFileStream
: public IO::SystemFile
, public DrillerOutputStream
{
ZLib* m_zlib;
vector<unsigned char>::type m_compressionBuffer;
vector<unsigned char>::type m_dataBuffer;
public:
AZ_CLASS_ALLOCATOR(DrillerOutputFileStream, OSAllocator, 0)
DrillerOutputFileStream();
~DrillerOutputFileStream();
bool Open(const char* fileName, int mode, int platformFlags = 0);
void Close();
void WriteBinary(const void* data, unsigned int dataSize) override;
};
/**
* Reads driller data from a file.
*/
class DrillerInputFileStream
: public AZ::IO::SystemFile
, public DrillerInputStream
{
ZLib* m_zlib;
vector<unsigned char>::type m_compressedData;
public:
AZ_CLASS_ALLOCATOR(DrillerInputFileStream, OSAllocator, 0)
DrillerInputFileStream();
~DrillerInputFileStream();
bool Open(const char* fileName, int mode, int platformFlags = 0);
unsigned int ReadBinary(void* data, unsigned int maxDataSize) override;
void Close();
};
/**
* SAX like stream parser for driller data. We can stream the data
* and we will trigger events as tags and data (attributes) arrive. We use less memory this way.
* \note SAX is used as reference name, we are NOT trying to compatible with
* any specs. (not that SAX has specs)
* IMPORTANT: All data callbacks (tag and data) are called in the order they were at store. You can
* use this order as event index.
*/
class DrillerSAXParser
{
public:
struct Data
{
u32 m_name; ///< Crc name of the data entry.
void* m_data; ///< Pointer to copy if the loaded data.
unsigned int m_dataSize; ///< Data size in bytes.
mutable bool m_isEndianSwap; ///< True if the user will need to swap the endian when he access the data. We swap the data is the storage so we can read it multiple times without swap.
DrillerStringPool* m_stringPool; ///< Pointer to optional data string pool.
bool m_isPooledString; ///< True if we have a pooled string (stored in the stringPool already).
bool m_isPooledStringCrc32; ///< True is we have stored a crc32 (4 bytes) which refers to a string from the String Pool.
//////////////////////////////////////////////////////////////////////////
// Generic
template<class T>
inline void Read(T& t) const
{
static_assert(AZStd::is_pod<T>::value, "T must be plain-old-data");
AZ_Assert(sizeof(t) >= m_dataSize, "You are about to lose some data, this is wrong.");
if (m_dataSize == sizeof(t))
{
// do a memcpy as alignment might be required for some data types! This is not performance critical as we usually load drill files on x86/x64
// which doesn't care about alignment.
memcpy(&t, m_data, m_dataSize);
}
else
{
AZ_Assert(AZStd::is_pointer<T>::value || AZStd::is_integral<T>::value, "We support extending only for integral types, float and pointers up to 8 bytes!");
if (AZStd::is_signed<T>::value)
{
switch (m_dataSize)
{
case 1:
t = static_cast<T>(*reinterpret_cast<s8*>(m_data));
break;
case 2:
t = static_cast<T>(*reinterpret_cast<s16*>(m_data));
break;
case 4:
t = static_cast<T>(*reinterpret_cast<s32*>(m_data));
break;
default:
AZ_Assert(false, "Source data size unsupported... we can extend only 1,2,4 bytes into 2,4,8 bytes integrals");
}
}
else
{
switch (m_dataSize)
{
case 1:
t = static_cast<T>(*reinterpret_cast<u8*>(m_data));
break;
case 2:
t = static_cast<T>(*reinterpret_cast<u16*>(m_data));
break;
case 4:
t = static_cast<T>(*reinterpret_cast<u32*>(m_data));
break;
default:
AZ_Assert(false, "Source data size unsupported... we can extend only 1,2,4 bytes into 2,4,8 bytes integrals");
}
}
}
if (m_isEndianSwap)
{
AZStd::endian_swap(t);
}
}
inline void Read(bool& b) const
{
u8* data = reinterpret_cast<u8*>(m_data);
b = false;
for (unsigned int i = 0; i < m_dataSize; ++i)
{
if (data[i] != 0)
{
b = true;
return;
}
}
}
//////////////////////////////////////////////////////////////////////////
// Binary and strings
inline unsigned int Read(void* buffer, unsigned int bufferSize) const
{
unsigned int dataToCopy = AZStd::GetMin(m_dataSize, bufferSize);
memcpy(buffer, m_data, dataToCopy);
// no data swap
return dataToCopy;
}
// a call avilable only when we use a string pool, it will return the pointer of string in the pool, so you don't need to copy it or do any fancy procedures.
inline const char* ReadPooledString() const
{
AZ_Assert(m_stringPool != nullptr, "This read type is supported only when we use string pool!");
unsigned int srcDataSize;
return PrepareString(srcDataSize);
}
inline unsigned int Read(char* string, unsigned int maxNumChars) const
{
unsigned int srcDataSize;
const char* srcData = PrepareString(srcDataSize);
unsigned int dataToCopy = AZStd::GetMin(maxNumChars - 1, srcDataSize);
memcpy(string, srcData, dataToCopy);
string[dataToCopy] = '\0';
return dataToCopy;
}
template<class Allocator>
inline unsigned int Read(AZStd::basic_string<AZStd::string::value_type, AZStd::string::traits_type, Allocator>& str) const
{
unsigned int srcDataSize;
const char* srcData = PrepareString(srcDataSize);
str = AZStd::basic_string<AZStd::string::value_type, AZStd::string::traits_type, Allocator>(static_cast<const AZStd::string::value_type*>(srcData), srcDataSize);
return m_dataSize;
}
template<class Allocator>
inline unsigned int Read(AZStd::basic_string<AZStd::wstring::value_type, AZStd::wstring::traits_type, Allocator>& str) const
{
// wstring pooling not supported yet
str = AZStd::basic_string<AZStd::wstring::value_type, AZStd::wstring::traits_type, Allocator>(static_cast<const AZStd::wstring::value_type*>(m_data), m_dataSize / 2);
if (m_isEndianSwap)
{
AZStd::endian_swap(str.begin(), str.end());
}
return m_dataSize;
}
//////////////////////////////////////////////////////////////////////////
// math types
void Read(AZ::Vector3& v) const;
void Read(AZ::Vector4& v) const;
void Read(AZ::Aabb& aabb) const;
void Read(AZ::Obb& obb) const;
void Read(AZ::Transform& tm) const;
void Read(AZ::Matrix3x3& tm) const;
void Read(AZ::Matrix4x4& tm) const;
void Read(AZ::Quaternion& tm) const;
void Read(AZ::Plane& plane) const;
//////////////////////////////////////////////////////////////////////////
// containers
template<class Container>
inline void Read(AZStd::insert_iterator<Container>& iter) const
{
typedef typename AZStd::insert_iterator<Container> InsertIterator;
// we can specialize for contiguous_iterator_tag so have only 1 write for all elements
const size_t elementSize = sizeof(InsertIterator::container_type::value_type);
size_t numElements = m_dataSize / elementSize;
AZ_Assert(m_dataSize % elementSize == 0, "Stored elements size doesn't match the read parameters!");
Data elementEntry = *this;
elementEntry.m_dataSize = elementSize;
char* dataPtr = reinterpret_cast<char*>(m_data);
for (size_t i = 0; i < numElements; ++i, ++iter)
{
typename InsertIterator::container_type::value_type value;
elementEntry.m_data = dataPtr;
Read(elementEntry, value);
iter = value;
dataPtr += elementSize;
}
}
//////////////////////////////////////////////////////////////////////////
private:
const char* PrepareString(unsigned int& stringLength) const;
};
typedef AZStd::delegate<void (u32 /*name*/, bool /*isOpen*/)> TagCallbackType;
typedef AZStd::delegate<void (const Data&)> DataCallbackType;
AZ_CLASS_ALLOCATOR(DrillerSAXParser, OSAllocator, 0)
DrillerSAXParser(const TagCallbackType& tcb, const DataCallbackType& dcb);
/// Processes an input stream until all data is consumed (read returns 0 bytes).
void ProcessStream(DrillerInputStream& stream);
protected:
typedef vector<char>::type BufferType;
BufferType m_buffer;
TagCallbackType m_tagCallback;
DataCallbackType m_dataCallback;
};
/**
* DOM like parser, we will load the entire stream in memory (ProcessStream function).
* Depending on the data size this can be very memory consuming.
* \note DOM is used as reference we are NOT compliant with the DOM specs in any way.
* IMPORTANT: All data is stored (for parsing) in the same order the events occurred
* or the remote machine. Each next tad or data was recorded in the way. You can use
* this as an event index.
*/
class DrillerDOMParser
: public DrillerSAXParser
{
public:
struct Node
{
typedef forward_list<Data>::type DataListType;
typedef forward_list<Node>::type NodeListType;
u32 m_name;
Node* m_parent;
DataListType m_data;
NodeListType m_tags;
/// Return a pointer to the first tag with specific name.
const Node* GetTag(u32 tagName) const;
/// Returns pointer to the first data entry with specific name. NULL if not data has been found.
const Data* GetData(u32 dataName) const;
/// Returns pointer to the first data entry with specific name. If it can't be found it will assert
const Data* GetDataRequired(u32 dataName) const
{
const Data* dataNode = GetData(dataName);
AZ_Assert(dataNode != NULL, "Data node in tag 0x%08x with name 0x%08x is required but missing!", m_name, dataName);
return dataNode;
}
};
AZ_CLASS_ALLOCATOR(DrillerDOMParser, OSAllocator, 0)
DrillerDOMParser(bool isPersistentInputData = false);
~DrillerDOMParser();
/// return true if we are at top level of the tree and we can parse the data safely (there may be still more data, but it's top level only).
bool CanParse() const { return m_topNode == &m_root; }
const Node* GetRootNode() const { return &m_root; }
protected:
Node m_root;
Node* m_topNode;
bool m_isPersistentInputData; ///< true if data that we process is persistent so we don't need to copy it internally, false otherwise.
void OnTag(u32 name, bool isOpen);
void OnData(const Data& data);
void DeleteNode(Node& node);
};
/**
* Base class for handling a Tag with a specific name. Handlers are kept in a hierarchy
* with one required by DrillerSAXParserHandler to be able to handle tags at a root
* level for the driller data stream.
*/
class DrillerHandlerParser
{
public:
DrillerHandlerParser(bool isWarnOnUnsupportedTags = true)
: m_isWarnOnUnsupportedTags(isWarnOnUnsupportedTags) {}
virtual ~DrillerHandlerParser() {}
/// Enumerate all the child tags that we support for the tag we are handling. If the tag is not know you should return NULL
virtual DrillerHandlerParser* OnEnterTag(u32 tagName) { (void)tagName; return NULL; }
/// Exit tag you are not required to implement this, we always exist tags in order FILO.
virtual void OnExitTag(DrillerHandlerParser* handler, u32 tagName) { (void)handler; (void)tagName; }
/// Handle that data for the tag we are handling.
virtual void OnData(const DrillerSAXParser::Data& dataNode) { (void)dataNode; }
/// Return the warning state on unsupported tags (sometime you might want to warn usually) and sometimes not (if you load newer drills, etc.)
inline bool IsWarnOnUnsupportedTags() const { return m_isWarnOnUnsupportedTags; }
protected:
bool m_isWarnOnUnsupportedTags;
};
/**
* Processes a driller driller and dispatches the data based on the
* the DrillerHandlerParser (handlers) and their ability to handle specific tags.
* If a tag is NOT found as a child of the current one it will display a warning with the tag name
* (useless it's allowed by DrillerHandlerParser::IsWarnOnUnsupportedTags) and process the stream
* is a safe manner by skipping all the data and tags we can't handle.
*/
class DrillerSAXParserHandler
: public DrillerSAXParser
{
public:
AZ_CLASS_ALLOCATOR(DrillerSAXParserHandler, OSAllocator, 0)
DrillerSAXParserHandler(DrillerHandlerParser* rootHandler);
protected:
/// Called from DrillerSAXParser when we have an open tag.
void OnTag(u32 name, bool isOpen);
/// Called from DrillerSAXParser when we have data, which will be forwarded to the handler.
void OnData(const DrillerSAXParser::Data& data);
typedef vector<DrillerHandlerParser*>::type DrillerHandlerStackType;
DrillerHandlerStackType m_stack;
};
} // namespace Debug
} // namespace AZ
#endif // AZCORE_DRILLER_STREAM_H
#pragma once
+17 -9
View File
@@ -160,8 +160,8 @@ namespace AZ
/**
* Locking primitive that is used when executing events in the event queue.
*/
using EventQueueMutexType = typename AZStd::Utils::if_c<AZStd::is_same<typename Traits::EventQueueMutexType, NullMutex>::value, // if EventQueueMutexType==NullMutex use MutexType otherwise EventQueueMutexType
MutexType, typename Traits::EventQueueMutexType>::type;
using EventQueueMutexType = AZStd::conditional_t<AZStd::is_same<typename Traits::EventQueueMutexType, NullMutex>::value, // if EventQueueMutexType==NullMutex use MutexType otherwise EventQueueMutexType
MutexType, typename Traits::EventQueueMutexType>;
/**
* Pointer to an address on the bus.
@@ -180,14 +180,22 @@ namespace AZ
* `<BusName>::ExecuteQueuedEvents()`.
* By default, the event queue is disabled.
*/
static const bool EnableEventQueue = Traits::EnableEventQueue;
static const bool EventQueueingActiveByDefault = Traits::EventQueueingActiveByDefault;
static const bool EnableQueuedReferences = Traits::EnableQueuedReferences;
static constexpr bool EnableEventQueue = Traits::EnableEventQueue;
static constexpr bool EventQueueingActiveByDefault = Traits::EventQueueingActiveByDefault;
static constexpr bool EnableQueuedReferences = Traits::EnableQueuedReferences;
/**
* True if the EBus supports more than one address. Otherwise, false.
*/
static const bool HasId = Traits::AddressPolicy != EBusAddressPolicy::Single;
static constexpr bool HasId = Traits::AddressPolicy != EBusAddressPolicy::Single;
/**
* Template Lock Guard class that wraps around the Mutex
* The EBus uses for Dispatching Events.
* This is not the EBus Context Mutex if LocklessDispatch is true
*/
template <typename DispatchMutex>
using DispatchLockGuard = typename Traits::template DispatchLockGuard<DispatchMutex, Traits::LocklessDispatch>;
};
/**
@@ -460,7 +468,7 @@ namespace AZ
using BusPtr = typename Traits::BusPtr;
/**
* Helper to queue an event by BusIdType only when function queueing is enabled
* Helper to queue an event by BusIdType only when function queueing is enabled
* @param id Address ID. Handlers that are connected to this ID will receive the event.
* @param func Function pointer of the event to dispatch.
* @param args Function arguments that are passed to each handler.
@@ -581,7 +589,7 @@ namespace AZ
, public EBusBroadcaster<Bus, Traits>
, public EBusEventer<Bus, Traits>
, public EBusEventEnumerator<Bus, Traits>
, public AZStd::Utils::if_c<Traits::EnableEventQueue, EBusEventQueue<Bus, Traits>, EBusNullQueue>::type
, public AZStd::conditional_t<Traits::EnableEventQueue, EBusEventQueue<Bus, Traits>, EBusNullQueue>
{
};
@@ -599,7 +607,7 @@ namespace AZ
: public EventDispatcher<Bus, Traits>
, public EBusBroadcaster<Bus, Traits>
, public EBusBroadcastEnumerator<Bus, Traits>
, public AZStd::Utils::if_c<Traits::EnableEventQueue, EBusBroadcastQueue<Bus, Traits>, EBusNullQueue>::type
, public AZStd::conditional_t<Traits::EnableEventQueue, EBusBroadcastQueue<Bus, Traits>, EBusNullQueue>
{
};
+50 -15
View File
@@ -12,21 +12,23 @@
* that Open 3D Engine uses to dispatch notifications and receive requests.
* EBuses are configurable and support many different use cases.
* For more information about %EBuses, see AZ::EBus in this guide and
* [Event Bus](http://docs.aws.amazon.com/lumberyard/latest/developerguide/asset-pipeline-ebus.html)
* [Event Bus](https://o3de.org/docs/user-guide/engine/ebus/)
* in the *Open 3D Engine Developer Guide*.
*/
#pragma once
#include <AzCore/EBus/BusImpl.h>
#include <AzCore/EBus/Environment.h>
#include <AzCore/EBus/Results.h>
#include <AzCore/EBus/Internal/Debug.h>
// Included for backwards compatibility purposes
#include <AzCore/std/typetraits/typetraits.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/typetraits/is_same.h>
// End backwards compat
#include <AzCore/std/utils.h>
#include <AzCore/std/parallel/scoped_lock.h>
@@ -65,7 +67,7 @@ namespace AZ
* @endcode
*
* For more information about %EBuses, see EBus in this guide and
* [Event Bus](http://docs.aws.amazon.com/lumberyard/latest/developerguide/asset-pipeline-ebus.html)
* [Event Bus](https://o3de.org/docs/user-guide/engine/ebus/)
* in the *Open 3D Engine Developer Guide*.
*/
struct EBusTraits
@@ -80,9 +82,11 @@ namespace AZ
public:
/**
* Allocator used by the EBus.
* The default setting is AZStd::allocator, which uses AZ::SystemAllocator.
* The default setting is Internal EBusEnvironmentAllocator
* EBus code stores their Context instances in static memory
* Therfore the configured allocator must last as long as the EBus in a module
*/
using AllocatorType = AZStd::allocator;
using AllocatorType = AZ::Internal::EBusEnvironmentAllocator;
/**
* Defines how many handlers can connect to an address on the EBus
@@ -90,14 +94,14 @@ namespace AZ
* For available settings, see AZ::EBusHandlerPolicy.
* By default, an EBus supports any number of handlers.
*/
static const EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Multiple;
static constexpr EBusHandlerPolicy HandlerPolicy = EBusHandlerPolicy::Multiple;
/**
* Defines how many addresses exist on the EBus.
* For available settings, see AZ::EBusAddressPolicy.
* By default, an EBus uses a single address.
*/
static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::Single;
static constexpr EBusAddressPolicy AddressPolicy = EBusAddressPolicy::Single;
/**
* The type of ID that is used to address the EBus.
@@ -152,14 +156,14 @@ namespace AZ
* `<BusName>::ExecuteQueuedEvents()`.
* By default, the event queue is disabled.
*/
static const bool EnableEventQueue = false;
static constexpr bool EnableEventQueue = false;
/**
* Specifies whether the bus should accept queued messages by default or not.
* If set to false, Bus::AllowFunctionQueuing(true) must be called before events are accepted.
* Used only when #EnableEventQueue is true.
*/
static const bool EventQueueingActiveByDefault = true;
static constexpr bool EventQueueingActiveByDefault = true;
/**
* Specifies whether the EBus supports queueing functions which take reference
@@ -168,7 +172,7 @@ namespace AZ
* You should only use this if you know that the data being passed as arguments will
* outlive the dispatch of the queued event.
*/
static const bool EnableQueuedReferences = false;
static constexpr bool EnableQueuedReferences = false;
/**
* Locking primitive that is used when adding and removing
@@ -197,7 +201,7 @@ namespace AZ
* to do.
* By default, the standard policy is used, which locks around all dispatches
*/
static const bool LocklessDispatch = false;
static constexpr bool LocklessDispatch = false;
/**
* Specifies where EBus data is stored.
@@ -239,6 +243,17 @@ namespace AZ
* code before or after an event.
*/
using EventProcessingPolicy = EBusEventProcessingPolicy;
/**
* Template Lock Guard class that wraps around the Mutex
* The EBus Context uses the LockGuard when dispatching
* (either AZStd::scoped_lock<MutexType> or NullLockGuard<MutexType>)
* The IsLocklessDispatch bool is there to defer evaluation of the LocklessDispatch constant
* Otherwise the value above in EBusTraits.h is always used and not the value
* that the derived trait class sets.
*/
template <typename DispatchMutex, bool IsLocklessDispatch>
using DispatchLockGuard = AZStd::conditional_t<IsLocklessDispatch, AZ::Internal::NullLockGuard<DispatchMutex>, AZStd::scoped_lock<DispatchMutex>>;
};
namespace Internal
@@ -262,8 +277,8 @@ namespace AZ
*
* EBuses are configurable and support many different use cases.
* For more information about EBuses, see
* [Event Bus](http://docs.aws.amazon.com/lumberyard/latest/developerguide/asset-pipeline-ebus.html)
* and [Components and EBuses: Best Practices ](http://docs.aws.amazon.com/lumberyard/latest/developerguide/component-entity-system-pg-components-ebuses-best-practices.html)
* [Event Bus](https://o3de.org/docs/user-guide/engine/ebus/)
* and [Components and EBuses: Best Practices ](https://o3de.org/docs/user-guide/components/development/entity-system-pg-components-ebuses-best-practices/)
* in the *Open 3D Engine Developer Guide*.
*
* ## How Components Use EBuses
@@ -499,6 +514,14 @@ namespace AZ
*/
static const bool HasId = Traits::AddressPolicy != EBusAddressPolicy::Single;
/**
* Template Lock Guard class that wraps around the Mutex
* The EBus uses for Dispatching Events.
* This is not EBus Context Mutex when LocklessDispatch is set
*/
template <typename DispatchMutex>
using DispatchLockGuardTemplate = typename ImplTraits::template DispatchLockGuard<DispatchMutex>;
//////////////////////////////////////////////////////////////////////////
// Check to help identify common mistakes
/// @cond EXCLUDE_DOCS
@@ -623,11 +646,11 @@ namespace AZ
using ContextMutexType = AZStd::conditional_t<BusTraits::LocklessDispatch && AZStd::is_same_v<MutexType, AZ::NullMutex>, AZStd::shared_mutex, MutexType>;
/**
* The scoped lock guard to use (either AZStd::scoped_lock<MutexType> or NullLockGuard<MutexType>
* The scoped lock guard to use
* during broadcast/event dispatch.
* @see EBusTraits::LocklessDispatch
*/
using DispatchLockGuard = AZStd::conditional_t<BusTraits::LocklessDispatch, AZ::Internal::NullLockGuard<ContextMutexType>, AZStd::scoped_lock<ContextMutexType>>;
using DispatchLockGuard = DispatchLockGuardTemplate<ContextMutexType>;
/**
* The scoped lock guard to use during connection. Some specialized policies execute handler methods which
@@ -707,6 +730,11 @@ namespace AZ
static Context& GetOrCreateContext(bool trackCallstack=true);
static bool IsInDispatch(Context* context = GetContext(false));
/**
* Returns whether the EBus context is in the middle of a dispatch on the current thread
*/
static bool IsInDispatchThisThread(Context* context = GetContext(false));
/// @cond EXCLUDE_DOCS
struct RouterCallstackEntry
: public CallstackEntry
@@ -1211,6 +1239,13 @@ AZ_POP_DISABLE_WARNING
return context != nullptr && context->m_dispatches > 0;
}
template<class Interface, class Traits>
bool EBus<Interface, Traits>::IsInDispatchThisThread(Context* context)
{
return context != nullptr && context->s_callstack != nullptr
&& context->s_callstack->m_prev != nullptr;
}
//=========================================================================
template<class Interface, class Traits>
EBus<Interface, Traits>::RouterCallstackEntry::RouterCallstackEntry(Iterator it, const BusIdType* busId, bool isQueued, bool isReverse)
@@ -60,7 +60,7 @@ namespace AZ
void EventSchedulerSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
TimeMs startTime = GetElapsedTimeMs();
TimeMs startTime = AZ::GetElapsedTimeMs();
bool usingTimeslice = bg_maxScheduledEventProcessTimeMs != TimeMs{ 0 };
while (!m_queue.empty())
@@ -76,7 +76,7 @@ namespace AZ
while (!m_pendingQueue.empty())
{
if (usingTimeslice && (GetElapsedTimeMs() - startTime > bg_maxScheduledEventProcessTimeMs))
if (usingTimeslice && (AZ::GetElapsedTimeMs() - startTime > bg_maxScheduledEventProcessTimeMs))
{
AZLOG_WARN("Failed to trigger all pending scheduled events, %u events remain on the pending queue", aznumeric_cast<uint32_t>(m_pendingQueue.size()));
break;
@@ -103,7 +103,7 @@ namespace AZ
durationMs = TimeMs{ 0 };
}
TimeMs currentMilliseconds = GetElapsedTimeMs();
TimeMs currentMilliseconds = AZ::GetElapsedTimeMs();
if (timedEvent->m_handle == nullptr)
{
timedEvent->m_handle = AllocateHandle();
@@ -122,7 +122,7 @@ namespace AZ
durationMs = TimeMs{ 0 };
}
TimeMs currentMilliseconds = GetElapsedTimeMs();
TimeMs currentMilliseconds = AZ::GetElapsedTimeMs();
ScheduledEvent* timedEvent = AllocateManagedEvent(callback, eventName);
const bool ownsScheduledEvent = true;
*(timedEvent->m_handle) = ScheduledEventHandle(TimeMs(currentMilliseconds + durationMs), durationMs, timedEvent, ownsScheduledEvent);
@@ -13,6 +13,7 @@
#include <AzCore/EBus/EBus.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ
{
@@ -93,14 +93,14 @@ namespace AZ
// This struct will hold the handlers per address
struct HandlerHolder;
// This struct will hold each handler
using HandlerNode = HandlerNode<Interface, Traits, HandlerHolder>;
using HandlerNode = AZ::Internal::HandlerNode<Interface, Traits, HandlerHolder>;
// Defines how handler holders are stored (will be some sort of map-like structure from id -> handler holder)
using AddressStorage = AddressStoragePolicy<Traits, HandlerHolder>;
// Defines how handlers are stored per address (will be some sort of list)
using HandlerStorage = HandlerStoragePolicy<Interface, Traits, HandlerNode>;
using Handler = IdHandler<Interface, Traits, ContainerType>;
using MultiHandler = MultiHandler<Interface, Traits, ContainerType>;
using MultiHandler = AZ::Internal::MultiHandler<Interface, Traits, ContainerType>;
using BusPtr = AZStd::intrusive_ptr<HandlerHolder>;
EBusContainer() = default;
@@ -774,13 +774,13 @@ namespace AZ
// This struct will hold the handler per address
struct HandlerHolder;
// This struct will hold each handler
using HandlerNode = HandlerNode<Interface, Traits, HandlerHolder>;
using HandlerNode = AZ::Internal::HandlerNode<Interface, Traits, HandlerHolder>;
// Defines how handler holders are stored (will be some sort of map-like structure from id -> handler holder)
using AddressStorage = AddressStoragePolicy<Traits, HandlerHolder>;
// No need for HandlerStorage, there's only 1 so it will always just be a HandlerNode*
using Handler = IdHandler<Interface, Traits, ContainerType>;
using MultiHandler = MultiHandler<Interface, Traits, ContainerType>;
using MultiHandler = AZ::Internal::MultiHandler<Interface, Traits, ContainerType>;
using BusPtr = AZStd::intrusive_ptr<HandlerHolder>;
EBusContainer() = default;
@@ -1316,7 +1316,7 @@ namespace AZ
// This struct will hold the handlers per address
struct HandlerHolder;
// This struct will hold each handler
using HandlerNode = HandlerNode<Interface, Traits, HandlerHolder>;
using HandlerNode = AZ::Internal::HandlerNode<Interface, Traits, HandlerHolder>;
// Defines how handlers are stored per address (will be some sort of list)
using HandlerStorage = HandlerStoragePolicy<Interface, Traits, HandlerNode>;
// No need for AddressStorage, there's only 1
@@ -161,7 +161,7 @@ namespace AZ
template <class C>
struct EBusCallstackStorage<C, true>
{
AZ_THREAD_LOCAL static C* s_entry;
static AZ_THREAD_LOCAL C* s_entry;
EBusCallstackStorage() = default;
~EBusCallstackStorage() = default;
+14 -23
View File
@@ -18,9 +18,8 @@
#include <AzCore/std/function/invoke.h>
#include <AzCore/std/containers/queue.h>
#include <AzCore/std/containers/intrusive_set.h>
#include <AzCore/std/parallel/scoped_lock.h>
#include <AzCore/Module/Environment.h>
#include <AzCore/EBus/Environment.h>
namespace AZ
{
@@ -251,29 +250,21 @@ namespace AZ
void Execute()
{
AZ_Warning("System", m_isActive, "You are calling execute queued functions on a bus which has not activated its function queuing! Call YourBus::AllowFunctionQueuing(true)!");
while (true)
MessageQueueType localMessages;
// Swap the current list of queue functions with a local instance
{
BusMessageCall invoke;
AZStd::scoped_lock lock(m_messagesMutex);
AZStd::swap(localMessages, m_messages);
}
//////////////////////////////////////////////////////////////////////////
// Pop element from the queue.
{
AZStd::lock_guard<MutexType> lock(m_messagesMutex);
size_t numMessages = m_messages.size();
if (numMessages == 0)
{
break;
}
AZStd::swap(invoke, m_messages.front());
m_messages.pop();
if (numMessages == 1)
{
m_messages = {};
}
}
//////////////////////////////////////////////////////////////////////////
invoke();
// Execute the queue functions safely now that are owned by the function
while (!localMessages.empty())
{
const BusMessageCall& localMessage = localMessages.front();
localMessage();
localMessages.pop();
}
}
@@ -76,7 +76,7 @@ namespace AZ
TimeMs ScheduledEvent::TimeInQueueMs() const
{
return GetElapsedTimeMs() - m_timeInserted;
return AZ::GetElapsedTimeMs() - m_timeInserted;
}
TimeMs ScheduledEvent::RemainingTimeInQueueMs() const
@@ -8,38 +8,35 @@
#include <AzCore/IO/CompressionBus.h>
namespace AZ
namespace AZ::IO
{
namespace IO
CompressionInfo::CompressionInfo(CompressionInfo&& rhs)
{
CompressionInfo::CompressionInfo(CompressionInfo&& rhs)
{
*this = AZStd::move(rhs);
}
*this = AZStd::move(rhs);
}
CompressionInfo& CompressionInfo::operator=(CompressionInfo&& rhs)
{
m_decompressor = AZStd::move(rhs.m_decompressor);
m_archiveFilename = AZStd::move(rhs.m_archiveFilename);
m_compressionTag = rhs.m_compressionTag;
m_offset = rhs.m_offset;
m_compressedSize = rhs.m_compressedSize;
m_uncompressedSize = rhs.m_uncompressedSize;
m_conflictResolution = rhs.m_conflictResolution;
m_isCompressed = rhs.m_isCompressed;
m_isSharedPak = rhs.m_isSharedPak;
CompressionInfo& CompressionInfo::operator=(CompressionInfo&& rhs)
{
m_decompressor = AZStd::move(rhs.m_decompressor);
m_archiveFilename = AZStd::move(rhs.m_archiveFilename);
m_compressionTag = rhs.m_compressionTag;
m_offset = rhs.m_offset;
m_compressedSize = rhs.m_compressedSize;
m_uncompressedSize = rhs.m_uncompressedSize;
m_conflictResolution = rhs.m_conflictResolution;
m_isCompressed = rhs.m_isCompressed;
m_isSharedPak = rhs.m_isSharedPak;
return *this;
}
return *this;
}
namespace CompressionUtils
namespace CompressionUtils
{
bool FindCompressionInfo(CompressionInfo& info, const AZStd::string_view filename)
{
bool FindCompressionInfo(CompressionInfo& info, const AZStd::string_view filename)
{
bool result = false;
CompressionBus::Broadcast(&CompressionBus::Events::FindCompressionInfo, result, info, filename);
return result;
}
bool result = false;
CompressionBus::Broadcast(&CompressionBus::Events::FindCompressionInfo, result, info, filename);
return result;
}
}
}
} // namespace AZ::IO
+22 -25
View File
@@ -10,32 +10,29 @@
#include <AzCore/IO/Compressor.h>
#include <AzCore/IO/CompressorStream.h>
namespace AZ
namespace AZ::IO
{
namespace IO
//=========================================================================
// WriteHeaderAndData
// [12/13/2012]
//=========================================================================
bool Compressor::WriteHeaderAndData(CompressorStream* compressorStream)
{
//=========================================================================
// WriteHeaderAndData
// [12/13/2012]
//=========================================================================
bool Compressor::WriteHeaderAndData(CompressorStream* compressorStream)
AZ_Assert(compressorStream->CanWrite(), "Stream is not open for write!");
AZ_Assert(compressorStream->GetCompressorData(), "Stream doesn't have attached compressor, call WriteCompressed first!");
AZ_Assert(compressorStream->GetCompressorData()->m_compressor == this, "Invalid compressor data! Data belongs to a different compressor");
CompressorHeader header;
header.SetAZCS();
header.m_compressorId = GetTypeId();
header.m_uncompressedSize = compressorStream->GetCompressorData()->m_uncompressedSize;
AZStd::endian_swap(header.m_compressorId);
AZStd::endian_swap(header.m_uncompressedSize);
GenericStream* baseStream = compressorStream->GetWrappedStream();
if (baseStream->WriteAtOffset(sizeof(CompressorHeader), &header, 0U) == sizeof(CompressorHeader))
{
AZ_Assert(compressorStream->CanWrite(), "Stream is not open for write!");
AZ_Assert(compressorStream->GetCompressorData(), "Stream doesn't have attached compressor, call WriteCompressed first!");
AZ_Assert(compressorStream->GetCompressorData()->m_compressor == this, "Invalid compressor data! Data belongs to a different compressor");
CompressorHeader header;
header.SetAZCS();
header.m_compressorId = GetTypeId();
header.m_uncompressedSize = compressorStream->GetCompressorData()->m_uncompressedSize;
AZStd::endian_swap(header.m_compressorId);
AZStd::endian_swap(header.m_uncompressedSize);
GenericStream* baseStream = compressorStream->GetWrappedStream();
if (baseStream->WriteAtOffset(sizeof(CompressorHeader), &header, 0U) == sizeof(CompressorHeader))
{
return true;
}
return false;
return true;
}
} // namespace IO
} // namespace AZ
return false;
}
} // namespace AZ::IO
+53 -60
View File
@@ -5,74 +5,67 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZCORE_IO_COMPRESSOR_H
#define AZCORE_IO_COMPRESSOR_H
#pragma once
#include <AzCore/base.h>
namespace AZ
namespace AZ::IO
{
namespace IO
class CompressorStream;
/**
* Compressor/Decompressor base interface.
* Used for all stream compressors.
*/
class Compressor
{
class CompressorStream;
public:
typedef AZ::u64 SizeType;
static const int m_maxHeaderSize = 4096; /// When we open a stream to check if it's compressed we read the first m_maxHeaderSize bytes.
/**
* Compressor/Decompressor base interface.
* Used for all stream compressors.
*/
class Compressor
{
public:
typedef AZ::u64 SizeType;
static const int m_maxHeaderSize = 4096; /// When we open a stream to check if it's compressed we read the first m_maxHeaderSize bytes.
virtual ~Compressor() {}
/// Return compressor type id.
virtual AZ::u32 GetTypeId() const = 0;
/// Called when we open a stream to Read for the first time. Data contains the first. dataSize <= m_maxHeaderSize.
virtual bool ReadHeaderAndData(CompressorStream* stream, AZ::u8* data, unsigned int dataSize) = 0;
/// Called when we are about to start writing to a compressed stream. (Must be called first to write compressor header)
virtual bool WriteHeaderAndData(CompressorStream* stream);
/// Forwarded function from the Device when we from a compressed stream.
virtual SizeType Read(CompressorStream* stream, SizeType byteSize, SizeType offset, void* buffer) = 0;
/// Forwarded function from the Device when we write to a compressed stream.
virtual SizeType Write(CompressorStream* stream, SizeType byteSize, const void* data, SizeType offset = SizeType(-1)) = 0;
/// Write a seek point.
virtual bool WriteSeekPoint(CompressorStream* stream) { (void)stream; return false; }
/// Initializes Compressor for writing data.
virtual bool StartCompressor(CompressorStream* stream, int compressionLevel, SizeType autoSeekDataSize) { (void)stream; (void)compressionLevel; (void)autoSeekDataSize; return false; }
/// Called just before we close the stream. All compression data will be flushed and finalized. (You can't add data afterwards).
virtual bool Close(CompressorStream* stream) = 0;
};
virtual ~Compressor() {}
/// Return compressor type id.
virtual AZ::u32 GetTypeId() const = 0;
/// Called when we open a stream to Read for the first time. Data contains the first. dataSize <= m_maxHeaderSize.
virtual bool ReadHeaderAndData(CompressorStream* stream, AZ::u8* data, unsigned int dataSize) = 0;
/// Called when we are about to start writing to a compressed stream. (Must be called first to write compressor header)
virtual bool WriteHeaderAndData(CompressorStream* stream);
/// Forwarded function from the Device when we from a compressed stream.
virtual SizeType Read(CompressorStream* stream, SizeType byteSize, SizeType offset, void* buffer) = 0;
/// Forwarded function from the Device when we write to a compressed stream.
virtual SizeType Write(CompressorStream* stream, SizeType byteSize, const void* data, SizeType offset = SizeType(-1)) = 0;
/// Write a seek point.
virtual bool WriteSeekPoint(CompressorStream* stream) { (void)stream; return false; }
/// Initializes Compressor for writing data.
virtual bool StartCompressor(CompressorStream* stream, int compressionLevel, SizeType autoSeekDataSize) { (void)stream; (void)compressionLevel; (void)autoSeekDataSize; return false; }
/// Called just before we close the stream. All compression data will be flushed and finalized. (You can't add data afterwards).
virtual bool Close(CompressorStream* stream) = 0;
};
/**
* Base compressor data assigned for all compressors.
*/
class CompressorData
{
public:
virtual ~CompressorData() {}
/**
* Base compressor data assigned for all compressors.
*/
class CompressorData
{
public:
virtual ~CompressorData() {}
Compressor* m_compressor;
AZ::u64 m_uncompressedSize;
};
Compressor* m_compressor;
AZ::u64 m_uncompressedSize;
};
/**
* All data is stored in network order (big endian).
*/
struct CompressorHeader
{
CompressorHeader() { m_azcs[0] = 0; m_azcs[1] = 0; m_azcs[2] = 0; m_azcs[3] = 0; }
/**
* All data is stored in network order (big endian).
*/
struct CompressorHeader
{
CompressorHeader() { m_azcs[0] = 0; m_azcs[1] = 0; m_azcs[2] = 0; m_azcs[3] = 0; }
bool IsValid() const { return (m_azcs[0] == 'A' && m_azcs[1] == 'Z' && m_azcs[2] == 'C' && m_azcs[3] == 'S'); }
void SetAZCS() { m_azcs[0] = 'A'; m_azcs[1] = 'Z'; m_azcs[2] = 'C'; m_azcs[3] = 'S'; }
inline bool IsValid() const { return (m_azcs[0] == 'A' && m_azcs[1] == 'Z' && m_azcs[2] == 'C' && m_azcs[3] == 'S'); }
void SetAZCS() { m_azcs[0] = 'A'; m_azcs[1] = 'Z'; m_azcs[2] = 'C'; m_azcs[3] = 'S'; }
char m_azcs[4]; ///< String contains 'AZCS' AmaZon Compressed Stream
AZ::u32 m_compressorId; ///< Compression method.
AZ::u64 m_uncompressedSize; ///< Uncompressed file size.
};
} // namespace IO
} // namespace AZ
#endif // AZCORE_IO_COMPRESSOR_H
#pragma once
char m_azcs[4]; ///< String contains 'AZCS' AmaZon Compressed Stream
AZ::u32 m_compressorId; ///< Compression method.
AZ::u64 m_uncompressedSize; ///< Uncompressed file size.
};
} // namespace AZ::IO
@@ -15,9 +15,7 @@
#include <AzCore/IO/FileIO.h>
#include <AzCore/Memory/Memory.h>
namespace AZ
{
namespace IO
namespace AZ::IO
{
/*!
\brief Constructs a compressor stream using the supplied filename and OpenFlags to open a file on disk
@@ -300,7 +298,4 @@ Compressor* CompressorStream::CreateCompressor(AZ::u32 compressorId)
return m_compressor.get();
}
} // namespace IO
} // namespace AZ
} // namespace AZ::IO

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