Merging last dev

Signed-off-by: Gene Walters <genewalt@amazon.com>
This commit is contained in:
Gene Walters
2021-11-19 23:12:59 -08:00
2657 changed files with 249197 additions and 30535 deletions
-1
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>
+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
@@ -556,16 +556,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();
}
}
}
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
@@ -12,207 +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>."));
}
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->FixUpAsset(*instance);
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::SetAssetFixUp(AssetFixUp assetFixUpCallback)
{
m_assetFixUpCallback = AZStd::move(assetFixUpCallback);
}
void SerializedAssetTracker::FixUpAsset(Asset<AssetData>& asset)
{
if (m_assetFixUpCallback)
{
m_assetFixUpCallback(asset);
result.Combine(context.Report(result, "Failed to retrieve asset id for Asset<T>."));
}
}
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
File diff suppressed because it is too large Load Diff
@@ -169,14 +169,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 +295,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 +316,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 +366,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 +452,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 +482,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 +496,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 +525,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>
@@ -129,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; }
@@ -19,7 +19,6 @@
#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>
@@ -40,7 +39,6 @@ namespace AZ
SliceComponent::CreateDescriptor(),
SliceSystemComponent::CreateDescriptor(),
SliceMetadataInfoComponent::CreateDescriptor(),
TimeSystemComponent::CreateDescriptor(),
LoggerSystemComponent::CreateDescriptor(),
EventSchedulerSystemComponent::CreateDescriptor(),
TaskGraphSystemComponent::CreateDescriptor(),
@@ -59,7 +57,6 @@ namespace AZ
{
return AZ::ComponentTypeList
{
azrtti_typeid<TimeSystemComponent>(),
azrtti_typeid<LoggerSystemComponent>(),
azrtti_typeid<EventSchedulerSystemComponent>(),
azrtti_typeid<TaskGraphSystemComponent>(),
@@ -72,6 +72,7 @@
#include <AzCore/Module/Environment.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/Time/TimeSystem.h>
static void PrintEntityName(const AZ::ConsoleCommandContainer& arguments)
{
@@ -416,6 +417,7 @@ namespace AZ
ComponentApplication::ComponentApplication(int argC, char** argV)
: m_eventLogger{}
, m_timeSystem(AZStd::make_unique<TimeSystem>())
{
if (Interface<ComponentApplicationRequests>::Get() == nullptr)
{
@@ -485,16 +487,6 @@ namespace AZ
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();
SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(*m_settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry);
@@ -584,7 +576,6 @@ namespace AZ
DestroyAllocator();
}
void ReportBadEngineRoot()
{
AZStd::string errorMessage = {"Unable to determine a valid path to the engine.\n"
@@ -614,7 +605,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;
@@ -686,7 +678,6 @@ namespace AZ
ComponentApplicationBus::Handler::BusConnect();
m_currentTime = AZStd::chrono::system_clock::now();
TickRequestBus::Handler::BusConnect();
#if defined(AZ_ENABLE_DEBUG_TOOLS)
@@ -1180,6 +1171,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
//=========================================================================
@@ -1404,31 +1413,23 @@ namespace AZ
#endif
}
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));
}
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();
}
void ComponentApplication::TickSystem()
@@ -1485,27 +1486,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
@@ -1531,13 +1511,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);
}
//=========================================================================
@@ -1546,7 +1523,8 @@ namespace AZ
//=========================================================================
ScriptTimePoint ComponentApplication::GetTimeAtCurrentTick()
{
return ScriptTimePoint(m_currentTime);
const AZ::TimeUs lastGameTickTime = m_timeSystem->GetLastSimulationTickTime();
return ScriptTimePoint(AZ::TimeUsToChrono(lastGameTickTime));
}
//=========================================================================
@@ -30,12 +30,14 @@
#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
{
@@ -221,13 +223,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
@@ -240,7 +239,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.
@@ -352,15 +351,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();
/// Deprecated: The term "AppRoot" has no meaning
void CalculateAppRoot();
template<typename Iterator>
static void NormalizePath(Iterator begin, Iterator end, bool doLowercase = true)
{
@@ -371,8 +361,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;
@@ -388,14 +376,13 @@ 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_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
AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle m_settingsRegistryConsoleFunctors;
@@ -175,10 +175,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;
@@ -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
@@ -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));
@@ -14,323 +14,313 @@
#include <AzCore/std/containers/map.h>
#include <AzCore/std/smart_ptr/make_shared.h>
namespace AZ
namespace AZ::Debug
{
namespace Debug
namespace
{
namespace
struct AssetTreeNode;
// Per-thread data that needs to be stored.
struct ThreadData
{
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;
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)
{
AssetTrackingImpl::AssetTrackingImpl(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable) :
m_assetRoot(&assetTree->GetRoot()),
m_allocationTable(allocationTable)
{
AZ_Assert(!GetSharedInstance(), "Only one AssetTrackingImpl can exist!");
AZ_Assert(!GetSharedInstance(), "Only one AssetTrackingImpl can exist!");
GetEnvironmentVariable().Set(this);
AllocatorManager::Instance().EnterProfilingMode();
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;
}
AssetTrackingImpl::~AssetTrackingImpl()
{
AllocatorManager::Instance().ExitProfilingMode();
GetEnvironmentVariable().Reset();
}
lock_type lock(m_mutex);
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);
// Locate or create the primary record for this asset
auto primaryItr = m_primaryAssets.find(assetId);
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)
if (primaryItr != m_primaryAssets.end())
{
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>");
assetPrimaryInfo = &primaryItr->second;
}
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;
}
}
auto insertResult = m_primaryAssets.emplace(assetId, AssetPrimaryInfo());
assetPrimaryInfo = &insertResult.first->second;
assetPrimaryInfo->m_id = &insertResult.first->first;
}
return buffer;
#else
return "";
#endif
// Add this asset to the stack for this thread's context
childAsset = parentAsset->FindOrAddChild(assetId, assetPrimaryInfo);
}
AssetTracking::AssetTracking(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable)
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)
{
m_impl.reset(aznew AssetTrackingImpl(assetTree, allocationTable));
return *environmentVariable;
}
AssetTracking::~AssetTracking()
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;
}
AssetTreeNodeBase* AssetTracking::GetCurrentThreadAsset() const
{
const auto& assetStack = m_impl->GetThreadData().m_currentAssetStack;
AssetTreeNodeBase* result = assetStack.empty() ? nullptr : assetStack.back();
return *data;
}
return result;
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
@@ -11,19 +11,19 @@
#include <AzCore/std/time.h>
#include <AzCore/std/parallel/thread.h>
namespace AZ
namespace AZ::Debug
{
namespace Debug
EventTrace::ScopedSlice::ScopedSlice(const char* name, const char* category)
: m_Name(name)
, m_Category(category)
, m_Time(AZStd::GetTimeNowMicroSecond())
{
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));
}
}
}
EventTrace::ScopedSlice::~ScopedSlice()
{
EventTraceDrillerBus::TryQueueBroadcast(
&EventTraceDrillerInterface::RecordSlice, m_Name, m_Category, AZStd::this_thread::get_id(), m_Time,
(uint32_t)(AZStd::GetTimeNowMicroSecond() - m_Time));
}
} // namespace AZ::Debug
@@ -11,151 +11,149 @@
#include <AzCore/std/containers/array.h>
#include <algorithm>
namespace AZ
namespace AZ::Debug
{
namespace Debug
namespace Crc
{
namespace Crc
constexpr u32 EventTraceDriller = AZ_CRC_CE("EventTraceDriller");
constexpr u32 Slice = AZ_CRC_CE("Slice");
constexpr u32 ThreadInfo = AZ_CRC_CE("ThreadInfo");
constexpr u32 Name = AZ_CRC_CE("Name");
constexpr u32 Category = AZ_CRC_CE("Category");
constexpr u32 ThreadId = AZ_CRC_CE("ThreadId");
constexpr u32 Timestamp = AZ_CRC_CE("Timestamp");
constexpr u32 Duration = AZ_CRC_CE("Duration");
constexpr u32 Instant = AZ_CRC_CE("Instant");
}
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)
{
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);
SetThreadName(id, desc->m_name);
}
}
EventTraceDriller::EventTraceDriller()
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.empty())
{
EventTraceDrillerSetupBus::Handler::BusConnect();
AZStd::ThreadDrillerEventBus::Handler::BusConnect();
return;
}
// Main bus mutex guards m_output.
auto& context = EventTraceDrillerBus::GetOrCreateContext();
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)
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::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->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);
}
} // namespace AZ::Debug
+33 -2
View File
@@ -79,6 +79,7 @@ namespace AZ::Debug
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.
@@ -280,6 +281,13 @@ namespace AZ::Debug
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;
@@ -305,7 +313,10 @@ namespace AZ::Debug
}
Output(g_dbgSystemWnd, "------------------------------------------------\n");
PrintCallstack(g_dbgSystemWnd, 1);
if (!bg_alwaysShowCallstack)
{
PrintCallstack(g_dbgSystemWnd, 1);
}
Output(g_dbgSystemWnd, "==================================================================\n");
char dialogBoxText[g_maxMessageLength];
@@ -530,6 +541,16 @@ namespace AZ::Debug
}*/
}
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 };
@@ -574,9 +595,19 @@ namespace AZ::Debug
}
azstrcat(lines[i], AZ_ARRAY_SIZE(lines[i]), "\n");
// Use Output instead of AZ_Printf to be consistent with the exception output code and avoid
// this accidentally being suppressed as a normal message
Output(window, lines[i]);
if (bg_alwaysShowCallstack)
{
// Use Raw Output as this cannot be suppressed
RawOutput(window, lines[i]);
}
else
{
Output(window, lines[i]);
}
}
}
}
@@ -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
@@ -9,94 +9,91 @@
#include <AzCore/Debug/TraceMessagesDriller.h>
#include <AzCore/Math/Crc.h>
namespace AZ
namespace AZ::Debug
{
namespace Debug
//=========================================================================
// Start
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::Start(const Param* params, int numParams)
{
//=========================================================================
// Start
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::Start(const Param* params, int numParams)
{
(void)params;
(void)numParams;
BusConnect();
}
(void)params;
(void)numParams;
BusConnect();
}
//=========================================================================
// Stop
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::Stop()
{
BusDisconnect();
}
//=========================================================================
// 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));
}
//=========================================================================
// 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_CE("TraceMessagesDriller"));
m_output->Write(AZ_CRC_CE("OnAssert"), message);
m_output->EndTag(AZ_CRC_CE("TraceMessagesDriller"));
}
//=========================================================================
// 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));
}
//=========================================================================
// 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_CE("TraceMessagesDriller"));
m_output->Write(AZ_CRC_CE("OnException"), message);
m_output->EndTag(AZ_CRC_CE("TraceMessagesDriller"));
}
//=========================================================================
// 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));
}
//=========================================================================
// OnError
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::OnError(const char* window, const char* message)
{
m_output->BeginTag(AZ_CRC_CE("TraceMessagesDriller"));
m_output->BeginTag(AZ_CRC_CE("OnError"));
m_output->Write(AZ_CRC_CE("Window"), window);
m_output->Write(AZ_CRC_CE("Message"), message);
m_output->EndTag(AZ_CRC_CE("OnError"));
m_output->EndTag(AZ_CRC_CE("TraceMessagesDriller"));
}
//=========================================================================
// 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));
}
//=========================================================================
// OnWarning
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::OnWarning(const char* window, const char* message)
{
m_output->BeginTag(AZ_CRC_CE("TraceMessagesDriller"));
m_output->BeginTag(AZ_CRC_CE("OnWarning"));
m_output->Write(AZ_CRC_CE("Window"), window);
m_output->Write(AZ_CRC_CE("Message"), message);
m_output->EndTag(AZ_CRC_CE("OnWarning"));
m_output->EndTag(AZ_CRC_CE("TraceMessagesDriller"));
}
//=========================================================================
// 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
//=========================================================================
// OnPrintf
// [2/6/2013]
//=========================================================================
void TraceMessagesDriller::OnPrintf(const char* window, const char* message)
{
m_output->BeginTag(AZ_CRC_CE("TraceMessagesDriller"));
m_output->BeginTag(AZ_CRC_CE("OnPrintf"));
m_output->Write(AZ_CRC_CE("Window"), window);
m_output->Write(AZ_CRC_CE("Message"), message);
m_output->EndTag(AZ_CRC_CE("OnPrintf"));
m_output->EndTag(AZ_CRC_CE("TraceMessagesDriller"));
}
} // 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
+236 -239
View File
@@ -14,291 +14,288 @@
#include <AzCore/Math/Crc.h>
namespace AZ
namespace AZ::Debug
{
namespace Debug
class DrillerManagerImpl
: public DrillerManager
{
class DrillerManagerImpl
: public DrillerManager
public:
AZ_CLASS_ALLOCATOR(DrillerManagerImpl, OSAllocator, 0);
using SessionListType = forward_list<DrillerSession>::type;
SessionListType m_sessions;
using DrillerArrayType = vector<Driller*>::type;
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)
{
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());
AZ::AllocatorInstance<OSAllocator>::Create();
}
//////////////////////////////////////////////////////////////////////////
DrillerManagerImpl* impl = aznew DrillerManagerImpl;
impl->m_ownsOSAllocator = createAllocator;
return impl;
}
//////////////////////////////////////////////////////////////////////////
// Driller Manager
//=========================================================================
// Register
// [3/17/2011]
//=========================================================================
DrillerManager* DrillerManager::Create(/*const Descriptor& desc*/)
//=========================================================================
// Register
// [3/17/2011]
//=========================================================================
void DrillerManager::Destroy(DrillerManager* manager)
{
const bool allocatorCreated = manager->m_ownsOSAllocator;
delete manager;
if (allocatorCreated)
{
const bool createAllocator = !AZ::AllocatorInstance<OSAllocator>::IsReady();
if (createAllocator)
{
AZ::AllocatorInstance<OSAllocator>::Create();
}
AZ::AllocatorInstance<OSAllocator>::Destroy();
}
}
DrillerManagerImpl* impl = aznew DrillerManagerImpl;
impl->m_ownsOSAllocator = createAllocator;
return impl;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// DrillerManagerImpl
//=========================================================================
// ~DrillerManagerImpl
// [3/17/2011]
//=========================================================================
DrillerManagerImpl::~DrillerManagerImpl()
{
while (!m_sessions.empty())
{
Stop(&m_sessions.front());
}
//=========================================================================
// Register
// [3/17/2011]
//=========================================================================
void DrillerManager::Destroy(DrillerManager* manager)
while (!m_drillers.empty())
{
const bool allocatorCreated = manager->m_ownsOSAllocator;
delete manager;
if (allocatorCreated)
{
AZ::AllocatorInstance<OSAllocator>::Destroy();
}
Driller* driller = m_drillers[0];
Unregister(driller);
delete driller;
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// DrillerManagerImpl
//=========================================================================
// ~DrillerManagerImpl
// [3/17/2011]
//=========================================================================
DrillerManagerImpl::~DrillerManagerImpl()
//=========================================================================
// 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)
{
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())
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);
}
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(); )
//=========================================================================
// 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())
{
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;
m_drillers.erase(iter);
return;
}
}
//=========================================================================
// Start
// [3/17/2011]
//=========================================================================
DrillerSession*
DrillerManagerImpl::Start(DrillerOutputStream& output, const DrillerListType& drillerList, int numFrames)
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())
{
if (drillerList.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)
{
return nullptr;
s.drillers[i]->Update();
}
m_sessions.push_back();
DrillerSession& s = m_sessions.back();
s.curFrame = 0;
s.numFrames = numFrames;
s.output = &output;
s.output->EndTag(AZ_CRC("Frame", 0xb5f83ccd));
s.output->WriteHeader(); // first write the header in the stream
s.output->OnEndOfFrame();
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)
s.curFrame++;
if (s.numFrames != -1)
{
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)
if (s.curFrame == s.numFrames)
{
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));
Stop(&s);
continue;
}
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;
++sessionIter;
}
}
//=========================================================================
// Stop
// [3/17/2011]
//=========================================================================
void
DrillerManagerImpl::Stop(DrillerSession* session)
//=========================================================================
// Start
// [3/17/2011]
//=========================================================================
DrillerSession*
DrillerManagerImpl::Start(DrillerOutputStream& output, const DrillerListType& drillerList, int numFrames)
{
if (drillerList.empty())
{
SessionListType::iterator iter;
for (iter = m_sessions.begin(); iter != m_sessions.end(); ++iter)
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)
{
if (&*iter == session)
{
break;
}
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));
AZ_Assert(iter != m_sessions.end(), "We did not find session ID 0x%08x in the list!", session);
if (iter != m_sessions.end())
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)
{
DrillerSession& s = *session;
Driller* driller = nullptr;
const DrillerInfo& di = *iDriller;
for (size_t iDesc = 0; iDesc < m_drillers.size(); ++iDesc)
{
AZStd::lock_guard<DrillerEBusMutex::MutexType> lock(DrillerEBusMutex::GetMutex());
for (size_t i = 0; i < s.drillers.size(); ++i)
if (m_drillers[iDesc]->GetId() == di.id)
{
s.drillers[i]->Stop();
s.drillers[i]->m_output = nullptr;
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;
}
}
s.output->EndTag(AZ_CRC("Frame", 0xb5f83ccd));
m_sessions.erase(iter);
AZ_Warning("Driller", driller != nullptr, "We can't start a driller with id %d!", di.id);
}
}
} // namespace Debug
} // namespace AZ
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 AZ::Debug
@@ -12,57 +12,54 @@
#include <AzCore/Module/Environment.h>
#include <AzCore/std/parallel/mutex.h>
namespace AZ
namespace AZ::Debug
{
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()
{
//////////////////////////////////////////////////////////////////////////
// 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;
//////////////////////////////////////////////////////////////////////////
GetMutex().lock();
}
//=========================================================================
// lock
// [4/11/2011]
//=========================================================================
void DrillerEBusMutex::lock()
{
GetMutex().lock();
}
//=========================================================================
// try_lock
// [4/11/2011]
//=========================================================================
bool DrillerEBusMutex::try_lock()
{
return GetMutex().try_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]
//=========================================================================
void DrillerEBusMutex::unlock()
//=========================================================================
// unlock
// [4/11/2011]
//=========================================================================
AZStd::recursive_mutex& DrillerEBusMutex::GetMutex()
{
if (!s_drillerGlobalMutex)
{
GetMutex().unlock();
s_drillerGlobalMutex = Environment::CreateVariable<AZStd::recursive_mutex>(AZ_FUNCTION_SIGNATURE);
}
//=========================================================================
// 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
return *s_drillerGlobalMutex;
}
} // namespace AZ::Debug
File diff suppressed because it is too large Load Diff
@@ -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);
@@ -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
File diff suppressed because it is too large Load Diff
+393 -396
View File
@@ -14,478 +14,475 @@
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Casting/numeric_cast.h>
namespace AZ
namespace AZ::IO
{
namespace IO
CompressorZStd::CompressorZStd(unsigned int decompressionCachePerStream, unsigned int dataBufferSize)
: m_compressedDataBufferSize(dataBufferSize)
, m_decompressionCachePerStream(decompressionCachePerStream)
{
CompressorZStd::CompressorZStd(unsigned int decompressionCachePerStream, unsigned int dataBufferSize)
: m_compressedDataBufferSize(dataBufferSize)
, m_decompressionCachePerStream(decompressionCachePerStream)
AZ_Assert((dataBufferSize % (32 * 1024)) == 0, "Data buffer size %d must be multiple of 32 KB.", dataBufferSize);
AZ_Assert((decompressionCachePerStream % (32 * 1024)) == 0, "Decompress cache size %d must be multiple of 32 KB.", decompressionCachePerStream);
}
CompressorZStd::~CompressorZStd()
{
AZ_Warning("IO", m_compressedDataBufferUseCount == 0, "CompressorZStd has it's data buffer still referenced, it means that %d compressed streams have NOT closed. Freeing data...", m_compressedDataBufferUseCount);
while (m_compressedDataBufferUseCount)
{
AZ_Assert((dataBufferSize % (32 * 1024)) == 0, "Data buffer size %d must be multiple of 32 KB.", dataBufferSize);
AZ_Assert((decompressionCachePerStream % (32 * 1024)) == 0, "Decompress cache size %d must be multiple of 32 KB.", decompressionCachePerStream);
ReleaseDataBuffer();
}
}
CompressorZStd::~CompressorZStd()
AZ::u32 CompressorZStd::TypeId()
{
return AZ_CRC("ZStd", 0x72fd505e);
}
bool CompressorZStd::ReadHeaderAndData(CompressorStream* stream, AZ::u8* data, unsigned int dataSize)
{
if (stream->GetCompressorData() != nullptr) // we already have compressor data
{
AZ_Warning("IO", m_compressedDataBufferUseCount == 0, "CompressorZStd has it's data buffer still referenced, it means that %d compressed streams have NOT closed. Freeing data...", m_compressedDataBufferUseCount);
while (m_compressedDataBufferUseCount)
{
ReleaseDataBuffer();
}
}
AZ::u32 CompressorZStd::TypeId()
{
return AZ_CRC("ZStd", 0x72fd505e);
}
bool CompressorZStd::ReadHeaderAndData(CompressorStream* stream, AZ::u8* data, unsigned int dataSize)
{
if (stream->GetCompressorData() != nullptr) // we already have compressor data
{
return false;
}
// Read the ZStd header should be after the default compression header...
// We should not be in this function otherwise.
if (dataSize < sizeof(CompressorZStdHeader) + sizeof(ZStd::Header))
{
AZ_Error("CompressorZStd", false, "We did not read enough data, we have only %d bytes left in the buffer and we need %d.", dataSize, sizeof(CompressorZStdHeader) + sizeof(ZStd::Header));
return false;
}
AcquireDataBuffer();
CompressorZStdHeader* hdr = reinterpret_cast<CompressorZStdHeader*>(data);
dataSize -= sizeof(CompressorZStdHeader);
data += sizeof(CompressorZStdHeader);
AZStd::unique_ptr<CompressorZStdData> zstdData = AZStd::make_unique<CompressorZStdData>();
zstdData->m_compressor = this;
zstdData->m_uncompressedSize = 0;
zstdData->m_zstdHeader = *reinterpret_cast<ZStd::Header*>(data);
dataSize -= sizeof(zstdData->m_zstdHeader);
data += sizeof(zstdData->m_zstdHeader);
zstdData->m_decompressNextOffset = sizeof(CompressorHeader) + sizeof(CompressorZStdHeader) + sizeof(ZStd::Header); // start after the headers
AZ_Error("CompressorZStd", hdr->m_numSeekPoints > 0, "We should have at least one seek point for the entire stream.");
// go the end of the file and read all sync points.
SizeType compressedFileEnd = stream->GetLength();
if (compressedFileEnd == 0)
{
return false;
}
zstdData->m_seekPoints.resize(hdr->m_numSeekPoints);
SizeType dataToRead = sizeof(CompressorZStdSeekPoint) * static_cast<SizeType>(hdr->m_numSeekPoints);
SizeType seekPointOffset = compressedFileEnd - dataToRead;
if (seekPointOffset > compressedFileEnd)
{
AZ_Error("CompressorZStd", false, "We have an invalid archive, this is impossible.");
return false;
}
GenericStream* baseStream = stream->GetWrappedStream();
if (baseStream->ReadAtOffset(dataToRead, zstdData->m_seekPoints.data(), seekPointOffset) != dataToRead)
{
return false;
}
if (m_decompressionCachePerStream)
{
zstdData->m_decompressedCache = reinterpret_cast<unsigned char*>(azmalloc(m_decompressionCachePerStream, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZStd"));
}
zstdData->m_decompressLastOffset = seekPointOffset; // set the start address of the seek points as the last valid read address for the compressed stream.
zstdData->m_zstd.StartDecompressor();
stream->SetCompressorData(zstdData.release());
return true;
}
bool CompressorZStd::WriteHeaderAndData(CompressorStream* stream)
{
if (!Compressor::WriteHeaderAndData(stream))
{
return false;
}
CompressorZStdData* compressorData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
CompressorZStdHeader header;
header.m_numSeekPoints = aznumeric_caster(compressorData->m_seekPoints.size());
GenericStream* baseStream = stream->GetWrappedStream();
if (baseStream->WriteAtOffset(sizeof(header), &header, sizeof(CompressorHeader)) == sizeof(header))
{
return true;
}
return false;
}
inline CompressorZStd::SizeType CompressorZStd::FillFromDecompressCache(CompressorZStdData* zstdData, void*& buffer, SizeType& byteSize, SizeType& offset)
// Read the ZStd header should be after the default compression header...
// We should not be in this function otherwise.
if (dataSize < sizeof(CompressorZStdHeader) + sizeof(ZStd::Header))
{
SizeType firstOffsetInCache = zstdData->m_decompressedCacheOffset;
SizeType lastOffsetInCache = firstOffsetInCache + zstdData->m_decompressedCacheDataSize;
SizeType firstDataOffset = offset;
SizeType lastDataOffset = offset + byteSize;
SizeType numCopied = 0;
if (firstOffsetInCache < lastDataOffset && lastOffsetInCache >= firstDataOffset) // check if there is data in the cache
{
size_t copyOffsetStart = 0;
size_t copyOffsetEnd = zstdData->m_decompressedCacheDataSize;
size_t bufferCopyOffset = 0;
if (firstOffsetInCache < firstDataOffset)
{
copyOffsetStart = aznumeric_caster(firstDataOffset - firstOffsetInCache);
}
else
{
bufferCopyOffset = aznumeric_caster(firstOffsetInCache - firstDataOffset);
}
if (lastOffsetInCache >= lastDataOffset)
{
copyOffsetEnd -= static_cast<size_t>(lastOffsetInCache - lastDataOffset);
}
else if (bufferCopyOffset > 0) // the cache block is in the middle of the data, we can't use it (since we need to split buffer request into 2)
{
return 0;
}
numCopied = copyOffsetEnd - copyOffsetStart;
memcpy(static_cast<char*>(buffer) + bufferCopyOffset, zstdData->m_decompressedCache + copyOffsetStart, static_cast<size_t>(numCopied));
// adjust pointers and sizes
byteSize -= numCopied;
if (bufferCopyOffset == 0)
{
// copied in the start
buffer = reinterpret_cast<char*>(buffer) + numCopied;
offset += numCopied;
}
}
return numCopied;
AZ_Error("CompressorZStd", false, "We did not read enough data, we have only %d bytes left in the buffer and we need %d.", dataSize, sizeof(CompressorZStdHeader) + sizeof(ZStd::Header));
return false;
}
inline CompressorZStd::SizeType CompressorZStd::FillCompressedBuffer(CompressorStream* stream)
AcquireDataBuffer();
CompressorZStdHeader* hdr = reinterpret_cast<CompressorZStdHeader*>(data);
dataSize -= sizeof(CompressorZStdHeader);
data += sizeof(CompressorZStdHeader);
AZStd::unique_ptr<CompressorZStdData> zstdData = AZStd::make_unique<CompressorZStdData>();
zstdData->m_compressor = this;
zstdData->m_uncompressedSize = 0;
zstdData->m_zstdHeader = *reinterpret_cast<ZStd::Header*>(data);
dataSize -= sizeof(zstdData->m_zstdHeader);
data += sizeof(zstdData->m_zstdHeader);
zstdData->m_decompressNextOffset = sizeof(CompressorHeader) + sizeof(CompressorZStdHeader) + sizeof(ZStd::Header); // start after the headers
AZ_Error("CompressorZStd", hdr->m_numSeekPoints > 0, "We should have at least one seek point for the entire stream.");
// go the end of the file and read all sync points.
SizeType compressedFileEnd = stream->GetLength();
if (compressedFileEnd == 0)
{
CompressorZStdData* zstdData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
SizeType dataFromBuffer = 0;
if (stream == m_lastReadStream) // if the buffer is filled with data from the current stream, try to reuse
{
if (zstdData->m_decompressNextOffset > m_lastReadStreamOffset)
{
SizeType offsetInCache = zstdData->m_decompressNextOffset - m_lastReadStreamOffset;
if (offsetInCache < m_lastReadStreamSize) // last check if there is data overlap
{
// copy the usable part at the start of the buffer
SizeType toMove = m_lastReadStreamSize - offsetInCache;
memcpy(m_compressedDataBuffer, &m_compressedDataBuffer[static_cast<size_t>(offsetInCache)], static_cast<size_t>(toMove));
dataFromBuffer += toMove;
}
}
}
SizeType toReadFromStream = m_compressedDataBufferSize - dataFromBuffer;
SizeType readOffset = zstdData->m_decompressNextOffset + dataFromBuffer;
if (readOffset + toReadFromStream > zstdData->m_decompressLastOffset)
{
// don't read past the end
AZ_Assert(readOffset <= zstdData->m_decompressLastOffset, "Read offset should always be before the end of stream.");
toReadFromStream = zstdData->m_decompressLastOffset - readOffset;
}
SizeType numReadFromStream = 0;
if (toReadFromStream) // if we did not reuse the whole buffer, read some data from the stream
{
GenericStream* baseStream = stream->GetWrappedStream();
numReadFromStream = baseStream->ReadAtOffset(toReadFromStream, &m_compressedDataBuffer[static_cast<size_t>(dataFromBuffer)], readOffset);
}
// update what's actually in the read data buffer.
m_lastReadStream = stream;
m_lastReadStreamOffset = zstdData->m_decompressNextOffset;
m_lastReadStreamSize = dataFromBuffer + numReadFromStream;
return m_lastReadStreamSize;
return false;
}
struct ZStdCompareUpper
zstdData->m_seekPoints.resize(hdr->m_numSeekPoints);
SizeType dataToRead = sizeof(CompressorZStdSeekPoint) * static_cast<SizeType>(hdr->m_numSeekPoints);
SizeType seekPointOffset = compressedFileEnd - dataToRead;
if (seekPointOffset > compressedFileEnd)
{
bool operator()(const AZ::u64& offset, const CompressorZStdSeekPoint& sp) const
{
return offset < sp.m_uncompressedOffset;
}
};
AZ_Error("CompressorZStd", false, "We have an invalid archive, this is impossible.");
return false;
}
CompressorZStd::SizeType CompressorZStd::Read(CompressorStream* stream, SizeType byteSize, SizeType offset, void* buffer)
GenericStream* baseStream = stream->GetWrappedStream();
if (baseStream->ReadAtOffset(dataToRead, zstdData->m_seekPoints.data(), seekPointOffset) != dataToRead)
{
AZ_Assert(stream->GetCompressorData(), "This stream doesn't have decompression enabled.");
CompressorZStdData* zstdData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
AZ_Assert(!zstdData->m_zstd.IsCompressorStarted(), "You can't read/decompress while writing a compressed stream %s.");
return false;
}
// check if the request can be finished from the decompressed cache
SizeType numRead = FillFromDecompressCache(zstdData, buffer, byteSize, offset);
if (byteSize == 0) // are we done
if (m_decompressionCachePerStream)
{
zstdData->m_decompressedCache = reinterpret_cast<unsigned char*>(azmalloc(m_decompressionCachePerStream, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZStd"));
}
zstdData->m_decompressLastOffset = seekPointOffset; // set the start address of the seek points as the last valid read address for the compressed stream.
zstdData->m_zstd.StartDecompressor();
stream->SetCompressorData(zstdData.release());
return true;
}
bool CompressorZStd::WriteHeaderAndData(CompressorStream* stream)
{
if (!Compressor::WriteHeaderAndData(stream))
{
return false;
}
CompressorZStdData* compressorData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
CompressorZStdHeader header;
header.m_numSeekPoints = aznumeric_caster(compressorData->m_seekPoints.size());
GenericStream* baseStream = stream->GetWrappedStream();
if (baseStream->WriteAtOffset(sizeof(header), &header, sizeof(CompressorHeader)) == sizeof(header))
{
return true;
}
return false;
}
inline CompressorZStd::SizeType CompressorZStd::FillFromDecompressCache(CompressorZStdData* zstdData, void*& buffer, SizeType& byteSize, SizeType& offset)
{
SizeType firstOffsetInCache = zstdData->m_decompressedCacheOffset;
SizeType lastOffsetInCache = firstOffsetInCache + zstdData->m_decompressedCacheDataSize;
SizeType firstDataOffset = offset;
SizeType lastDataOffset = offset + byteSize;
SizeType numCopied = 0;
if (firstOffsetInCache < lastDataOffset && lastOffsetInCache >= firstDataOffset) // check if there is data in the cache
{
size_t copyOffsetStart = 0;
size_t copyOffsetEnd = zstdData->m_decompressedCacheDataSize;
size_t bufferCopyOffset = 0;
if (firstOffsetInCache < firstDataOffset)
{
return numRead;
copyOffsetStart = aznumeric_caster(firstDataOffset - firstOffsetInCache);
}
else
{
bufferCopyOffset = aznumeric_caster(firstOffsetInCache - firstDataOffset);
}
// find the best seek point for current offset
CompressorZStdData::SeekPointArray::iterator it = AZStd::upper_bound(zstdData->m_seekPoints.begin(), zstdData->m_seekPoints.end(), offset, ZStdCompareUpper());
AZ_Assert(it != zstdData->m_seekPoints.begin(), "This should be impossible, we should always have a valid seek point at 0 offset.");
const CompressorZStdSeekPoint& bestSeekPoint = *(--it); // get the previous (so it includes the current offset)
// if read is continuous continue with decompression
bool isJumpToSeekPoint = false;
SizeType lastOffsetInCache = zstdData->m_decompressedCacheOffset + zstdData->m_decompressedCacheDataSize;
if (bestSeekPoint.m_uncompressedOffset > lastOffsetInCache) // if the best seek point is forward, jump forward to it.
if (lastOffsetInCache >= lastDataOffset)
{
isJumpToSeekPoint = true;
copyOffsetEnd -= static_cast<size_t>(lastOffsetInCache - lastDataOffset);
}
else if (offset < lastOffsetInCache) // if the seek point is in the past and the requested offset is not in the cache jump back to it.
else if (bufferCopyOffset > 0) // the cache block is in the middle of the data, we can't use it (since we need to split buffer request into 2)
{
isJumpToSeekPoint = true;
return 0;
}
if (isJumpToSeekPoint)
{
zstdData->m_decompressNextOffset = bestSeekPoint.m_compressedOffset; // set next read point
zstdData->m_decompressedCacheOffset = bestSeekPoint.m_uncompressedOffset; // set uncompressed offset
zstdData->m_decompressedCacheDataSize = 0; // invalidate the cache
zstdData->m_zstd.ResetDecompressor(&zstdData->m_zstdHeader); // reset decompressor and setup the header.
}
numCopied = copyOffsetEnd - copyOffsetStart;
memcpy(static_cast<char*>(buffer) + bufferCopyOffset, zstdData->m_decompressedCache + copyOffsetStart, static_cast<size_t>(numCopied));
// decompress and move forward until the request is done
while (byteSize > 0)
// adjust pointers and sizes
byteSize -= numCopied;
if (bufferCopyOffset == 0)
{
// fill buffer with compressed data
SizeType compressedDataSize = FillCompressedBuffer(stream);
if (compressedDataSize == 0)
// copied in the start
buffer = reinterpret_cast<char*>(buffer) + numCopied;
offset += numCopied;
}
}
return numCopied;
}
inline CompressorZStd::SizeType CompressorZStd::FillCompressedBuffer(CompressorStream* stream)
{
CompressorZStdData* zstdData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
SizeType dataFromBuffer = 0;
if (stream == m_lastReadStream) // if the buffer is filled with data from the current stream, try to reuse
{
if (zstdData->m_decompressNextOffset > m_lastReadStreamOffset)
{
SizeType offsetInCache = zstdData->m_decompressNextOffset - m_lastReadStreamOffset;
if (offsetInCache < m_lastReadStreamSize) // last check if there is data overlap
{
return numRead; // we are done reading and obviously we did not managed to read all data
// copy the usable part at the start of the buffer
SizeType toMove = m_lastReadStreamSize - offsetInCache;
memcpy(m_compressedDataBuffer, &m_compressedDataBuffer[static_cast<size_t>(offsetInCache)], static_cast<size_t>(toMove));
dataFromBuffer += toMove;
}
unsigned int processedCompressedData = 0;
while (byteSize > 0 && processedCompressedData < compressedDataSize) // decompressed data either until we are done with the request (byteSize == 0) or we need to fill the compression buffer again.
{
// if we have data in the cache move to the next offset, we always move forward by default.
zstdData->m_decompressedCacheOffset += zstdData->m_decompressedCacheDataSize;
// decompress in the cache buffer
u32 availDecompressedCacheSize = m_decompressionCachePerStream; // reset buffer size
size_t nextBlockSize;
unsigned int processed = zstdData->m_zstd.Decompress(&m_compressedDataBuffer[processedCompressedData],
static_cast<unsigned int>(compressedDataSize) - processedCompressedData,
zstdData->m_decompressedCache,
availDecompressedCacheSize,
&nextBlockSize);
zstdData->m_decompressedCacheDataSize = m_decompressionCachePerStream - availDecompressedCacheSize;
if (processed == 0)
{
break; // we processed everything we could, load more compressed data.
}
processedCompressedData += processed;
// fill what we can from the cache
numRead += FillFromDecompressCache(zstdData, buffer, byteSize, offset);
}
// update next read position the the compressed stream
zstdData->m_decompressNextOffset += processedCompressedData;
}
}
SizeType toReadFromStream = m_compressedDataBufferSize - dataFromBuffer;
SizeType readOffset = zstdData->m_decompressNextOffset + dataFromBuffer;
if (readOffset + toReadFromStream > zstdData->m_decompressLastOffset)
{
// don't read past the end
AZ_Assert(readOffset <= zstdData->m_decompressLastOffset, "Read offset should always be before the end of stream.");
toReadFromStream = zstdData->m_decompressLastOffset - readOffset;
}
SizeType numReadFromStream = 0;
if (toReadFromStream) // if we did not reuse the whole buffer, read some data from the stream
{
GenericStream* baseStream = stream->GetWrappedStream();
numReadFromStream = baseStream->ReadAtOffset(toReadFromStream, &m_compressedDataBuffer[static_cast<size_t>(dataFromBuffer)], readOffset);
}
// update what's actually in the read data buffer.
m_lastReadStream = stream;
m_lastReadStreamOffset = zstdData->m_decompressNextOffset;
m_lastReadStreamSize = dataFromBuffer + numReadFromStream;
return m_lastReadStreamSize;
}
struct ZStdCompareUpper
{
bool operator()(const AZ::u64& offset, const CompressorZStdSeekPoint& sp) const
{
return offset < sp.m_uncompressedOffset;
}
};
CompressorZStd::SizeType CompressorZStd::Read(CompressorStream* stream, SizeType byteSize, SizeType offset, void* buffer)
{
AZ_Assert(stream->GetCompressorData(), "This stream doesn't have decompression enabled.");
CompressorZStdData* zstdData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
AZ_Assert(!zstdData->m_zstd.IsCompressorStarted(), "You can't read/decompress while writing a compressed stream %s.");
// check if the request can be finished from the decompressed cache
SizeType numRead = FillFromDecompressCache(zstdData, buffer, byteSize, offset);
if (byteSize == 0) // are we done
{
return numRead;
}
CompressorZStd::SizeType CompressorZStd::Write(CompressorStream* stream, SizeType byteSize, const void* data, SizeType offset)
// find the best seek point for current offset
CompressorZStdData::SeekPointArray::iterator it = AZStd::upper_bound(zstdData->m_seekPoints.begin(), zstdData->m_seekPoints.end(), offset, ZStdCompareUpper());
AZ_Assert(it != zstdData->m_seekPoints.begin(), "This should be impossible, we should always have a valid seek point at 0 offset.");
const CompressorZStdSeekPoint& bestSeekPoint = *(--it); // get the previous (so it includes the current offset)
// if read is continuous continue with decompression
bool isJumpToSeekPoint = false;
SizeType lastOffsetInCache = zstdData->m_decompressedCacheOffset + zstdData->m_decompressedCacheDataSize;
if (bestSeekPoint.m_uncompressedOffset > lastOffsetInCache) // if the best seek point is forward, jump forward to it.
{
AZ_UNUSED(offset);
isJumpToSeekPoint = true;
}
else if (offset < lastOffsetInCache) // if the seek point is in the past and the requested offset is not in the cache jump back to it.
{
isJumpToSeekPoint = true;
}
AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled. Call Stream::WriteCompressed after you create the file.");
AZ_Assert(offset == SizeType(-1) || offset == stream->GetCurPos(), "We can write compressed data only at the end of the stream.");
if (isJumpToSeekPoint)
{
zstdData->m_decompressNextOffset = bestSeekPoint.m_compressedOffset; // set next read point
zstdData->m_decompressedCacheOffset = bestSeekPoint.m_uncompressedOffset; // set uncompressed offset
zstdData->m_decompressedCacheDataSize = 0; // invalidate the cache
zstdData->m_zstd.ResetDecompressor(&zstdData->m_zstdHeader); // reset decompressor and setup the header.
}
m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it).
CompressorZStdData* zstdData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
AZ_Assert(!zstdData->m_zstd.IsDecompressorStarted(), "You can't write while reading/decompressing a compressed stream.");
const u8* bytes = reinterpret_cast<const u8*>(data);
unsigned int dataToCompress = aznumeric_caster(byteSize);
while (dataToCompress != 0)
// decompress and move forward until the request is done
while (byteSize > 0)
{
// fill buffer with compressed data
SizeType compressedDataSize = FillCompressedBuffer(stream);
if (compressedDataSize == 0)
{
unsigned int oldDataToCompress = dataToCompress;
unsigned int compressedSize = zstdData->m_zstd.Compress(bytes, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize);
if (compressedSize)
{
GenericStream* baseStream = stream->GetWrappedStream();
SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer);
if (numWritten != compressedSize)
{
return numWritten; // error we could not write all data
}
}
bytes += oldDataToCompress - dataToCompress;
return numRead; // we are done reading and obviously we did not managed to read all data
}
zstdData->m_uncompressedSize += byteSize;
if (zstdData->m_autoSeekSize > 0)
unsigned int processedCompressedData = 0;
while (byteSize > 0 && processedCompressedData < compressedDataSize) // decompressed data either until we are done with the request (byteSize == 0) or we need to fill the compression buffer again.
{
// insert a seek point if needed.
if (zstdData->m_seekPoints.empty())
// if we have data in the cache move to the next offset, we always move forward by default.
zstdData->m_decompressedCacheOffset += zstdData->m_decompressedCacheDataSize;
// decompress in the cache buffer
u32 availDecompressedCacheSize = m_decompressionCachePerStream; // reset buffer size
size_t nextBlockSize;
unsigned int processed = zstdData->m_zstd.Decompress(&m_compressedDataBuffer[processedCompressedData],
static_cast<unsigned int>(compressedDataSize) - processedCompressedData,
zstdData->m_decompressedCache,
availDecompressedCacheSize,
&nextBlockSize);
zstdData->m_decompressedCacheDataSize = m_decompressionCachePerStream - availDecompressedCacheSize;
if (processed == 0)
{
if (zstdData->m_uncompressedSize >= zstdData->m_autoSeekSize)
{
WriteSeekPoint(stream);
}
break; // we processed everything we could, load more compressed data.
}
else if ((zstdData->m_uncompressedSize - zstdData->m_seekPoints.back().m_uncompressedOffset) > zstdData->m_autoSeekSize)
processedCompressedData += processed;
// fill what we can from the cache
numRead += FillFromDecompressCache(zstdData, buffer, byteSize, offset);
}
// update next read position the the compressed stream
zstdData->m_decompressNextOffset += processedCompressedData;
}
return numRead;
}
CompressorZStd::SizeType CompressorZStd::Write(CompressorStream* stream, SizeType byteSize, const void* data, SizeType offset)
{
AZ_UNUSED(offset);
AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled. Call Stream::WriteCompressed after you create the file.");
AZ_Assert(offset == SizeType(-1) || offset == stream->GetCurPos(), "We can write compressed data only at the end of the stream.");
m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it).
CompressorZStdData* zstdData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
AZ_Assert(!zstdData->m_zstd.IsDecompressorStarted(), "You can't write while reading/decompressing a compressed stream.");
const u8* bytes = reinterpret_cast<const u8*>(data);
unsigned int dataToCompress = aznumeric_caster(byteSize);
while (dataToCompress != 0)
{
unsigned int oldDataToCompress = dataToCompress;
unsigned int compressedSize = zstdData->m_zstd.Compress(bytes, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize);
if (compressedSize)
{
GenericStream* baseStream = stream->GetWrappedStream();
SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer);
if (numWritten != compressedSize)
{
return numWritten; // error we could not write all data
}
}
bytes += oldDataToCompress - dataToCompress;
}
zstdData->m_uncompressedSize += byteSize;
if (zstdData->m_autoSeekSize > 0)
{
// insert a seek point if needed.
if (zstdData->m_seekPoints.empty())
{
if (zstdData->m_uncompressedSize >= zstdData->m_autoSeekSize)
{
WriteSeekPoint(stream);
}
}
return byteSize;
else if ((zstdData->m_uncompressedSize - zstdData->m_seekPoints.back().m_uncompressedOffset) > zstdData->m_autoSeekSize)
{
WriteSeekPoint(stream);
}
}
return byteSize;
}
bool CompressorZStd::WriteSeekPoint(CompressorStream* stream)
bool CompressorZStd::WriteSeekPoint(CompressorStream* stream)
{
AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled. Call Stream::WriteCompressed after you create the file.");
CompressorZStdData* zstdData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it).
unsigned int compressedSize;
unsigned int dataToCompress = 0;
do
{
AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled. Call Stream::WriteCompressed after you create the file.");
CompressorZStdData* zstdData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
compressedSize = zstdData->m_zstd.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZStd::FT_FULL_FLUSH);
if (compressedSize)
{
GenericStream* baseStream = stream->GetWrappedStream();
SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer);
if (numWritten != compressedSize)
{
return false; // error we wrote less than than requested!
}
}
} while (dataToCompress != 0);
CompressorZStdSeekPoint sp;
sp.m_compressedOffset = stream->GetLength();
sp.m_uncompressedOffset = zstdData->m_uncompressedSize;
zstdData->m_seekPoints.push_back(sp);
return true;
}
bool CompressorZStd::StartCompressor(CompressorStream* stream, int compressionLevel, SizeType autoSeekDataSize)
{
AZ_Assert(stream && !stream->GetCompressorData(), "Stream has compressor already enabled.");
AcquireDataBuffer();
CompressorZStdData* zstdData = aznew CompressorZStdData;
zstdData->m_compressor = this;
zstdData->m_zstdHeader = 0; // not used for compression
zstdData->m_uncompressedSize = 0;
zstdData->m_autoSeekSize = autoSeekDataSize;
compressionLevel = AZ::GetClamp(compressionLevel, 1, 9); // remap to zlib levels
zstdData->m_zstd.StartCompressor(compressionLevel);
stream->SetCompressorData(zstdData);
if (WriteHeaderAndData(stream))
{
// add the first and always present seek point at the start of the compressed stream
CompressorZStdSeekPoint sp;
sp.m_compressedOffset = sizeof(CompressorHeader) + sizeof(CompressorZStdHeader) + sizeof(zstdData->m_zstdHeader);
sp.m_uncompressedOffset = 0;
zstdData->m_seekPoints.push_back(sp);
return true;
}
return false;
}
bool CompressorZStd::Close(CompressorStream* stream)
{
AZ_Assert(stream->IsOpen(), "Stream is not open to be closed.");
CompressorZStdData* zstdData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
GenericStream* baseStream = stream->GetWrappedStream();
bool result = true;
if (zstdData->m_zstd.IsCompressorStarted())
{
m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it).
// flush all compressed data
unsigned int compressedSize;
unsigned int dataToCompress = 0;
do
{
compressedSize = zstdData->m_zstd.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZStd::FT_FULL_FLUSH);
compressedSize = zstdData->m_zstd.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZStd::FT_FINISH);
if (compressedSize)
{
GenericStream* baseStream = stream->GetWrappedStream();
SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer);
if (numWritten != compressedSize)
{
return false; // error we wrote less than than requested!
}
baseStream->Write(compressedSize, m_compressedDataBuffer);
}
} while (dataToCompress != 0);
CompressorZStdSeekPoint sp;
sp.m_compressedOffset = stream->GetLength();
sp.m_uncompressedOffset = zstdData->m_uncompressedSize;
zstdData->m_seekPoints.push_back(sp);
return true;
result = WriteHeaderAndData(stream);
if (result)
{
SizeType dataToWrite = zstdData->m_seekPoints.size() * sizeof(CompressorZStdSeekPoint);
baseStream->Seek(0U, GenericStream::SeekMode::ST_SEEK_END);
result = (baseStream->Write(dataToWrite, zstdData->m_seekPoints.data()) == dataToWrite);
}
}
bool CompressorZStd::StartCompressor(CompressorStream* stream, int compressionLevel, SizeType autoSeekDataSize)
else
{
AZ_Assert(stream && !stream->GetCompressorData(), "Stream has compressor already enabled.");
AcquireDataBuffer();
CompressorZStdData* zstdData = aznew CompressorZStdData;
zstdData->m_compressor = this;
zstdData->m_zstdHeader = 0; // not used for compression
zstdData->m_uncompressedSize = 0;
zstdData->m_autoSeekSize = autoSeekDataSize;
compressionLevel = AZ::GetClamp(compressionLevel, 1, 9); // remap to zlib levels
zstdData->m_zstd.StartCompressor(compressionLevel);
stream->SetCompressorData(zstdData);
if (WriteHeaderAndData(stream))
if (m_lastReadStream == stream)
{
// add the first and always present seek point at the start of the compressed stream
CompressorZStdSeekPoint sp;
sp.m_compressedOffset = sizeof(CompressorHeader) + sizeof(CompressorZStdHeader) + sizeof(zstdData->m_zstdHeader);
sp.m_uncompressedOffset = 0;
zstdData->m_seekPoints.push_back(sp);
return true;
m_lastReadStream = nullptr; // invalidate the data in m_dataBuffer if it was from the current stream.
}
return false;
}
bool CompressorZStd::Close(CompressorStream* stream)
// if we have decompressor cache delete it
if (zstdData->m_decompressedCache)
{
AZ_Assert(stream->IsOpen(), "Stream is not open to be closed.");
CompressorZStdData* zstdData = static_cast<CompressorZStdData*>(stream->GetCompressorData());
GenericStream* baseStream = stream->GetWrappedStream();
bool result = true;
if (zstdData->m_zstd.IsCompressorStarted())
{
m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it).
// flush all compressed data
unsigned int compressedSize;
unsigned int dataToCompress = 0;
do
{
compressedSize = zstdData->m_zstd.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZStd::FT_FINISH);
if (compressedSize)
{
baseStream->Write(compressedSize, m_compressedDataBuffer);
}
} while (dataToCompress != 0);
result = WriteHeaderAndData(stream);
if (result)
{
SizeType dataToWrite = zstdData->m_seekPoints.size() * sizeof(CompressorZStdSeekPoint);
baseStream->Seek(0U, GenericStream::SeekMode::ST_SEEK_END);
result = (baseStream->Write(dataToWrite, zstdData->m_seekPoints.data()) == dataToWrite);
}
}
else
{
if (m_lastReadStream == stream)
{
m_lastReadStream = nullptr; // invalidate the data in m_dataBuffer if it was from the current stream.
}
}
// if we have decompressor cache delete it
if (zstdData->m_decompressedCache)
{
azfree(zstdData->m_decompressedCache, AZ::SystemAllocator, m_decompressionCachePerStream, m_CompressedDataBufferAlignment);
}
ReleaseDataBuffer();
// last step reset strream compressor data.
stream->SetCompressorData(nullptr);
return result;
azfree(zstdData->m_decompressedCache, AZ::SystemAllocator, m_decompressionCachePerStream, m_CompressedDataBufferAlignment);
}
void CompressorZStd::AcquireDataBuffer()
ReleaseDataBuffer();
// last step reset strream compressor data.
stream->SetCompressorData(nullptr);
return result;
}
void CompressorZStd::AcquireDataBuffer()
{
if (m_compressedDataBuffer == nullptr)
{
if (m_compressedDataBuffer == nullptr)
{
AZ_Assert(m_compressedDataBufferUseCount == 0, "Buffer usecount should be 0 if the buffer is NULL");
m_compressedDataBuffer = reinterpret_cast<unsigned char*>(azmalloc(m_compressedDataBufferSize, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZStd"));
m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer
}
++m_compressedDataBufferUseCount;
AZ_Assert(m_compressedDataBufferUseCount == 0, "Buffer usecount should be 0 if the buffer is NULL");
m_compressedDataBuffer = reinterpret_cast<unsigned char*>(azmalloc(m_compressedDataBufferSize, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZStd"));
m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer
}
++m_compressedDataBufferUseCount;
}
void CompressorZStd::ReleaseDataBuffer()
void CompressorZStd::ReleaseDataBuffer()
{
--m_compressedDataBufferUseCount;
if (m_compressedDataBufferUseCount == 0)
{
--m_compressedDataBufferUseCount;
if (m_compressedDataBufferUseCount == 0)
{
AZ_Assert(m_compressedDataBuffer, "Invalid data buffer. We should have a non null pointer.");
azfree(m_compressedDataBuffer, AZ::SystemAllocator, m_compressedDataBufferSize, m_CompressedDataBufferAlignment);
m_compressedDataBuffer = nullptr;
m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer
}
AZ_Assert(m_compressedDataBuffer, "Invalid data buffer. We should have a non null pointer.");
azfree(m_compressedDataBuffer, AZ::SystemAllocator, m_compressedDataBufferSize, m_CompressedDataBufferAlignment);
m_compressedDataBuffer = nullptr;
m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer
}
} // namespace IO
} // namespace AZ
}
} // namespace AZ::IO
#endif // #if !defined(AZCORE_EXCLUDE_ZSTD)
+392 -395
View File
@@ -21,485 +21,482 @@
# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
#endif
namespace AZ
namespace AZ::IO
{
namespace IO
static EnvironmentVariable<FileIOBase*> g_fileIOInstance;
static EnvironmentVariable<FileIOBase*> g_directFileIOInstance;
static const char* s_EngineFileIOName = "EngineFileIO";
static const char* s_DirectFileIOName = "DirectFileIO";
FileIOBase* FileIOBase::GetInstance()
{
static EnvironmentVariable<FileIOBase*> g_fileIOInstance;
static EnvironmentVariable<FileIOBase*> g_directFileIOInstance;
static const char* s_EngineFileIOName = "EngineFileIO";
static const char* s_DirectFileIOName = "DirectFileIO";
FileIOBase* FileIOBase::GetInstance()
if (!g_fileIOInstance)
{
if (!g_fileIOInstance)
{
g_fileIOInstance = Environment::FindVariable<FileIOBase*>(s_EngineFileIOName);
}
return g_fileIOInstance ? (*g_fileIOInstance) : nullptr;
g_fileIOInstance = Environment::FindVariable<FileIOBase*>(s_EngineFileIOName);
}
void FileIOBase::SetInstance(FileIOBase* instance)
return g_fileIOInstance ? (*g_fileIOInstance) : nullptr;
}
void FileIOBase::SetInstance(FileIOBase* instance)
{
if (!g_fileIOInstance)
{
if (!g_fileIOInstance)
{
g_fileIOInstance = Environment::CreateVariable<FileIOBase*>(s_EngineFileIOName);
(*g_fileIOInstance) = nullptr;
}
// at this point we're guaranteed to have g_fileIOInstance. Its value might be null.
if ((instance) && (g_fileIOInstance) && (*g_fileIOInstance))
{
AZ_Error("FileIO", false, "FileIOBase::SetInstance was called without first destroying the old instance and setting it to nullptr");
}
(*g_fileIOInstance) = instance;
g_fileIOInstance = Environment::CreateVariable<FileIOBase*>(s_EngineFileIOName);
(*g_fileIOInstance) = nullptr;
}
FileIOBase* FileIOBase::GetDirectInstance()
// at this point we're guaranteed to have g_fileIOInstance. Its value might be null.
if ((instance) && (g_fileIOInstance) && (*g_fileIOInstance))
{
if (!g_directFileIOInstance)
{
g_directFileIOInstance = Environment::FindVariable<FileIOBase*>(s_DirectFileIOName);
}
// for backwards compatibilty, return the regular instance if this is not attached
if (!g_directFileIOInstance)
{
return GetInstance();
}
return g_directFileIOInstance ? (*g_directFileIOInstance) : nullptr;
AZ_Error("FileIO", false, "FileIOBase::SetInstance was called without first destroying the old instance and setting it to nullptr");
}
void FileIOBase::SetDirectInstance(FileIOBase* instance)
(*g_fileIOInstance) = instance;
}
FileIOBase* FileIOBase::GetDirectInstance()
{
if (!g_directFileIOInstance)
{
if (!g_directFileIOInstance)
{
g_directFileIOInstance = Environment::CreateVariable<FileIOBase*>(s_DirectFileIOName);
(*g_directFileIOInstance) = nullptr;
}
// at this point we're guaranteed to have g_directFileIOInstance. Its value might be null.
if ((instance) && (g_directFileIOInstance) && (*g_directFileIOInstance))
{
AZ_Error("FileIO", false, "FileIOBase::SetDirectInstance was called without first destroying the old instance and setting it to nullptr");
}
(*g_directFileIOInstance) = instance;
g_directFileIOInstance = Environment::FindVariable<FileIOBase*>(s_DirectFileIOName);
}
AZStd::optional<AZ::IO::FixedMaxPath> FileIOBase::ConvertToAlias(const AZ::IO::PathView& path) const
// for backwards compatibilty, return the regular instance if this is not attached
if (!g_directFileIOInstance)
{
AZ::IO::FixedMaxPath convertedPath;
if (ConvertToAlias(convertedPath, path))
{
return convertedPath;
}
return AZStd::nullopt;
return GetInstance();
}
AZStd::optional<AZ::IO::FixedMaxPath> FileIOBase::ResolvePath(const AZ::IO::PathView& path) const
{
AZ::IO::FixedMaxPath resolvedPath;
if (ResolvePath(resolvedPath, path))
{
return resolvedPath;
}
return g_directFileIOInstance ? (*g_directFileIOInstance) : nullptr;
}
return AZStd::nullopt;
void FileIOBase::SetDirectInstance(FileIOBase* instance)
{
if (!g_directFileIOInstance)
{
g_directFileIOInstance = Environment::CreateVariable<FileIOBase*>(s_DirectFileIOName);
(*g_directFileIOInstance) = nullptr;
}
SeekType GetSeekTypeFromFSeekMode(int mode)
{
switch (mode)
{
case SEEK_SET:
return SeekType::SeekFromStart;
case SEEK_CUR:
return SeekType::SeekFromCurrent;
case SEEK_END:
return SeekType::SeekFromEnd;
}
// at this point we're guaranteed to have g_directFileIOInstance. Its value might be null.
// Must have some default, hitting here means some random int mode
if ((instance) && (g_directFileIOInstance) && (*g_directFileIOInstance))
{
AZ_Error("FileIO", false, "FileIOBase::SetDirectInstance was called without first destroying the old instance and setting it to nullptr");
}
(*g_directFileIOInstance) = instance;
}
AZStd::optional<AZ::IO::FixedMaxPath> FileIOBase::ConvertToAlias(const AZ::IO::PathView& path) const
{
AZ::IO::FixedMaxPath convertedPath;
if (ConvertToAlias(convertedPath, path))
{
return convertedPath;
}
return AZStd::nullopt;
}
AZStd::optional<AZ::IO::FixedMaxPath> FileIOBase::ResolvePath(const AZ::IO::PathView& path) const
{
AZ::IO::FixedMaxPath resolvedPath;
if (ResolvePath(resolvedPath, path))
{
return resolvedPath;
}
return AZStd::nullopt;
}
SeekType GetSeekTypeFromFSeekMode(int mode)
{
switch (mode)
{
case SEEK_SET:
return SeekType::SeekFromStart;
case SEEK_CUR:
return SeekType::SeekFromCurrent;
case SEEK_END:
return SeekType::SeekFromEnd;
}
int GetFSeekModeFromSeekType(SeekType type)
{
switch (type)
{
case SeekType::SeekFromStart:
return SEEK_SET;
case SeekType::SeekFromCurrent:
return SEEK_CUR;
case SeekType::SeekFromEnd:
return SEEK_END;
}
// Must have some default, hitting here means some random int mode
return SeekType::SeekFromStart;
}
int GetFSeekModeFromSeekType(SeekType type)
{
switch (type)
{
case SeekType::SeekFromStart:
return SEEK_SET;
case SeekType::SeekFromCurrent:
return SEEK_CUR;
case SeekType::SeekFromEnd:
return SEEK_END;
}
void UpdateOpenModeForReading(OpenMode& openMode)
return SEEK_SET;
}
void UpdateOpenModeForReading(OpenMode& openMode)
{
if (AnyFlag(openMode & OpenMode::ModeRead))
{
if (AnyFlag(openMode & OpenMode::ModeRead))
if (AnyFlag(openMode & OpenMode::ModeText))
{
if (AnyFlag(openMode & OpenMode::ModeText))
{
OpenMode extraModes = openMode & (OpenMode::ModeUpdate | OpenMode::ModeAppend);
openMode = OpenMode::ModeRead | OpenMode::ModeBinary | extraModes;
}
else if (!AnyFlag(openMode & OpenMode::ModeBinary))
{
// if you haven't supplied any flag, supply binary
openMode = openMode | OpenMode::ModeBinary;
}
OpenMode extraModes = openMode & (OpenMode::ModeUpdate | OpenMode::ModeAppend);
openMode = OpenMode::ModeRead | OpenMode::ModeBinary | extraModes;
}
else if (!AnyFlag(openMode & OpenMode::ModeBinary))
{
// if you haven't supplied any flag, supply binary
openMode = openMode | OpenMode::ModeBinary;
}
}
}
OpenMode GetOpenModeFromStringMode(const char* mode)
OpenMode GetOpenModeFromStringMode(const char* mode)
{
OpenMode openMode = OpenMode::Invalid;
if (strstr(mode, "w"))
{
OpenMode openMode = OpenMode::Invalid;
if (strstr(mode, "w"))
{
openMode |= OpenMode::ModeWrite;
}
if (strstr(mode, "r"))
{
openMode |= OpenMode::ModeRead;
}
if (strstr(mode, "a"))
{
openMode |= OpenMode::ModeAppend;
}
if (strstr(mode, "b"))
{
openMode |= OpenMode::ModeBinary;
}
if (strstr(mode, "t"))
{
openMode |= OpenMode::ModeText;
}
if (strstr(mode, "+"))
{
openMode |= OpenMode::ModeUpdate;
}
UpdateOpenModeForReading(openMode);
return openMode;
openMode |= OpenMode::ModeWrite;
}
const char* GetStringModeFromOpenMode(OpenMode mode)
if (strstr(mode, "r"))
{
UpdateOpenModeForReading(mode);
// Append is highest priority, followed by write and then read
// APPEND
if (AnyFlag(mode & OpenMode::ModeAppend))
openMode |= OpenMode::ModeRead;
}
if (strstr(mode, "a"))
{
openMode |= OpenMode::ModeAppend;
}
if (strstr(mode, "b"))
{
openMode |= OpenMode::ModeBinary;
}
if (strstr(mode, "t"))
{
openMode |= OpenMode::ModeText;
}
if (strstr(mode, "+"))
{
openMode |= OpenMode::ModeUpdate;
}
UpdateOpenModeForReading(openMode);
return openMode;
}
const char* GetStringModeFromOpenMode(OpenMode mode)
{
UpdateOpenModeForReading(mode);
// Append is highest priority, followed by write and then read
// APPEND
if (AnyFlag(mode & OpenMode::ModeAppend))
{
if (AnyFlag(mode & OpenMode::ModeUpdate))
{
if (AnyFlag(mode & OpenMode::ModeUpdate))
{
if (AnyFlag(mode & OpenMode::ModeBinary))
{
return "a+b";
}
if (AnyFlag(mode & OpenMode::ModeText))
{
return "a+t";
}
return "a+";
}
if (AnyFlag(mode & OpenMode::ModeBinary))
{
return "ab";
return "a+b";
}
if (AnyFlag(mode & OpenMode::ModeText))
{
return "at";
return "a+t";
}
return "a";
return "a+";
}
// WRITE
if (AnyFlag(mode & OpenMode::ModeWrite))
if (AnyFlag(mode & OpenMode::ModeBinary))
{
return "ab";
}
if (AnyFlag(mode & OpenMode::ModeText))
{
return "at";
}
return "a";
}
// WRITE
if (AnyFlag(mode & OpenMode::ModeWrite))
{
if (AnyFlag(mode & OpenMode::ModeUpdate))
{
if (AnyFlag(mode & OpenMode::ModeUpdate))
{
if (AnyFlag(mode & OpenMode::ModeBinary))
{
return "w+b";
}
if (AnyFlag(mode & OpenMode::ModeText))
{
return "w+t";
}
return "w+";
}
if (AnyFlag(mode & OpenMode::ModeBinary))
{
return "wb";
return "w+b";
}
if (AnyFlag(mode & OpenMode::ModeText))
{
return "wt";
return "w+t";
}
return "w";
return "w+";
}
// READ
if (AnyFlag(mode & OpenMode::ModeRead))
if (AnyFlag(mode & OpenMode::ModeBinary))
{
return "wb";
}
if (AnyFlag(mode & OpenMode::ModeText))
{
return "wt";
}
return "w";
}
// READ
if (AnyFlag(mode & OpenMode::ModeRead))
{
if (AnyFlag(mode & OpenMode::ModeUpdate))
{
if (AnyFlag(mode & OpenMode::ModeUpdate))
{
if (AnyFlag(mode & OpenMode::ModeBinary))
{
return "r+b";
}
if (AnyFlag(mode & OpenMode::ModeText))
{
return "r+t";
}
return "r+";
}
if (AnyFlag(mode & OpenMode::ModeBinary))
{
return "rb";
return "r+b";
}
if (AnyFlag(mode & OpenMode::ModeText))
{
return "rt";
return "r+t";
}
return "r";
return "r+";
}
// Bad open mode passed in
AZ_Error("FileIO", false, "A bad open mode was sent to GetStringModeFromOpenMode()");
return "";
if (AnyFlag(mode & OpenMode::ModeBinary))
{
return "rb";
}
if (AnyFlag(mode & OpenMode::ModeText))
{
return "rt";
}
return "r";
}
bool NameMatchesFilter(const char* name, const char* filter)
// Bad open mode passed in
AZ_Error("FileIO", false, "A bad open mode was sent to GetStringModeFromOpenMode()");
return "";
}
bool NameMatchesFilter(const char* name, const char* filter)
{
return AZStd::wildcard_match(filter, name);
}
FileIOStream::FileIOStream()
: m_handle(InvalidHandle)
, m_mode(OpenMode::Invalid)
, m_ownsHandle(true)
{
}
FileIOStream::FileIOStream(HandleType fileHandle, AZ::IO::OpenMode mode, bool ownsHandle)
: m_handle(fileHandle)
, m_mode(mode)
, m_ownsHandle(ownsHandle)
{
FileIOBase* fileIO = FileIOBase::GetInstance();
AZ_Assert(fileIO, "FileIO is not initialized.");
AZStd::array<char, MaxPathLength> resolvedPath{ {0} };
fileIO->GetFilename(m_handle, resolvedPath.data(), resolvedPath.size() - 1);
m_filename = resolvedPath.data();
}
FileIOStream::FileIOStream(const char* path, AZ::IO::OpenMode mode, bool errorOnFailure)
: m_handle(InvalidHandle)
, m_mode(mode)
, m_errorOnFailure(errorOnFailure)
{
Open(path, mode);
}
FileIOStream::~FileIOStream()
{
if (m_ownsHandle)
{
return AZStd::wildcard_match(filter, name);
Close();
}
}
FileIOStream::FileIOStream()
: m_handle(InvalidHandle)
, m_mode(OpenMode::Invalid)
, m_ownsHandle(true)
bool FileIOStream::Open(const char* path, OpenMode mode)
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
FileIOBase* fileIO = FileIOBase::GetInstance();
Close();
const Result result = fileIO->Open(path, mode, m_handle);
m_ownsHandle = IsOpen();
m_mode = mode;
if (IsOpen())
{
}
FileIOStream::FileIOStream(HandleType fileHandle, AZ::IO::OpenMode mode, bool ownsHandle)
: m_handle(fileHandle)
, m_mode(mode)
, m_ownsHandle(ownsHandle)
{
FileIOBase* fileIO = FileIOBase::GetInstance();
AZ_Assert(fileIO, "FileIO is not initialized.");
// Not using supplied path parameter as it may be unresolved
AZStd::array<char, MaxPathLength> resolvedPath{ {0} };
fileIO->GetFilename(m_handle, resolvedPath.data(), resolvedPath.size() - 1);
m_filename = resolvedPath.data();
}
FileIOStream::FileIOStream(const char* path, AZ::IO::OpenMode mode, bool errorOnFailure)
: m_handle(InvalidHandle)
, m_mode(mode)
, m_errorOnFailure(errorOnFailure)
else
{
Open(path, mode);
// remember the file name so you can try again with ReOpen
m_filename = path;
}
FileIOStream::~FileIOStream()
{
if (m_ownsHandle)
{
Close();
}
}
AZ_PROFILE_INTERVAL_START_COLORED(AzCore, &m_filename, 0xff0000ff, "FileIO: %s", m_filename.c_str());
return result;
}
bool FileIOStream::Open(const char* path, OpenMode mode)
bool FileIOStream::ReOpen()
{
Close();
return (m_mode != OpenMode::Invalid) ? Open(m_filename.data(), m_mode) : false;
}
void FileIOStream::Close()
{
if (m_handle != InvalidHandle)
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
FileIOBase* fileIO = FileIOBase::GetInstance();
Close();
const Result result = fileIO->Open(path, mode, m_handle);
m_ownsHandle = IsOpen();
m_mode = mode;
if (IsOpen())
{
// Not using supplied path parameter as it may be unresolved
AZStd::array<char, MaxPathLength> resolvedPath{ {0} };
fileIO->GetFilename(m_handle, resolvedPath.data(), resolvedPath.size() - 1);
m_filename = resolvedPath.data();
}
else
{
// remember the file name so you can try again with ReOpen
m_filename = path;
}
AZ_PROFILE_INTERVAL_START_COLORED(AzCore, &m_filename, 0xff0000ff, "FileIO: %s", m_filename.c_str());
return result;
FileIOBase::GetInstance()->Close(m_handle);
m_handle = InvalidHandle;
m_ownsHandle = false;
AZ_PROFILE_INTERVAL_END(AzCore, &m_filename);
}
}
bool FileIOStream::ReOpen()
bool FileIOStream::IsOpen() const
{
return (m_handle != InvalidHandle);
}
/*!
\brief Retrieves underlying FileIO Handle from file stream
\return HandleType
*/
HandleType FileIOStream::GetHandle() const
{
return m_handle;
}
/*!
\brief Retrieves filename
\return const char*
*/
const char* FileIOStream::GetFilename() const
{
return m_filename.data();
}
/*!
\brief Retrieves OpenMode flags used to open this file
\return OpenMode
*/
AZ::IO::OpenMode FileIOStream::GetModeFlags() const
{
return m_mode;
}
bool FileIOStream::CanSeek() const
{
return true;
}
bool FileIOStream::CanRead() const
{
return (m_mode & (OpenMode::ModeRead | OpenMode::ModeUpdate)) != OpenMode::Invalid;
}
bool FileIOStream::CanWrite() const
{
return (m_mode & (OpenMode::ModeWrite | OpenMode::ModeAppend | OpenMode::ModeUpdate)) != OpenMode::Invalid;
}
void FileIOStream::Seek(OffsetType bytes, SeekMode mode)
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
AZ_Assert(IsOpen(), "Cannot seek on a FileIOStream that is not open.");
SeekType seekType = SeekType::SeekFromCurrent;
switch (mode)
{
Close();
return (m_mode != OpenMode::Invalid) ? Open(m_filename.data(), m_mode) : false;
case GenericStream::ST_SEEK_BEGIN:
seekType = SeekType::SeekFromStart;
break;
case GenericStream::ST_SEEK_CUR:
seekType = SeekType::SeekFromCurrent;
break;
case GenericStream::ST_SEEK_END:
seekType = SeekType::SeekFromEnd;
break;
default:
seekType = SeekType::SeekFromCurrent;
break;
}
void FileIOStream::Close()
const Result result = FileIOBase::GetInstance()->Seek(m_handle, static_cast<AZ::s64>(bytes), seekType);
(void)result;
AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Seek failed.");
}
SizeType FileIOStream::Read(SizeType bytes, void* oBuffer)
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
AZ_Assert(IsOpen(), "Cannot read from a FileIOStream that is not open.");
AZ::u64 bytesRead = 0;
const Result result = FileIOBase::GetInstance()->Read(m_handle, oBuffer, bytes, m_errorOnFailure, &bytesRead);
(void)result;
AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Read failed in file %s.", m_filename.empty() ? "NULL" : m_filename.c_str());
return static_cast<SizeType>(bytesRead);
}
SizeType FileIOStream::Write(SizeType bytes, const void* iBuffer)
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
AZ_Assert(IsOpen(), "Cannot write to a FileIOStream that is not open.");
AZ::u64 bytesWritten = 0;
const Result result = FileIOBase::GetInstance()->Write(m_handle, iBuffer, bytes, &bytesWritten);
(void)result;
AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Write failed.");
return static_cast<SizeType>(bytesWritten);
}
SizeType FileIOStream::GetCurPos() const
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
AZ_Assert(IsOpen(), "Cannot use a FileIOStream that is not open.");
AZ::u64 currentPosition = 0;
const Result result = FileIOBase::GetInstance()->Tell(m_handle, currentPosition);
(void)result;
AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "GetCurPos failed.");
return static_cast<SizeType>(currentPosition);
}
SizeType FileIOStream::GetLength() const
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
AZ_Assert(IsOpen(), "Cannot use a FileIOStream that is not open.");
SizeType fileLengthBytes = 0;
if (!FileIOBase::GetInstance()->Size(m_handle, fileLengthBytes))
{
if (m_handle != InvalidHandle)
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
FileIOBase::GetInstance()->Close(m_handle);
m_handle = InvalidHandle;
m_ownsHandle = false;
AZ_PROFILE_INTERVAL_END(AzCore, &m_filename);
}
AZ_Error("FileIOStream", false, "GetLength failed.");
}
bool FileIOStream::IsOpen() const
{
return (m_handle != InvalidHandle);
}
return fileLengthBytes;
}
/*!
\brief Retrieves underlying FileIO Handle from file stream
\return HandleType
*/
HandleType FileIOStream::GetHandle() const
{
return m_handle;
}
/*!
\brief Retrieves filename
\return const char*
*/
const char* FileIOStream::GetFilename() const
{
return m_filename.data();
}
/*!
\brief Retrieves OpenMode flags used to open this file
\return OpenMode
*/
AZ::IO::OpenMode FileIOStream::GetModeFlags() const
{
return m_mode;
}
bool FileIOStream::CanSeek() const
{
return true;
}
bool FileIOStream::CanRead() const
{
return (m_mode & (OpenMode::ModeRead | OpenMode::ModeUpdate)) != OpenMode::Invalid;
}
bool FileIOStream::CanWrite() const
{
return (m_mode & (OpenMode::ModeWrite | OpenMode::ModeAppend | OpenMode::ModeUpdate)) != OpenMode::Invalid;
}
void FileIOStream::Seek(OffsetType bytes, SeekMode mode)
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
AZ_Assert(IsOpen(), "Cannot seek on a FileIOStream that is not open.");
SeekType seekType = SeekType::SeekFromCurrent;
switch (mode)
{
case GenericStream::ST_SEEK_BEGIN:
seekType = SeekType::SeekFromStart;
break;
case GenericStream::ST_SEEK_CUR:
seekType = SeekType::SeekFromCurrent;
break;
case GenericStream::ST_SEEK_END:
seekType = SeekType::SeekFromEnd;
break;
default:
seekType = SeekType::SeekFromCurrent;
break;
}
const Result result = FileIOBase::GetInstance()->Seek(m_handle, static_cast<AZ::s64>(bytes), seekType);
(void)result;
AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Seek failed.");
}
SizeType FileIOStream::Read(SizeType bytes, void* oBuffer)
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
AZ_Assert(IsOpen(), "Cannot read from a FileIOStream that is not open.");
AZ::u64 bytesRead = 0;
const Result result = FileIOBase::GetInstance()->Read(m_handle, oBuffer, bytes, m_errorOnFailure, &bytesRead);
(void)result;
AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Read failed in file %s.", m_filename.empty() ? "NULL" : m_filename.c_str());
return static_cast<SizeType>(bytesRead);
}
SizeType FileIOStream::Write(SizeType bytes, const void* iBuffer)
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
AZ_Assert(IsOpen(), "Cannot write to a FileIOStream that is not open.");
AZ::u64 bytesWritten = 0;
const Result result = FileIOBase::GetInstance()->Write(m_handle, iBuffer, bytes, &bytesWritten);
(void)result;
AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Write failed.");
return static_cast<SizeType>(bytesWritten);
}
SizeType FileIOStream::GetCurPos() const
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
AZ_Assert(IsOpen(), "Cannot use a FileIOStream that is not open.");
AZ::u64 currentPosition = 0;
const Result result = FileIOBase::GetInstance()->Tell(m_handle, currentPosition);
(void)result;
AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "GetCurPos failed.");
return static_cast<SizeType>(currentPosition);
}
SizeType FileIOStream::GetLength() const
{
AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized.");
AZ_Assert(IsOpen(), "Cannot use a FileIOStream that is not open.");
SizeType fileLengthBytes = 0;
if (!FileIOBase::GetInstance()->Size(m_handle, fileLengthBytes))
{
AZ_Error("FileIOStream", false, "GetLength failed.");
}
return fileLengthBytes;
}
} // namespace IO
} // namespace AZ
} // namespace AZ::IO
+52 -57
View File
@@ -10,69 +10,64 @@
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/parallel/thread.h> /// this_thread sleep_for.
namespace AZ
namespace AZ::IO
{
namespace IO
{
int TranslateOpenModeToSystemFileMode(const char* path, OpenMode mode)
int TranslateOpenModeToSystemFileMode(const char* path, OpenMode mode)
{
int systemFileMode = 0;
bool read = AnyFlag(mode & OpenMode::ModeRead) || AnyFlag(mode & OpenMode::ModeUpdate);
bool write = AnyFlag(mode & OpenMode::ModeWrite) || AnyFlag(mode & OpenMode::ModeUpdate) || AnyFlag(mode & OpenMode::ModeAppend);
if (write)
{
int systemFileMode = 0;
bool read = AnyFlag(mode & OpenMode::ModeRead) || AnyFlag(mode & OpenMode::ModeUpdate);
bool write = AnyFlag(mode & OpenMode::ModeWrite) || AnyFlag(mode & OpenMode::ModeUpdate) || AnyFlag(mode & OpenMode::ModeAppend);
if (write)
// If writing the file, create the file in all cases (except r+)
if (!SystemFile::Exists(path) && !(AnyFlag(mode & OpenMode::ModeRead) && AnyFlag(mode & OpenMode::ModeUpdate)))
{
// If writing the file, create the file in all cases (except r+)
if (!SystemFile::Exists(path) && !(AnyFlag(mode & OpenMode::ModeRead) && AnyFlag(mode & OpenMode::ModeUpdate)))
{
// LocalFileIO creates by default
systemFileMode |= SystemFile::SF_OPEN_CREATE;
}
if (AnyFlag(mode & OpenMode::ModeCreatePath))
{
systemFileMode |= SystemFile::SF_OPEN_CREATE_PATH;
}
// If appending, append.
if (AnyFlag(mode & OpenMode::ModeAppend))
{
systemFileMode |= SystemFile::SF_OPEN_APPEND;
}
// If writing and not appending, empty the file
else if (AnyFlag(mode & OpenMode::ModeWrite))
{
systemFileMode |= SystemFile::SF_OPEN_TRUNCATE;
}
// If reading, set read/write, otherwise just write
if (read)
{
systemFileMode |= SystemFile::SF_OPEN_READ_WRITE;
}
else
{
systemFileMode |= SystemFile::SF_OPEN_WRITE_ONLY;
}
}
else if (read)
{
systemFileMode |= SystemFile::SF_OPEN_READ_ONLY;
// LocalFileIO creates by default
systemFileMode |= SystemFile::SF_OPEN_CREATE;
}
return systemFileMode;
if (AnyFlag(mode & OpenMode::ModeCreatePath))
{
systemFileMode |= SystemFile::SF_OPEN_CREATE_PATH;
}
// If appending, append.
if (AnyFlag(mode & OpenMode::ModeAppend))
{
systemFileMode |= SystemFile::SF_OPEN_APPEND;
}
// If writing and not appending, empty the file
else if (AnyFlag(mode & OpenMode::ModeWrite))
{
systemFileMode |= SystemFile::SF_OPEN_TRUNCATE;
}
// If reading, set read/write, otherwise just write
if (read)
{
systemFileMode |= SystemFile::SF_OPEN_READ_WRITE;
}
else
{
systemFileMode |= SystemFile::SF_OPEN_WRITE_ONLY;
}
}
else if (read)
{
systemFileMode |= SystemFile::SF_OPEN_READ_ONLY;
}
bool RetryOpenStream(FileIOStream& stream, int numRetries, int delayBetweenRetry)
return systemFileMode;
}
bool RetryOpenStream(FileIOStream& stream, int numRetries, int delayBetweenRetry)
{
while ((!stream.IsOpen()) && (numRetries > 0))
{
while ((!stream.IsOpen()) && (numRetries > 0))
{
numRetries--;
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(delayBetweenRetry));
stream.ReOpen();
}
return stream.IsOpen();
numRetries--;
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(delayBetweenRetry));
stream.ReOpen();
}
} // namespace IO
} // namespace AZ
return stream.IsOpen();
}
} // namespace AZ::IO
File diff suppressed because it is too large Load Diff
@@ -12,320 +12,317 @@
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/make_shared.h>
namespace AZ
namespace AZ::IO
{
namespace IO
AZStd::shared_ptr<StreamStackEntry> DedicatedCacheConfig::AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent)
{
AZStd::shared_ptr<StreamStackEntry> DedicatedCacheConfig::AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent)
size_t blockSize;
switch (m_blockSize)
{
size_t blockSize;
switch (m_blockSize)
case BlockCacheConfig::BlockSize::MaxTransfer:
blockSize = hardware.m_maxTransfer;
break;
case BlockCacheConfig::BlockSize::MemoryAlignment:
blockSize = hardware.m_maxPhysicalSectorSize;
break;
case BlockCacheConfig::BlockSize::SizeAlignment:
blockSize = hardware.m_maxLogicalSectorSize;
break;
default:
blockSize = m_blockSize;
break;
}
u32 cacheSize = static_cast<AZ::u32>(m_cacheSizeMib * 1_mib);
if (blockSize > cacheSize)
{
AZ_Warning("Streamer", false, "Size (%u) for DedicatedCache isn't big enough to hold at least one cache blocks of size (%zu). "
"The cache size will be increased to fit one cache block.", cacheSize, blockSize);
cacheSize = aznumeric_caster(blockSize);
}
auto stackEntry = AZStd::make_shared<DedicatedCache>(
cacheSize, aznumeric_cast<AZ::u32>(blockSize), aznumeric_cast<AZ::u32>(hardware.m_maxPhysicalSectorSize), m_writeOnlyEpilog);
stackEntry->SetNext(AZStd::move(parent));
return stackEntry;
}
void DedicatedCacheConfig::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<DedicatedCacheConfig, IStreamerStackConfig>()
->Version(1)
->Field("CacheSizeMib", &DedicatedCacheConfig::m_cacheSizeMib)
->Field("BlockSize", &DedicatedCacheConfig::m_blockSize)
->Field("WriteOnlyEpilog", &DedicatedCacheConfig::m_writeOnlyEpilog);
}
}
//
// DedicatedCache
//
DedicatedCache::DedicatedCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites)
: StreamStackEntry("Dedicated cache")
, m_cacheSize(cacheSize)
, m_alignment(alignment)
, m_blockSize(blockSize)
, m_onlyEpilogWrites(onlyEpilogWrites)
{
}
void DedicatedCache::SetNext(AZStd::shared_ptr<StreamStackEntry> next)
{
m_next = AZStd::move(next);
for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
cache->SetNext(m_next);
}
}
void DedicatedCache::SetContext(StreamerContext& context)
{
StreamStackEntry::SetContext(context);
for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
cache->SetContext(context);
}
}
void DedicatedCache::PrepareRequest(FileRequest* request)
{
AZ_Assert(request, "PrepareRequest was provided a null request.");
// Claim the requests so other entries can't claim it and make updates.
AZStd::visit([this, request](auto&& args)
{
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, FileRequest::CreateDedicatedCacheData>)
{
case BlockCacheConfig::BlockSize::MaxTransfer:
blockSize = hardware.m_maxTransfer;
break;
case BlockCacheConfig::BlockSize::MemoryAlignment:
blockSize = hardware.m_maxPhysicalSectorSize;
break;
case BlockCacheConfig::BlockSize::SizeAlignment:
blockSize = hardware.m_maxLogicalSectorSize;
break;
default:
blockSize = m_blockSize;
break;
args.m_range = FileRange::CreateRangeForEntireFile();
m_context->PushPreparedRequest(request);
}
u32 cacheSize = static_cast<AZ::u32>(m_cacheSizeMib * 1_mib);
if (blockSize > cacheSize)
else if constexpr (AZStd::is_same_v<Command, FileRequest::DestroyDedicatedCacheData>)
{
AZ_Warning("Streamer", false, "Size (%u) for DedicatedCache isn't big enough to hold at least one cache blocks of size (%zu). "
"The cache size will be increased to fit one cache block.", cacheSize, blockSize);
cacheSize = aznumeric_caster(blockSize);
}
auto stackEntry = AZStd::make_shared<DedicatedCache>(
cacheSize, aznumeric_cast<AZ::u32>(blockSize), aznumeric_cast<AZ::u32>(hardware.m_maxPhysicalSectorSize), m_writeOnlyEpilog);
stackEntry->SetNext(AZStd::move(parent));
return stackEntry;
}
void DedicatedCacheConfig::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Class<DedicatedCacheConfig, IStreamerStackConfig>()
->Version(1)
->Field("CacheSizeMib", &DedicatedCacheConfig::m_cacheSizeMib)
->Field("BlockSize", &DedicatedCacheConfig::m_blockSize)
->Field("WriteOnlyEpilog", &DedicatedCacheConfig::m_writeOnlyEpilog);
}
}
//
// DedicatedCache
//
DedicatedCache::DedicatedCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites)
: StreamStackEntry("Dedicated cache")
, m_cacheSize(cacheSize)
, m_alignment(alignment)
, m_blockSize(blockSize)
, m_onlyEpilogWrites(onlyEpilogWrites)
{
}
void DedicatedCache::SetNext(AZStd::shared_ptr<StreamStackEntry> next)
{
m_next = AZStd::move(next);
for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
cache->SetNext(m_next);
}
}
void DedicatedCache::SetContext(StreamerContext& context)
{
StreamStackEntry::SetContext(context);
for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
cache->SetContext(context);
}
}
void DedicatedCache::PrepareRequest(FileRequest* request)
{
AZ_Assert(request, "PrepareRequest was provided a null request.");
// Claim the requests so other entries can't claim it and make updates.
AZStd::visit([this, request](auto&& args)
{
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, FileRequest::CreateDedicatedCacheData>)
{
args.m_range = FileRange::CreateRangeForEntireFile();
m_context->PushPreparedRequest(request);
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::DestroyDedicatedCacheData>)
{
args.m_range = FileRange::CreateRangeForEntireFile();
m_context->PushPreparedRequest(request);
}
else
{
StreamStackEntry::PrepareRequest(request);
}
}, request->GetCommand());
}
void DedicatedCache::QueueRequest(FileRequest* request)
{
AZ_Assert(request, "QueueRequest was provided a null request.");
AZStd::visit([this, request](auto&& args)
{
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData>)
{
ReadFile(request, args);
return;
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::CreateDedicatedCacheData>)
{
CreateDedicatedCache(request, args);
return;
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::DestroyDedicatedCacheData>)
{
DestroyDedicatedCache(request, args);
return;
}
else
{
if constexpr (AZStd::is_same_v<Command, FileRequest::FlushData>)
{
FlushCache(args.m_path);
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::FlushAllData>)
{
FlushEntireCache();
}
StreamStackEntry::QueueRequest(request);
}
}, request->GetCommand());
}
bool DedicatedCache::ExecuteRequests()
{
bool hasProcessedRequest = false;
for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
hasProcessedRequest = cache->ExecuteRequests() || hasProcessedRequest;
}
return StreamStackEntry::ExecuteRequests() || hasProcessedRequest;
}
void DedicatedCache::UpdateStatus(Status& status) const
{
// Available slots are not updated because the dedicated caches are often
// small and specific to a tiny subset of files that are loaded. It would therefore
// return a small number of slots that would needlessly hamper streaming as it doesn't
// apply to the majority of files.
bool isIdle = true;
for (auto& cache : m_cachedFileCaches)
{
Status blockStatus;
cache->UpdateStatus(blockStatus);
isIdle = isIdle && blockStatus.m_isIdle;
}
status.m_isIdle = status.m_isIdle && isIdle;
StreamStackEntry::UpdateStatus(status);
}
void DedicatedCache::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now,
AZStd::vector<FileRequest*>& internalPending, StreamerContext::PreparedQueue::iterator pendingBegin,
StreamerContext::PreparedQueue::iterator pendingEnd)
{
for (auto& cache : m_cachedFileCaches)
{
cache->AddDelayedRequests(internalPending);
}
StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd);
for (auto& cache : m_cachedFileCaches)
{
cache->UpdatePendingRequestEstimations();
}
}
void DedicatedCache::ReadFile(FileRequest* request, FileRequest::ReadData& data)
{
size_t index = FindCache(data.m_path, data.m_offset);
if (index == s_fileNotFound)
{
m_usagePercentageStat.PushSample(0.0);
if (m_next)
{
m_next->QueueRequest(request);
}
args.m_range = FileRange::CreateRangeForEntireFile();
m_context->PushPreparedRequest(request);
}
else
{
m_usagePercentageStat.PushSample(1.0);
BlockCache& cache = *m_cachedFileCaches[index];
cache.QueueRequest(request);
#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
m_overallHitRateStat.PushSample(cache.CalculateHitRatePercentage());
m_overallCacheableRateStat.PushSample(cache.CalculateCacheableRatePercentage());
#endif
StreamStackEntry::PrepareRequest(request);
}
}
}, request->GetCommand());
}
void DedicatedCache::FlushCache(const RequestPath& filePath)
void DedicatedCache::QueueRequest(FileRequest* request)
{
AZ_Assert(request, "QueueRequest was provided a null request.");
AZStd::visit([this, request](auto&& args)
{
size_t count = m_cachedFileNames.size();
for (size_t i = 0; i < count; ++i)
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData>)
{
if (m_cachedFileNames[i] == filePath)
{
// Flush the entire block cache as it's entirely dedicated to the found file.
m_cachedFileCaches[i]->FlushEntireCache();
}
ReadFile(request, args);
return;
}
}
void DedicatedCache::FlushEntireCache()
{
for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
else if constexpr (AZStd::is_same_v<Command, FileRequest::CreateDedicatedCacheData>)
{
cache->FlushEntireCache();
CreateDedicatedCache(request, args);
return;
}
}
void DedicatedCache::CollectStatistics(AZStd::vector<Statistic>& statistics) const
{
statistics.push_back(Statistic::CreatePercentage(m_name, "Reads from dedicated cache", m_usagePercentageStat.GetAverage()));
#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
statistics.push_back(Statistic::CreatePercentage(m_name, "Overall cacheable rate", m_overallCacheableRateStat.GetAverage()));
statistics.push_back(Statistic::CreatePercentage(m_name, "Overall hit rate", m_overallHitRateStat.GetAverage()));
#endif
statistics.push_back(Statistic::CreateInteger(m_name, "Num dedicated caches", aznumeric_caster(m_cachedFileNames.size())));
StreamStackEntry::CollectStatistics(statistics);
}
void DedicatedCache::CreateDedicatedCache(FileRequest* request, FileRequest::CreateDedicatedCacheData& data)
{
size_t index = FindCache(data.m_path, data.m_range);
if (index == s_fileNotFound)
else if constexpr (AZStd::is_same_v<Command, FileRequest::DestroyDedicatedCacheData>)
{
index = m_cachedFileCaches.size();
m_cachedFileNames.push_back(data.m_path);
m_cachedFileRanges.push_back(data.m_range);
m_cachedFileCaches.push_back(AZStd::make_unique<BlockCache>(m_cacheSize, m_blockSize, m_alignment, m_onlyEpilogWrites));
m_cachedFileCaches[index]->SetNext(m_next);
m_cachedFileCaches[index]->SetContext(*m_context);
m_cachedFileRefCounts.push_back(1);
DestroyDedicatedCache(request, args);
return;
}
else
{
++m_cachedFileRefCounts[index];
if constexpr (AZStd::is_same_v<Command, FileRequest::FlushData>)
{
FlushCache(args.m_path);
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::FlushAllData>)
{
FlushEntireCache();
}
StreamStackEntry::QueueRequest(request);
}
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
m_context->MarkRequestAsCompleted(request);
}, request->GetCommand());
}
bool DedicatedCache::ExecuteRequests()
{
bool hasProcessedRequest = false;
for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
hasProcessedRequest = cache->ExecuteRequests() || hasProcessedRequest;
}
return StreamStackEntry::ExecuteRequests() || hasProcessedRequest;
}
void DedicatedCache::UpdateStatus(Status& status) const
{
// Available slots are not updated because the dedicated caches are often
// small and specific to a tiny subset of files that are loaded. It would therefore
// return a small number of slots that would needlessly hamper streaming as it doesn't
// apply to the majority of files.
bool isIdle = true;
for (auto& cache : m_cachedFileCaches)
{
Status blockStatus;
cache->UpdateStatus(blockStatus);
isIdle = isIdle && blockStatus.m_isIdle;
}
status.m_isIdle = status.m_isIdle && isIdle;
StreamStackEntry::UpdateStatus(status);
}
void DedicatedCache::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now,
AZStd::vector<FileRequest*>& internalPending, StreamerContext::PreparedQueue::iterator pendingBegin,
StreamerContext::PreparedQueue::iterator pendingEnd)
{
for (auto& cache : m_cachedFileCaches)
{
cache->AddDelayedRequests(internalPending);
}
void DedicatedCache::DestroyDedicatedCache(FileRequest* request, FileRequest::DestroyDedicatedCacheData& data)
{
size_t index = FindCache(data.m_path, data.m_range);
if (index != s_fileNotFound)
{
if (m_cachedFileRefCounts[index] > 0)
{
--m_cachedFileRefCounts[index];
if (m_cachedFileRefCounts[index] == 0)
{
m_cachedFileNames.erase(m_cachedFileNames.begin() + index);
m_cachedFileRanges.erase(m_cachedFileRanges.begin() + index);
m_cachedFileCaches.erase(m_cachedFileCaches.begin() + index);
m_cachedFileRefCounts.erase(m_cachedFileRefCounts.begin() + index);
}
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
m_context->MarkRequestAsCompleted(request);
return;
}
}
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
m_context->MarkRequestAsCompleted(request);
}
StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd);
size_t DedicatedCache::FindCache(const RequestPath& filename, FileRange range)
for (auto& cache : m_cachedFileCaches)
{
size_t count = m_cachedFileNames.size();
for (size_t i = 0; i < count; ++i)
{
if (m_cachedFileNames[i] == filename && m_cachedFileRanges[i] == range)
{
return i;
}
}
return s_fileNotFound;
cache->UpdatePendingRequestEstimations();
}
}
size_t DedicatedCache::FindCache(const RequestPath& filename, u64 offset)
void DedicatedCache::ReadFile(FileRequest* request, FileRequest::ReadData& data)
{
size_t index = FindCache(data.m_path, data.m_offset);
if (index == s_fileNotFound)
{
size_t count = m_cachedFileNames.size();
for (size_t i = 0; i < count; ++i)
m_usagePercentageStat.PushSample(0.0);
if (m_next)
{
if (m_cachedFileNames[i] == filename && m_cachedFileRanges[i].IsInRange(offset))
{
return i;
}
m_next->QueueRequest(request);
}
return s_fileNotFound;
}
} // namespace IO
} // namespace AZ
else
{
m_usagePercentageStat.PushSample(1.0);
BlockCache& cache = *m_cachedFileCaches[index];
cache.QueueRequest(request);
#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
m_overallHitRateStat.PushSample(cache.CalculateHitRatePercentage());
m_overallCacheableRateStat.PushSample(cache.CalculateCacheableRatePercentage());
#endif
}
}
void DedicatedCache::FlushCache(const RequestPath& filePath)
{
size_t count = m_cachedFileNames.size();
for (size_t i = 0; i < count; ++i)
{
if (m_cachedFileNames[i] == filePath)
{
// Flush the entire block cache as it's entirely dedicated to the found file.
m_cachedFileCaches[i]->FlushEntireCache();
}
}
}
void DedicatedCache::FlushEntireCache()
{
for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
cache->FlushEntireCache();
}
}
void DedicatedCache::CollectStatistics(AZStd::vector<Statistic>& statistics) const
{
statistics.push_back(Statistic::CreatePercentage(m_name, "Reads from dedicated cache", m_usagePercentageStat.GetAverage()));
#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
statistics.push_back(Statistic::CreatePercentage(m_name, "Overall cacheable rate", m_overallCacheableRateStat.GetAverage()));
statistics.push_back(Statistic::CreatePercentage(m_name, "Overall hit rate", m_overallHitRateStat.GetAverage()));
#endif
statistics.push_back(Statistic::CreateInteger(m_name, "Num dedicated caches", aznumeric_caster(m_cachedFileNames.size())));
StreamStackEntry::CollectStatistics(statistics);
}
void DedicatedCache::CreateDedicatedCache(FileRequest* request, FileRequest::CreateDedicatedCacheData& data)
{
size_t index = FindCache(data.m_path, data.m_range);
if (index == s_fileNotFound)
{
index = m_cachedFileCaches.size();
m_cachedFileNames.push_back(data.m_path);
m_cachedFileRanges.push_back(data.m_range);
m_cachedFileCaches.push_back(AZStd::make_unique<BlockCache>(m_cacheSize, m_blockSize, m_alignment, m_onlyEpilogWrites));
m_cachedFileCaches[index]->SetNext(m_next);
m_cachedFileCaches[index]->SetContext(*m_context);
m_cachedFileRefCounts.push_back(1);
}
else
{
++m_cachedFileRefCounts[index];
}
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
m_context->MarkRequestAsCompleted(request);
}
void DedicatedCache::DestroyDedicatedCache(FileRequest* request, FileRequest::DestroyDedicatedCacheData& data)
{
size_t index = FindCache(data.m_path, data.m_range);
if (index != s_fileNotFound)
{
if (m_cachedFileRefCounts[index] > 0)
{
--m_cachedFileRefCounts[index];
if (m_cachedFileRefCounts[index] == 0)
{
m_cachedFileNames.erase(m_cachedFileNames.begin() + index);
m_cachedFileRanges.erase(m_cachedFileRanges.begin() + index);
m_cachedFileCaches.erase(m_cachedFileCaches.begin() + index);
m_cachedFileRefCounts.erase(m_cachedFileRefCounts.begin() + index);
}
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
m_context->MarkRequestAsCompleted(request);
return;
}
}
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
m_context->MarkRequestAsCompleted(request);
}
size_t DedicatedCache::FindCache(const RequestPath& filename, FileRange range)
{
size_t count = m_cachedFileNames.size();
for (size_t i = 0; i < count; ++i)
{
if (m_cachedFileNames[i] == filename && m_cachedFileRanges[i] == range)
{
return i;
}
}
return s_fileNotFound;
}
size_t DedicatedCache::FindCache(const RequestPath& filename, u64 offset)
{
size_t count = m_cachedFileNames.size();
for (size_t i = 0; i < count; ++i)
{
if (m_cachedFileNames[i] == filename && m_cachedFileRanges[i].IsInRange(offset))
{
return i;
}
}
return s_fileNotFound;
}
} // namespace AZ::IO
@@ -8,104 +8,101 @@
#include <AzCore/IO/Streamer/FileRange.h>
namespace AZ
namespace AZ::IO
{
namespace IO
FileRange FileRange::CreateRange(u64 offset, u64 size)
{
FileRange FileRange::CreateRange(u64 offset, u64 size)
{
FileRange result;
result.m_hasOffsetEndSet = true;
result.m_isEntireFile = false;
result.m_offsetBegin = offset;
result.m_offsetEnd = offset + size;
return result;
}
FileRange result;
result.m_hasOffsetEndSet = true;
result.m_isEntireFile = false;
result.m_offsetBegin = offset;
result.m_offsetEnd = offset + size;
return result;
}
FileRange FileRange::CreateRangeForEntireFile()
{
FileRange result;
result.m_hasOffsetEndSet = false;
result.m_isEntireFile = true;
result.m_offsetBegin = 0;
result.m_offsetEnd = (static_cast<u64>(1) << 63) - 1;
return result;
}
FileRange FileRange::CreateRangeForEntireFile()
{
FileRange result;
result.m_hasOffsetEndSet = false;
result.m_isEntireFile = true;
result.m_offsetBegin = 0;
result.m_offsetEnd = (static_cast<u64>(1) << 63) - 1;
return result;
}
FileRange FileRange::CreateRangeForEntireFile(u64 fileSize)
{
FileRange result;
result.m_hasOffsetEndSet = true;
result.m_isEntireFile = true;
result.m_offsetBegin = 0;
result.m_offsetEnd = fileSize;
return result;
}
FileRange FileRange::CreateRangeForEntireFile(u64 fileSize)
{
FileRange result;
result.m_hasOffsetEndSet = true;
result.m_isEntireFile = true;
result.m_offsetBegin = 0;
result.m_offsetEnd = fileSize;
return result;
}
FileRange::FileRange()
: m_isEntireFile(false)
, m_offsetBegin(0)
, m_hasOffsetEndSet(false)
, m_offsetEnd(0)
{
}
FileRange::FileRange()
: m_isEntireFile(false)
, m_offsetBegin(0)
, m_hasOffsetEndSet(false)
, m_offsetEnd(0)
{
}
bool FileRange::operator==(const FileRange& rhs) const
bool FileRange::operator==(const FileRange& rhs) const
{
if (m_isEntireFile)
{
if (m_isEntireFile)
{
return rhs.m_isEntireFile && m_offsetBegin == rhs.m_offsetBegin;
}
else
{
return m_offsetBegin == rhs.m_offsetBegin && m_offsetEnd == rhs.m_offsetEnd;
}
return rhs.m_isEntireFile && m_offsetBegin == rhs.m_offsetBegin;
}
else
{
return m_offsetBegin == rhs.m_offsetBegin && m_offsetEnd == rhs.m_offsetEnd;
}
}
bool FileRange::operator!=(const FileRange& rhs) const
bool FileRange::operator!=(const FileRange& rhs) const
{
if (m_isEntireFile)
{
if (m_isEntireFile)
{
return !rhs.m_isEntireFile || m_offsetBegin != rhs.m_offsetBegin;
}
else
{
return m_offsetBegin != rhs.m_offsetBegin || m_offsetEnd != rhs.m_offsetEnd;
}
return !rhs.m_isEntireFile || m_offsetBegin != rhs.m_offsetBegin;
}
else
{
return m_offsetBegin != rhs.m_offsetBegin || m_offsetEnd != rhs.m_offsetEnd;
}
}
bool FileRange::IsEntireFile() const
{
return m_isEntireFile != 0;
}
bool FileRange::IsEntireFile() const
{
return m_isEntireFile != 0;
}
bool FileRange::IsSizeKnown() const
{
// m_hasOffsetEndSet being zero has the special meaning that the file size has not
// specifically been set yet.
return m_hasOffsetEndSet != 0;
}
bool FileRange::IsSizeKnown() const
{
// m_hasOffsetEndSet being zero has the special meaning that the file size has not
// specifically been set yet.
return m_hasOffsetEndSet != 0;
}
bool FileRange::IsInRange(u64 offset) const
{
return m_offsetBegin <= offset && offset < m_offsetEnd;
}
bool FileRange::IsInRange(u64 offset) const
{
return m_offsetBegin <= offset && offset < m_offsetEnd;
}
u64 FileRange::GetOffset() const
{
return m_offsetBegin;
}
u64 FileRange::GetOffset() const
{
return m_offsetBegin;
}
u64 FileRange::GetSize() const
{
AZ_Assert(m_hasOffsetEndSet, "Calling GetSize on a FileRange that doesn't have a size specified.");
return m_offsetEnd - m_offsetBegin;
}
u64 FileRange::GetSize() const
{
AZ_Assert(m_hasOffsetEndSet, "Calling GetSize on a FileRange that doesn't have a size specified.");
return m_offsetEnd - m_offsetBegin;
}
u64 FileRange::GetEndPoint() const
{
AZ_Assert(m_hasOffsetEndSet, "Calling GetEndPoint on a FileRange that doesn't have an end offset specified.");
return m_offsetEnd;
}
} // namespace IO
} // namesapce AZ
u64 FileRange::GetEndPoint() const
{
AZ_Assert(m_hasOffsetEndSet, "Calling GetEndPoint on a FileRange that doesn't have an end offset specified.");
return m_offsetEnd;
}
} // namespace AZ::IO
@@ -12,469 +12,466 @@
#include <AzCore/IO/Streamer/FileRequest.h>
#include <AzCore/IO/Streamer/StreamerContext.h>
namespace AZ
namespace AZ::IO
{
namespace IO
//
// Command structures.
//
FileRequest::ExternalRequestData::ExternalRequestData(FileRequestPtr&& request)
: m_request(AZStd::move(request))
{}
FileRequest::RequestPathStoreData::RequestPathStoreData(RequestPath path)
: m_path(AZStd::move(path))
{}
FileRequest::ReadRequestData::ReadRequestData(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority)
: m_path(AZStd::move(path))
, m_allocator(nullptr)
, m_deadline(deadline)
, m_output(output)
, m_outputSize(outputSize)
, m_offset(offset)
, m_size(size)
, m_priority(priority)
, m_memoryType(IStreamerTypes::MemoryType::ReadWrite) // Only generic memory can be assigned externally.
{}
FileRequest::ReadRequestData::ReadRequestData(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator,
u64 offset, u64 size, AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority)
: m_path(AZStd::move(path))
, m_allocator(allocator)
, m_deadline(deadline)
, m_output(nullptr)
, m_outputSize(0)
, m_offset(offset)
, m_size(size)
, m_priority(priority)
, m_memoryType(IStreamerTypes::MemoryType::ReadWrite) // Only generic memory can be assigned externally.
{}
FileRequest::ReadRequestData::~ReadRequestData()
{
//
// Command structures.
//
FileRequest::ExternalRequestData::ExternalRequestData(FileRequestPtr&& request)
: m_request(AZStd::move(request))
{}
FileRequest::RequestPathStoreData::RequestPathStoreData(RequestPath path)
: m_path(AZStd::move(path))
{}
FileRequest::ReadRequestData::ReadRequestData(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority)
: m_path(AZStd::move(path))
, m_allocator(nullptr)
, m_deadline(deadline)
, m_output(output)
, m_outputSize(outputSize)
, m_offset(offset)
, m_size(size)
, m_priority(priority)
, m_memoryType(IStreamerTypes::MemoryType::ReadWrite) // Only generic memory can be assigned externally.
{}
FileRequest::ReadRequestData::ReadRequestData(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator,
u64 offset, u64 size, AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority)
: m_path(AZStd::move(path))
, m_allocator(allocator)
, m_deadline(deadline)
, m_output(nullptr)
, m_outputSize(0)
, m_offset(offset)
, m_size(size)
, m_priority(priority)
, m_memoryType(IStreamerTypes::MemoryType::ReadWrite) // Only generic memory can be assigned externally.
{}
FileRequest::ReadRequestData::~ReadRequestData()
if (m_allocator != nullptr)
{
if (m_allocator != nullptr)
if (m_output != nullptr)
{
if (m_output != nullptr)
{
m_allocator->Release(m_output);
}
m_allocator->UnlockAllocator();
m_allocator->Release(m_output);
}
m_allocator->UnlockAllocator();
}
}
FileRequest::ReadData::ReadData(void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead)
: m_output(output)
, m_outputSize(outputSize)
, m_path(path)
, m_offset(offset)
, m_size(size)
, m_sharedRead(sharedRead)
{}
FileRequest::ReadData::ReadData(void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead)
: m_output(output)
, m_outputSize(outputSize)
, m_path(path)
, m_offset(offset)
, m_size(size)
, m_sharedRead(sharedRead)
{}
FileRequest::CompressedReadData::CompressedReadData(CompressionInfo&& compressionInfo, void* output, u64 readOffset, u64 readSize)
: m_compressionInfo(AZStd::move(compressionInfo))
, m_output(output)
, m_readOffset(readOffset)
, m_readSize(readSize)
{}
FileRequest::CompressedReadData::CompressedReadData(CompressionInfo&& compressionInfo, void* output, u64 readOffset, u64 readSize)
: m_compressionInfo(AZStd::move(compressionInfo))
, m_output(output)
, m_readOffset(readOffset)
, m_readSize(readSize)
{}
FileRequest::FileExistsCheckData::FileExistsCheckData(const RequestPath& path)
: m_path(path)
{}
FileRequest::FileExistsCheckData::FileExistsCheckData(const RequestPath& path)
: m_path(path)
{}
FileRequest::FileMetaDataRetrievalData::FileMetaDataRetrievalData(const RequestPath& path)
: m_path(path)
{}
FileRequest::FileMetaDataRetrievalData::FileMetaDataRetrievalData(const RequestPath& path)
: m_path(path)
{}
FileRequest::CancelData::CancelData(FileRequestPtr target)
: m_target(AZStd::move(target))
{}
FileRequest::CancelData::CancelData(FileRequestPtr target)
: m_target(AZStd::move(target))
{}
FileRequest::FlushData::FlushData(RequestPath path)
: m_path(AZStd::move(path))
{}
FileRequest::FlushData::FlushData(RequestPath path)
: m_path(AZStd::move(path))
{}
FileRequest::RescheduleData::RescheduleData(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline,
IStreamerTypes::Priority newPriority)
: m_target(AZStd::move(target))
, m_newDeadline(newDeadline)
, m_newPriority(newPriority)
{}
FileRequest::RescheduleData::RescheduleData(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline,
IStreamerTypes::Priority newPriority)
: m_target(AZStd::move(target))
, m_newDeadline(newDeadline)
, m_newPriority(newPriority)
{}
FileRequest::CreateDedicatedCacheData::CreateDedicatedCacheData(RequestPath path, const FileRange& range)
: m_path(AZStd::move(path))
, m_range(range)
{}
FileRequest::CreateDedicatedCacheData::CreateDedicatedCacheData(RequestPath path, const FileRange& range)
: m_path(AZStd::move(path))
, m_range(range)
{}
FileRequest::DestroyDedicatedCacheData::DestroyDedicatedCacheData(RequestPath path, const FileRange& range)
: m_path(AZStd::move(path))
, m_range(range)
{}
FileRequest::DestroyDedicatedCacheData::DestroyDedicatedCacheData(RequestPath path, const FileRange& range)
: m_path(AZStd::move(path))
, m_range(range)
{}
FileRequest::ReportData::ReportData(ReportType reportType)
: m_reportType(reportType)
{}
FileRequest::ReportData::ReportData(ReportType reportType)
: m_reportType(reportType)
{}
FileRequest::CustomData::CustomData(AZStd::any data, bool failWhenUnhandled)
: m_data(AZStd::move(data))
, m_failWhenUnhandled(failWhenUnhandled)
{}
FileRequest::CustomData::CustomData(AZStd::any data, bool failWhenUnhandled)
: m_data(AZStd::move(data))
, m_failWhenUnhandled(failWhenUnhandled)
{}
//
// FileRequest
//
//
// FileRequest
//
FileRequest::FileRequest(Usage usage)
: m_usage(usage)
FileRequest::FileRequest(Usage usage)
: m_usage(usage)
{
Reset();
}
FileRequest::~FileRequest()
{
Reset();
}
void FileRequest::CreateRequestLink(FileRequestPtr&& request)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'RequestLink', but another task was already assigned.");
m_parent = request->m_request.m_parent;
request->m_request.m_parent = this;
m_dependencies++;
m_command.emplace<ExternalRequestData>(AZStd::move(request));
}
void FileRequest::CreateRequestPathStore(FileRequest* parent, RequestPath path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'CreateRequestPathStore', but another task was already assigned.");
m_command.emplace<RequestPathStoreData>(AZStd::move(path));
SetOptionalParent(parent);
}
void FileRequest::CreateReadRequest(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'ReadRequest', but another task was already assigned.");
m_command.emplace<ReadRequestData>(AZStd::move(path), output, outputSize, offset, size, deadline, priority);
}
void FileRequest::CreateReadRequest(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'ReadRequest', but another task was already assigned.");
m_command.emplace<ReadRequestData>(AZStd::move(path), allocator, offset, size, deadline, priority);
}
void FileRequest::CreateRead(FileRequest* parent, void* output, u64 outputSize, const RequestPath& path,
u64 offset, u64 size, bool sharedRead)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Read', but another task was already assigned.");
m_command.emplace<ReadData>(output, outputSize, AZStd::move(path), offset, size, sharedRead);
SetOptionalParent(parent);
}
void FileRequest::CreateCompressedRead(FileRequest* parent, const CompressionInfo& compressionInfo,
void* output, u64 readOffset, u64 readSize)
{
CreateCompressedRead(parent, CompressionInfo(compressionInfo), output, readOffset, readSize);
}
void FileRequest::CreateCompressedRead(FileRequest* parent, CompressionInfo&& compressionInfo,
void* output, u64 readOffset, u64 readSize)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'CompressedRead', but another task was already assigned.");
m_command.emplace<CompressedReadData>(AZStd::move(compressionInfo), output, readOffset, readSize);
SetOptionalParent(parent);
}
void FileRequest::CreateWait(FileRequest* parent)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Wait', but another task was already assigned.");
m_command.emplace<WaitData>();
SetOptionalParent(parent);
}
void FileRequest::CreateFileExistsCheck(const RequestPath& path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'FileExistsCheck', but another task was already assigned.");
m_command.emplace<FileExistsCheckData>(path);
}
void FileRequest::CreateFileMetaDataRetrieval(const RequestPath& path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'FileMetaDataRetrieval', but another task was already assigned.");
m_command.emplace<FileMetaDataRetrievalData>(path);
}
void FileRequest::CreateCancel(FileRequestPtr target)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Cancel', but another task was already assigned.");
m_command.emplace<CancelData>(AZStd::move(target));
}
void FileRequest::CreateReschedule(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline,
IStreamerTypes::Priority newPriority)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Reschedule', but another task was already assigned.");
m_command.emplace<RescheduleData>(AZStd::move(target), newDeadline, newPriority);
}
void FileRequest::CreateFlush(RequestPath path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Flush', but another task was already assigned.");
m_command.emplace<FlushData>(AZStd::move(path));
}
void FileRequest::CreateFlushAll()
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'FlushAll', but another task was already assigned.");
m_command.emplace<FlushAllData>();
}
void FileRequest::CreateDedicatedCacheCreation(RequestPath path, const FileRange& range, FileRequest* parent)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'CreateDedicateCache', but another task was already assigned.");
m_command.emplace<CreateDedicatedCacheData>(AZStd::move(path), range);
SetOptionalParent(parent);
}
void FileRequest::CreateDedicatedCacheDestruction(RequestPath path, const FileRange& range, FileRequest* parent)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'DestroyDedicateCache', but another task was already assigned.");
m_command.emplace<DestroyDedicatedCacheData>(AZStd::move(path), range);
SetOptionalParent(parent);
}
void FileRequest::CreateReport(ReportData::ReportType reportType)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Report', but another task was already assigned.");
m_command.emplace<ReportData>(reportType);
}
void FileRequest::CreateCustom(AZStd::any data, bool failWhenUnhandled, FileRequest* parent)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Custom', but another task was already assigned.");
m_command.emplace<CustomData>(AZStd::move(data), failWhenUnhandled);
SetOptionalParent(parent);
}
void FileRequest::SetCompletionCallback(OnCompletionCallback callback)
{
m_onCompletion = AZStd::move(callback);
}
FileRequest::CommandVariant& FileRequest::GetCommand()
{
return m_command;
}
const FileRequest::CommandVariant& FileRequest::GetCommand() const
{
return m_command;
}
IStreamerTypes::RequestStatus FileRequest::GetStatus() const
{
return m_status;
}
void FileRequest::SetStatus(IStreamerTypes::RequestStatus newStatus)
{
IStreamerTypes::RequestStatus currentStatus = m_status;
switch (newStatus)
{
Reset();
}
FileRequest::~FileRequest()
{
Reset();
}
void FileRequest::CreateRequestLink(FileRequestPtr&& request)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'RequestLink', but another task was already assigned.");
m_parent = request->m_request.m_parent;
request->m_request.m_parent = this;
m_dependencies++;
m_command.emplace<ExternalRequestData>(AZStd::move(request));
}
void FileRequest::CreateRequestPathStore(FileRequest* parent, RequestPath path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'CreateRequestPathStore', but another task was already assigned.");
m_command.emplace<RequestPathStoreData>(AZStd::move(path));
SetOptionalParent(parent);
}
void FileRequest::CreateReadRequest(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'ReadRequest', but another task was already assigned.");
m_command.emplace<ReadRequestData>(AZStd::move(path), output, outputSize, offset, size, deadline, priority);
}
void FileRequest::CreateReadRequest(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'ReadRequest', but another task was already assigned.");
m_command.emplace<ReadRequestData>(AZStd::move(path), allocator, offset, size, deadline, priority);
}
void FileRequest::CreateRead(FileRequest* parent, void* output, u64 outputSize, const RequestPath& path,
u64 offset, u64 size, bool sharedRead)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Read', but another task was already assigned.");
m_command.emplace<ReadData>(output, outputSize, AZStd::move(path), offset, size, sharedRead);
SetOptionalParent(parent);
}
void FileRequest::CreateCompressedRead(FileRequest* parent, const CompressionInfo& compressionInfo,
void* output, u64 readOffset, u64 readSize)
{
CreateCompressedRead(parent, CompressionInfo(compressionInfo), output, readOffset, readSize);
}
void FileRequest::CreateCompressedRead(FileRequest* parent, CompressionInfo&& compressionInfo,
void* output, u64 readOffset, u64 readSize)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'CompressedRead', but another task was already assigned.");
m_command.emplace<CompressedReadData>(AZStd::move(compressionInfo), output, readOffset, readSize);
SetOptionalParent(parent);
}
void FileRequest::CreateWait(FileRequest* parent)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Wait', but another task was already assigned.");
m_command.emplace<WaitData>();
SetOptionalParent(parent);
}
void FileRequest::CreateFileExistsCheck(const RequestPath& path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'FileExistsCheck', but another task was already assigned.");
m_command.emplace<FileExistsCheckData>(path);
}
void FileRequest::CreateFileMetaDataRetrieval(const RequestPath& path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'FileMetaDataRetrieval', but another task was already assigned.");
m_command.emplace<FileMetaDataRetrievalData>(path);
}
void FileRequest::CreateCancel(FileRequestPtr target)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Cancel', but another task was already assigned.");
m_command.emplace<CancelData>(AZStd::move(target));
}
void FileRequest::CreateReschedule(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline,
IStreamerTypes::Priority newPriority)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Reschedule', but another task was already assigned.");
m_command.emplace<RescheduleData>(AZStd::move(target), newDeadline, newPriority);
}
void FileRequest::CreateFlush(RequestPath path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Flush', but another task was already assigned.");
m_command.emplace<FlushData>(AZStd::move(path));
}
void FileRequest::CreateFlushAll()
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'FlushAll', but another task was already assigned.");
m_command.emplace<FlushAllData>();
}
void FileRequest::CreateDedicatedCacheCreation(RequestPath path, const FileRange& range, FileRequest* parent)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'CreateDedicateCache', but another task was already assigned.");
m_command.emplace<CreateDedicatedCacheData>(AZStd::move(path), range);
SetOptionalParent(parent);
}
void FileRequest::CreateDedicatedCacheDestruction(RequestPath path, const FileRange& range, FileRequest* parent)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'DestroyDedicateCache', but another task was already assigned.");
m_command.emplace<DestroyDedicatedCacheData>(AZStd::move(path), range);
SetOptionalParent(parent);
}
void FileRequest::CreateReport(ReportData::ReportType reportType)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Report', but another task was already assigned.");
m_command.emplace<ReportData>(reportType);
}
void FileRequest::CreateCustom(AZStd::any data, bool failWhenUnhandled, FileRequest* parent)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Custom', but another task was already assigned.");
m_command.emplace<CustomData>(AZStd::move(data), failWhenUnhandled);
SetOptionalParent(parent);
}
void FileRequest::SetCompletionCallback(OnCompletionCallback callback)
{
m_onCompletion = AZStd::move(callback);
}
FileRequest::CommandVariant& FileRequest::GetCommand()
{
return m_command;
}
const FileRequest::CommandVariant& FileRequest::GetCommand() const
{
return m_command;
}
IStreamerTypes::RequestStatus FileRequest::GetStatus() const
{
return m_status;
}
void FileRequest::SetStatus(IStreamerTypes::RequestStatus newStatus)
{
IStreamerTypes::RequestStatus currentStatus = m_status;
switch (newStatus)
case IStreamerTypes::RequestStatus::Pending:
[[fallthrough]];
case IStreamerTypes::RequestStatus::Queued:
[[fallthrough]];
case IStreamerTypes::RequestStatus::Processing:
if (currentStatus == IStreamerTypes::RequestStatus::Failed ||
currentStatus == IStreamerTypes::RequestStatus::Canceled ||
currentStatus == IStreamerTypes::RequestStatus::Completed)
{
case IStreamerTypes::RequestStatus::Pending:
[[fallthrough]];
case IStreamerTypes::RequestStatus::Queued:
[[fallthrough]];
case IStreamerTypes::RequestStatus::Processing:
if (currentStatus == IStreamerTypes::RequestStatus::Failed ||
currentStatus == IStreamerTypes::RequestStatus::Canceled ||
currentStatus == IStreamerTypes::RequestStatus::Completed)
{
return;
}
break;
case IStreamerTypes::RequestStatus::Completed:
if (currentStatus == IStreamerTypes::RequestStatus::Failed || currentStatus == IStreamerTypes::RequestStatus::Canceled)
{
return;
}
break;
case IStreamerTypes::RequestStatus::Canceled:
if (currentStatus == IStreamerTypes::RequestStatus::Failed || currentStatus == IStreamerTypes::RequestStatus::Completed)
{
return;
}
break;
case IStreamerTypes::RequestStatus::Failed:
[[fallthrough]];
default:
break;
return;
}
m_status = newStatus;
}
FileRequest* FileRequest::GetParent()
{
return m_parent;
}
const FileRequest* FileRequest::GetParent() const
{
return m_parent;
}
size_t FileRequest::GetNumDependencies() const
{
return m_dependencies;
}
bool FileRequest::FailsWhenUnhandled() const
{
return AZStd::visit([](auto&& args)
break;
case IStreamerTypes::RequestStatus::Completed:
if (currentStatus == IStreamerTypes::RequestStatus::Failed || currentStatus == IStreamerTypes::RequestStatus::Canceled)
{
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, AZStd::monostate>)
{
AZ_Assert(false,
"Request does not contain a valid command. It may have been reset already or was never assigned a command.");
return true;
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::CustomData>)
{
return args.m_failWhenUnhandled;
}
else
{
return Command::s_failWhenUnhandled;
}
}, m_command);
}
void FileRequest::Reset()
{
m_command = AZStd::monostate{};
m_onCompletion = &OnCompletionPlaceholder;
m_estimatedCompletion = AZStd::chrono::system_clock::time_point();
m_parent = nullptr;
m_status = IStreamerTypes::RequestStatus::Pending;
m_dependencies = 0;
}
void FileRequest::SetOptionalParent(FileRequest* parent)
{
if (parent)
{
m_parent = parent;
AZ_Assert(parent->m_dependencies < std::numeric_limits<decltype(parent->m_dependencies)>::max(),
"A file request dependency was added, but the parent can't have any more dependencies.");
++parent->m_dependencies;
return;
}
}
bool FileRequest::WorksOn(FileRequestPtr& request) const
{
const FileRequest* current = this;
while (current)
break;
case IStreamerTypes::RequestStatus::Canceled:
if (currentStatus == IStreamerTypes::RequestStatus::Failed || currentStatus == IStreamerTypes::RequestStatus::Completed)
{
auto* link = AZStd::get_if<ExternalRequestData>(&current->m_command);
if (!link)
{
current = current->m_parent;
}
else
{
return link->m_request == request;
}
return;
}
return false;
break;
case IStreamerTypes::RequestStatus::Failed:
[[fallthrough]];
default:
break;
}
m_status = newStatus;
}
size_t FileRequest::GetPendingId() const
{
return m_pendingId;
}
FileRequest* FileRequest::GetParent()
{
return m_parent;
}
void FileRequest::SetEstimatedCompletion(AZStd::chrono::system_clock::time_point time)
const FileRequest* FileRequest::GetParent() const
{
return m_parent;
}
size_t FileRequest::GetNumDependencies() const
{
return m_dependencies;
}
bool FileRequest::FailsWhenUnhandled() const
{
return AZStd::visit([](auto&& args)
{
FileRequest* current = this;
do
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, AZStd::monostate>)
{
AZ_Assert(false,
"Request does not contain a valid command. It may have been reset already or was never assigned a command.");
return true;
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::CustomData>)
{
return args.m_failWhenUnhandled;
}
else
{
return Command::s_failWhenUnhandled;
}
}, m_command);
}
void FileRequest::Reset()
{
m_command = AZStd::monostate{};
m_onCompletion = &OnCompletionPlaceholder;
m_estimatedCompletion = AZStd::chrono::system_clock::time_point();
m_parent = nullptr;
m_status = IStreamerTypes::RequestStatus::Pending;
m_dependencies = 0;
}
void FileRequest::SetOptionalParent(FileRequest* parent)
{
if (parent)
{
m_parent = parent;
AZ_Assert(parent->m_dependencies < std::numeric_limits<decltype(parent->m_dependencies)>::max(),
"A file request dependency was added, but the parent can't have any more dependencies.");
++parent->m_dependencies;
}
}
bool FileRequest::WorksOn(FileRequestPtr& request) const
{
const FileRequest* current = this;
while (current)
{
auto* link = AZStd::get_if<ExternalRequestData>(&current->m_command);
if (!link)
{
current->m_estimatedCompletion = time;
current = current->m_parent;
} while (current);
}
AZStd::chrono::system_clock::time_point FileRequest::GetEstimatedCompletion() const
{
return m_estimatedCompletion;
}
//
// ExternalFileRequest
//
ExternalFileRequest::ExternalFileRequest(StreamerContext* owner)
: m_request(FileRequest::Usage::External)
, m_owner(owner)
{
}
void ExternalFileRequest::add_ref()
{
m_refCount++;
}
void ExternalFileRequest::release()
{
if (--m_refCount == 0)
}
else
{
AZ_Assert(m_owner, "No owning context set for the file request.");
m_owner->RecycleRequest(this);
return link->m_request == request;
}
}
return false;
}
bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs)
{
return lhs.m_request == &rhs->m_request;
}
size_t FileRequest::GetPendingId() const
{
return m_pendingId;
}
bool operator==(const FileRequestPtr& lhs, const FileRequestHandle& rhs)
void FileRequest::SetEstimatedCompletion(AZStd::chrono::system_clock::time_point time)
{
FileRequest* current = this;
do
{
return rhs == lhs;
}
current->m_estimatedCompletion = time;
current = current->m_parent;
} while (current);
}
bool operator!=(const FileRequestHandle& lhs, const FileRequestPtr& rhs)
{
return !(lhs == rhs);
}
AZStd::chrono::system_clock::time_point FileRequest::GetEstimatedCompletion() const
{
return m_estimatedCompletion;
}
bool operator!=(const FileRequestPtr& lhs, const FileRequestHandle& rhs)
//
// ExternalFileRequest
//
ExternalFileRequest::ExternalFileRequest(StreamerContext* owner)
: m_request(FileRequest::Usage::External)
, m_owner(owner)
{
}
void ExternalFileRequest::add_ref()
{
m_refCount++;
}
void ExternalFileRequest::release()
{
if (--m_refCount == 0)
{
return !(rhs == lhs);
AZ_Assert(m_owner, "No owning context set for the file request.");
m_owner->RecycleRequest(this);
}
} // namespace IO
} // namespace AZ
}
bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs)
{
return lhs.m_request == &rhs->m_request;
}
bool operator==(const FileRequestPtr& lhs, const FileRequestHandle& rhs)
{
return rhs == lhs;
}
bool operator!=(const FileRequestHandle& lhs, const FileRequestPtr& rhs)
{
return !(lhs == rhs);
}
bool operator!=(const FileRequestPtr& lhs, const FileRequestHandle& rhs)
{
return !(rhs == lhs);
}
} // namespace AZ::IO
File diff suppressed because it is too large Load Diff
@@ -14,376 +14,373 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
namespace AZ
namespace AZ::IO
{
namespace IO
AZStd::shared_ptr<StreamStackEntry> ReadSplitterConfig::AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent)
{
AZStd::shared_ptr<StreamStackEntry> ReadSplitterConfig::AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent)
size_t splitSize;
switch (m_splitSize)
{
size_t splitSize;
switch (m_splitSize)
{
case SplitSize::MaxTransfer:
splitSize = hardware.m_maxTransfer;
break;
case SplitSize::MemoryAlignment:
splitSize = hardware.m_maxPhysicalSectorSize;
break;
default:
splitSize = m_splitSize;
break;
}
size_t bufferSize = m_bufferSizeMib * 1_mib;
if (bufferSize < splitSize)
{
AZ_Warning("Streamer", false, "The buffer size for the Read Splitter is smaller than the individual split size. "
"It will be increased to fit at least one split.");
bufferSize = splitSize;
}
auto stackEntry = AZStd::make_shared<ReadSplitter>(
splitSize,
aznumeric_caster(hardware.m_maxPhysicalSectorSize),
aznumeric_caster(hardware.m_maxLogicalSectorSize),
bufferSize, m_adjustOffset, m_splitAlignedRequests);
stackEntry->SetNext(AZStd::move(parent));
return stackEntry;
case SplitSize::MaxTransfer:
splitSize = hardware.m_maxTransfer;
break;
case SplitSize::MemoryAlignment:
splitSize = hardware.m_maxPhysicalSectorSize;
break;
default:
splitSize = m_splitSize;
break;
}
void ReadSplitterConfig::Reflect(AZ::ReflectContext* context)
size_t bufferSize = m_bufferSizeMib * 1_mib;
if (bufferSize < splitSize)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
serializeContext->Enum<SplitSize>()
->Version(1)
->Value("MaxTransfer", SplitSize::MaxTransfer)
->Value("MemoryAlignment", SplitSize::MemoryAlignment);
serializeContext->Class<ReadSplitterConfig, IStreamerStackConfig>()
->Version(1)
->Field("BufferSizeMib", &ReadSplitterConfig::m_bufferSizeMib)
->Field("SplitSize", &ReadSplitterConfig::m_splitSize)
->Field("AdjustOffset", &ReadSplitterConfig::m_adjustOffset)
->Field("SplitAlignedRequests", &ReadSplitterConfig::m_splitAlignedRequests);
}
AZ_Warning("Streamer", false, "The buffer size for the Read Splitter is smaller than the individual split size. "
"It will be increased to fit at least one split.");
bufferSize = splitSize;
}
static constexpr char AvgNumSubReadsName[] = "Avg. num sub reads";
static constexpr char AlignedReadsName[] = "Aligned reads";
static constexpr char NumAvailableBufferSlotsName[] = "Num available buffer slots";
static constexpr char NumPendingReadsName[] = "Num pending reads";
auto stackEntry = AZStd::make_shared<ReadSplitter>(
splitSize,
aznumeric_caster(hardware.m_maxPhysicalSectorSize),
aznumeric_caster(hardware.m_maxLogicalSectorSize),
bufferSize, m_adjustOffset, m_splitAlignedRequests);
stackEntry->SetNext(AZStd::move(parent));
return stackEntry;
}
ReadSplitter::ReadSplitter(u64 maxReadSize, u32 memoryAlignment, u32 sizeAlignment, size_t bufferSize,
bool adjustOffset, bool splitAlignedRequests)
: StreamStackEntry("Read splitter")
, m_buffer(nullptr)
, m_bufferSize(bufferSize)
, m_maxReadSize(maxReadSize)
, m_memoryAlignment(memoryAlignment)
, m_sizeAlignment(sizeAlignment)
, m_adjustOffset(adjustOffset)
, m_splitAlignedRequests(splitAlignedRequests)
void ReadSplitterConfig::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
AZ_Assert(IStreamerTypes::IsPowerOf2(memoryAlignment), "Memory alignment needs to be a power of 2");
AZ_Assert(IStreamerTypes::IsPowerOf2(sizeAlignment), "Size alignment needs to be a power of 2");
AZ_Assert(IStreamerTypes::IsAlignedTo(maxReadSize, sizeAlignment),
"Maximum read size isn't aligned to a multiple of the size alignment.");
serializeContext->Enum<SplitSize>()
->Version(1)
->Value("MaxTransfer", SplitSize::MaxTransfer)
->Value("MemoryAlignment", SplitSize::MemoryAlignment);
size_t numBufferSlots = bufferSize / maxReadSize;
// Don't divide the reads up in more sub-reads than there are dependencies available.
numBufferSlots = AZStd::min(numBufferSlots, FileRequest::GetMaxNumDependencies());
m_bufferCopyInformation = AZStd::unique_ptr<BufferCopyInformation[]>(new BufferCopyInformation[numBufferSlots]);
m_availableBufferSlots.reserve(numBufferSlots);
for (u32 i = aznumeric_caster(numBufferSlots); i > 0; --i)
{
m_availableBufferSlots.push_back(i - 1);
}
serializeContext->Class<ReadSplitterConfig, IStreamerStackConfig>()
->Version(1)
->Field("BufferSizeMib", &ReadSplitterConfig::m_bufferSizeMib)
->Field("SplitSize", &ReadSplitterConfig::m_splitSize)
->Field("AdjustOffset", &ReadSplitterConfig::m_adjustOffset)
->Field("SplitAlignedRequests", &ReadSplitterConfig::m_splitAlignedRequests);
}
}
static constexpr char AvgNumSubReadsName[] = "Avg. num sub reads";
static constexpr char AlignedReadsName[] = "Aligned reads";
static constexpr char NumAvailableBufferSlotsName[] = "Num available buffer slots";
static constexpr char NumPendingReadsName[] = "Num pending reads";
ReadSplitter::ReadSplitter(u64 maxReadSize, u32 memoryAlignment, u32 sizeAlignment, size_t bufferSize,
bool adjustOffset, bool splitAlignedRequests)
: StreamStackEntry("Read splitter")
, m_buffer(nullptr)
, m_bufferSize(bufferSize)
, m_maxReadSize(maxReadSize)
, m_memoryAlignment(memoryAlignment)
, m_sizeAlignment(sizeAlignment)
, m_adjustOffset(adjustOffset)
, m_splitAlignedRequests(splitAlignedRequests)
{
AZ_Assert(IStreamerTypes::IsPowerOf2(memoryAlignment), "Memory alignment needs to be a power of 2");
AZ_Assert(IStreamerTypes::IsPowerOf2(sizeAlignment), "Size alignment needs to be a power of 2");
AZ_Assert(IStreamerTypes::IsAlignedTo(maxReadSize, sizeAlignment),
"Maximum read size isn't aligned to a multiple of the size alignment.");
size_t numBufferSlots = bufferSize / maxReadSize;
// Don't divide the reads up in more sub-reads than there are dependencies available.
numBufferSlots = AZStd::min(numBufferSlots, FileRequest::GetMaxNumDependencies());
m_bufferCopyInformation = AZStd::unique_ptr<BufferCopyInformation[]>(new BufferCopyInformation[numBufferSlots]);
m_availableBufferSlots.reserve(numBufferSlots);
for (u32 i = aznumeric_caster(numBufferSlots); i > 0; --i)
{
m_availableBufferSlots.push_back(i - 1);
}
}
ReadSplitter::~ReadSplitter()
{
if (m_buffer)
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Get().DeAllocate(m_buffer, m_bufferSize, m_memoryAlignment);
}
}
void ReadSplitter::QueueRequest(FileRequest* request)
{
AZ_Assert(request, "QueueRequest was provided a null request.");
if (!m_next)
{
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
m_context->MarkRequestAsCompleted(request);
return;
}
ReadSplitter::~ReadSplitter()
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
if (data == nullptr)
{
if (m_buffer)
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Get().DeAllocate(m_buffer, m_bufferSize, m_memoryAlignment);
}
StreamStackEntry::QueueRequest(request);
return;
}
void ReadSplitter::QueueRequest(FileRequest* request)
{
AZ_Assert(request, "QueueRequest was provided a null request.");
if (!m_next)
{
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
m_context->MarkRequestAsCompleted(request);
return;
}
m_averageNumSubReadsStat.PushSample(aznumeric_cast<double>((data->m_size / m_maxReadSize) + 1));
Statistic::PlotImmediate(m_name, AvgNumSubReadsName, m_averageNumSubReadsStat.GetMostRecentSample());
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
if (data == nullptr)
bool isAligned = IStreamerTypes::IsAlignedTo(data->m_output, m_memoryAlignment);
if (m_adjustOffset)
{
isAligned = isAligned && IStreamerTypes::IsAlignedTo(data->m_offset, m_sizeAlignment);
}
if (isAligned || m_bufferSize == 0)
{
m_alignedReadsStat.PushSample(isAligned ? 1.0 : 0.0);
if (!m_splitAlignedRequests)
{
StreamStackEntry::QueueRequest(request);
return;
}
m_averageNumSubReadsStat.PushSample(aznumeric_cast<double>((data->m_size / m_maxReadSize) + 1));
Statistic::PlotImmediate(m_name, AvgNumSubReadsName, m_averageNumSubReadsStat.GetMostRecentSample());
bool isAligned = IStreamerTypes::IsAlignedTo(data->m_output, m_memoryAlignment);
if (m_adjustOffset)
{
isAligned = isAligned && IStreamerTypes::IsAlignedTo(data->m_offset, m_sizeAlignment);
}
if (isAligned || m_bufferSize == 0)
{
m_alignedReadsStat.PushSample(isAligned ? 1.0 : 0.0);
if (!m_splitAlignedRequests)
{
StreamStackEntry::QueueRequest(request);
}
else
{
QueueAlignedRead(request);
}
}
else
{
m_alignedReadsStat.PushSample(0.0);
InitializeBuffer();
QueueBufferedRead(request);
QueueAlignedRead(request);
}
}
void ReadSplitter::QueueAlignedRead(FileRequest* request)
else
{
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command.");
m_alignedReadsStat.PushSample(0.0);
InitializeBuffer();
QueueBufferedRead(request);
}
}
if (data->m_size <= m_maxReadSize)
{
StreamStackEntry::QueueRequest(request);
return;
}
void ReadSplitter::QueueAlignedRead(FileRequest* request)
{
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command.");
PendingRead pendingRead;
pendingRead.m_request = request;
pendingRead.m_output = reinterpret_cast<u8*>(data->m_output);
pendingRead.m_outputSize = data->m_outputSize;
pendingRead.m_readSize = data->m_size;
pendingRead.m_offset = data->m_offset;
pendingRead.m_isBuffered = false;
if (!m_pendingReads.empty())
{
m_pendingReads.push_back(pendingRead);
return;
}
if (!QueueAlignedRead(pendingRead))
{
m_pendingReads.push_back(pendingRead);
}
if (data->m_size <= m_maxReadSize)
{
StreamStackEntry::QueueRequest(request);
return;
}
bool ReadSplitter::QueueAlignedRead(PendingRead& pending)
PendingRead pendingRead;
pendingRead.m_request = request;
pendingRead.m_output = reinterpret_cast<u8*>(data->m_output);
pendingRead.m_outputSize = data->m_outputSize;
pendingRead.m_readSize = data->m_size;
pendingRead.m_offset = data->m_offset;
pendingRead.m_isBuffered = false;
if (!m_pendingReads.empty())
{
auto data = AZStd::get_if<FileRequest::ReadData>(&pending.m_request->GetCommand());
AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command.");
m_pendingReads.push_back(pendingRead);
return;
}
while (pending.m_readSize > 0)
if (!QueueAlignedRead(pendingRead))
{
m_pendingReads.push_back(pendingRead);
}
}
bool ReadSplitter::QueueAlignedRead(PendingRead& pending)
{
auto data = AZStd::get_if<FileRequest::ReadData>(&pending.m_request->GetCommand());
AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command.");
while (pending.m_readSize > 0)
{
if (pending.m_request->GetNumDependencies() >= FileRequest::GetMaxNumDependencies())
{
if (pending.m_request->GetNumDependencies() >= FileRequest::GetMaxNumDependencies())
// Add a wait to make sure the read request isn't completed if all sub-reads completed before
// the ReadSplitter has had a chance to add new sub-reads to complete the read.
if (pending.m_wait == nullptr)
{
// Add a wait to make sure the read request isn't completed if all sub-reads completed before
// the ReadSplitter has had a chance to add new sub-reads to complete the read.
if (pending.m_wait == nullptr)
{
pending.m_wait = m_context->GetNewInternalRequest();
pending.m_wait->CreateWait(pending.m_request);
}
return false;
pending.m_wait = m_context->GetNewInternalRequest();
pending.m_wait->CreateWait(pending.m_request);
}
return false;
}
u64 readSize = m_maxReadSize;
size_t bufferSize = m_maxReadSize;
if (pending.m_readSize < m_maxReadSize)
u64 readSize = m_maxReadSize;
size_t bufferSize = m_maxReadSize;
if (pending.m_readSize < m_maxReadSize)
{
readSize = pending.m_readSize;
// This will be the last read so give the remainder of the output buffer to the final request.
bufferSize = pending.m_outputSize;
}
FileRequest* subRequest = m_context->GetNewInternalRequest();
subRequest->CreateRead(pending.m_request, pending.m_output, bufferSize, data->m_path, pending.m_offset, readSize, data->m_sharedRead);
subRequest->SetCompletionCallback([this](FileRequest&)
{
readSize = pending.m_readSize;
// This will be the last read so give the remainder of the output buffer to the final request.
bufferSize = pending.m_outputSize;
AZ_PROFILE_FUNCTION(AzCore);
QueuePendingRequest();
});
m_next->QueueRequest(subRequest);
pending.m_offset += readSize;
pending.m_readSize -= readSize;
pending.m_outputSize -= bufferSize;
pending.m_output += readSize;
}
if (pending.m_wait != nullptr)
{
m_context->MarkRequestAsCompleted(pending.m_wait);
pending.m_wait = nullptr;
}
return true;
}
void ReadSplitter::QueueBufferedRead(FileRequest* request)
{
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command.");
PendingRead pendingRead;
pendingRead.m_request = request;
pendingRead.m_output = reinterpret_cast<u8*>(data->m_output);
pendingRead.m_outputSize = data->m_outputSize;
pendingRead.m_readSize = data->m_size;
pendingRead.m_offset = data->m_offset;
pendingRead.m_isBuffered = true;
if (!m_pendingReads.empty())
{
m_pendingReads.push_back(pendingRead);
return;
}
if (!QueueBufferedRead(pendingRead))
{
m_pendingReads.push_back(pendingRead);
}
}
bool ReadSplitter::QueueBufferedRead(PendingRead& pending)
{
auto data = AZStd::get_if<FileRequest::ReadData>(&pending.m_request->GetCommand());
AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command.");
while (pending.m_readSize > 0)
{
if (!m_availableBufferSlots.empty())
{
u32 bufferSlot = m_availableBufferSlots.back();
m_availableBufferSlots.pop_back();
u64 readSize;
u64 copySize;
u64 offset;
BufferCopyInformation& copyInfo = m_bufferCopyInformation[bufferSlot];
copyInfo.m_target = pending.m_output;
if (m_adjustOffset)
{
offset = AZ_SIZE_ALIGN_DOWN(pending.m_offset, aznumeric_cast<u64>(m_sizeAlignment));
size_t bufferOffset = pending.m_offset - offset;
copyInfo.m_bufferOffset = bufferOffset;
readSize = AZStd::min(pending.m_readSize + bufferOffset, m_maxReadSize);
copySize = readSize - bufferOffset;
}
else
{
offset = pending.m_offset;
readSize = AZStd::min(pending.m_readSize, m_maxReadSize);
copySize = readSize;
}
AZ_Assert(readSize <= m_maxReadSize, "Read size %llu in read splitter exceeds the maximum split size of %llu.",
readSize, m_maxReadSize);
copyInfo.m_size = copySize;
FileRequest* subRequest = m_context->GetNewInternalRequest();
subRequest->CreateRead(pending.m_request, pending.m_output, bufferSize, data->m_path, pending.m_offset, readSize, data->m_sharedRead);
subRequest->SetCompletionCallback([this](FileRequest&)
subRequest->CreateRead(pending.m_request, GetBufferSlot(bufferSlot), m_maxReadSize, data->m_path,
offset, readSize, data->m_sharedRead);
subRequest->SetCompletionCallback([this, bufferSlot]([[maybe_unused]] FileRequest& request)
{
AZ_PROFILE_FUNCTION(AzCore);
BufferCopyInformation& copyInfo = m_bufferCopyInformation[bufferSlot];
memcpy(copyInfo.m_target, GetBufferSlot(bufferSlot) + copyInfo.m_bufferOffset, copyInfo.m_size);
m_availableBufferSlots.push_back(bufferSlot);
QueuePendingRequest();
});
m_next->QueueRequest(subRequest);
pending.m_offset += readSize;
pending.m_readSize -= readSize;
pending.m_outputSize -= bufferSize;
pending.m_output += readSize;
pending.m_offset += copySize;
pending.m_readSize -= copySize;
pending.m_outputSize -= copySize;
pending.m_output += copySize;
}
if (pending.m_wait != nullptr)
else
{
m_context->MarkRequestAsCompleted(pending.m_wait);
pending.m_wait = nullptr;
}
return true;
}
void ReadSplitter::QueueBufferedRead(FileRequest* request)
{
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command.");
PendingRead pendingRead;
pendingRead.m_request = request;
pendingRead.m_output = reinterpret_cast<u8*>(data->m_output);
pendingRead.m_outputSize = data->m_outputSize;
pendingRead.m_readSize = data->m_size;
pendingRead.m_offset = data->m_offset;
pendingRead.m_isBuffered = true;
if (!m_pendingReads.empty())
{
m_pendingReads.push_back(pendingRead);
return;
}
if (!QueueBufferedRead(pendingRead))
{
m_pendingReads.push_back(pendingRead);
}
}
bool ReadSplitter::QueueBufferedRead(PendingRead& pending)
{
auto data = AZStd::get_if<FileRequest::ReadData>(&pending.m_request->GetCommand());
AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command.");
while (pending.m_readSize > 0)
{
if (!m_availableBufferSlots.empty())
// Add a wait to make sure the read request isn't completed if all sub-reads completed before
// the ReadSplitter has had a chance to add new sub-reads to complete the read.
if (pending.m_wait == nullptr)
{
u32 bufferSlot = m_availableBufferSlots.back();
m_availableBufferSlots.pop_back();
u64 readSize;
u64 copySize;
u64 offset;
BufferCopyInformation& copyInfo = m_bufferCopyInformation[bufferSlot];
copyInfo.m_target = pending.m_output;
if (m_adjustOffset)
{
offset = AZ_SIZE_ALIGN_DOWN(pending.m_offset, aznumeric_cast<u64>(m_sizeAlignment));
size_t bufferOffset = pending.m_offset - offset;
copyInfo.m_bufferOffset = bufferOffset;
readSize = AZStd::min(pending.m_readSize + bufferOffset, m_maxReadSize);
copySize = readSize - bufferOffset;
}
else
{
offset = pending.m_offset;
readSize = AZStd::min(pending.m_readSize, m_maxReadSize);
copySize = readSize;
}
AZ_Assert(readSize <= m_maxReadSize, "Read size %llu in read splitter exceeds the maximum split size of %llu.",
readSize, m_maxReadSize);
copyInfo.m_size = copySize;
FileRequest* subRequest = m_context->GetNewInternalRequest();
subRequest->CreateRead(pending.m_request, GetBufferSlot(bufferSlot), m_maxReadSize, data->m_path,
offset, readSize, data->m_sharedRead);
subRequest->SetCompletionCallback([this, bufferSlot]([[maybe_unused]] FileRequest& request)
{
AZ_PROFILE_FUNCTION(AzCore);
BufferCopyInformation& copyInfo = m_bufferCopyInformation[bufferSlot];
memcpy(copyInfo.m_target, GetBufferSlot(bufferSlot) + copyInfo.m_bufferOffset, copyInfo.m_size);
m_availableBufferSlots.push_back(bufferSlot);
QueuePendingRequest();
});
m_next->QueueRequest(subRequest);
pending.m_offset += copySize;
pending.m_readSize -= copySize;
pending.m_outputSize -= copySize;
pending.m_output += copySize;
}
else
{
// Add a wait to make sure the read request isn't completed if all sub-reads completed before
// the ReadSplitter has had a chance to add new sub-reads to complete the read.
if (pending.m_wait == nullptr)
{
pending.m_wait = m_context->GetNewInternalRequest();
pending.m_wait->CreateWait(pending.m_request);
}
return false;
pending.m_wait = m_context->GetNewInternalRequest();
pending.m_wait->CreateWait(pending.m_request);
}
return false;
}
if (pending.m_wait != nullptr)
}
if (pending.m_wait != nullptr)
{
m_context->MarkRequestAsCompleted(pending.m_wait);
pending.m_wait = nullptr;
}
return true;
}
void ReadSplitter::QueuePendingRequest()
{
if (!m_pendingReads.empty())
{
PendingRead& pendingRead = m_pendingReads.front();
if (pendingRead.m_isBuffered ? QueueBufferedRead(pendingRead) : QueueAlignedRead(pendingRead))
{
m_context->MarkRequestAsCompleted(pending.m_wait);
pending.m_wait = nullptr;
}
return true;
}
void ReadSplitter::QueuePendingRequest()
{
if (!m_pendingReads.empty())
{
PendingRead& pendingRead = m_pendingReads.front();
if (pendingRead.m_isBuffered ? QueueBufferedRead(pendingRead) : QueueAlignedRead(pendingRead))
{
m_pendingReads.pop_front();
}
m_pendingReads.pop_front();
}
}
}
void ReadSplitter::UpdateStatus(Status& status) const
void ReadSplitter::UpdateStatus(Status& status) const
{
StreamStackEntry::UpdateStatus(status);
if (m_bufferSize > 0)
{
StreamStackEntry::UpdateStatus(status);
if (m_bufferSize > 0)
{
s32 numAvailableSlots = aznumeric_cast<s32>(m_availableBufferSlots.size());
status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, numAvailableSlots);
status.m_isIdle = status.m_isIdle && m_pendingReads.empty();
}
s32 numAvailableSlots = aznumeric_cast<s32>(m_availableBufferSlots.size());
status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, numAvailableSlots);
status.m_isIdle = status.m_isIdle && m_pendingReads.empty();
}
}
void ReadSplitter::CollectStatistics(AZStd::vector<Statistic>& statistics) const
{
statistics.push_back(Statistic::CreateFloat(m_name, AvgNumSubReadsName, m_averageNumSubReadsStat.GetAverage()));
statistics.push_back(Statistic::CreatePercentage(m_name, AlignedReadsName, m_alignedReadsStat.GetAverage()));
statistics.push_back(Statistic::CreateInteger(m_name, NumAvailableBufferSlotsName, aznumeric_caster(m_availableBufferSlots.size())));
statistics.push_back(Statistic::CreateInteger(m_name, NumPendingReadsName, aznumeric_caster(m_pendingReads.size())));
StreamStackEntry::CollectStatistics(statistics);
}
void ReadSplitter::CollectStatistics(AZStd::vector<Statistic>& statistics) const
{
statistics.push_back(Statistic::CreateFloat(m_name, AvgNumSubReadsName, m_averageNumSubReadsStat.GetAverage()));
statistics.push_back(Statistic::CreatePercentage(m_name, AlignedReadsName, m_alignedReadsStat.GetAverage()));
statistics.push_back(Statistic::CreateInteger(m_name, NumAvailableBufferSlotsName, aznumeric_caster(m_availableBufferSlots.size())));
statistics.push_back(Statistic::CreateInteger(m_name, NumPendingReadsName, aznumeric_caster(m_pendingReads.size())));
StreamStackEntry::CollectStatistics(statistics);
}
void ReadSplitter::InitializeBuffer()
void ReadSplitter::InitializeBuffer()
{
// Lazy initialization to avoid allocating memory if it's not needed.
if (m_bufferSize != 0 && m_buffer == nullptr)
{
// Lazy initialization to avoid allocating memory if it's not needed.
if (m_bufferSize != 0 && m_buffer == nullptr)
{
m_buffer = reinterpret_cast<u8*>(AZ::AllocatorInstance<AZ::SystemAllocator>::Get().Allocate(
m_bufferSize, m_memoryAlignment, 0, "AZ::IO::Streamer ReadSplitter", __FILE__, __LINE__));
}
m_buffer = reinterpret_cast<u8*>(AZ::AllocatorInstance<AZ::SystemAllocator>::Get().Allocate(
m_bufferSize, m_memoryAlignment, 0, "AZ::IO::Streamer ReadSplitter", __FILE__, __LINE__));
}
}
u8* ReadSplitter::GetBufferSlot(size_t index)
{
AZ_Assert(m_buffer != nullptr, "A buffer slot was requested by the Read Splitter before the buffer was initialized.");
return m_buffer + (index * m_maxReadSize);
}
} // namespace IO
} // namesapce AZ
u8* ReadSplitter::GetBufferSlot(size_t index)
{
AZ_Assert(m_buffer != nullptr, "A buffer slot was requested by the Read Splitter before the buffer was initialized.");
return m_buffer + (index * m_maxReadSize);
}
} // namespace AZ::IO
@@ -13,23 +13,18 @@
#include <AzCore/std/parallel/spin_mutex.h>
namespace AZ
namespace AZ::Internal
{
namespace Internal
struct RingData
{
struct RingData
{
AZ::u32 m_readOffset;
AZ::u32 m_writeOffset;
AZ::u32 m_startOffset;
AZ::u32 m_endOffset;
AZ::u32 m_dataToRead;
AZ::u8 m_pad[32 - sizeof(AZStd::spin_mutex)];
};
} // namespace Internal
} // namespace AZ
AZ::u32 m_readOffset;
AZ::u32 m_writeOffset;
AZ::u32 m_startOffset;
AZ::u32 m_endOffset;
AZ::u32 m_dataToRead;
AZ::u8 m_pad[32 - sizeof(AZStd::spin_mutex)];
};
} // namespace AZ::Internal
using namespace AZ;
@@ -14,6 +14,7 @@
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/Debug/Profiler.h>
@@ -83,7 +84,7 @@ AZ_THREAD_LOCAL JobManagerWorkStealing::ThreadInfo* JobManagerWorkStealing::m_cu
JobManagerWorkStealing::JobManagerWorkStealing(const JobManagerDesc& desc)
: m_isAsynchronous(!desc.m_workerThreads.empty())
, m_workerThreads(AZStd::move(CreateWorkerThreads(desc.m_workerThreads)))
, m_workerThreads(AZStd::move(CreateWorkerThreads(desc)))
{
//allow workers to begin processing after they have all been created, needed to wait since they may access each others queues
m_initSemaphore.release(static_cast<unsigned int>(desc.m_workerThreads.size()));
@@ -618,8 +619,9 @@ JobManagerWorkStealing::ThreadInfo* JobManagerWorkStealing::FindCurrentThreadInf
return info;
}
JobManagerWorkStealing::ThreadList JobManagerWorkStealing::CreateWorkerThreads(const JobManagerDesc::DescList& workerDescList)
JobManagerWorkStealing::ThreadList JobManagerWorkStealing::CreateWorkerThreads(const JobManagerDesc& jmDesc)
{
const JobManagerDesc::DescList& workerDescList = jmDesc.m_workerThreads;
ThreadList workerThreads(workerDescList.size());
m_threads.reserve(workerDescList.size());
@@ -632,8 +634,12 @@ JobManagerWorkStealing::ThreadList JobManagerWorkStealing::CreateWorkerThreads(c
info->m_owningManager = this;
info->m_workerId = iThread;
AZStd::fixed_string<128> threadName = AZStd::fixed_string<128>::format(
"%s worker thread %d",
jmDesc.m_jobManagerName[0] != '\0' ? jmDesc.m_jobManagerName : "AZ JobManager",
iThread);
AZStd::thread_desc threadDesc;
threadDesc.m_name = "AZ JobManager worker thread";
threadDesc.m_name = threadName.c_str();
threadDesc.m_cpuId = desc.m_cpuId;
threadDesc.m_priority = desc.m_priority;
if (desc.m_stackSize != 0)
@@ -115,7 +115,7 @@ namespace AZ
void ProcessJobsAssist(ThreadInfo* info, Job* suspendedJob, AZStd::atomic<bool>* notifyFlag);
void ProcessJobsSynchronous(ThreadInfo* info, Job* suspendedJob, AZStd::atomic<bool>* notifyFlag);
void ProcessJobsInternal(ThreadInfo* info, Job* suspendedJob, AZStd::atomic<bool>* notifyFlag);
ThreadList CreateWorkerThreads(const JobManagerDesc::DescList& workerDescList);
ThreadList CreateWorkerThreads(const JobManagerDesc& jmDesc);
#ifndef AZ_MONOLITHIC_BUILD
ThreadInfo* CrossModuleFindAndSetWorkerThreadInfo() const;
#endif
@@ -51,16 +51,18 @@ namespace AZ
JobManagerBus::Handler::BusConnect();
JobManagerDesc desc;
desc.m_jobManagerName = "Default JobManager";
JobManagerThreadDesc threadDesc;
int numberOfWorkerThreads = m_numberOfWorkerThreads;
if (numberOfWorkerThreads <= 0) // spawn default number of threads
{
#if (AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS)
numberOfWorkerThreads = AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS;
#else
uint32_t scaledHardwareThreads = Threading::CalcNumWorkerThreads(cl_jobThreadsConcurrencyRatio, cl_jobThreadsMinNumber, cl_jobThreadsNumReserved);
numberOfWorkerThreads = AZ::GetMin(static_cast<unsigned int>(desc.m_workerThreads.capacity()), scaledHardwareThreads);
#if (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS)
numberOfWorkerThreads = AZ::GetMin(numberOfWorkerThreads, AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS);
#endif // (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS)
#endif // (AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS)
}
threadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS;
@@ -51,6 +51,8 @@ namespace AZ
{
JobManagerDesc() {}
const char* m_jobManagerName = "";
using DescList = AZStd::fixed_vector<JobManagerThreadDesc, 64>;
DescList m_workerThreads; ///< List of worker threads to create
};
@@ -8,138 +8,135 @@
#include <AzCore/Math/Geometry2DUtils.h>
namespace AZ
namespace AZ::Geometry2DUtils
{
namespace Geometry2DUtils
float ShortestDistanceSqPointSegment(const Vector2& point, const Vector2& segmentStart, const Vector2& segmentEnd,
float epsilon)
{
float ShortestDistanceSqPointSegment(const Vector2& point, const Vector2& segmentStart, const Vector2& segmentEnd,
float epsilon)
const AZ::Vector2 segmentVector = segmentEnd - segmentStart;
// check if the line degenerates to a point
const float segmentLengthSq = segmentVector.GetLengthSq();
if (segmentLengthSq < epsilon * epsilon)
{
const AZ::Vector2 segmentVector = segmentEnd - segmentStart;
// check if the line degenerates to a point
const float segmentLengthSq = segmentVector.GetLengthSq();
if (segmentLengthSq < epsilon * epsilon)
{
return (point - segmentStart).GetLengthSq();
}
// if the point projects on to the line segment then the shortest distance is the perpendicular
const float projection = (point - segmentStart).Dot(segmentVector);
if (projection >= 0.0f && projection <= segmentLengthSq)
{
const Vector2 perpendicular = (point - segmentStart - projection / segmentLengthSq * segmentVector);
return perpendicular.GetLengthSq();
}
// otherwise the point must be closest to one of the end points of the segment
return GetMin(
(point - segmentStart).GetLengthSq(),
(point - segmentEnd).GetLengthSq());
return (point - segmentStart).GetLengthSq();
}
float Signed2DTriangleArea(const Vector2& a, const Vector2& b, const Vector2& c)
// if the point projects on to the line segment then the shortest distance is the perpendicular
const float projection = (point - segmentStart).Dot(segmentVector);
if (projection >= 0.0f && projection <= segmentLengthSq)
{
return 0.5f * ((a.GetX() - c.GetX()) * (b.GetY() - c.GetY()) - (a.GetY() - c.GetY()) * (b.GetX() - c.GetX()));
const Vector2 perpendicular = (point - segmentStart - projection / segmentLengthSq * segmentVector);
return perpendicular.GetLengthSq();
}
float ShortestDistanceSqSegmentSegment(
const Vector2& segment1Start, const Vector2& segment1End,
const Vector2& segment2Start, const Vector2& segment2End)
// otherwise the point must be closest to one of the end points of the segment
return GetMin(
(point - segmentStart).GetLengthSq(),
(point - segmentEnd).GetLengthSq());
}
float Signed2DTriangleArea(const Vector2& a, const Vector2& b, const Vector2& c)
{
return 0.5f * ((a.GetX() - c.GetX()) * (b.GetY() - c.GetY()) - (a.GetY() - c.GetY()) * (b.GetX() - c.GetX()));
}
float ShortestDistanceSqSegmentSegment(
const Vector2& segment1Start, const Vector2& segment1End,
const Vector2& segment2Start, const Vector2& segment2End)
{
// if the segments cross, then the distance is zero
// if the two ends of segment 2 are on different sides of segment 1, then these two triangles will have
// different winding orders (see Real-Time Collision Detection, Christer Ericson, ISBN 978-1558607323,
// Chapter 5.1.9.1)
const float area1 = Signed2DTriangleArea(segment1Start, segment1End, segment2End);
const float area2 = Signed2DTriangleArea(segment1Start, segment1End, segment2Start);
if (area1 * area2 < 0.0f)
{
// if the segments cross, then the distance is zero
// if the two ends of segment 2 are on different sides of segment 1, then these two triangles will have
// different winding orders (see Real-Time Collision Detection, Christer Ericson, ISBN 978-1558607323,
// Chapter 5.1.9.1)
const float area1 = Signed2DTriangleArea(segment1Start, segment1End, segment2End);
const float area2 = Signed2DTriangleArea(segment1Start, segment1End, segment2Start);
if (area1 * area2 < 0.0f)
// similarly we can check if the two ends of segment 1 are on different sides of segment 2
const float area3 = Signed2DTriangleArea(segment2Start, segment2End, segment1Start);
const float area4 = area3 + area2 - area1;
if (area3 * area4 < 0.0f)
{
// similarly we can check if the two ends of segment 1 are on different sides of segment 2
const float area3 = Signed2DTriangleArea(segment2Start, segment2End, segment1Start);
const float area4 = area3 + area2 - area1;
if (area3 * area4 < 0.0f)
{
return 0.0f;
}
return 0.0f;
}
// otherwise the shortest distance must be between one of the segment end points and the other segment
return GetMin(
GetMin(
ShortestDistanceSqPointSegment(segment1Start, segment2Start, segment2End),
ShortestDistanceSqPointSegment(segment1End, segment2Start, segment2End)),
GetMin(
ShortestDistanceSqPointSegment(segment2Start, segment1Start, segment1End),
ShortestDistanceSqPointSegment(segment2End, segment1Start, segment1End))
);
}
bool IsSimplePolygon(const AZStd::vector<AZ::Vector2>& vertices, float epsilon)
// otherwise the shortest distance must be between one of the segment end points and the other segment
return GetMin(
GetMin(
ShortestDistanceSqPointSegment(segment1Start, segment2Start, segment2End),
ShortestDistanceSqPointSegment(segment1End, segment2Start, segment2End)),
GetMin(
ShortestDistanceSqPointSegment(segment2Start, segment1Start, segment1End),
ShortestDistanceSqPointSegment(segment2End, segment1Start, segment1End))
);
}
bool IsSimplePolygon(const AZStd::vector<AZ::Vector2>& vertices, float epsilon)
{
// note that this implementation is quadratic in the number of vertices
// if it becomes a bottleneck, there are approaches which are O(n log n), e.g. the Bentley-Ottmann algorithm
const size_t vertexCount = vertices.size();
if (vertexCount < 3)
{
// note that this implementation is quadratic in the number of vertices
// if it becomes a bottleneck, there are approaches which are O(n log n), e.g. the Bentley-Ottmann algorithm
const size_t vertexCount = vertices.size();
if (vertexCount < 3)
{
return false;
}
if (vertexCount == 3)
{
return true;
}
const float epsilonSq = epsilon * epsilon;
for (size_t i = 0; i < vertexCount; ++i)
{
// make it easy to nicely wrap indices
const size_t safeIndex = i + vertexCount;
const size_t endIndex = (safeIndex - 1) % vertexCount;
const size_t beginIndex = (safeIndex + 2) % vertexCount;
for (size_t j = beginIndex; j != endIndex; j = (j + 1) % vertexCount)
{
const float distSq = ShortestDistanceSqSegmentSegment(
vertices[i],
vertices[(i + 1) % vertexCount],
vertices[j],
vertices[(j + 1) % vertexCount]
);
if (distSq < epsilonSq)
{
return false;
}
}
}
return false;
}
if (vertexCount == 3)
{
return true;
}
bool IsConvex(const AZStd::vector<AZ::Vector2>& vertices)
const float epsilonSq = epsilon * epsilon;
for (size_t i = 0; i < vertexCount; ++i)
{
const size_t vertexCount = vertices.size();
// make it easy to nicely wrap indices
const size_t safeIndex = i + vertexCount;
if (vertexCount < 3)
{
return false;
}
const size_t endIndex = (safeIndex - 1) % vertexCount;
const size_t beginIndex = (safeIndex + 2) % vertexCount;
for (size_t i = 0; i < vertexCount; ++i)
for (size_t j = beginIndex; j != endIndex; j = (j + 1) % vertexCount)
{
if (Signed2DTriangleArea(vertices[i], vertices[(i + 1) % vertexCount], vertices[(i + 2) % vertexCount]) < 0.0f)
const float distSq = ShortestDistanceSqSegmentSegment(
vertices[i],
vertices[(i + 1) % vertexCount],
vertices[j],
vertices[(j + 1) % vertexCount]
);
if (distSq < epsilonSq)
{
return false;
}
}
return true;
}
} // namespace Geometry2DUtils
} // namespace AZ
return true;
}
bool IsConvex(const AZStd::vector<AZ::Vector2>& vertices)
{
const size_t vertexCount = vertices.size();
if (vertexCount < 3)
{
return false;
}
for (size_t i = 0; i < vertexCount; ++i)
{
if (Signed2DTriangleArea(vertices[i], vertices[(i + 1) % vertexCount], vertices[(i + 2) % vertexCount]) < 0.0f)
{
return false;
}
}
return true;
}
} // namespace AZ::Geometry2DUtils
+248 -252
View File
@@ -14,28 +14,26 @@
#include <string.h> // for memset
namespace AZ
namespace AZ::SfmtInternal
{
namespace SfmtInternal
{
static const int N32 = N * 4;
static const int N64 = N * 2;
static const int POS1 = 122;
static const int SL1 = 18;
static const int SR1 = 11;
static const int SL2 = 1;
static const int SR2 = 1;
static const unsigned int MSK1 = 0xdfffffefU;
static const unsigned int MSK2 = 0xddfecb7fU;
static const unsigned int MSK3 = 0xbffaffffU;
static const unsigned int MSK4 = 0xbffffff6U;
static const unsigned int PARITY1 = 0x00000001U;
static const unsigned int PARITY2 = 0x00000000U;
static const unsigned int PARITY3 = 0x00000000U;
static const unsigned int PARITY4 = 0x13c9e684U;
static const int N32 = N * 4;
static const int N64 = N * 2;
static const int POS1 = 122;
static const int SL1 = 18;
static const int SR1 = 11;
static const int SL2 = 1;
static const int SR2 = 1;
static const unsigned int MSK1 = 0xdfffffefU;
static const unsigned int MSK2 = 0xddfecb7fU;
static const unsigned int MSK3 = 0xbffaffffU;
static const unsigned int MSK4 = 0xbffffff6U;
static const unsigned int PARITY1 = 0x00000001U;
static const unsigned int PARITY2 = 0x00000000U;
static const unsigned int PARITY3 = 0x00000000U;
static const unsigned int PARITY4 = 0x13c9e684U;
/** a parity check vector which certificate the period of 2^{MEXP} */
static unsigned int parity[4] = {PARITY1, PARITY2, PARITY3, PARITY4};
/** a parity check vector which certificate the period of 2^{MEXP} */
static unsigned int parity[4] = {PARITY1, PARITY2, PARITY3, PARITY4};
#ifdef ONLY64
# define idxof(_i) (_i ^ 1)
@@ -45,259 +43,257 @@ namespace AZ
#if AZ_TRAIT_USE_PLATFORM_SIMD_SSE
/**
* This function represents the recursion formula.
* @param a a 128-bit part of the internal state array
* @param b a 128-bit part of the internal state array
* @param c a 128-bit part of the internal state array
* @param d a 128-bit part of the internal state array
* @param mask 128-bit mask
* @return output
*/
AZ_FORCE_INLINE static Simd::Vec4::Int32Type simd_recursion(Simd::Vec4::Int32Type* a, Simd::Vec4::Int32Type* b, Simd::Vec4::Int32Type c, Simd::Vec4::Int32Type d, Simd::Vec4::Int32Type mask)
/**
* This function represents the recursion formula.
* @param a a 128-bit part of the internal state array
* @param b a 128-bit part of the internal state array
* @param c a 128-bit part of the internal state array
* @param d a 128-bit part of the internal state array
* @param mask 128-bit mask
* @return output
*/
AZ_FORCE_INLINE static Simd::Vec4::Int32Type simd_recursion(Simd::Vec4::Int32Type* a, Simd::Vec4::Int32Type* b, Simd::Vec4::Int32Type c, Simd::Vec4::Int32Type d, Simd::Vec4::Int32Type mask)
{
Simd::Vec4::Int32Type v, x, y, z;
x = *a;
y = _mm_srli_epi32(*b, SR1);
z = _mm_srli_si128(c, SR2);
v = _mm_slli_epi32(d, SL1);
z = Simd::Vec4::Xor(z, x);
z = Simd::Vec4::Xor(z, v);
x = _mm_slli_si128(x, SL2);
y = Simd::Vec4::And(y, mask);
z = Simd::Vec4::Xor(z, x);
z = Simd::Vec4::Xor(z, y);
return z;
}
/**
* This function fills the internal state array with pseudorandom
* integers.
*/
inline void gen_rand_all(Sfmt& g)
{
int i;
Simd::Vec4::Int32Type r, r1, r2, mask;
mask = Simd::Vec4::LoadImmediate((int32_t)MSK4, (int32_t)MSK3, (int32_t)MSK2, (int32_t)MSK1);
r1 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 2].si);
r2 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 1].si);
for (i = 0; i < N - POS1; i++)
{
Simd::Vec4::Int32Type v, x, y, z;
x = *a;
y = _mm_srli_epi32(*b, SR1);
z = _mm_srli_si128(c, SR2);
v = _mm_slli_epi32(d, SL1);
z = Simd::Vec4::Xor(z, x);
z = Simd::Vec4::Xor(z, v);
x = _mm_slli_si128(x, SL2);
y = Simd::Vec4::And(y, mask);
z = Simd::Vec4::Xor(z, x);
z = Simd::Vec4::Xor(z, y);
return z;
r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[i].si, r);
r1 = r2;
r2 = r;
}
/**
* This function fills the internal state array with pseudorandom
* integers.
*/
inline void gen_rand_all(Sfmt& g)
for (; i < N; i++)
{
int i;
Simd::Vec4::Int32Type r, r1, r2, mask;
mask = Simd::Vec4::LoadImmediate((int32_t)MSK4, (int32_t)MSK3, (int32_t)MSK2, (int32_t)MSK1);
r1 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 2].si);
r2 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 1].si);
for (i = 0; i < N - POS1; i++)
{
r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[i].si, r);
r1 = r2;
r2 = r;
}
for (; i < N; i++)
{
r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1 - N].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[i].si, r);
r1 = r2;
r2 = r;
}
r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1 - N].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[i].si, r);
r1 = r2;
r2 = r;
}
}
/**
* This function fills the user-specified array with pseudorandom
* integers.
*
* @param array an 128-bit array to be filled by pseudorandom numbers.
* @param size number of 128-bit pesudorandom numbers to be generated.
*/
inline void gen_rand_array(Sfmt& g, w128_t* array, int size)
/**
* This function fills the user-specified array with pseudorandom
* integers.
*
* @param array an 128-bit array to be filled by pseudorandom numbers.
* @param size number of 128-bit pesudorandom numbers to be generated.
*/
inline void gen_rand_array(Sfmt& g, w128_t* array, int size)
{
int i, j;
Simd::Vec4::Int32Type r, r1, r2, mask;
mask = Simd::Vec4::LoadImmediate((int32_t)MSK4, (int32_t)MSK3, (int32_t)MSK2, (int32_t)MSK1);
r1 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 2].si);
r2 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 1].si);
for (i = 0; i < N - POS1; i++)
{
int i, j;
Simd::Vec4::Int32Type r, r1, r2, mask;
mask = Simd::Vec4::LoadImmediate((int32_t)MSK4, (int32_t)MSK3, (int32_t)MSK2, (int32_t)MSK1);
r1 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 2].si);
r2 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 1].si);
for (i = 0; i < N - POS1; i++)
{
r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r);
r1 = r2;
r2 = r;
}
for (; i < N; i++)
{
r = simd_recursion(&g.m_sfmt[i].si, &array[i + POS1 - N].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r);
r1 = r2;
r2 = r;
}
/* main loop */
for (; i < size - N; i++)
{
r = simd_recursion(&array[i - N].si, &array[i + POS1 - N].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r);
r1 = r2;
r2 = r;
}
for (j = 0; j < 2 * N - size; j++)
{
r = Simd::Vec4::LoadAligned((const int32_t*)&array[j + size - N].si);
Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[j].si, r);
}
for (; i < size; i++)
{
r = simd_recursion(&array[i - N].si, &array[i + POS1 - N].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r);
Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[j++].si, r);
r1 = r2;
r2 = r;
}
r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r);
r1 = r2;
r2 = r;
}
for (; i < N; i++)
{
r = simd_recursion(&g.m_sfmt[i].si, &array[i + POS1 - N].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r);
r1 = r2;
r2 = r;
}
/* main loop */
for (; i < size - N; i++)
{
r = simd_recursion(&array[i - N].si, &array[i + POS1 - N].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r);
r1 = r2;
r2 = r;
}
for (j = 0; j < 2 * N - size; j++)
{
r = Simd::Vec4::LoadAligned((const int32_t*)&array[j + size - N].si);
Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[j].si, r);
}
for (; i < size; i++)
{
r = simd_recursion(&array[i - N].si, &array[i + POS1 - N].si, r1, r2, mask);
Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r);
Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[j++].si, r);
r1 = r2;
r2 = r;
}
}
#else
inline void rshift128(w128_t* out, w128_t const* in, int shift)
inline void rshift128(w128_t* out, w128_t const* in, int shift)
{
AZ::u64 th, tl, oh, ol;
#ifdef ONLY64
th = ((AZ::u64)in->u[2] << 32) | ((AZ::u64)in->u[3]);
tl = ((AZ::u64)in->u[0] << 32) | ((AZ::u64)in->u[1]);
oh = th >> (shift * 8);
ol = tl >> (shift * 8);
ol |= th << (64 - shift * 8);
out->u[0] = (AZ::u32)(ol >> 32);
out->u[1] = (AZ::u32)ol;
out->u[2] = (AZ::u32)(oh >> 32);
out->u[3] = (AZ::u32)oh;
#else
th = ((AZ::u64)in->u[3] << 32) | ((AZ::u64)in->u[2]);
tl = ((AZ::u64)in->u[1] << 32) | ((AZ::u64)in->u[0]);
oh = th >> (shift * 8);
ol = tl >> (shift * 8);
ol |= th << (64 - shift * 8);
out->u[1] = (AZ::u32)(ol >> 32);
out->u[0] = (AZ::u32)ol;
out->u[3] = (AZ::u32)(oh >> 32);
out->u[2] = (AZ::u32)oh;
#endif
}
inline void lshift128(w128_t* out, w128_t const* in, int shift)
{
AZ::u64 th, tl, oh, ol;
#ifdef ONLY64
th = ((AZ::u64)in->u[2] << 32) | ((AZ::u64)in->u[3]);
tl = ((AZ::u64)in->u[0] << 32) | ((AZ::u64)in->u[1]);
oh = th << (shift * 8);
ol = tl << (shift * 8);
oh |= tl >> (64 - shift * 8);
out->u[0] = (AZ::u32)(ol >> 32);
out->u[1] = (AZ::u32)ol;
out->u[2] = (AZ::u32)(oh >> 32);
out->u[3] = (AZ::u32)oh;
#else
th = ((AZ::u64)in->u[3] << 32) | ((AZ::u64)in->u[2]);
tl = ((AZ::u64)in->u[1] << 32) | ((AZ::u64)in->u[0]);
oh = th << (shift * 8);
ol = tl << (shift * 8);
oh |= tl >> (64 - shift * 8);
out->u[1] = (AZ::u32)(ol >> 32);
out->u[0] = (AZ::u32)ol;
out->u[3] = (AZ::u32)(oh >> 32);
out->u[2] = (AZ::u32)oh;
#endif
}
inline void do_recursion(w128_t* r, w128_t* a, w128_t* b, w128_t* c, w128_t* d)
{
w128_t x;
w128_t y;
lshift128(&x, a, SL2);
rshift128(&y, c, SR2);
#ifdef ONLY64
r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK2) ^ y.u[0] ^ (d->u[0] << SL1);
r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK1) ^ y.u[1] ^ (d->u[1] << SL1);
r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK4) ^ y.u[2] ^ (d->u[2] << SL1);
r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK3) ^ y.u[3] ^ (d->u[3] << SL1);
#else
r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK1) ^ y.u[0] ^ (d->u[0] << SL1);
r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK2) ^ y.u[1] ^ (d->u[1] << SL1);
r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK3) ^ y.u[2] ^ (d->u[2] << SL1);
r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK4) ^ y.u[3] ^ (d->u[3] << SL1);
#endif
}
/**
* This function fills the internal state array with pseudorandom
* integers.
*/
inline void gen_rand_all(Sfmt& g)
{
int i;
w128_t* r1, * r2;
r1 = &g.m_sfmt[N - 2];
r2 = &g.m_sfmt[N - 1];
for (i = 0; i < N - POS1; i++)
{
AZ::u64 th, tl, oh, ol;
#ifdef ONLY64
th = ((AZ::u64)in->u[2] << 32) | ((AZ::u64)in->u[3]);
tl = ((AZ::u64)in->u[0] << 32) | ((AZ::u64)in->u[1]);
oh = th >> (shift * 8);
ol = tl >> (shift * 8);
ol |= th << (64 - shift * 8);
out->u[0] = (AZ::u32)(ol >> 32);
out->u[1] = (AZ::u32)ol;
out->u[2] = (AZ::u32)(oh >> 32);
out->u[3] = (AZ::u32)oh;
#else
th = ((AZ::u64)in->u[3] << 32) | ((AZ::u64)in->u[2]);
tl = ((AZ::u64)in->u[1] << 32) | ((AZ::u64)in->u[0]);
oh = th >> (shift * 8);
ol = tl >> (shift * 8);
ol |= th << (64 - shift * 8);
out->u[1] = (AZ::u32)(ol >> 32);
out->u[0] = (AZ::u32)ol;
out->u[3] = (AZ::u32)(oh >> 32);
out->u[2] = (AZ::u32)oh;
#endif
do_recursion(&g.m_sfmt[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1], r1, r2);
r1 = r2;
r2 = &g.m_sfmt[i];
}
inline void lshift128(w128_t* out, w128_t const* in, int shift)
for (; i < N; i++)
{
AZ::u64 th, tl, oh, ol;
#ifdef ONLY64
th = ((AZ::u64)in->u[2] << 32) | ((AZ::u64)in->u[3]);
tl = ((AZ::u64)in->u[0] << 32) | ((AZ::u64)in->u[1]);
oh = th << (shift * 8);
ol = tl << (shift * 8);
oh |= tl >> (64 - shift * 8);
out->u[0] = (AZ::u32)(ol >> 32);
out->u[1] = (AZ::u32)ol;
out->u[2] = (AZ::u32)(oh >> 32);
out->u[3] = (AZ::u32)oh;
#else
th = ((AZ::u64)in->u[3] << 32) | ((AZ::u64)in->u[2]);
tl = ((AZ::u64)in->u[1] << 32) | ((AZ::u64)in->u[0]);
oh = th << (shift * 8);
ol = tl << (shift * 8);
oh |= tl >> (64 - shift * 8);
out->u[1] = (AZ::u32)(ol >> 32);
out->u[0] = (AZ::u32)ol;
out->u[3] = (AZ::u32)(oh >> 32);
out->u[2] = (AZ::u32)oh;
#endif
do_recursion(&g.m_sfmt[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1 - N], r1, r2);
r1 = r2;
r2 = &g.m_sfmt[i];
}
}
inline void do_recursion(w128_t* r, w128_t* a, w128_t* b, w128_t* c, w128_t* d)
/**
* This function fills the user-specified array with pseudorandom
* integers.
*
* @param array an 128-bit array to be filled by pseudorandom numbers.
* @param size number of 128-bit pseudorandom numbers to be generated.
*/
inline void gen_rand_array(Sfmt& g, w128_t* array, int size)
{
int i, j;
w128_t* r1, * r2;
r1 = &g.m_sfmt[N - 2];
r2 = &g.m_sfmt[N - 1];
for (i = 0; i < N - POS1; i++)
{
w128_t x;
w128_t y;
lshift128(&x, a, SL2);
rshift128(&y, c, SR2);
#ifdef ONLY64
r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK2) ^ y.u[0] ^ (d->u[0] << SL1);
r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK1) ^ y.u[1] ^ (d->u[1] << SL1);
r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK4) ^ y.u[2] ^ (d->u[2] << SL1);
r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK3) ^ y.u[3] ^ (d->u[3] << SL1);
#else
r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK1) ^ y.u[0] ^ (d->u[0] << SL1);
r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK2) ^ y.u[1] ^ (d->u[1] << SL1);
r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK3) ^ y.u[2] ^ (d->u[2] << SL1);
r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK4) ^ y.u[3] ^ (d->u[3] << SL1);
#endif
do_recursion(&array[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1], r1, r2);
r1 = r2;
r2 = &array[i];
}
/**
* This function fills the internal state array with pseudorandom
* integers.
*/
inline void gen_rand_all(Sfmt& g)
for (; i < N; i++)
{
int i;
w128_t* r1, * r2;
r1 = &g.m_sfmt[N - 2];
r2 = &g.m_sfmt[N - 1];
for (i = 0; i < N - POS1; i++)
{
do_recursion(&g.m_sfmt[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1], r1, r2);
r1 = r2;
r2 = &g.m_sfmt[i];
}
for (; i < N; i++)
{
do_recursion(&g.m_sfmt[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1 - N], r1, r2);
r1 = r2;
r2 = &g.m_sfmt[i];
}
do_recursion(&array[i], &g.m_sfmt[i], &array[i + POS1 - N], r1, r2);
r1 = r2;
r2 = &array[i];
}
/**
* This function fills the user-specified array with pseudorandom
* integers.
*
* @param array an 128-bit array to be filled by pseudorandom numbers.
* @param size number of 128-bit pseudorandom numbers to be generated.
*/
inline void gen_rand_array(Sfmt& g, w128_t* array, int size)
for (; i < size - N; i++)
{
int i, j;
w128_t* r1, * r2;
r1 = &g.m_sfmt[N - 2];
r2 = &g.m_sfmt[N - 1];
for (i = 0; i < N - POS1; i++)
{
do_recursion(&array[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1], r1, r2);
r1 = r2;
r2 = &array[i];
}
for (; i < N; i++)
{
do_recursion(&array[i], &g.m_sfmt[i], &array[i + POS1 - N], r1, r2);
r1 = r2;
r2 = &array[i];
}
for (; i < size - N; i++)
{
do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2);
r1 = r2;
r2 = &array[i];
}
for (j = 0; j < 2 * N - size; j++)
{
g.m_sfmt[j] = array[j + size - N];
}
for (; i < size; i++, j++)
{
do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2);
r1 = r2;
r2 = &array[i];
g.m_sfmt[j] = array[i];
}
do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2);
r1 = r2;
r2 = &array[i];
}
for (j = 0; j < 2 * N - size; j++)
{
g.m_sfmt[j] = array[j + size - N];
}
for (; i < size; i++, j++)
{
do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2);
r1 = r2;
r2 = &array[i];
g.m_sfmt[j] = array[i];
}
}
#endif
} // SmftInternal
} // AZ
} // namespace AZ::SfmtInternal
using namespace AZ;
+1 -1
View File
@@ -45,7 +45,7 @@ namespace AZ
static constexpr int ValidUuidStringLength = 32; /// Number of characters (data only, no extra formatting) in a valid UUID string
static const size_t MaxStringBuffer = 39; /// 32 Uuid + 4 dashes + 2 brackets + 1 terminate
Uuid() {}
Uuid() = default;
Uuid(const char* string, size_t stringLength = 0) { *this = CreateString(string, stringLength); }
static Uuid CreateNull();
+1 -1
View File
@@ -30,7 +30,7 @@ namespace AZ
Vector2() = default;
Vector2(const Vector2& v);
Vector2(const Vector2& v) = default;
//! Constructs vector with all components set to the same specified value.
explicit Vector2(float x);
@@ -8,13 +8,6 @@
namespace AZ
{
AZ_MATH_INLINE Vector2::Vector2(const Vector2& v)
: m_value(v.m_value)
{
;
}
AZ_MATH_INLINE Vector2::Vector2(float x)
: m_value(Simd::Vec2::Splat(x))
{
+1 -1
View File
@@ -100,7 +100,7 @@ namespace AZ
void Set(float x, float y, float z);
//! Sets components from an array of 3 floats in xyz order.
void Set(float values[]);
void Set(const float values[]);
//! Indexed access using operator(), just for convenience.
float operator()(int32_t index) const;
@@ -186,7 +186,7 @@ namespace AZ
}
AZ_MATH_INLINE void Vector3::Set(float values[])
AZ_MATH_INLINE void Vector3::Set(const float values[])
{
m_value = Simd::Vec3::LoadImmediate(values[0], values[1], values[2]);
}
@@ -20,44 +20,42 @@
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/containers/array.h>
using namespace AZ;
#if !defined(RELEASE) && !defined(AZCORE_MEMORY_ENABLE_OVERRIDES)
# define AZCORE_MEMORY_ENABLE_OVERRIDES
#endif
namespace AZ
namespace AZ::Internal
{
namespace Internal
struct AMStringHasher
{
struct AMStringHasher
using is_transparent = void;
template<typename ConvertibleToStringView>
size_t operator()(const ConvertibleToStringView& key)
{
using is_transparent = void;
template<typename ConvertibleToStringView>
size_t operator()(const ConvertibleToStringView& key)
{
return AZStd::hash<AZStd::string_view>{}(key);
}
};
using AMString = AZStd::basic_string<char, AZStd::char_traits<char>, AZStdIAllocator>;
using AllocatorNameMap = AZStd::unordered_map<AMString, IAllocator*, AMStringHasher, AZStd::equal_to<>, AZStdIAllocator>;
using AllocatorRemappings = AZStd::unordered_map<AMString, AMString, AMStringHasher, AZStd::equal_to<>, AZStdIAllocator>;
return AZStd::hash<AZStd::string_view>{}(key);
}
};
using AMString = AZStd::basic_string<char, AZStd::char_traits<char>, AZStdIAllocator>;
using AllocatorNameMap = AZStd::unordered_map<AMString, IAllocator*, AMStringHasher, AZStd::equal_to<>, AZStdIAllocator>;
using AllocatorRemappings = AZStd::unordered_map<AMString, AMString, AMStringHasher, AZStd::equal_to<>, AZStdIAllocator>;
// For allocators that are created before we have an environment, we keep some module-local data for them so that we can register them
// properly once the environment is attached.
struct PreEnvironmentAttachData
{
static const int MAX_UNREGISTERED_ALLOCATORS = 8;
AZStd::mutex m_mutex;
MallocSchema m_mallocSchema;
IAllocator* m_unregisteredAllocators[MAX_UNREGISTERED_ALLOCATORS];
int m_unregisteredAllocatorCount = 0;
};
// For allocators that are created before we have an environment, we keep some module-local data for them so that we can register them
// properly once the environment is attached.
struct PreEnvironmentAttachData
{
static const int MAX_UNREGISTERED_ALLOCATORS = 8;
AZStd::mutex m_mutex;
MallocSchema m_mallocSchema;
IAllocator* m_unregisteredAllocators[MAX_UNREGISTERED_ALLOCATORS];
int m_unregisteredAllocatorCount = 0;
};
}
}
struct AZ::AllocatorManager::InternalData
namespace AZ
{
struct AllocatorManager::InternalData
{
explicit InternalData(const AZStdIAllocator& alloc)
: m_allocatorMap(alloc)
@@ -69,13 +67,13 @@ struct AZ::AllocatorManager::InternalData
Internal::AllocatorRemappings m_remappingsReverse;
};
static AZ::EnvironmentVariable<AllocatorManager> s_allocManager = nullptr;
static EnvironmentVariable<AllocatorManager> s_allocManager = nullptr;
static AllocatorManager* s_allocManagerDebug = nullptr; // For easier viewing in crash dumps
/// Returns a module-local instance of data to use for allocators that are created before the environment is attached.
static AZ::Internal::PreEnvironmentAttachData& GetPreEnvironmentAttachData()
static Internal::PreEnvironmentAttachData& GetPreEnvironmentAttachData()
{
static AZ::Internal::PreEnvironmentAttachData s_data;
static Internal::PreEnvironmentAttachData s_data;
return s_data;
}
@@ -131,7 +129,7 @@ AllocatorManager& AllocatorManager::Instance()
if (!s_allocManager)
{
AZ_Assert(Environment::IsReady(), "Environment must be ready before calling Instance()");
s_allocManager = AZ::Environment::CreateVariable<AllocatorManager>(AZ_CRC("AZ::AllocatorManager::s_allocManager", 0x6bdd908c));
s_allocManager = Environment::CreateVariable<AllocatorManager>(AZ_CRC_CE("AZ::AllocatorManager::s_allocManager"));
// Register any allocators that were created in this module before we attached to the environment
auto& data = GetPreEnvironmentAttachData();
@@ -156,9 +154,9 @@ AllocatorManager& AllocatorManager::Instance()
//////////////////////////////////////////////////////////////////////////
// Create malloc schema using custom AZ_OS_MALLOC allocator.
AZ::MallocSchema* AllocatorManager::CreateMallocSchema()
MallocSchema* AllocatorManager::CreateMallocSchema()
{
return static_cast<AZ::MallocSchema*>(new(AZ_OS_MALLOC(sizeof(AZ::MallocSchema), alignof(AZ::MallocSchema))) AZ::MallocSchema());
return static_cast<MallocSchema*>(new(AZ_OS_MALLOC(sizeof(MallocSchema), alignof(MallocSchema))) MallocSchema());
}
@@ -168,7 +166,7 @@ AZ::MallocSchema* AllocatorManager::CreateMallocSchema()
//=========================================================================
AllocatorManager::AllocatorManager()
: m_profilingRefcount(0)
, m_mallocSchema(CreateMallocSchema(), [](AZ::MallocSchema* schema)
, m_mallocSchema(CreateMallocSchema(), [](MallocSchema* schema)
{
if (schema)
{
@@ -182,7 +180,7 @@ AllocatorManager::AllocatorManager()
m_numAllocators = 0;
m_isAllocatorLeaking = false;
m_configurationFinalized = false;
m_defaultTrackingRecordMode = AZ::Debug::AllocationRecords::RECORD_NO_RECORDS;
m_defaultTrackingRecordMode = Debug::AllocationRecords::RECORD_NO_RECORDS;
m_data = new (m_mallocSchema->Allocate(sizeof(InternalData), AZStd::alignment_of<InternalData>::value, 0)) InternalData(AZStdIAllocator(m_mallocSchema.get()));
}
@@ -411,12 +409,12 @@ AllocatorManager::RemoveOutOfMemoryListener()
// [9/16/2011]
//=========================================================================
void
AllocatorManager::SetTrackingMode(AZ::Debug::AllocationRecords::Mode mode)
AllocatorManager::SetTrackingMode(Debug::AllocationRecords::Mode mode)
{
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorListMutex);
for (int i = 0; i < m_numAllocators; ++i)
{
AZ::Debug::AllocationRecords* records = m_allocators[i]->GetRecords();
Debug::AllocationRecords* records = m_allocators[i]->GetRecords();
if (records)
{
records->SetMode(mode);
@@ -595,31 +593,31 @@ void AllocatorManager::GetAllocatorStats(size_t& allocatedBytes, size_t& capacit
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorListMutex);
const int allocatorCount = GetNumAllocators();
AZStd::unordered_map<AZ::IAllocatorAllocate*, AZ::IAllocator*> existingAllocators;
AZStd::unordered_map<AZ::IAllocatorAllocate*, AZ::IAllocator*> sourcesToAllocators;
AZStd::unordered_map<IAllocatorAllocate*, IAllocator*> existingAllocators;
AZStd::unordered_map<IAllocatorAllocate*, IAllocator*> sourcesToAllocators;
// Build a mapping of original allocator sources to their allocators
for (int i = 0; i < allocatorCount; ++i)
{
AZ::IAllocator* allocator = GetAllocator(i);
IAllocator* allocator = GetAllocator(i);
sourcesToAllocators.emplace(allocator->GetOriginalAllocationSource(), allocator);
}
for (int i = 0; i < allocatorCount; ++i)
{
AZ::IAllocator* allocator = GetAllocator(i);
AZ::IAllocatorAllocate* source = allocator->GetAllocationSource();
AZ::IAllocatorAllocate* originalSource = allocator->GetOriginalAllocationSource();
AZ::IAllocatorAllocate* schema = allocator->GetSchema();
AZ::IAllocator* alias = (source != originalSource) ? sourcesToAllocators[source] : nullptr;
IAllocator* allocator = GetAllocator(i);
IAllocatorAllocate* source = allocator->GetAllocationSource();
IAllocatorAllocate* originalSource = allocator->GetOriginalAllocationSource();
IAllocatorAllocate* schema = allocator->GetSchema();
IAllocator* alias = (source != originalSource) ? sourcesToAllocators[source] : nullptr;
if (schema && !alias)
{
// Check to see if this allocator's source maps to another allocator
// Need to check both the schema and the allocator itself, as either one might be used as the alias depending on how it's implemented
AZStd::array<AZ::IAllocatorAllocate*, 2> checkAllocators = { { schema, allocator->GetAllocationSource() } };
AZStd::array<IAllocatorAllocate*, 2> checkAllocators = { { schema, allocator->GetAllocationSource() } };
for (AZ::IAllocatorAllocate* check : checkAllocators)
for (IAllocatorAllocate* check : checkAllocators)
{
auto existing = existingAllocators.emplace(check, allocator);
@@ -631,7 +629,7 @@ void AllocatorManager::GetAllocatorStats(size_t& allocatedBytes, size_t& capacit
}
}
static const AZ::IAllocator* OS_ALLOCATOR = &AZ::AllocatorInstance<AZ::OSAllocator>::GetAllocator();
static const IAllocator* OS_ALLOCATOR = &AllocatorInstance<OSAllocator>::GetAllocator();
size_t sourceAllocatedBytes = source->NumAllocatedBytes();
size_t sourceCapacityBytes = source->Capacity();
@@ -742,3 +740,5 @@ AllocatorManager::DebugBreak(void* address, const Debug::AllocationInfo& info)
}
}
}
} // namespace AZ
@@ -8,223 +8,220 @@
#include <AzCore/Memory/AllocatorOverrideShim.h>
namespace AZ
namespace AZ::Internal
{
namespace Internal
AllocatorOverrideShim* AllocatorOverrideShim::Create(IAllocator* owningAllocator, IAllocatorAllocate* shimAllocationSource)
{
AllocatorOverrideShim* AllocatorOverrideShim::Create(IAllocator* owningAllocator, IAllocatorAllocate* shimAllocationSource)
void* memory = shimAllocationSource->Allocate(sizeof(AllocatorOverrideShim), AZStd::alignment_of<AllocatorOverrideShim>::value, 0);
auto result = new (memory) AllocatorOverrideShim(owningAllocator, shimAllocationSource);
return result;
}
void AllocatorOverrideShim::Destroy(AllocatorOverrideShim* source)
{
auto shimAllocationSource = source->m_shimAllocationSource;
source->~AllocatorOverrideShim();
shimAllocationSource->DeAllocate(source);
}
AllocatorOverrideShim::AllocatorOverrideShim(IAllocator* owningAllocator, IAllocatorAllocate* shimAllocationSource)
: m_owningAllocator(owningAllocator)
, m_source(owningAllocator->GetOriginalAllocationSource())
, m_overridingSource(owningAllocator->GetOriginalAllocationSource())
, m_shimAllocationSource(shimAllocationSource)
, m_records(typename AllocationSet::hasher(), typename AllocationSet::key_eq(), StdAllocationSrc(shimAllocationSource))
{
}
void AllocatorOverrideShim::SetOverride(IAllocatorAllocate* source)
{
m_overridingSource = source;
}
IAllocatorAllocate* AllocatorOverrideShim::GetOverride() const
{
return m_overridingSource;
}
bool AllocatorOverrideShim::IsOverridden() const
{
return m_source != m_overridingSource;
}
bool AllocatorOverrideShim::HasOrphanedAllocations() const
{
return !m_records.empty();
}
void AllocatorOverrideShim::SetFinalizedConfiguration()
{
m_finalizedConfiguration = true;
}
typename AllocatorOverrideShim::pointer_type AllocatorOverrideShim::Allocate(size_type byteSize, size_type alignment, int flags, const char* name, const char* fileName, int lineNum, unsigned int suppressStackRecord)
{
pointer_type ptr = m_overridingSource->Allocate(byteSize, alignment, flags, name, fileName, lineNum, suppressStackRecord);
if (!IsOverridden())
{
void* memory = shimAllocationSource->Allocate(sizeof(AllocatorOverrideShim), AZStd::alignment_of<AllocatorOverrideShim>::value, 0);
auto result = new (memory) AllocatorOverrideShim(owningAllocator, shimAllocationSource);
return result;
lock_type lock(m_mutex);
m_records.insert(ptr); // Record in case we need to orphan this allocation later
}
void AllocatorOverrideShim::Destroy(AllocatorOverrideShim* source)
return ptr;
}
void AllocatorOverrideShim::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment)
{
IAllocatorAllocate* source = m_overridingSource;
bool destroy = false;
{
auto shimAllocationSource = source->m_shimAllocationSource;
source->~AllocatorOverrideShim();
shimAllocationSource->DeAllocate(source);
lock_type lock(m_mutex);
// Check to see if this came from a prior allocation source
if (m_records.erase(ptr) && IsOverridden())
{
source = m_source;
if (m_records.empty() && m_finalizedConfiguration)
{
// All orphaned records are gone; we are no longer needed
m_owningAllocator->SetAllocationSource(m_overridingSource);
destroy = true; // Must destroy outside the lock
}
}
}
AllocatorOverrideShim::AllocatorOverrideShim(IAllocator* owningAllocator, IAllocatorAllocate* shimAllocationSource)
: m_owningAllocator(owningAllocator)
, m_source(owningAllocator->GetOriginalAllocationSource())
, m_overridingSource(owningAllocator->GetOriginalAllocationSource())
, m_shimAllocationSource(shimAllocationSource)
, m_records(typename AllocationSet::hasher(), typename AllocationSet::key_eq(), StdAllocationSrc(shimAllocationSource))
source->DeAllocate(ptr, byteSize, alignment);
if (destroy)
{
Destroy(this);
}
}
typename AllocatorOverrideShim::size_type AllocatorOverrideShim::Resize(pointer_type ptr, size_type newSize)
{
IAllocatorAllocate* source = m_overridingSource;
if (IsOverridden())
{
// Determine who owns the allocation
lock_type lock(m_mutex);
if (m_records.count(ptr))
{
source = m_source;
}
}
void AllocatorOverrideShim::SetOverride(IAllocatorAllocate* source)
size_t result = source->Resize(ptr, newSize);
return result;
}
typename AllocatorOverrideShim::pointer_type AllocatorOverrideShim::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment)
{
pointer_type newPtr = nullptr;
bool useOverride = true;
bool destroy = false;
if (IsOverridden())
{
m_overridingSource = source;
lock_type lock(m_mutex);
if (m_records.erase(ptr))
{
// An old allocation needs to be transferred to the new, overriding allocator.
useOverride = false; // We'll do the reallocation here
size_t oldSize = m_source->AllocationSize(ptr);
if (newSize)
{
newPtr = m_overridingSource->Allocate(newSize, newAlignment, 0);
memcpy(newPtr, ptr, AZStd::min(newSize, oldSize));
}
m_source->DeAllocate(ptr, oldSize);
if (m_records.empty() && m_finalizedConfiguration)
{
// All orphaned records are gone; we are no longer needed
m_owningAllocator->SetAllocationSource(m_overridingSource);
destroy = true; // Must destroy outside the lock
}
}
}
IAllocatorAllocate* AllocatorOverrideShim::GetOverride() const
if (useOverride)
{
return m_overridingSource;
}
bool AllocatorOverrideShim::IsOverridden() const
{
return m_source != m_overridingSource;
}
bool AllocatorOverrideShim::HasOrphanedAllocations() const
{
return !m_records.empty();
}
void AllocatorOverrideShim::SetFinalizedConfiguration()
{
m_finalizedConfiguration = true;
}
typename AllocatorOverrideShim::pointer_type AllocatorOverrideShim::Allocate(size_type byteSize, size_type alignment, int flags, const char* name, const char* fileName, int lineNum, unsigned int suppressStackRecord)
{
pointer_type ptr = m_overridingSource->Allocate(byteSize, alignment, flags, name, fileName, lineNum, suppressStackRecord);
// Default behavior, we weren't deleting an old allocation
newPtr = m_overridingSource->ReAllocate(ptr, newSize, newAlignment);
if (!IsOverridden())
{
// Still need to do bookkeeping if we haven't been overridden yet
lock_type lock(m_mutex);
m_records.insert(ptr); // Record in case we need to orphan this allocation later
}
return ptr;
}
void AllocatorOverrideShim::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment)
{
IAllocatorAllocate* source = m_overridingSource;
bool destroy = false;
{
lock_type lock(m_mutex);
// Check to see if this came from a prior allocation source
if (m_records.erase(ptr) && IsOverridden())
{
source = m_source;
if (m_records.empty() && m_finalizedConfiguration)
{
// All orphaned records are gone; we are no longer needed
m_owningAllocator->SetAllocationSource(m_overridingSource);
destroy = true; // Must destroy outside the lock
}
}
}
source->DeAllocate(ptr, byteSize, alignment);
if (destroy)
{
Destroy(this);
m_records.erase(ptr);
m_records.insert(newPtr);
}
}
typename AllocatorOverrideShim::size_type AllocatorOverrideShim::Resize(pointer_type ptr, size_type newSize)
if (destroy)
{
IAllocatorAllocate* source = m_overridingSource;
if (IsOverridden())
{
// Determine who owns the allocation
lock_type lock(m_mutex);
if (m_records.count(ptr))
{
source = m_source;
}
}
size_t result = source->Resize(ptr, newSize);
return result;
}
typename AllocatorOverrideShim::pointer_type AllocatorOverrideShim::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment)
{
pointer_type newPtr = nullptr;
bool useOverride = true;
bool destroy = false;
if (IsOverridden())
{
lock_type lock(m_mutex);
if (m_records.erase(ptr))
{
// An old allocation needs to be transferred to the new, overriding allocator.
useOverride = false; // We'll do the reallocation here
size_t oldSize = m_source->AllocationSize(ptr);
if (newSize)
{
newPtr = m_overridingSource->Allocate(newSize, newAlignment, 0);
memcpy(newPtr, ptr, AZStd::min(newSize, oldSize));
}
m_source->DeAllocate(ptr, oldSize);
if (m_records.empty() && m_finalizedConfiguration)
{
// All orphaned records are gone; we are no longer needed
m_owningAllocator->SetAllocationSource(m_overridingSource);
destroy = true; // Must destroy outside the lock
}
}
}
if (useOverride)
{
// Default behavior, we weren't deleting an old allocation
newPtr = m_overridingSource->ReAllocate(ptr, newSize, newAlignment);
if (!IsOverridden())
{
// Still need to do bookkeeping if we haven't been overridden yet
lock_type lock(m_mutex);
m_records.erase(ptr);
m_records.insert(newPtr);
}
}
if (destroy)
{
Destroy(this);
}
return newPtr;
}
typename AllocatorOverrideShim::size_type AllocatorOverrideShim::AllocationSize(pointer_type ptr)
{
IAllocatorAllocate* source = m_overridingSource;
if (IsOverridden())
{
// Determine who owns the allocation
lock_type lock(m_mutex);
if (m_records.count(ptr))
{
source = m_source;
}
}
return source->AllocationSize(ptr);
}
void AllocatorOverrideShim::GarbageCollect()
{
m_source->GarbageCollect();
}
typename AllocatorOverrideShim::size_type AllocatorOverrideShim::NumAllocatedBytes() const
{
return m_source->NumAllocatedBytes();
}
typename AllocatorOverrideShim::size_type AllocatorOverrideShim::Capacity() const
{
return m_source->Capacity();
}
typename AllocatorOverrideShim::size_type AllocatorOverrideShim::GetMaxAllocationSize() const
{
return m_source->GetMaxAllocationSize();
}
auto AllocatorOverrideShim::GetMaxContiguousAllocationSize() const -> size_type
{
return m_source->GetMaxContiguousAllocationSize();
}
IAllocatorAllocate* AllocatorOverrideShim::GetSubAllocator()
{
return m_source->GetSubAllocator();
Destroy(this);
}
return newPtr;
}
}
typename AllocatorOverrideShim::size_type AllocatorOverrideShim::AllocationSize(pointer_type ptr)
{
IAllocatorAllocate* source = m_overridingSource;
if (IsOverridden())
{
// Determine who owns the allocation
lock_type lock(m_mutex);
if (m_records.count(ptr))
{
source = m_source;
}
}
return source->AllocationSize(ptr);
}
void AllocatorOverrideShim::GarbageCollect()
{
m_source->GarbageCollect();
}
typename AllocatorOverrideShim::size_type AllocatorOverrideShim::NumAllocatedBytes() const
{
return m_source->NumAllocatedBytes();
}
typename AllocatorOverrideShim::size_type AllocatorOverrideShim::Capacity() const
{
return m_source->Capacity();
}
typename AllocatorOverrideShim::size_type AllocatorOverrideShim::GetMaxAllocationSize() const
{
return m_source->GetMaxAllocationSize();
}
auto AllocatorOverrideShim::GetMaxContiguousAllocationSize() const -> size_type
{
return m_source->GetMaxContiguousAllocationSize();
}
IAllocatorAllocate* AllocatorOverrideShim::GetSubAllocator()
{
return m_source->GetSubAllocator();
}
} // namespace AZ::Internal
@@ -140,8 +140,8 @@ namespace AZ {
class const_iterator;
class iterator
{
typedef T& reference;
typedef T* pointer;
using reference = T&;
using pointer = T*;
friend class const_iterator;
T* mPtr;
public:
@@ -171,8 +171,8 @@ namespace AZ {
class const_iterator
{
typedef const T& reference;
typedef const T* pointer;
using reference = const T &;
using pointer = const T *;
const T* mPtr;
public:
const_iterator()
@@ -327,7 +327,7 @@ namespace AZ {
uint64_t mSizeAndFlags;
public:
typedef block_header* block_ptr;
using block_ptr = block_header *;
size_t size() const { return mSizeAndFlags & ~BL_FLAG_MASK; }
block_ptr next() const {return (block_ptr)((char*)mem() + size()); }
block_ptr prev() const {return mPrev; }
@@ -415,7 +415,7 @@ namespace AZ {
void dec_ref() { HPPA_ASSERT(mUseCount > 0); mUseCount--; }
bool check_marker(size_t marker) const { return mMarker == (marker ^ ((size_t)this)); }
};
typedef intrusive_list<page> page_list;
using page_list = intrusive_list<page>;
class bucket
{
page_list mPageList;
@@ -11,149 +11,168 @@
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/std/algorithm.h>
namespace AZ::Internal
{
struct Header
{
uint32_t offset;
uint32_t size;
};
} // namespace AZ::Internal
namespace AZ
{
namespace Internal
//---------------------------------------------------------------------
// MallocSchema methods
//---------------------------------------------------------------------
MallocSchema::MallocSchema(const Descriptor& desc)
: m_bytesAllocated(0)
{
struct Header
if (desc.m_useAZMalloc)
{
uint32_t offset;
uint32_t size;
};
static const int DEFAULT_ALIGNMENT = sizeof(void*) * 2; // Default malloc alignment
m_mallocFn = [](size_t byteSize)
{
return AZ_OS_MALLOC(byteSize, DEFAULT_ALIGNMENT);
};
m_freeFn = [](void* ptr)
{
AZ_OS_FREE(ptr);
};
}
else
{
m_mallocFn = &malloc;
m_freeFn = &free;
}
}
}
//---------------------------------------------------------------------
// MallocSchema methods
//---------------------------------------------------------------------
AZ::MallocSchema::MallocSchema(const Descriptor& desc) :
m_bytesAllocated(0)
{
if (desc.m_useAZMalloc)
MallocSchema::~MallocSchema()
{
static const int DEFAULT_ALIGNMENT = sizeof(void*) * 2; // Default malloc alignment
m_mallocFn = [](size_t byteSize) { return AZ_OS_MALLOC(byteSize, DEFAULT_ALIGNMENT); };
m_freeFn = [](void* ptr) { AZ_OS_FREE(ptr); };
}
else
MallocSchema::pointer_type MallocSchema::Allocate(
size_type byteSize,
size_type alignment,
int flags,
const char* name,
const char* fileName,
int lineNum,
unsigned int suppressStackRecord)
{
m_mallocFn = &malloc;
m_freeFn = &free;
(void)flags;
(void)name;
(void)fileName;
(void)lineNum;
(void)suppressStackRecord;
if (!byteSize)
{
return nullptr;
}
if (alignment == 0)
{
alignment = sizeof(void*) * 2; // Default malloc alignment
}
AZ_Assert(byteSize < 0x100000000ull, "Malloc allocator only allocates up to 4GB");
size_type required = byteSize + sizeof(Internal::Header) +
((alignment > sizeof(double))
? alignment
: 0); // Malloc will align to a minimum boundary for native objects, so we only pad if aligning to a large value
void* data = (*m_mallocFn)(required);
void* result = PointerAlignUp(reinterpret_cast<void*>(reinterpret_cast<size_t>(data) + sizeof(Internal::Header)), alignment);
Internal::Header* header = PointerAlignDown<Internal::Header>(
(Internal::Header*)(reinterpret_cast<size_t>(result) - sizeof(Internal::Header)), AZStd::alignment_of<Internal::Header>::value);
header->offset = static_cast<uint32_t>(reinterpret_cast<size_type>(result) - reinterpret_cast<size_type>(data));
header->size = static_cast<uint32_t>(byteSize);
m_bytesAllocated += byteSize;
return result;
}
}
AZ::MallocSchema::~MallocSchema()
{
}
void MallocSchema::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment)
{
(void)byteSize;
(void)alignment;
AZ::MallocSchema::pointer_type AZ::MallocSchema::Allocate(size_type byteSize, size_type alignment, int flags, const char* name, const char* fileName, int lineNum, unsigned int suppressStackRecord)
{
(void)flags;
(void)name;
(void)fileName;
(void)lineNum;
(void)suppressStackRecord;
if (!ptr)
{
return;
}
if (!byteSize)
Internal::Header* header = PointerAlignDown(
reinterpret_cast<Internal::Header*>(reinterpret_cast<size_t>(ptr) - sizeof(Internal::Header)),
AZStd::alignment_of<Internal::Header>::value);
void* freePtr = reinterpret_cast<void*>(reinterpret_cast<size_t>(ptr) - static_cast<size_t>(header->offset));
m_bytesAllocated -= header->size;
(*m_freeFn)(freePtr);
}
MallocSchema::pointer_type MallocSchema::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment)
{
void* newPtr = Allocate(newSize, newAlignment, 0);
size_t oldSize = AllocationSize(ptr);
memcpy(newPtr, ptr, AZStd::min(oldSize, newSize));
DeAllocate(ptr, 0, 0);
return newPtr;
}
MallocSchema::size_type MallocSchema::Resize(pointer_type ptr, size_type newSize)
{
(void)ptr;
(void)newSize;
return 0;
}
MallocSchema::size_type MallocSchema::AllocationSize(pointer_type ptr)
{
if (!ptr)
{
return 0;
}
Internal::Header* header = PointerAlignDown(
reinterpret_cast<Internal::Header*>(reinterpret_cast<size_t>(ptr) - sizeof(Internal::Header)),
AZStd::alignment_of<Internal::Header>::value);
return header->size;
}
MallocSchema::size_type MallocSchema::NumAllocatedBytes() const
{
return m_bytesAllocated;
}
MallocSchema::size_type MallocSchema::Capacity() const
{
return 0;
}
MallocSchema::size_type MallocSchema::GetMaxAllocationSize() const
{
return 0xFFFFFFFFull;
}
MallocSchema::size_type MallocSchema::GetMaxContiguousAllocationSize() const
{
return AZ_CORE_MAX_ALLOCATOR_SIZE;
}
IAllocatorAllocate* MallocSchema::GetSubAllocator()
{
return nullptr;
}
if (alignment == 0)
void MallocSchema::GarbageCollect()
{
alignment = sizeof(void*) * 2; // Default malloc alignment
}
AZ_Assert(byteSize < 0x100000000ull, "Malloc allocator only allocates up to 4GB");
size_type required = byteSize + sizeof(Internal::Header) + ((alignment > sizeof(double)) ? alignment : 0); // Malloc will align to a minimum boundary for native objects, so we only pad if aligning to a large value
void* data = (*m_mallocFn)(required);
void* result = PointerAlignUp(reinterpret_cast<void*>(reinterpret_cast<size_t>(data) + sizeof(Internal::Header)), alignment);
Internal::Header* header = PointerAlignDown<Internal::Header>((Internal::Header*)(reinterpret_cast<size_t>(result) - sizeof(Internal::Header)), AZStd::alignment_of<Internal::Header>::value);
header->offset = static_cast<uint32_t>(reinterpret_cast<size_type>(result) - reinterpret_cast<size_type>(data));
header->size = static_cast<uint32_t>(byteSize);
m_bytesAllocated += byteSize;
return result;
}
void AZ::MallocSchema::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment)
{
(void)byteSize;
(void)alignment;
if (!ptr)
{
return;
}
Internal::Header* header = PointerAlignDown(reinterpret_cast<Internal::Header*>(reinterpret_cast<size_t>(ptr) - sizeof(Internal::Header)), AZStd::alignment_of<Internal::Header>::value);
void* freePtr = reinterpret_cast<void*>(reinterpret_cast<size_t>(ptr) - static_cast<size_t>(header->offset));
m_bytesAllocated -= header->size;
(*m_freeFn)(freePtr);
}
AZ::MallocSchema::pointer_type AZ::MallocSchema::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment)
{
void* newPtr = Allocate(newSize, newAlignment, 0);
size_t oldSize = AllocationSize(ptr);
memcpy(newPtr, ptr, AZStd::min(oldSize, newSize));
DeAllocate(ptr, 0, 0);
return newPtr;
}
AZ::MallocSchema::size_type AZ::MallocSchema::Resize(pointer_type ptr, size_type newSize)
{
(void)ptr;
(void)newSize;
return 0;
}
AZ::MallocSchema::size_type AZ::MallocSchema::AllocationSize(pointer_type ptr)
{
size_type result = 0;
if (ptr)
{
Internal::Header* header = PointerAlignDown(reinterpret_cast<Internal::Header*>(reinterpret_cast<size_t>(ptr) - sizeof(Internal::Header)), AZStd::alignment_of<Internal::Header>::value);
result = header->size;
}
return result;
}
AZ::MallocSchema::size_type AZ::MallocSchema::NumAllocatedBytes() const
{
return m_bytesAllocated;
}
AZ::MallocSchema::size_type AZ::MallocSchema::Capacity() const
{
return 0;
}
AZ::MallocSchema::size_type AZ::MallocSchema::GetMaxAllocationSize() const
{
return 0xFFFFFFFFull;
}
AZ::MallocSchema::size_type AZ::MallocSchema::GetMaxContiguousAllocationSize() const
{
return AZ_CORE_MAX_ALLOCATOR_SIZE;
}
AZ::IAllocatorAllocate* AZ::MallocSchema::GetSubAllocator()
{
return nullptr;
}
void AZ::MallocSchema::GarbageCollect()
{
}
} // namespace AZ
@@ -15,280 +15,277 @@
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Debug/StackTracer.h>
namespace AZ
namespace AZ::Debug
{
namespace Debug
//=========================================================================
// MemoryDriller
// [2/6/2013]
//=========================================================================
MemoryDriller::MemoryDriller(const Descriptor& desc)
{
//=========================================================================
// MemoryDriller
// [2/6/2013]
//=========================================================================
MemoryDriller::MemoryDriller(const Descriptor& desc)
(void)desc;
BusConnect();
AllocatorManager::Instance().EnterProfilingMode();
{
(void)desc;
BusConnect();
AllocatorManager::Instance().EnterProfilingMode();
{
// Register all allocators that were created before the driller existed
auto allocatorLock = AllocatorManager::Instance().LockAllocators();
for (int i = 0; i < AllocatorManager::Instance().GetNumAllocators(); ++i)
{
IAllocator* allocator = AllocatorManager::Instance().GetAllocator(i);
RegisterAllocator(allocator);
}
}
}
//=========================================================================
// ~MemoryDriller
// [2/6/2013]
//=========================================================================
MemoryDriller::~MemoryDriller()
{
BusDisconnect();
AllocatorManager::Instance().ExitProfilingMode();
}
//=========================================================================
// Start
// [2/6/2013]
//=========================================================================
void MemoryDriller::Start(const Param* params, int numParams)
{
(void)params;
(void)numParams;
// dump current allocations for all allocators with tracking
// Register all allocators that were created before the driller existed
auto allocatorLock = AllocatorManager::Instance().LockAllocators();
for (int i = 0; i < AllocatorManager::Instance().GetNumAllocators(); ++i)
{
IAllocator* allocator = AllocatorManager::Instance().GetAllocator(i);
if (auto records = allocator->GetRecords())
{
RegisterAllocatorOutput(allocator);
const AllocationRecordsType& allocMap = records->GetMap();
for (AllocationRecordsType::const_iterator allocIt = allocMap.begin(); allocIt != allocMap.end(); ++allocIt)
{
RegisterAllocationOutput(allocator, allocIt->first, &allocIt->second);
}
}
RegisterAllocator(allocator);
}
}
}
//=========================================================================
// Stop
// [2/6/2013]
//=========================================================================
void MemoryDriller::Stop()
//=========================================================================
// ~MemoryDriller
// [2/6/2013]
//=========================================================================
MemoryDriller::~MemoryDriller()
{
BusDisconnect();
AllocatorManager::Instance().ExitProfilingMode();
}
//=========================================================================
// Start
// [2/6/2013]
//=========================================================================
void MemoryDriller::Start(const Param* params, int numParams)
{
(void)params;
(void)numParams;
// dump current allocations for all allocators with tracking
auto allocatorLock = AllocatorManager::Instance().LockAllocators();
for (int i = 0; i < AllocatorManager::Instance().GetNumAllocators(); ++i)
{
}
//=========================================================================
// RegisterAllocator
// [2/6/2013]
//=========================================================================
void MemoryDriller::RegisterAllocator(IAllocator* allocator)
{
// Ignore if our allocator is already registered
if (allocator->GetRecords() != nullptr)
IAllocator* allocator = AllocatorManager::Instance().GetAllocator(i);
if (auto records = allocator->GetRecords())
{
return;
}
auto debugConfig = allocator->GetDebugConfig();
if (!debugConfig.m_excludeFromDebugging)
{
allocator->SetRecords(aznew Debug::AllocationRecords((unsigned char)debugConfig.m_stackRecordLevels, debugConfig.m_usesMemoryGuards, debugConfig.m_marksUnallocatedMemory, allocator->GetName()));
m_allAllocatorRecords.push_back(allocator->GetRecords());
if (m_output == nullptr)
{
return; // we have no active output
}
RegisterAllocatorOutput(allocator);
}
}
//=========================================================================
// RegisterAllocatorOutput
// [2/6/2013]
//=========================================================================
void MemoryDriller::RegisterAllocatorOutput(IAllocator* allocator)
{
auto records = allocator->GetRecords();
m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d));
m_output->BeginTag(AZ_CRC("RegisterAllocator", 0x19f08114));
m_output->Write(AZ_CRC("Name", 0x5e237e06), allocator->GetName());
m_output->Write(AZ_CRC("Id", 0xbf396750), allocator);
m_output->Write(AZ_CRC("Capacity", 0xb5e8b174), allocator->GetAllocationSource()->Capacity());
m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records);
if (records)
{
m_output->Write(AZ_CRC("RecordsMode", 0x764c147a), (char)records->GetMode());
m_output->Write(AZ_CRC("NumStackLevels", 0xad9cff15), records->GetNumStackLevels());
}
m_output->EndTag(AZ_CRC("RegisterAllocator", 0x19f08114));
m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d));
}
//=========================================================================
// UnregisterAllocator
// [2/6/2013]
//=========================================================================
void MemoryDriller::UnregisterAllocator(IAllocator* allocator)
{
auto allocatorRecords = allocator->GetRecords();
AZ_Assert(allocatorRecords, "This allocator is not registered with the memory driller!");
for (auto records : m_allAllocatorRecords)
{
if (records == allocatorRecords)
const AllocationRecordsType& allocMap = records->GetMap();
for (AllocationRecordsType::const_iterator allocIt = allocMap.begin(); allocIt != allocMap.end(); ++allocIt)
{
m_allAllocatorRecords.remove(records);
break;
RegisterAllocationOutput(allocator, allocIt->first, &allocIt->second);
}
}
delete allocatorRecords;
allocator->SetRecords(nullptr);
}
}
//=========================================================================
// Stop
// [2/6/2013]
//=========================================================================
void MemoryDriller::Stop()
{
}
//=========================================================================
// RegisterAllocator
// [2/6/2013]
//=========================================================================
void MemoryDriller::RegisterAllocator(IAllocator* allocator)
{
// Ignore if our allocator is already registered
if (allocator->GetRecords() != nullptr)
{
return;
}
auto debugConfig = allocator->GetDebugConfig();
if (!debugConfig.m_excludeFromDebugging)
{
allocator->SetRecords(aznew Debug::AllocationRecords((unsigned char)debugConfig.m_stackRecordLevels, debugConfig.m_usesMemoryGuards, debugConfig.m_marksUnallocatedMemory, allocator->GetName()));
m_allAllocatorRecords.push_back(allocator->GetRecords());
if (m_output == nullptr)
{
return; // we have no active output
}
RegisterAllocatorOutput(allocator);
}
}
//=========================================================================
// RegisterAllocatorOutput
// [2/6/2013]
//=========================================================================
void MemoryDriller::RegisterAllocatorOutput(IAllocator* allocator)
{
auto records = allocator->GetRecords();
m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d));
m_output->BeginTag(AZ_CRC("RegisterAllocator", 0x19f08114));
m_output->Write(AZ_CRC("Name", 0x5e237e06), allocator->GetName());
m_output->Write(AZ_CRC("Id", 0xbf396750), allocator);
m_output->Write(AZ_CRC("Capacity", 0xb5e8b174), allocator->GetAllocationSource()->Capacity());
m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records);
if (records)
{
m_output->Write(AZ_CRC("RecordsMode", 0x764c147a), (char)records->GetMode());
m_output->Write(AZ_CRC("NumStackLevels", 0xad9cff15), records->GetNumStackLevels());
}
m_output->EndTag(AZ_CRC("RegisterAllocator", 0x19f08114));
m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d));
}
//=========================================================================
// UnregisterAllocator
// [2/6/2013]
//=========================================================================
void MemoryDriller::UnregisterAllocator(IAllocator* allocator)
{
auto allocatorRecords = allocator->GetRecords();
AZ_Assert(allocatorRecords, "This allocator is not registered with the memory driller!");
for (auto records : m_allAllocatorRecords)
{
if (records == allocatorRecords)
{
m_allAllocatorRecords.remove(records);
break;
}
}
delete allocatorRecords;
allocator->SetRecords(nullptr);
if (m_output == nullptr)
{
return; // we have no active output
}
m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d));
m_output->Write(AZ_CRC("UnregisterAllocator", 0xb2b54f93), allocator);
m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d));
}
//=========================================================================
// RegisterAllocation
// [2/6/2013]
//=========================================================================
void MemoryDriller::RegisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount)
{
auto records = allocator->GetRecords();
if (records)
{
const AllocationInfo* info = records->RegisterAllocation(address, byteSize, alignment, name, fileName, lineNum, stackSuppressCount + 1);
if (m_output == nullptr)
{
return; // we have no active output
}
RegisterAllocationOutput(allocator, address, info);
}
}
//=========================================================================
// RegisterAllocationOutput
// [2/6/2013]
//=========================================================================
void MemoryDriller::RegisterAllocationOutput(IAllocator* allocator, void* address, const AllocationInfo* info)
{
auto records = allocator->GetRecords();
m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d));
m_output->BeginTag(AZ_CRC("RegisterAllocation", 0x992a9780));
m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records);
m_output->Write(AZ_CRC("Address", 0x0d4e6f81), address);
if (info)
{
if (info->m_name)
{
m_output->Write(AZ_CRC("Name", 0x5e237e06), info->m_name);
}
m_output->Write(AZ_CRC("Alignment", 0x2cce1e5c), info->m_alignment);
m_output->Write(AZ_CRC("Size", 0xf7c0246a), info->m_byteSize);
if (info->m_fileName)
{
m_output->Write(AZ_CRC("FileName", 0x3c0be965), info->m_fileName);
m_output->Write(AZ_CRC("FileLine", 0xb33c2395), info->m_lineNum);
}
// copy the stack frames directly, resolving the stack should happen later as this is a SLOW procedure.
if (info->m_stackFrames)
{
m_output->Write(AZ_CRC("Stack", 0x41a87b6a), info->m_stackFrames, info->m_stackFrames + records->GetNumStackLevels());
}
}
m_output->EndTag(AZ_CRC("RegisterAllocation", 0x992a9780));
m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d));
}
//=========================================================================
// UnRegisterAllocation
// [2/6/2013]
//=========================================================================
void MemoryDriller::UnregisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, AllocationInfo* info)
{
auto records = allocator->GetRecords();
if (records)
{
records->UnregisterAllocation(address, byteSize, alignment, info);
if (m_output == nullptr)
{
return; // we have no active output
}
m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d));
m_output->Write(AZ_CRC("UnregisterAllocator", 0xb2b54f93), allocator);
m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d));
}
//=========================================================================
// RegisterAllocation
// [2/6/2013]
//=========================================================================
void MemoryDriller::RegisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount)
{
auto records = allocator->GetRecords();
if (records)
{
const AllocationInfo* info = records->RegisterAllocation(address, byteSize, alignment, name, fileName, lineNum, stackSuppressCount + 1);
if (m_output == nullptr)
{
return; // we have no active output
}
RegisterAllocationOutput(allocator, address, info);
}
}
//=========================================================================
// RegisterAllocationOutput
// [2/6/2013]
//=========================================================================
void MemoryDriller::RegisterAllocationOutput(IAllocator* allocator, void* address, const AllocationInfo* info)
{
auto records = allocator->GetRecords();
m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d));
m_output->BeginTag(AZ_CRC("RegisterAllocation", 0x992a9780));
m_output->BeginTag(AZ_CRC("UnRegisterAllocation", 0xea5dc4cd));
m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records);
m_output->Write(AZ_CRC("Address", 0x0d4e6f81), address);
if (info)
{
if (info->m_name)
{
m_output->Write(AZ_CRC("Name", 0x5e237e06), info->m_name);
}
m_output->Write(AZ_CRC("Alignment", 0x2cce1e5c), info->m_alignment);
m_output->Write(AZ_CRC("Size", 0xf7c0246a), info->m_byteSize);
if (info->m_fileName)
{
m_output->Write(AZ_CRC("FileName", 0x3c0be965), info->m_fileName);
m_output->Write(AZ_CRC("FileLine", 0xb33c2395), info->m_lineNum);
}
// copy the stack frames directly, resolving the stack should happen later as this is a SLOW procedure.
if (info->m_stackFrames)
{
m_output->Write(AZ_CRC("Stack", 0x41a87b6a), info->m_stackFrames, info->m_stackFrames + records->GetNumStackLevels());
}
}
m_output->EndTag(AZ_CRC("RegisterAllocation", 0x992a9780));
m_output->EndTag(AZ_CRC("UnRegisterAllocation", 0xea5dc4cd));
m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d));
}
}
//=========================================================================
// UnRegisterAllocation
// [2/6/2013]
//=========================================================================
void MemoryDriller::UnregisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, AllocationInfo* info)
//=========================================================================
// ReallocateAllocation
// [10/1/2018]
//=========================================================================
void MemoryDriller::ReallocateAllocation(IAllocator* allocator, void* prevAddress, void* newAddress, size_t newByteSize, size_t newAlignment)
{
AllocationInfo info;
UnregisterAllocation(allocator, prevAddress, 0, 0, &info);
RegisterAllocation(allocator, newAddress, newByteSize, newAlignment, info.m_name, info.m_fileName, info.m_lineNum, 0);
}
//=========================================================================
// ResizeAllocation
// [2/6/2013]
//=========================================================================
void MemoryDriller::ResizeAllocation(IAllocator* allocator, void* address, size_t newSize)
{
auto records = allocator->GetRecords();
if (records)
{
auto records = allocator->GetRecords();
if (records)
{
records->UnregisterAllocation(address, byteSize, alignment, info);
records->ResizeAllocation(address, newSize);
if (m_output == nullptr)
{
return; // we have no active output
}
m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d));
m_output->BeginTag(AZ_CRC("UnRegisterAllocation", 0xea5dc4cd));
m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records);
m_output->Write(AZ_CRC("Address", 0x0d4e6f81), address);
m_output->EndTag(AZ_CRC("UnRegisterAllocation", 0xea5dc4cd));
m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d));
if (m_output == nullptr)
{
return; // we have no active output
}
m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d));
m_output->BeginTag(AZ_CRC("ResizeAllocation", 0x8a9c78dc));
m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records);
m_output->Write(AZ_CRC("Address", 0x0d4e6f81), address);
m_output->Write(AZ_CRC("Size", 0xf7c0246a), newSize);
m_output->EndTag(AZ_CRC("ResizeAllocation", 0x8a9c78dc));
m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d));
}
}
void MemoryDriller::DumpAllAllocations()
{
// Create a copy so allocations done during the printing dont end up affecting the container
const AZStd::list<Debug::AllocationRecords*, OSStdAllocator> allocationRecords = m_allAllocatorRecords;
for (auto records : allocationRecords)
{
// Skip if we have had no allocations made
if (records->RequestedAllocs())
{
records->EnumerateAllocations(AZ::Debug::PrintAllocationsCB(true, true));
}
}
}
//=========================================================================
// ReallocateAllocation
// [10/1/2018]
//=========================================================================
void MemoryDriller::ReallocateAllocation(IAllocator* allocator, void* prevAddress, void* newAddress, size_t newByteSize, size_t newAlignment)
{
AllocationInfo info;
UnregisterAllocation(allocator, prevAddress, 0, 0, &info);
RegisterAllocation(allocator, newAddress, newByteSize, newAlignment, info.m_name, info.m_fileName, info.m_lineNum, 0);
}
//=========================================================================
// ResizeAllocation
// [2/6/2013]
//=========================================================================
void MemoryDriller::ResizeAllocation(IAllocator* allocator, void* address, size_t newSize)
{
auto records = allocator->GetRecords();
if (records)
{
records->ResizeAllocation(address, newSize);
if (m_output == nullptr)
{
return; // we have no active output
}
m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d));
m_output->BeginTag(AZ_CRC("ResizeAllocation", 0x8a9c78dc));
m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records);
m_output->Write(AZ_CRC("Address", 0x0d4e6f81), address);
m_output->Write(AZ_CRC("Size", 0xf7c0246a), newSize);
m_output->EndTag(AZ_CRC("ResizeAllocation", 0x8a9c78dc));
m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d));
}
}
void MemoryDriller::DumpAllAllocations()
{
// Create a copy so allocations done during the printing dont end up affecting the container
const AZStd::list<Debug::AllocationRecords*, OSStdAllocator> allocationRecords = m_allAllocatorRecords;
for (auto records : allocationRecords)
{
// Skip if we have had no allocations made
if (records->RequestedAllocs())
{
records->EnumerateAllocations(AZ::Debug::PrintAllocationsCB(true, true));
}
}
}
}// namespace Debug
} // namespace AZ
} // namespace AZ::Debug
@@ -35,8 +35,8 @@ namespace AZ
public:
AZ_CLASS_ALLOCATOR(PoolAllocation<Allocator>, SystemAllocator, 0)
typedef typename Allocator::Page PageType;
typedef typename Allocator::Bucket BucketType;
using PageType = typename Allocator::Page;
using BucketType = typename Allocator::Bucket;
PoolAllocation(Allocator* alloc, size_t pageSize, size_t minAllocationSize, size_t maxAllocationSize);
virtual ~PoolAllocation();
@@ -89,7 +89,7 @@ namespace AZ
void SetupFreeList(size_t elementSize, size_t pageDataBlockSize);
/// We just use a free list of nodes which we cast to the pool type.
typedef AZStd::intrusive_slist<FakeNode, AZStd::slist_base_hook<FakeNode> > FreeListType;
using FreeListType = AZStd::intrusive_slist<FakeNode, AZStd::slist_base_hook<FakeNode>>;
FreeListType m_freeList;
u32 m_bin;
@@ -103,7 +103,7 @@ namespace AZ
*/
struct Bucket
{
typedef AZStd::intrusive_list<Page, AZStd::list_base_hook<Page> > PageListType;
using PageListType = AZStd::intrusive_list<Page, AZStd::list_base_hook<Page>>;
PageListType m_pages;
};
@@ -162,7 +162,7 @@ namespace AZ
return page;
}
typedef PoolAllocation<PoolSchemaImpl> AllocatorType;
using AllocatorType = PoolAllocation<PoolSchemaImpl>;
IAllocatorAllocate* m_pageAllocator;
AllocatorType m_allocator;
void* m_staticDataBlock;
@@ -199,7 +199,7 @@ namespace AZ
void SetupFreeList(size_t elementSize, size_t pageDataBlockSize);
/// We just use a free list of nodes which we cast to the pool type.
typedef AZStd::intrusive_slist<FakeNode, AZStd::slist_base_hook<FakeNode> > FreeListType;
using FreeListType = AZStd::intrusive_slist<FakeNode, AZStd::slist_base_hook<FakeNode>>;
FreeListType m_freeList;
AZStd::lock_free_intrusive_stack_node<Page> m_lfStack; ///< Lock Free stack node
@@ -215,7 +215,7 @@ namespace AZ
*/
struct Bucket
{
typedef AZStd::intrusive_list<Page, AZStd::list_base_hook<Page> > PageListType;
using PageListType = AZStd::intrusive_list<Page, AZStd::list_base_hook<Page>>;
PageListType m_pages;
};
@@ -291,7 +291,7 @@ namespace AZ
ThreadPoolSchema::SetThreadPoolData m_threadPoolSetter;
// Fox X64 we push/pop pages using the m_mutex to sync. Pages are
typedef Bucket::PageListType FreePagesType;
using FreePagesType = Bucket::PageListType;
FreePagesType m_freePages;
AZStd::vector<ThreadPoolData*> m_threads; ///< Array with all separate thread data. Used to traverse end free elements.
@@ -313,12 +313,12 @@ namespace AZ
ThreadPoolData(ThreadPoolSchemaImpl* alloc, size_t pageSize, size_t minAllocationSize, size_t maxAllocationSize);
~ThreadPoolData();
typedef PoolAllocation<ThreadPoolSchemaImpl> AllocatorType;
using AllocatorType = PoolAllocation<ThreadPoolSchemaImpl>;
/**
* Stack with freed elements from other threads. We don't need stamped stack since the ABA problem can not
* happen here. We push from many threads and pop from only one (we don't push from it).
*/
typedef AZStd::lock_free_intrusive_stack<ThreadPoolSchemaImpl::Page::FakeNodeLF, AZStd::lock_free_intrusive_stack_base_hook<ThreadPoolSchemaImpl::Page::FakeNodeLF> > FreedElementsStack;
using FreedElementsStack = AZStd::lock_free_intrusive_stack<ThreadPoolSchemaImpl::Page::FakeNodeLF, AZStd::lock_free_intrusive_stack_base_hook<ThreadPoolSchemaImpl::Page::FakeNodeLF>>;
AllocatorType m_allocator;
FreedElementsStack m_freedElements;
@@ -24,10 +24,10 @@ namespace AZ
class OSStdAllocator
{
public:
typedef void* pointer_type;
typedef AZStd::size_t size_type;
typedef AZStd::ptrdiff_t difference_type;
typedef AZStd::false_type allow_memory_leaks; ///< Regular allocators should not leak.
using pointer_type = void *;
using size_type = AZStd::size_t;
using difference_type = AZStd::ptrdiff_t;
using allow_memory_leaks = AZStd::false_type; ///< Regular allocators should not leak.
OSStdAllocator(Environment::AllocatorInterface* allocator)
: m_name("GlobalEnvironmentAllocator")
@@ -122,7 +122,7 @@ namespace AZ
: public EnvironmentInterface
{
public:
typedef AZStd::unordered_map<u32, void*, AZStd::hash<u32>, AZStd::equal_to<u32>, OSStdAllocator> MapType;
using MapType = AZStd::unordered_map<u32, void *, AZStd::hash<u32>, AZStd::equal_to<u32>, OSStdAllocator>;
static EnvironmentInterface* Get();
static void Attach(EnvironmentInstance sourceEnvironment, bool useAsGetFallback);
@@ -10,21 +10,18 @@
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Module/ModuleManagerBus.h>
namespace AZ
namespace AZ::Internal
{
namespace Internal
AZ::OSString ModuleManagerSearchPathTool::GetModuleDirectory(const AZ::DynamicModuleDescriptor& moduleDesc)
{
AZ::OSString ModuleManagerSearchPathTool::GetModuleDirectory(const AZ::DynamicModuleDescriptor& moduleDesc)
// For each module that is loaded, attempt to set the module's folder as a path for dependent module resolution
AZ::OSString modulePath = moduleDesc.m_dynamicLibraryPath;
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationBus::Events::ResolveModulePath, modulePath);
auto lastPathSep = modulePath.find_last_of(AZ_TRAIT_OS_PATH_SEPARATOR);
if (lastPathSep != modulePath.npos)
{
// For each module that is loaded, attempt to set the module's folder as a path for dependent module resolution
AZ::OSString modulePath = moduleDesc.m_dynamicLibraryPath;
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationBus::Events::ResolveModulePath, modulePath);
auto lastPathSep = modulePath.find_last_of(AZ_TRAIT_OS_PATH_SEPARATOR);
if (lastPathSep != modulePath.npos)
{
modulePath = modulePath.substr(0, lastPathSep);
}
return modulePath;
modulePath = modulePath.substr(0, lastPathSep);
}
} // namespace Internal
} // namespace AZ
return modulePath;
}
} // namespace AZ::Internal
@@ -9,45 +9,41 @@
#include <AzCore/Name/Internal/NameData.h>
#include <AzCore/Name/NameDictionary.h>
namespace AZ
namespace AZ::Internal
{
namespace Internal
NameData::NameData(AZStd::string&& name, Hash hash)
: m_name{AZStd::move(name)}
, m_hash{hash}
{}
AZStd::string_view NameData::GetName() const
{
NameData::NameData(AZStd::string&& name, Hash hash)
: m_name{AZStd::move(name)}
, m_hash{hash}
{}
return m_name;
}
AZStd::string_view NameData::GetName() const
{
return m_name;
}
NameData::Hash NameData::GetHash() const
{
return m_hash;
}
NameData::Hash NameData::GetHash() const
{
return m_hash;
}
void NameData::add_ref()
{
AZ_Assert(m_useCount >= 0, "NameData has been deleted");
++m_useCount;
}
void NameData::add_ref()
void NameData::release()
{
// this could be released after we decrement the counter, therefore we will
// base the release on the hash which is stable
Hash hash = m_hash;
AZ_Assert(m_useCount > 0, "m_useCount is already 0!");
if (m_useCount.fetch_sub(1) == 1)
{
AZ_Assert(m_useCount >= 0, "NameData has been deleted");
++m_useCount;
}
void NameData::release()
{
// this could be released after we decrement the counter, therefore we will
// base the release on the hash which is stable
Hash hash = m_hash;
AZ_Assert(m_useCount > 0, "m_useCount is already 0!");
if (m_useCount.fetch_sub(1) == 1)
if (AZ::NameDictionary::IsReady())
{
if (AZ::NameDictionary::IsReady())
{
AZ::NameDictionary::Instance().TryReleaseName(hash);
}
AZ::NameDictionary::Instance().TryReleaseName(hash);
}
}
}
}
} // namespace AZ::Internal
@@ -32,7 +32,12 @@ namespace AZ
if (!s_instance)
{
s_instance = AZ::Environment::CreateVariable<NameDictionary>(NameDictionaryInstanceName);
// Because the NameDictionary allocates memory using the AZ::Allocator and it is created
// in the executable memory space, it's ownership cannot be transferred to other module memory spaces
// Otherwise this could cause the the NameDictionary to be destroyed in static de-init
// after the AZ::Allocators have been destroyed
// Therefore we supply the isTransferOwnership value of false using CreateVariableEx
s_instance = AZ::Environment::CreateVariableEx<NameDictionary>(NameDictionaryInstanceName, true, false);
}
}
@@ -50,7 +55,12 @@ namespace AZ
if (!s_instance)
{
s_instance = Environment::FindVariable<NameDictionary>(NameDictionaryInstanceName);
// Because the NameDictionary allocates memory using the AZ::Allocator and it is created
// in the executable memory space, it's ownership cannot be transferred to other module memory spaces
// Otherwise this could cause the the NameDictionary to be destroyed in static de-init
// after the AZ::Allocators have been destroyed
// Therefore we supply the isTransferOwnership value of false using CreateVariableEx
s_instance = AZ::Environment::CreateVariableEx<NameDictionary>(NameDictionaryInstanceName, true, false);
}
return s_instance.IsConstructed();
+8 -11
View File
@@ -8,19 +8,16 @@
#include <AzCore/Platform.h>
namespace AZ
namespace AZ::Platform
{
namespace Platform
{
MachineId s_machineId = MachineId(0);
MachineId s_machineId = MachineId(0);
void SetLocalMachineId(AZ::u32 machineId)
void SetLocalMachineId(AZ::u32 machineId)
{
AZ_Assert(machineId != 0, "0 machine ID is reserved!");
if (s_machineId != 0)
{
AZ_Assert(machineId != 0, "0 machine ID is reserved!");
if (s_machineId != 0)
{
s_machineId = machineId;
}
s_machineId = machineId;
}
}
}
} // namespace AZ::Platform
@@ -117,9 +117,9 @@ namespace AZ
if (!explicitOverloads.m_overloads.empty())
{
for (auto methodAndClass : explicitOverloads.m_overloads)
for (const auto& methodAndClass : explicitOverloads.m_overloads)
{
overloads.push_back({ methodAndClass.first, methodAndClass.second });
overloads.emplace_back(methodAndClass.first, methodAndClass.second);
}
}
else
@@ -128,7 +128,7 @@ namespace AZ
do
{
overloads.push_back({ overload, behaviorClass });
overloads.emplace_back(overload, behaviorClass);
overload = overload->m_overload;
}
while (overload);
@@ -170,61 +170,77 @@ ScriptContext* ScriptSystemComponent::AddContext(ScriptContext* context, int ga
ScriptContext* ScriptSystemComponent::AddContextWithId(ScriptContextId id)
{
AZ_Assert(m_contexts.empty() || id != ScriptContextIds::DefaultScriptContextId, "Default script context ID is reserved! Please provide a Unique context ID for you ScriptContext!");
if (GetContext(id) == nullptr)
if (GetContext(id) != nullptr)
{
m_contexts.emplace_back();
ContextContainer& cc = m_contexts.back();
cc.m_context = aznew ScriptContext(id);
cc.m_isOwner = true;
cc.m_garbageCollectorSteps = m_defaultGarbageCollectorSteps;
cc.m_context->SetRequireHook(AZStd::bind(&ScriptSystemComponent::DefaultRequireHook, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3));
if (id != ScriptContextIds::CryScriptContextId)
return nullptr;
}
m_contexts.emplace_back();
ContextContainer& cc = m_contexts.back();
cc.m_context = aznew ScriptContext(id);
cc.m_isOwner = true;
cc.m_garbageCollectorSteps = m_defaultGarbageCollectorSteps;
cc.m_context->SetRequireHook(
[this](lua_State* lua, ScriptContext* context, const char* module) -> int
{
// Reflect script classes
ComponentApplication* app = nullptr;
EBUS_EVENT_RESULT(app, ComponentApplicationBus, GetApplication);
if (app && app->GetDescriptor().m_enableScriptReflection)
return DefaultRequireHook(lua, context, module);
});
if (id != ScriptContextIds::CryScriptContextId)
{
// Reflect script classes
ComponentApplication* app = nullptr;
EBUS_EVENT_RESULT(app, ComponentApplicationBus, GetApplication);
if (app && app->GetDescriptor().m_enableScriptReflection)
{
if (app->GetBehaviorContext())
{
if (app->GetBehaviorContext())
{
cc.m_context->BindTo(app->GetBehaviorContext());
}
else
{
AZ_Error("Script", false, "We are asked to enabled scripting, but the Applicaion has no BehaviorContext! Scripting relies on BehaviorContext!");
}
cc.m_context->BindTo(app->GetBehaviorContext());
}
else
{
AZ_Error("Script", false, "We are asked to enabled scripting, but the Applicaion has no BehaviorContext! Scripting relies on BehaviorContext!");
}
}
return cc.m_context;
}
return nullptr;
return cc.m_context;
}
void ScriptSystemComponent::RestoreDefaultRequireHook(ScriptContextId id)
{
if (auto context = GetContext(id))
auto context = GetContext(id);
if (!context)
{
for (auto& inMemoryModule : m_inMemoryModules)
{
ClearAssetReferences(inMemoryModule.second->GetId());
}
m_inMemoryModules.clear();
context->SetRequireHook(AZStd::bind(&ScriptSystemComponent::DefaultRequireHook, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3));
return;
}
for (auto& inMemoryModule : m_inMemoryModules)
{
ClearAssetReferences(inMemoryModule.second->GetId());
}
m_inMemoryModules.clear();
context->SetRequireHook(
[this](lua_State* lua, ScriptContext* context, const char* module) -> int
{
return DefaultRequireHook(lua, context, module);
});
}
void ScriptSystemComponent::UseInMemoryRequireHook(const InMemoryScriptModules& modules, ScriptContextId id)
{
if (auto context = GetContext(id))
auto context = GetContext(id);
if (nullptr == context)
{
m_inMemoryModules = modules;
context->SetRequireHook(AZStd::bind(&ScriptSystemComponent::InMemoryRequireHook, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3));
return;
}
m_inMemoryModules = modules;
context->SetRequireHook(
[this](lua_State* lua, ScriptContext* context, const char* module) -> int
{
return InMemoryRequireHook(lua, context, module);
});
}
//=========================================================================
@@ -28,133 +28,130 @@
#include <AzCore/std/string/string_view.h>
#include <AzCore/StringFunc/StringFunc.h>
namespace AZ
namespace AZ::ScriptCanvasOnDemandReflection
{
namespace ScriptCanvasOnDemandReflection
// the use of this might have to come at the end of on demand reflection...instead of instantly
// basically, it required that dependent classes are reflected first, I'm not sure they are yet.
AZStd::string GetPrettyNameForAZTypeId(AZ::BehaviorContext& context, AZ::Uuid typeId)
{
// the use of this might have to come at the end of on demand reflection...instead of instantly
// basically, it required that dependent classes are reflected first, I'm not sure they are yet.
AZStd::string GetPrettyNameForAZTypeId(AZ::BehaviorContext& context, AZ::Uuid typeId)
// return capitalized versions of what we need, otherwise just the regular name
// then strip all the stuff
if (typeId == azrtti_typeid<Aabb>())
{
// return capitalized versions of what we need, otherwise just the regular name
// then strip all the stuff
if (typeId == azrtti_typeid<Aabb>())
return "AABB";
}
else if (typeId == azrtti_typeid<bool>())
{
return "Boolean";
}
else if (typeId == azrtti_typeid<Color>())
{
return "Color";
}
else if (typeId == azrtti_typeid<Crc32>())
{
return "CRC";
}
else if (typeId == azrtti_typeid<EntityId>())
{
return "EntityId";
}
else if (typeId == azrtti_typeid<Matrix3x3>())
{
return "Matrix3x3";
}
else if (typeId == azrtti_typeid<Matrix4x4>())
{
return "Matrix4x4";
}
else if (typeId == azrtti_typeid<AZ::s8>())
{
return "Number:s8";
}
else if (typeId == azrtti_typeid<AZ::s16>())
{
return "Number:s16";
}
else if (typeId == azrtti_typeid<AZ::s32>())
{
return "Number:s32";
}
else if (typeId == azrtti_typeid<AZ::s64>())
{
return "Number:s64";
}
else if (typeId == azrtti_typeid<AZ::u8>())
{
return "Number:u8";
}
else if (typeId == azrtti_typeid<AZ::u16>())
{
return "Number:u16";
}
else if (typeId == azrtti_typeid<AZ::u32>())
{
return "Number:u32";
}
else if (typeId == azrtti_typeid<AZ::u64>())
{
return "Number:u64";
}
else if (typeId == azrtti_typeid<float>())
{
return "Number:float";
}
else if (typeId == azrtti_typeid<double>())
{
return "Number";
}
else if (typeId == azrtti_typeid<Obb>())
{
return "OBB";
}
else if (typeId == azrtti_typeid<Plane>())
{
return "Plane";
}
else if (typeId == azrtti_typeid<Quaternion>())
{
return "Quaternion";
}
else if (typeId == azrtti_typeid<AZStd::string>() || typeId == azrtti_typeid<AZStd::string_view>())
{
return "String";
}
else if (typeId == azrtti_typeid<Transform>())
{
return "Transform";
}
else if (typeId == azrtti_typeid<Vector2>())
{
return "Vector2";
}
else if (typeId == azrtti_typeid<Vector3>())
{
return "Vector3";
}
else if (typeId == azrtti_typeid<Vector4>())
{
return "Vector4";
}
else
{
auto bcClassIter = context.m_typeToClassMap.find(typeId);
if (bcClassIter != context.m_typeToClassMap.end())
{
return "AABB";
}
else if (typeId == azrtti_typeid<bool>())
{
return "Boolean";
}
else if (typeId == azrtti_typeid<Color>())
{
return "Color";
}
else if (typeId == azrtti_typeid<Crc32>())
{
return "CRC";
}
else if (typeId == azrtti_typeid<EntityId>())
{
return "EntityId";
}
else if (typeId == azrtti_typeid<Matrix3x3>())
{
return "Matrix3x3";
}
else if (typeId == azrtti_typeid<Matrix4x4>())
{
return "Matrix4x4";
}
else if (typeId == azrtti_typeid<AZ::s8>())
{
return "Number:s8";
}
else if (typeId == azrtti_typeid<AZ::s16>())
{
return "Number:s16";
}
else if (typeId == azrtti_typeid<AZ::s32>())
{
return "Number:s32";
}
else if (typeId == azrtti_typeid<AZ::s64>())
{
return "Number:s64";
}
else if (typeId == azrtti_typeid<AZ::u8>())
{
return "Number:u8";
}
else if (typeId == azrtti_typeid<AZ::u16>())
{
return "Number:u16";
}
else if (typeId == azrtti_typeid<AZ::u32>())
{
return "Number:u32";
}
else if (typeId == azrtti_typeid<AZ::u64>())
{
return "Number:u64";
}
else if (typeId == azrtti_typeid<float>())
{
return "Number:float";
}
else if (typeId == azrtti_typeid<double>())
{
return "Number";
}
else if (typeId == azrtti_typeid<Obb>())
{
return "OBB";
}
else if (typeId == azrtti_typeid<Plane>())
{
return "Plane";
}
else if (typeId == azrtti_typeid<Quaternion>())
{
return "Quaternion";
}
else if (typeId == azrtti_typeid<AZStd::string>() || typeId == azrtti_typeid<AZStd::string_view>())
{
return "String";
}
else if (typeId == azrtti_typeid<Transform>())
{
return "Transform";
}
else if (typeId == azrtti_typeid<Vector2>())
{
return "Vector2";
}
else if (typeId == azrtti_typeid<Vector3>())
{
return "Vector3";
}
else if (typeId == azrtti_typeid<Vector4>())
{
return "Vector4";
const AZ::BehaviorClass& bcClass = *(bcClassIter->second);
AZStd::string uglyName = bcClass.m_name;
AZ::StringFunc::Replace(uglyName, "AZStd::", "", true);
AZ::StringFunc::Replace(uglyName, "AZ::", "", true);
AZ::StringFunc::Replace(uglyName, "::", ".", true);
return uglyName;
}
else
{
auto bcClassIter = context.m_typeToClassMap.find(typeId);
if (bcClassIter != context.m_typeToClassMap.end())
{
const AZ::BehaviorClass& bcClass = *(bcClassIter->second);
AZStd::string uglyName = bcClass.m_name;
AZ::StringFunc::Replace(uglyName, "AZStd::", "", true);
AZ::StringFunc::Replace(uglyName, "AZ::", "", true);
AZ::StringFunc::Replace(uglyName, "::", ".", true);
return uglyName;
}
else
{
return "Invalid";
}
return "Invalid";
}
}
} // namespace ScriptCanvasOnDemandReflection
} // namespace AZ
}
} // namespace AZ::ScriptCanvasOnDemandReflection
@@ -54,12 +54,8 @@ namespace AZStd
namespace AZ
{
//template<class T>
//class ScriptProperty;
namespace Internal
{
template <class ValueType>
void SetupClassElementFromType(SerializeContext::ClassElement& classElement)
{
@@ -86,15 +82,14 @@ namespace AZ
{
auto uuid = AzTypeInfo<ValueType>::Uuid();
using ContainerType = AttributeContainerType<AZ::TypeId>;
classElement.m_attributes.emplace_back(AZ_CRC("EnumType", 0xb177e1b5), CreateModuleAttribute<ContainerType>(AZStd::move(uuid)));
classElement.m_attributes.emplace_back(AZ_CRC("EnumType", 0xb177e1b5), CreateModuleAttribute(AZStd::move(uuid)));
}
}
template <class T>
AZStd::enable_if_t<std::is_pod<T>::value> InitializeDefaultIfPodType(T& t)
{
t = {};
t = T{};
}
template <class T>
@@ -648,7 +643,6 @@ namespace AZ
// Register our key type within an lvalue to rvalue wrapper as an attribute
AZ::TypeId uuid = azrtti_typeid<WrappedKeyType>();
using ContainerType = AttributeContainerType<AZ::TypeId>;
/**
* This should technically bind the reference value from the GetCurrentSerializeContextModule() call
@@ -658,7 +652,7 @@ namespace AZ
*/
m_classElement.m_attributes.set_allocator(AZStdFunctorAllocator([]() -> IAllocatorAllocate& { return GetCurrentSerializeContextModule().GetAllocator(); }));
m_classElement.m_attributes.emplace_back(AZ_CRC("KeyType", 0x15bc5303), CreateModuleAttribute<ContainerType>(AZStd::move(uuid)));
m_classElement.m_attributes.emplace_back(AZ_CRC("KeyType", 0x15bc5303), CreateModuleAttribute(AZStd::move(uuid)));
}
// Reflect our wrapped key and value types to serializeContext so that may later be used
@@ -19,8 +19,11 @@ namespace AZ
nodeStack.push_back(m_dataContainer);
SerializeContext::EnumerateInstanceCallContext callContext(
AZStd::bind(&DataOverlayTarget::ElementBegin, this, &nodeStack, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3),
AZStd::bind(&DataOverlayTarget::ElementEnd, this, &nodeStack),
[this, &nodeStack](void* instancePointer, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* classElement)->bool
{
return ElementBegin(&nodeStack, instancePointer, classData, classElement);
},
[this, &nodeStack]()->bool { return ElementEnd(&nodeStack); },
m_sc,
SerializeContext::ENUM_ACCESS_FOR_READ,
m_errorLogger
@@ -36,7 +36,7 @@ namespace AZ
class DataNode
{
public:
typedef AZStd::list<DataNode> ChildDataNodes;
using ChildDataNodes = AZStd::list<DataNode>;
DataNode()
{
@@ -148,25 +148,28 @@ namespace AZ
m_root.Reset();
m_currentNode = nullptr;
if (m_context && rootClassPtr)
if (!m_context || !rootClassPtr)
{
SerializeContext::EnumerateInstanceCallContext callContext(
AZStd::bind(&DataNodeTree::BeginNode, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3),
AZStd::bind(&DataNodeTree::EndNode, this),
m_context,
SerializeContext::ENUM_ACCESS_FOR_READ,
nullptr
);
m_context->EnumerateInstanceConst(
&callContext,
rootClassPtr,
rootClassId,
nullptr,
nullptr
);
return;
}
SerializeContext::EnumerateInstanceCallContext callContext(
[this](void* instancePointer, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* classElement)->bool
{
return BeginNode(instancePointer, classData, classElement);
},
[this]()->bool { return EndNode(); },
m_context,
SerializeContext::ENUM_ACCESS_FOR_READ,
nullptr
);
m_context->EnumerateInstanceConst(
&callContext,
rootClassPtr,
rootClassId,
nullptr,
nullptr
);
m_currentNode = nullptr;
}
@@ -34,7 +34,7 @@ namespace AZ
}
classIt->ClearElements();
}
for (auto enumIt : m_enumData)
for (auto& enumIt : m_enumData)
{
enumIt.second.ClearAttributes();
}
@@ -103,7 +103,7 @@ namespace AZ
//=========================================================================
void ElementData::ClearAttributes()
{
for (auto attrib : m_attributes)
for (auto& attrib : m_attributes)
{
delete attrib.second;
}
@@ -759,6 +759,7 @@ namespace AZ
else
{
typeIdResult.m_determination = JsonDeserializer::TypeIdDetermination::FailedToDetermine;
typeIdResult.m_typeId = Uuid::CreateNull();
}
}
else if (input.IsString())
@@ -768,6 +769,7 @@ namespace AZ
else
{
typeIdResult.m_determination = JsonDeserializer::TypeIdDetermination::FailedToDetermine;
typeIdResult.m_typeId = Uuid::CreateNull();
}
switch (typeIdResult.m_determination)
@@ -10,265 +10,264 @@
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/algorithm.h>
namespace AZ
namespace AZ::JsonSerializationResult::Internal
{
namespace JsonSerializationResult
template<typename StringType>
void AppendToString(AZ::JsonSerializationResult::ResultCode code,
StringType& target, AZStd::string_view path)
{
namespace Internal
if (code.GetTask() == static_cast<Tasks>(0))
{
template<typename StringType>
void AppendToString(AZ::JsonSerializationResult::ResultCode code,
StringType& target, AZStd::string_view path)
{
if (code.GetTask() == static_cast<Tasks>(0))
{
target.append("The result code wasn't initialized");
return;
}
target.append("The operation ");
switch (code.GetProcessing())
{
case Processing::Halted:
target.append("has halted during ");
break;
case Processing::Altered:
target.append("has taken an alternative approach for ");
break;
case Processing::PartialAlter:
target.append("has taken a partially alternative approach for ");
break;
case Processing::Completed:
target.append("has completed ");
break;
default:
target.append("has unknown processing status for ");
break;
}
switch (code.GetTask())
{
case Tasks::RetrieveInfo:
target.append("a retrieve info operation ");
break;
case Tasks::CreateDefault:
target.append("a create default operation ");
break;
case Tasks::Convert:
target.append("a convert operation ");
break;
case Tasks::ReadField:
target.append("a read field operation ");
break;
case Tasks::WriteValue:
target.append("a write value operation ");
break;
case Tasks::Merge:
target.append("a merge operation ");
break;
case Tasks::CreatePatch:
target.append("a create patch operation ");
break;
case Tasks::Import:
target.append("an import operation");
break;
default:
target.append("an unknown operation ");
break;
}
if (!path.empty())
{
target.append("for '");
target.append(path.begin(), path.end());
target.append("' ");
}
switch (code.GetOutcome())
{
case Outcomes::Success:
target.append("which resulted in success");
break;
case Outcomes::DefaultsUsed:
target.append("by using only default values");
break;
case Outcomes::PartialDefaults:
target.append("by using one or more default values");
break;
case Outcomes::Skipped:
target.append("because a field or value was skipped");
break;
case Outcomes::PartialSkip:
target.append("because one or more fields or values were skipped");
break;
case Outcomes::Unavailable:
target.append("because the target was unavailable");
break;
case Outcomes::Unsupported:
target.append("because the action was unsupported");
break;
case Outcomes::TypeMismatch:
target.append("because the source and target are unrelated types");
break;
case Outcomes::TestFailed:
target.append("because a test against a value failed");
break;
case Outcomes::Missing:
target.append("because a required field or value was missing");
break;
case Outcomes::Invalid:
target.append("because a field or element has an invalid value");
break;
case Outcomes::Unknown:
target.append("because information was missing");
break;
case Outcomes::Catastrophic:
target.append("because a catastrophic issue was encountered");
break;
default:
break;
}
}
} // namespace JsonSerializationResultInternal
ResultCode::ResultCode(Tasks task)
: m_code(0)
{
m_options.m_task = task;
target.append("The result code wasn't initialized");
return;
}
ResultCode::ResultCode(uint32_t code)
: m_code(code)
{}
ResultCode::ResultCode(Tasks task, Outcomes outcome)
target.append("The operation ");
switch (code.GetProcessing())
{
m_options.m_task = task;
switch (outcome)
{
case Outcomes::Success: // fall through
case Outcomes::Skipped: // fall through
case Outcomes::PartialSkip: // fall through
case Outcomes::DefaultsUsed: // fall through
case Outcomes::PartialDefaults:
m_options.m_processing = Processing::Completed;
break;
case Outcomes::Unavailable: // fall through
case Outcomes::Unsupported:
m_options.m_processing = Processing::Altered;
break;
case Outcomes::TypeMismatch: // fall through
case Outcomes::TestFailed: // fall through
case Outcomes::Missing: // fall through
case Outcomes::Invalid: // fall through
case Outcomes::Unknown: // fall through
case Outcomes::Catastrophic: // fall through
default:
m_options.m_processing = Processing::Halted;
break;
}
m_options.m_outcome = outcome;
case Processing::Halted:
target.append("has halted during ");
break;
case Processing::Altered:
target.append("has taken an alternative approach for ");
break;
case Processing::PartialAlter:
target.append("has taken a partially alternative approach for ");
break;
case Processing::Completed:
target.append("has completed ");
break;
default:
target.append("has unknown processing status for ");
break;
}
bool ResultCode::HasDoneWork() const
switch (code.GetTask())
{
return m_options.m_outcome != static_cast<Outcomes>(0);
case Tasks::RetrieveInfo:
target.append("a retrieve info operation ");
break;
case Tasks::CreateDefault:
target.append("a create default operation ");
break;
case Tasks::Convert:
target.append("a convert operation ");
break;
case Tasks::ReadField:
target.append("a read field operation ");
break;
case Tasks::WriteValue:
target.append("a write value operation ");
break;
case Tasks::Merge:
target.append("a merge operation ");
break;
case Tasks::CreatePatch:
target.append("a create patch operation ");
break;
case Tasks::Import:
target.append("an import operation");
break;
default:
target.append("an unknown operation ");
break;
}
ResultCode& ResultCode::Combine(ResultCode other)
if (!path.empty())
{
*this = Combine(*this, other);
return *this;
target.append("for '");
target.append(path.begin(), path.end());
target.append("' ");
}
ResultCode& ResultCode::Combine(const Result& other)
switch (code.GetOutcome())
{
*this = Combine(*this, other.GetResultCode());
return *this;
case Outcomes::Success:
target.append("which resulted in success");
break;
case Outcomes::DefaultsUsed:
target.append("by using only default values");
break;
case Outcomes::PartialDefaults:
target.append("by using one or more default values");
break;
case Outcomes::Skipped:
target.append("because a field or value was skipped");
break;
case Outcomes::PartialSkip:
target.append("because one or more fields or values were skipped");
break;
case Outcomes::Unavailable:
target.append("because the target was unavailable");
break;
case Outcomes::Unsupported:
target.append("because the action was unsupported");
break;
case Outcomes::TypeMismatch:
target.append("because the source and target are unrelated types");
break;
case Outcomes::TestFailed:
target.append("because a test against a value failed");
break;
case Outcomes::Missing:
target.append("because a required field or value was missing");
break;
case Outcomes::Invalid:
target.append("because a field or element has an invalid value");
break;
case Outcomes::Unknown:
target.append("because information was missing");
break;
case Outcomes::Catastrophic:
target.append("because a catastrophic issue was encountered");
break;
default:
break;
}
}
} // namespace AZ::JsonSerializationResult::Internal
ResultCode ResultCode::Combine(ResultCode lhs, ResultCode rhs)
namespace AZ::JsonSerializationResult
{
ResultCode::ResultCode(Tasks task)
: m_code(0)
{
m_options.m_task = task;
}
ResultCode::ResultCode(uint32_t code)
: m_code(code)
{}
ResultCode::ResultCode(Tasks task, Outcomes outcome)
{
m_options.m_task = task;
switch (outcome)
{
ResultCode result = ResultCode(AZStd::max(lhs.m_code, rhs.m_code));
if ((lhs.m_options.m_outcome == Outcomes::Success && rhs.m_options.m_outcome == Outcomes::DefaultsUsed) ||
(lhs.m_options.m_outcome == Outcomes::DefaultsUsed && rhs.m_options.m_outcome == Outcomes::Success))
{
result.m_options.m_outcome = Outcomes::PartialDefaults;
}
else if ((lhs.m_options.m_outcome == Outcomes::Success && rhs.m_options.m_outcome == Outcomes::Skipped) ||
(lhs.m_options.m_outcome == Outcomes::Skipped && rhs.m_options.m_outcome == Outcomes::Success))
{
result.m_options.m_outcome = Outcomes::PartialSkip;
}
if ((lhs.m_options.m_processing == Processing::Completed && rhs.m_options.m_processing == Processing::Altered) ||
(lhs.m_options.m_processing == Processing::Altered && rhs.m_options.m_processing == Processing::Completed))
{
result.m_options.m_processing = Processing::PartialAlter;
}
return result;
case Outcomes::Success: // fall through
case Outcomes::Skipped: // fall through
case Outcomes::PartialSkip: // fall through
case Outcomes::DefaultsUsed: // fall through
case Outcomes::PartialDefaults:
m_options.m_processing = Processing::Completed;
break;
case Outcomes::Unavailable: // fall through
case Outcomes::Unsupported:
m_options.m_processing = Processing::Altered;
break;
case Outcomes::TypeMismatch: // fall through
case Outcomes::TestFailed: // fall through
case Outcomes::Missing: // fall through
case Outcomes::Invalid: // fall through
case Outcomes::Unknown: // fall through
case Outcomes::Catastrophic: // fall through
default:
m_options.m_processing = Processing::Halted;
break;
}
m_options.m_outcome = outcome;
}
Tasks ResultCode::GetTask() const
bool ResultCode::HasDoneWork() const
{
return m_options.m_outcome != static_cast<Outcomes>(0);
}
ResultCode& ResultCode::Combine(ResultCode other)
{
*this = Combine(*this, other);
return *this;
}
ResultCode& ResultCode::Combine(const Result& other)
{
*this = Combine(*this, other.GetResultCode());
return *this;
}
ResultCode ResultCode::Combine(ResultCode lhs, ResultCode rhs)
{
ResultCode result = ResultCode(AZStd::max(lhs.m_code, rhs.m_code));
if ((lhs.m_options.m_outcome == Outcomes::Success && rhs.m_options.m_outcome == Outcomes::DefaultsUsed) ||
(lhs.m_options.m_outcome == Outcomes::DefaultsUsed && rhs.m_options.m_outcome == Outcomes::Success))
{
return m_options.m_task;
result.m_options.m_outcome = Outcomes::PartialDefaults;
}
Processing ResultCode::GetProcessing() const
else if ((lhs.m_options.m_outcome == Outcomes::Success && rhs.m_options.m_outcome == Outcomes::Skipped) ||
(lhs.m_options.m_outcome == Outcomes::Skipped && rhs.m_options.m_outcome == Outcomes::Success))
{
return m_options.m_processing == static_cast<Processing>(0) ?
Processing::Completed : m_options.m_processing;
result.m_options.m_outcome = Outcomes::PartialSkip;
}
Outcomes ResultCode::GetOutcome() const
if ((lhs.m_options.m_processing == Processing::Completed && rhs.m_options.m_processing == Processing::Altered) ||
(lhs.m_options.m_processing == Processing::Altered && rhs.m_options.m_processing == Processing::Completed))
{
return m_options.m_outcome == static_cast<Outcomes>(0) ?
Outcomes::DefaultsUsed : m_options.m_outcome;
result.m_options.m_processing = Processing::PartialAlter;
}
void ResultCode::AppendToString(AZ::OSString& target, AZStd::string_view path) const
{
Internal::AppendToString(*this, target, path);
}
return result;
}
void ResultCode::AppendToString(AZStd::string& target, AZStd::string_view path) const
{
Internal::AppendToString(*this, target, path);
}
Tasks ResultCode::GetTask() const
{
return m_options.m_task;
}
AZStd::string ResultCode::ToString(AZStd::string_view path) const
{
AZStd::string result;
AppendToString(result, path);
return result;
}
Processing ResultCode::GetProcessing() const
{
return m_options.m_processing == static_cast<Processing>(0) ?
Processing::Completed : m_options.m_processing;
}
AZ::OSString ResultCode::ToOSString(AZStd::string_view path) const
{
AZ::OSString result;
AppendToString(result, path);
return result;
}
Outcomes ResultCode::GetOutcome() const
{
return m_options.m_outcome == static_cast<Outcomes>(0) ?
Outcomes::DefaultsUsed : m_options.m_outcome;
}
void ResultCode::AppendToString(AZ::OSString& target, AZStd::string_view path) const
{
Internal::AppendToString(*this, target, path);
}
void ResultCode::AppendToString(AZStd::string& target, AZStd::string_view path) const
{
Internal::AppendToString(*this, target, path);
}
AZStd::string ResultCode::ToString(AZStd::string_view path) const
{
AZStd::string result;
AppendToString(result, path);
return result;
}
AZ::OSString ResultCode::ToOSString(AZStd::string_view path) const
{
AZ::OSString result;
AppendToString(result, path);
return result;
}
Result::Result(const JsonIssueCallback& callback, AZStd::string_view message, ResultCode result, AZStd::string_view path)
: m_result(callback(message, result, path))
{}
Result::Result(const JsonIssueCallback& callback, AZStd::string_view message, ResultCode result, AZStd::string_view path)
: m_result(callback(message, result, path))
{}
Result::Result(const JsonIssueCallback& callback, AZStd::string_view message, Tasks task, Outcomes outcome, AZStd::string_view path)
: m_result(callback(message, ResultCode(task, outcome), path))
{}
Result::Result(const JsonIssueCallback& callback, AZStd::string_view message, Tasks task, Outcomes outcome, AZStd::string_view path)
: m_result(callback(message, ResultCode(task, outcome), path))
{}
Result::operator ResultCode() const
{
return m_result;
}
Result::operator ResultCode() const
{
return m_result;
}
ResultCode Result::GetResultCode() const
{
return m_result;
}
} // namespace JsonSerializationResult
} // namespace AZ
ResultCode Result::GetResultCode() const
{
return m_result;
}
} // namespace AZ::JsonSerializationResult
@@ -104,6 +104,8 @@ namespace AZ
->HandlesType<AZStd::variant>();
jsonContext->Serializer<JsonOptionalSerializer>()
->HandlesType<AZStd::optional>();
jsonContext->Serializer<JsonBitsetSerializer>()
->HandlesType<AZStd::bitset>();
MathReflect(jsonContext);
}
@@ -24,438 +24,435 @@
#include <AzCore/Serialization/Json/JsonUtils.h>
namespace AZ
namespace AZ::JsonSerializationUtils
{
namespace JsonSerializationUtils
static const char* FileTypeTag = "Type";
static const char* FileType = "JsonSerialization";
static const char* VersionTag = "Version";
static const char* ClassNameTag = "ClassName";
static const char* ClassDataTag = "ClassData";
AZ::Outcome<void, AZStd::string> WriteJsonString(const rapidjson::Document& document, AZStd::string& jsonText, WriteJsonSettings settings)
{
static const char* FileTypeTag = "Type";
static const char* FileType = "JsonSerialization";
static const char* VersionTag = "Version";
static const char* ClassNameTag = "ClassName";
static const char* ClassDataTag = "ClassData";
AZ::IO::ByteContainerStream<AZStd::string> stream{&jsonText};
return WriteJsonStream(document, stream, settings);
}
AZ::Outcome<void, AZStd::string> WriteJsonString(const rapidjson::Document& document, AZStd::string& jsonText, WriteJsonSettings settings)
AZ::Outcome<void, AZStd::string> WriteJsonFile(const rapidjson::Document& document, AZStd::string_view filePath, WriteJsonSettings settings)
{
// Write the json into memory first and then write the file, rather than passing a file stream to rapidjson.
// This should avoid creating a large number of micro-writes to the file.
AZStd::string fileContent;
auto outcome = WriteJsonString(document, fileContent, settings);
if (!outcome.IsSuccess())
{
AZ::IO::ByteContainerStream<AZStd::string> stream{&jsonText};
return WriteJsonStream(document, stream, settings);
return outcome;
}
AZ::Outcome<void, AZStd::string> WriteJsonFile(const rapidjson::Document& document, AZStd::string_view filePath, WriteJsonSettings settings)
{
// Write the json into memory first and then write the file, rather than passing a file stream to rapidjson.
// This should avoid creating a large number of micro-writes to the file.
AZStd::string fileContent;
auto outcome = WriteJsonString(document, fileContent, settings);
if (!outcome.IsSuccess())
{
return outcome;
}
return AZ::Utils::WriteFile(fileContent, filePath);
}
return AZ::Utils::WriteFile(fileContent, filePath);
AZ::Outcome<void, AZStd::string> WriteJsonStream(const rapidjson::Document& document, IO::GenericStream& stream, WriteJsonSettings settings)
{
AZ::IO::RapidJSONStreamWriter jsonStreamWriter(&stream);
rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter> writer(jsonStreamWriter);
if (settings.m_maxDecimalPlaces >= 0)
{
writer.SetMaxDecimalPlaces(settings.m_maxDecimalPlaces);
}
AZ::Outcome<void, AZStd::string> WriteJsonStream(const rapidjson::Document& document, IO::GenericStream& stream, WriteJsonSettings settings)
if (document.Accept(writer))
{
AZ::IO::RapidJSONStreamWriter jsonStreamWriter(&stream);
return AZ::Success();
}
else
{
return AZ::Failure(AZStd::string{"Json Writer failed"});
}
}
rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter> writer(jsonStreamWriter);
if (settings.m_maxDecimalPlaces >= 0)
{
writer.SetMaxDecimalPlaces(settings.m_maxDecimalPlaces);
}
if (document.Accept(writer))
{
return AZ::Success();
}
else
{
return AZ::Failure(AZStd::string{"Json Writer failed"});
}
AZ::Outcome<void, AZStd::string> SaveObjectToStreamByType(const void* objectPtr, const Uuid& classId, IO::GenericStream& stream,
const void* defaultObjectPtr, const JsonSerializerSettings* settings)
{
if (!stream.CanWrite())
{
return AZ::Failure(AZStd::string("The GenericStream can't be written to"));
}
AZ::Outcome<void, AZStd::string> SaveObjectToStreamByType(const void* objectPtr, const Uuid& classId, IO::GenericStream& stream,
const void* defaultObjectPtr, const JsonSerializerSettings* settings)
JsonSerializerSettings saveSettings;
if (settings)
{
if (!stream.CanWrite())
{
return AZ::Failure(AZStd::string("The GenericStream can't be written to"));
}
saveSettings = *settings;
}
JsonSerializerSettings saveSettings;
if (settings)
{
saveSettings = *settings;
}
AZ::SerializeContext* serializeContext = saveSettings.m_serializeContext;
AZ::SerializeContext* serializeContext = saveSettings.m_serializeContext;
if (!serializeContext)
{
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (!serializeContext)
{
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (!serializeContext)
{
return AZ::Failure(AZStd::string::format("Need SerializeContext for saving"));
}
saveSettings.m_serializeContext = serializeContext;
return AZ::Failure(AZStd::string::format("Need SerializeContext for saving"));
}
rapidjson::Document jsonDocument;
jsonDocument.SetObject();
jsonDocument.AddMember(rapidjson::StringRef(FileTypeTag), rapidjson::StringRef(FileType), jsonDocument.GetAllocator());
rapidjson::Value serializedObject;
JsonSerializationResult::ResultCode jsonResult = JsonSerialization::Store(serializedObject, jsonDocument.GetAllocator(),
objectPtr, defaultObjectPtr, classId, saveSettings);
if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
{
return AZ::Failure(jsonResult.ToString(""));
}
const SerializeContext::ClassData* classData = serializeContext->FindClassData(classId);
jsonDocument.AddMember(rapidjson::StringRef(VersionTag), 1, jsonDocument.GetAllocator());
jsonDocument.AddMember(rapidjson::StringRef(ClassNameTag), rapidjson::StringRef(classData->m_name), jsonDocument.GetAllocator());
jsonDocument.AddMember(rapidjson::StringRef(ClassDataTag), AZStd::move(serializedObject), jsonDocument.GetAllocator());
AZ::IO::RapidJSONStreamWriter jsonStreamWriter(&stream);
rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter> writer(jsonStreamWriter);
bool jsonWriteResult = jsonDocument.Accept(writer);
if (!jsonWriteResult)
{
return AZ::Failure(AZStd::string::format("Unable to write class %s with json serialization format'",
classId.ToString<AZStd::string>().data()));
}
return AZ::Success();
saveSettings.m_serializeContext = serializeContext;
}
AZ::Outcome<void, AZStd::string> SaveObjectToFileByType(const void* classPtr, const Uuid& classId, const AZStd::string& filePath,
const void* defaultClassPtr, const JsonSerializerSettings* settings)
rapidjson::Document jsonDocument;
jsonDocument.SetObject();
jsonDocument.AddMember(rapidjson::StringRef(FileTypeTag), rapidjson::StringRef(FileType), jsonDocument.GetAllocator());
rapidjson::Value serializedObject;
JsonSerializationResult::ResultCode jsonResult = JsonSerialization::Store(serializedObject, jsonDocument.GetAllocator(),
objectPtr, defaultObjectPtr, classId, saveSettings);
if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
{
AZStd::vector<char> buffer;
buffer.reserve(1024);
AZ::IO::ByteContainerStream<AZStd::vector<char> > byteStream(&buffer);
auto saveResult = SaveObjectToStreamByType(classPtr, classId, byteStream, defaultClassPtr, settings);
if (saveResult.IsSuccess())
{
AZ::IO::FileIOStream outputFileStream;
if (!outputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath | AZ::IO::OpenMode::ModeText))
{
return AZ::Failure(AZStd::string::format("Error opening file '%s' for writing", filePath.c_str()));
}
outputFileStream.Write(buffer.size(), buffer.data());
}
return saveResult;
return AZ::Failure(jsonResult.ToString(""));
}
// Helper function to check whether the load outcome was success (for loading json serialization file)
bool WasLoadSuccess(JsonSerializationResult::Outcomes outcome)
{
return (outcome == JsonSerializationResult::Outcomes::Success
|| outcome == JsonSerializationResult::Outcomes::DefaultsUsed
|| outcome == JsonSerializationResult::Outcomes::PartialDefaults);
}
AZ::Outcome<void, AZStd::string> PrepareDeserializerSettings(const JsonDeserializerSettings* inputSettings, JsonDeserializerSettings& returnSettings
, AZStd::string& deserializeError)
{
if (inputSettings)
{
returnSettings = *inputSettings;
}
const SerializeContext::ClassData* classData = serializeContext->FindClassData(classId);
jsonDocument.AddMember(rapidjson::StringRef(VersionTag), 1, jsonDocument.GetAllocator());
jsonDocument.AddMember(rapidjson::StringRef(ClassNameTag), rapidjson::StringRef(classData->m_name), jsonDocument.GetAllocator());
jsonDocument.AddMember(rapidjson::StringRef(ClassDataTag), AZStd::move(serializedObject), jsonDocument.GetAllocator());
AZ::IO::RapidJSONStreamWriter jsonStreamWriter(&stream);
rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter> writer(jsonStreamWriter);
bool jsonWriteResult = jsonDocument.Accept(writer);
if (!jsonWriteResult)
{
return AZ::Failure(AZStd::string::format("Unable to write class %s with json serialization format'",
classId.ToString<AZStd::string>().data()));
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> SaveObjectToFileByType(const void* classPtr, const Uuid& classId, const AZStd::string& filePath,
const void* defaultClassPtr, const JsonSerializerSettings* settings)
{
AZStd::vector<char> buffer;
buffer.reserve(1024);
AZ::IO::ByteContainerStream<AZStd::vector<char> > byteStream(&buffer);
auto saveResult = SaveObjectToStreamByType(classPtr, classId, byteStream, defaultClassPtr, settings);
if (saveResult.IsSuccess())
{
AZ::IO::FileIOStream outputFileStream;
if (!outputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath | AZ::IO::OpenMode::ModeText))
{
return AZ::Failure(AZStd::string::format("Error opening file '%s' for writing", filePath.c_str()));
}
outputFileStream.Write(buffer.size(), buffer.data());
}
return saveResult;
}
// Helper function to check whether the load outcome was success (for loading json serialization file)
bool WasLoadSuccess(JsonSerializationResult::Outcomes outcome)
{
return (outcome == JsonSerializationResult::Outcomes::Success
|| outcome == JsonSerializationResult::Outcomes::DefaultsUsed
|| outcome == JsonSerializationResult::Outcomes::PartialDefaults);
}
AZ::Outcome<void, AZStd::string> PrepareDeserializerSettings(const JsonDeserializerSettings* inputSettings, JsonDeserializerSettings& returnSettings
, AZStd::string& deserializeError)
{
if (inputSettings)
{
returnSettings = *inputSettings;
}
if (!returnSettings.m_serializeContext)
{
AZ::ComponentApplicationBus::BroadcastResult(returnSettings.m_serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (!returnSettings.m_serializeContext)
{
AZ::ComponentApplicationBus::BroadcastResult(returnSettings.m_serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (!returnSettings.m_serializeContext)
return AZ::Failure(AZStd::string("Need SerializeContext for loading"));
}
}
// Report unused data field as error by default
auto reporting = returnSettings.m_reporting;
auto issueReportingCallback = [&deserializeError, reporting](AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view target) -> JsonSerializationResult::ResultCode
{
using namespace JsonSerializationResult;
if (!WasLoadSuccess(result.GetOutcome()))
{
// This if is a hack around fault in the JSON serialization system
// Jira: LY-106587
if (message != "No part of the string could be interpreted as a uuid.")
{
return AZ::Failure(AZStd::string("Need SerializeContext for loading"));
deserializeError.append(message);
deserializeError.append(AZStd::string::format(" '%s' \n", target.data()));
}
}
// Report unused data field as error by default
auto reporting = returnSettings.m_reporting;
auto issueReportingCallback = [&deserializeError, reporting](AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view target) -> JsonSerializationResult::ResultCode
if (reporting)
{
using namespace JsonSerializationResult;
result = reporting(message, result, target);
}
if (!WasLoadSuccess(result.GetOutcome()))
{
// This if is a hack around fault in the JSON serialization system
// Jira: LY-106587
if (message != "No part of the string could be interpreted as a uuid.")
{
deserializeError.append(message);
deserializeError.append(AZStd::string::format(" '%s' \n", target.data()));
}
}
return result;
};
if (reporting)
{
result = reporting(message, result, target);
}
returnSettings.m_reporting = issueReportingCallback;
return result;
};
return AZ::Success();
}
returnSettings.m_reporting = issueReportingCallback;
return AZ::Success();
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonString(AZStd::string_view jsonText)
{
if (jsonText.empty())
{
return AZ::Failure(AZStd::string("Failed to parse JSON: input string is empty."));
}
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonString(AZStd::string_view jsonText)
rapidjson::Document jsonDocument;
jsonDocument.Parse<rapidjson::kParseCommentsFlag>(jsonText.data(), jsonText.size());
if (jsonDocument.HasParseError())
{
if (jsonText.empty())
size_t lineNumber = 1;
const size_t errorOffset = jsonDocument.GetErrorOffset();
for (size_t searchOffset = jsonText.find('\n');
searchOffset < errorOffset && searchOffset < AZStd::string::npos;
searchOffset = jsonText.find('\n', searchOffset + 1))
{
return AZ::Failure(AZStd::string("Failed to parse JSON: input string is empty."));
lineNumber++;
}
rapidjson::Document jsonDocument;
jsonDocument.Parse<rapidjson::kParseCommentsFlag>(jsonText.data(), jsonText.size());
if (jsonDocument.HasParseError())
{
size_t lineNumber = 1;
return AZ::Failure(AZStd::string::format("JSON parse error at line %zu: %s", lineNumber, rapidjson::GetParseError_En(jsonDocument.GetParseError())));
}
else
{
return AZ::Success(AZStd::move(jsonDocument));
}
}
const size_t errorOffset = jsonDocument.GetErrorOffset();
for (size_t searchOffset = jsonText.find('\n');
searchOffset < errorOffset && searchOffset < AZStd::string::npos;
searchOffset = jsonText.find('\n', searchOffset + 1))
{
lineNumber++;
}
return AZ::Failure(AZStd::string::format("JSON parse error at line %zu: %s", lineNumber, rapidjson::GetParseError_En(jsonDocument.GetParseError())));
}
else
{
return AZ::Success(AZStd::move(jsonDocument));
}
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonStream(IO::GenericStream& stream)
{
IO::SizeType length = stream.GetLength();
AZStd::vector<char> memoryBuffer;
memoryBuffer.resize_no_construct(static_cast<AZStd::vector<char>::size_type>(static_cast<AZStd::vector<char>::size_type>(length) + 1));
IO::SizeType bytesRead = stream.Read(length, memoryBuffer.data());
if (bytesRead != length)
{
return AZ::Failure(AZStd::string{"Cannot to read input stream."});
}
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonStream(IO::GenericStream& stream)
memoryBuffer.back() = 0;
return ReadJsonString(AZStd::string_view{memoryBuffer.data(), memoryBuffer.size()});
}
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonFile(AZStd::string_view filePath, size_t maxFileSize)
{
// Read into memory first and then parse the json, rather than passing a file stream to rapidjson.
// This should avoid creating a large number of micro-reads from the file.
auto readResult = AZ::Utils::ReadFile<AZStd::string>(filePath, maxFileSize);
if(!readResult.IsSuccess())
{
IO::SizeType length = stream.GetLength();
AZStd::vector<char> memoryBuffer;
memoryBuffer.resize_no_construct(static_cast<AZStd::vector<char>::size_type>(static_cast<AZStd::vector<char>::size_type>(length) + 1));
IO::SizeType bytesRead = stream.Read(length, memoryBuffer.data());
if (bytesRead != length)
{
return AZ::Failure(AZStd::string{"Cannot to read input stream."});
}
memoryBuffer.back() = 0;
return ReadJsonString(AZStd::string_view{memoryBuffer.data(), memoryBuffer.size()});
return AZ::Failure(readResult.GetError());
}
AZ::Outcome<rapidjson::Document, AZStd::string> ReadJsonFile(AZStd::string_view filePath, size_t maxFileSize)
AZStd::string jsonContent = readResult.TakeValue();
auto result = ReadJsonString(jsonContent);
if (!result.IsSuccess())
{
// Read into memory first and then parse the json, rather than passing a file stream to rapidjson.
// This should avoid creating a large number of micro-reads from the file.
return AZ::Failure(AZStd::string::format("Failed to load '%.*s'. %s", AZ_STRING_ARG(filePath), result.GetError().c_str()));
}
else
{
return result;
}
}
auto readResult = AZ::Utils::ReadFile<AZStd::string>(filePath, maxFileSize);
if(!readResult.IsSuccess())
{
return AZ::Failure(readResult.GetError());
}
AZStd::string jsonContent = readResult.TakeValue();
auto result = ReadJsonString(jsonContent);
if (!result.IsSuccess())
{
return AZ::Failure(AZStd::string::format("Failed to load '%.*s'. %s", AZ_STRING_ARG(filePath), result.GetError().c_str()));
}
else
{
return result;
}
// Helper function to validate the JSON is structured with the standard header for a generic class
AZ::Outcome<void, AZStd::string> ValidateJsonClassHeader(const rapidjson::Document& jsonDocument)
{
auto typeItr = jsonDocument.FindMember(FileTypeTag);
if (typeItr == jsonDocument.MemberEnd() || !typeItr->value.IsString() || azstricmp(typeItr->value.GetString(), FileType) != 0)
{
return AZ::Failure(AZStd::string::format("Not a valid JsonSerialization file"));
}
// Helper function to validate the JSON is structured with the standard header for a generic class
AZ::Outcome<void, AZStd::string> ValidateJsonClassHeader(const rapidjson::Document& jsonDocument)
auto nameItr = jsonDocument.FindMember(ClassNameTag);
if (nameItr == jsonDocument.MemberEnd() || !nameItr->value.IsString())
{
auto typeItr = jsonDocument.FindMember(FileTypeTag);
if (typeItr == jsonDocument.MemberEnd() || !typeItr->value.IsString() || azstricmp(typeItr->value.GetString(), FileType) != 0)
{
return AZ::Failure(AZStd::string::format("Not a valid JsonSerialization file"));
}
auto nameItr = jsonDocument.FindMember(ClassNameTag);
if (nameItr == jsonDocument.MemberEnd() || !nameItr->value.IsString())
{
return AZ::Failure(AZStd::string::format("File should contain ClassName"));
}
auto dataItr = jsonDocument.FindMember(ClassDataTag);
// data can be empty but it should be an object
if (dataItr != jsonDocument.MemberEnd() && !dataItr->value.IsObject())
{
return AZ::Failure(AZStd::string::format("ClassData should be an object"));
}
return AZ::Success();
return AZ::Failure(AZStd::string::format("File should contain ClassName"));
}
AZ::Outcome<void, AZStd::string> LoadObjectFromStringByType(void* objectToLoad, const Uuid& classId, AZStd::string_view stream,
const JsonDeserializerSettings* settings)
auto dataItr = jsonDocument.FindMember(ClassDataTag);
// data can be empty but it should be an object
if (dataItr != jsonDocument.MemberEnd() && !dataItr->value.IsObject())
{
JsonDeserializerSettings loadSettings;
AZStd::string deserializeErrors;
auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors);
if (!prepare.IsSuccess())
{
return AZ::Failure(prepare.GetError());
}
return AZ::Failure(AZStd::string::format("ClassData should be an object"));
}
auto parseResult = ReadJsonString(stream);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
return AZ::Success();
}
const rapidjson::Document& jsonDocument = parseResult.GetValue();
AZ::Outcome<void, AZStd::string> LoadObjectFromStringByType(void* objectToLoad, const Uuid& classId, AZStd::string_view stream,
const JsonDeserializerSettings* settings)
{
JsonDeserializerSettings loadSettings;
AZStd::string deserializeErrors;
auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors);
if (!prepare.IsSuccess())
{
return AZ::Failure(prepare.GetError());
}
auto validateResult = ValidateJsonClassHeader(jsonDocument);
if (!validateResult.IsSuccess())
{
return AZ::Failure(validateResult.GetError());
}
auto parseResult = ReadJsonString(stream);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString();
const rapidjson::Document& jsonDocument = parseResult.GetValue();
// validate class name
auto classData = loadSettings.m_serializeContext->FindClassData(classId);
if (!classData)
{
return AZ::Failure(AZStd::string::format("Try to load class from Id %s", classId.ToString<AZStd::string>().c_str()));
}
auto validateResult = ValidateJsonClassHeader(jsonDocument);
if (!validateResult.IsSuccess())
{
return AZ::Failure(validateResult.GetError());
}
if (azstricmp(classData->m_name, className) != 0)
{
return AZ::Failure(AZStd::string::format("Try to load class %s from class %s data", classData->m_name, className));
}
const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString();
JsonSerializationResult::ResultCode result = JsonSerialization::Load(objectToLoad, classId, jsonDocument.FindMember(ClassDataTag)->value, loadSettings);
// validate class name
auto classData = loadSettings.m_serializeContext->FindClassData(classId);
if (!classData)
{
return AZ::Failure(AZStd::string::format("Try to load class from Id %s", classId.ToString<AZStd::string>().c_str()));
}
if (azstricmp(classData->m_name, className) != 0)
{
return AZ::Failure(AZStd::string::format("Try to load class %s from class %s data", classData->m_name, className));
}
JsonSerializationResult::ResultCode result = JsonSerialization::Load(objectToLoad, classId, jsonDocument.FindMember(ClassDataTag)->value, loadSettings);
if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty())
{
return AZ::Failure(deserializeErrors);
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> LoadObjectFromStreamByType(void* objectToLoad, const Uuid& classId, IO::GenericStream& stream,
const JsonDeserializerSettings* settings)
{
JsonDeserializerSettings loadSettings;
AZStd::string deserializeErrors;
auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors);
if (!prepare.IsSuccess())
{
return AZ::Failure(prepare.GetError());
}
auto parseResult = ReadJsonStream(stream);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
const rapidjson::Document& jsonDocument = parseResult.GetValue();
auto validateResult = ValidateJsonClassHeader(jsonDocument);
if (!validateResult.IsSuccess())
{
return AZ::Failure(validateResult.GetError());
}
const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString();
// validate class name
auto classData = loadSettings.m_serializeContext->FindClassData(classId);
if (!classData)
{
return AZ::Failure(AZStd::string::format("Try to load class from Id %s", classId.ToString<AZStd::string>().c_str()));
}
if (azstricmp(classData->m_name, className) != 0)
{
return AZ::Failure(AZStd::string::format("Try to load class %s from class %s data", classData->m_name, className));
}
JsonSerializationResult::ResultCode result = JsonSerialization::Load(objectToLoad, classId, jsonDocument.FindMember(ClassDataTag)->value, loadSettings);
if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty())
{
return AZ::Failure(deserializeErrors);
}
return AZ::Success();
}
AZ::Outcome<AZStd::any, AZStd::string> LoadAnyObjectFromStream(IO::GenericStream& stream, const JsonDeserializerSettings* settings)
{
JsonDeserializerSettings loadSettings;
AZStd::string deserializeErrors;
auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors);
if (!prepare.IsSuccess())
{
return AZ::Failure(prepare.GetError());
}
auto parseResult = ReadJsonStream(stream);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
const rapidjson::Document& jsonDocument = parseResult.GetValue();
auto validateResult = ValidateJsonClassHeader(jsonDocument);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString();
AZStd::vector<AZ::Uuid> ids = loadSettings.m_serializeContext->FindClassId(AZ::Crc32(className));
// Load with first found class id
if (ids.size() >= 1)
{
auto classId = ids[0];
AZStd::any anyData = loadSettings.m_serializeContext->CreateAny(classId);
auto& objectData = jsonDocument.FindMember(ClassDataTag)->value;
JsonSerializationResult::ResultCode result = JsonSerialization::Load(AZStd::any_cast<void>(&anyData), classId, objectData, loadSettings);
if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty())
{
return AZ::Failure(deserializeErrors);
}
return AZ::Success();
return AZ::Success(anyData);
}
AZ::Outcome<void, AZStd::string> LoadObjectFromStreamByType(void* objectToLoad, const Uuid& classId, IO::GenericStream& stream,
const JsonDeserializerSettings* settings)
return AZ::Failure(AZStd::string::format("Can't find serialize context for class %s", className));
}
AZ::Outcome<AZStd::any, AZStd::string> LoadAnyObjectFromFile(const AZStd::string& filePath, const JsonDeserializerSettings* settings)
{
AZ::IO::FileIOStream inputFileStream;
if (!inputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeText))
{
JsonDeserializerSettings loadSettings;
AZStd::string deserializeErrors;
auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors);
if (!prepare.IsSuccess())
{
return AZ::Failure(prepare.GetError());
}
auto parseResult = ReadJsonStream(stream);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
const rapidjson::Document& jsonDocument = parseResult.GetValue();
auto validateResult = ValidateJsonClassHeader(jsonDocument);
if (!validateResult.IsSuccess())
{
return AZ::Failure(validateResult.GetError());
}
const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString();
// validate class name
auto classData = loadSettings.m_serializeContext->FindClassData(classId);
if (!classData)
{
return AZ::Failure(AZStd::string::format("Try to load class from Id %s", classId.ToString<AZStd::string>().c_str()));
}
if (azstricmp(classData->m_name, className) != 0)
{
return AZ::Failure(AZStd::string::format("Try to load class %s from class %s data", classData->m_name, className));
}
JsonSerializationResult::ResultCode result = JsonSerialization::Load(objectToLoad, classId, jsonDocument.FindMember(ClassDataTag)->value, loadSettings);
if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty())
{
return AZ::Failure(deserializeErrors);
}
return AZ::Success();
}
AZ::Outcome<AZStd::any, AZStd::string> LoadAnyObjectFromStream(IO::GenericStream& stream, const JsonDeserializerSettings* settings)
{
JsonDeserializerSettings loadSettings;
AZStd::string deserializeErrors;
auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors);
if (!prepare.IsSuccess())
{
return AZ::Failure(prepare.GetError());
}
auto parseResult = ReadJsonStream(stream);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
const rapidjson::Document& jsonDocument = parseResult.GetValue();
auto validateResult = ValidateJsonClassHeader(jsonDocument);
if (!parseResult.IsSuccess())
{
return AZ::Failure(parseResult.GetError());
}
const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString();
AZStd::vector<AZ::Uuid> ids = loadSettings.m_serializeContext->FindClassId(AZ::Crc32(className));
// Load with first found class id
if (ids.size() >= 1)
{
auto classId = ids[0];
AZStd::any anyData = loadSettings.m_serializeContext->CreateAny(classId);
auto& objectData = jsonDocument.FindMember(ClassDataTag)->value;
JsonSerializationResult::ResultCode result = JsonSerialization::Load(AZStd::any_cast<void>(&anyData), classId, objectData, loadSettings);
if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty())
{
return AZ::Failure(deserializeErrors);
}
return AZ::Success(anyData);
}
return AZ::Failure(AZStd::string::format("Can't find serialize context for class %s", className));
return AZ::Failure(AZStd::string::format("Error opening file '%s' for reading", filePath.c_str()));
}
return LoadAnyObjectFromStream(inputFileStream, settings);
}
AZ::Outcome<AZStd::any, AZStd::string> LoadAnyObjectFromFile(const AZStd::string& filePath, const JsonDeserializerSettings* settings)
{
AZ::IO::FileIOStream inputFileStream;
if (!inputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeText))
{
return AZ::Failure(AZStd::string::format("Error opening file '%s' for reading", filePath.c_str()));
}
return LoadAnyObjectFromStream(inputFileStream, settings);
}
} // namespace JsonSerializationUtils
} // namespace AZ
} // namespace AZ::JsonSerializationUtils
@@ -15,6 +15,7 @@ namespace AZ
AZ_CLASS_ALLOCATOR_IMPL(JsonAnySerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonVariantSerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonOptionalSerializer, SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(JsonBitsetSerializer, SystemAllocator, 0);
JsonSerializationResult::Result JsonUnsupportedTypesSerializer::Load(void*, const Uuid&, const rapidjson::Value&,
JsonDeserializerContext& context)
@@ -49,4 +50,10 @@ namespace AZ
return "The Json Serialization doesn't support AZStd::optional by design. No JSON format has yet been found that wasn't deemed too "
"complex or overly verbose.";
}
AZStd::string_view JsonBitsetSerializer::GetMessage() const
{
return "The Json Serialization doesn't support AZStd::bitset by design. No JSON format has yet been found that is content creator "
"friendly i.e., easy to comprehend the intent.";
}
} // namespace AZ
@@ -65,4 +65,14 @@ namespace AZ
protected:
AZStd::string_view GetMessage() const override;
};
class JsonBitsetSerializer : public JsonUnsupportedTypesSerializer
{
public:
AZ_RTTI(JsonBitsetSerializer, "{10CE969D-D69E-4B3F-8593-069736F8F705}", JsonUnsupportedTypesSerializer);
AZ_CLASS_ALLOCATOR_DECL;
protected:
AZStd::string_view GetMessage() const override;
};
} // namespace AZ
@@ -1050,7 +1050,7 @@ namespace AZ
}
m_xmlNode = next;
Uuid specializedId;
Uuid specializedId = Uuid::CreateNull();
// now parse the node
rapidxml::xml_attribute<char>* attr = m_xmlNode->first_attribute();
while (attr)
@@ -1643,12 +1643,15 @@ namespace AZ
m_writeElementResultStack.push_back(WriteElement(ptr, classData, classElement));
return m_writeElementResultStack.back();
};
auto closeElementCB = [this, classData]()
auto closeElementCB = [this, classTypeId = classData->m_typeId]()
{
if (m_writeElementResultStack.empty())
{
AZ_UNUSED(classData); // Prevent unused warning in release builds
AZ_Error("Serialize", false, "CloseElement is attempted to be called without a corresponding WriteElement when writing class %s", classData->m_name);
// ClassData could be dangling pointer if it was unreflected by the ObjectStreamWriteOverrideCB
// So use the classTypeId instead
AZ_UNUSED(classTypeId);
AZ_Error("Serialize", false, "CloseElement is attempted to be called without a corresponding WriteElement when writing class %s",
classTypeId.ToString<AZStd::fixed_string<AZ::TypeId::MaxStringBuffer>>().c_str());
return true;
}
if (m_writeElementResultStack.back())
@@ -1665,16 +1668,14 @@ namespace AZ
SerializeContext::ENUM_ACCESS_FOR_READ,
&m_errorLogger
);
ObjectStreamWriteOverrideCB writeCB;
if (objectStreamWriteOverrideCB.Read<ObjectStreamWriteOverrideCB>(writeCB))
if (objectStreamWriteOverrideCB.Invoke<void>(callContext, objectPtr, *classData, classElement))
{
writeCB(callContext, objectPtr, *classData, classElement);
return false;
}
else
{
auto objectStreamError = AZStd::string::format("Unable to invoke ObjectStream Write Element Override for class element %s of class data %s",
classElement->m_name ? classElement->m_name : "", classData->m_name);
classElement && classElement->m_name ? classElement->m_name : "", classData->m_name);
m_errorLogger.ReportError(objectStreamError.c_str());
}
}
@@ -18,502 +18,499 @@
#include <AzCore/Debug/Profiler.h>
namespace AZ
namespace AZ::Utils
{
namespace Utils
bool LoadObjectFromStreamInPlace(IO::GenericStream& stream, AZ::SerializeContext* context, const SerializeContext::ClassData* objectClassData, void* targetPointer, const FilterDescriptor& filterDesc)
{
bool LoadObjectFromStreamInPlace(IO::GenericStream& stream, AZ::SerializeContext* context, const SerializeContext::ClassData* objectClassData, void* targetPointer, const FilterDescriptor& filterDesc)
AZ_PROFILE_FUNCTION(AzCore);
AZ_Assert(objectClassData, "Class data is required.");
if (!context)
{
AZ_PROFILE_FUNCTION(AzCore);
AZ_Assert(objectClassData, "Class data is required.");
if (!context)
{
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialize context");
}
if (!context)
{
return false;
}
AZ_Assert(targetPointer, "You must provide a target pointer");
bool foundSuccess = false;
using CreationCallback = AZStd::function<void (void **, const SerializeContext::ClassData **, const Uuid &, SerializeContext *)>;
auto handler = [&targetPointer, objectClassData, &foundSuccess](void** instance, const SerializeContext::ClassData** classData, const Uuid& classId, SerializeContext* context)
{
void* convertibleInstance{};
if (objectClassData->ConvertFromType(convertibleInstance, classId, targetPointer, *context))
{
foundSuccess = true;
if (instance)
{
// The ObjectStream will ask us for the address of the target to load into, so provide it.
*instance = convertibleInstance;
}
if (classData)
{
// The ObjectStream will ask us for the class data of the target being loaded into, so provide it if needed.
// This allows us to load directly into a generic object (templated containers, strings, etc).
*classData = objectClassData;
}
}
};
bool readSuccess = ObjectStream::LoadBlocking(&stream, *context, ObjectStream::ClassReadyCB(), filterDesc, CreationCallback(handler, AZ::OSStdAllocator()));
AZ_Warning("Serialization", readSuccess, "LoadObjectFromStreamInPlace: Stream did not deserialize correctly");
AZ_Warning("Serialization", foundSuccess, "LoadObjectFromStreamInPlace: Did not find the expected type in the stream");
return readSuccess && foundSuccess;
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialize context");
}
bool LoadObjectFromStreamInPlace(IO::GenericStream& stream, AZ::SerializeContext* context, const Uuid& targetClassId, void* targetPointer, const FilterDescriptor& filterDesc)
if (!context)
{
AZ_PROFILE_FUNCTION(AzCore);
if (!context)
{
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialize context");
}
if (!context)
{
return false;
}
const SerializeContext::ClassData* classData = context->FindClassData(targetClassId);
if (!classData)
{
AZ_Error("Serialization", false,
"Unable to locate class data for uuid \"%s\". This object cannot be serialized as a root element. "
"Make sure the Uuid is valid, or if this is a generic type, use the override that takes a ClassData pointer instead.",
targetClassId.ToString<AZStd::string>().c_str());
return false;
}
return LoadObjectFromStreamInPlace(stream, context, classData, targetPointer, filterDesc);
}
bool LoadObjectFromFileInPlace(const AZStd::string& filePath, const Uuid& targetClassId, void* destination, AZ::SerializeContext* context /*= nullptr*/, const FilterDescriptor& filterDesc /*= FilterDescriptor()*/)
{
AZ::IO::FileIOStream fileStream;
if (!fileStream.Open(filePath.c_str(), IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary))
{
return false;
}
return LoadObjectFromStreamInPlace(fileStream, context, targetClassId, destination, filterDesc);
}
void* LoadObjectFromStream(IO::GenericStream& stream, AZ::SerializeContext* context, const Uuid* targetClassId, const FilterDescriptor& filterDesc)
{
AZ_PROFILE_FUNCTION(AzCore);
if (!context)
{
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialize context");
}
if (!context)
{
return nullptr;
}
void* loadedInstance = nullptr;
bool success = ObjectStream::LoadBlocking(&stream, *context,
[&loadedInstance, targetClassId](void* classPtr, const Uuid& classId, const SerializeContext* serializeContext)
{
if (targetClassId)
{
void* instance = serializeContext->DownCast(classPtr, classId, *targetClassId);
// Given a valid object
if (instance)
{
AZ_Assert(!loadedInstance, "loadedInstance must be NULL, otherwise we are being invoked with multiple valid objects");
loadedInstance = instance;
return;
}
}
else
{
if (!loadedInstance)
{
loadedInstance = classPtr;
return;
}
}
auto classData = serializeContext->FindClassData(classId);
if (classData && classData->m_factory)
{
classData->m_factory->Destroy(classPtr);
}
},
filterDesc,
ObjectStream::InplaceLoadRootInfoCB()
);
if (!success)
{
return nullptr;
}
return loadedInstance;
}
void* LoadObjectFromFile(const AZStd::string& filePath, const Uuid& targetClassId, SerializeContext* context, const FilterDescriptor& filterDesc, int /*platformFlags*/)
{
AZ_PROFILE_FUNCTION(AzCore);
AZ::IO::FileIOStream fileStream;
if (!fileStream.Open(filePath.c_str(), IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary))
{
return nullptr;
}
void* loadedObject = LoadObjectFromStream(fileStream, context, &targetClassId, filterDesc);
return loadedObject;
}
bool SaveObjectToStream(IO::GenericStream& stream, DataStream::StreamType streamType, const void* classPtr, const Uuid& classId, SerializeContext* context, const SerializeContext::ClassData* classData)
{
AZ_PROFILE_FUNCTION(AzCore);
if (!context)
{
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
if(!context)
{
AZ_Assert(false, "No serialize context");
return false;
}
}
if (!classPtr)
{
AZ_Assert(false, "SaveObjectToStream: classPtr is null, object cannot be serialized.");
return false;
}
AZ::ObjectStream* objectStream = AZ::ObjectStream::Create(&stream, *context, streamType);
if (!objectStream)
{
return false;
}
if (!objectStream->WriteClass(classPtr, classId, classData))
{
objectStream->Finalize();
return false;
}
if (!objectStream->Finalize())
{
return false;
}
return true;
}
bool SaveStreamToFile(const AZStd::string& filePath, const AZStd::vector<AZ::u8>& streamData, int platformFlags)
{
AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FixedMaxPathString resolvedPath;
if (fileIo == nullptr || !fileIo->ResolvePath(filePath.c_str(), resolvedPath.data(), resolvedPath.capacity() + 1))
{
resolvedPath = filePath;
}
if (AZ::IO::SystemFile fileHandle; fileHandle.Open(resolvedPath.c_str(),
AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY,
platformFlags))
{
AZ::IO::SizeType bytesWritten = fileHandle.Write(streamData.data(), streamData.size());
return bytesWritten == streamData.size();
}
return false;
}
bool SaveObjectToFile(const AZStd::string& filePath, DataStream::StreamType fileType, const void* classPtr, const Uuid& classId, SerializeContext* context, int platformFlags)
{
AZ_PROFILE_FUNCTION(AzCore);
AZ_Assert(targetPointer, "You must provide a target pointer");
// \note This is ok for tools, but we should use the streamer to write objects directly (no memory store)
AZStd::vector<AZ::u8> dstData;
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8> > dstByteStream(&dstData);
if (!SaveObjectToStream(dstByteStream, fileType, classPtr, classId, context))
bool foundSuccess = false;
using CreationCallback = AZStd::function<void (void **, const SerializeContext::ClassData **, const Uuid &, SerializeContext *)>;
auto handler = [&targetPointer, objectClassData, &foundSuccess](void** instance, const SerializeContext::ClassData** classData, const Uuid& classId, SerializeContext* context)
{
return false;
}
return SaveStreamToFile(filePath, dstData, platformFlags);
}
/*!
\brief Finds any descendant DataElementNodes of the @classElement which match each Crc32 values in the supplied elementCrcQueue
\param context SerializeContext used for looking up ClassData
\param classElement Top level DataElementNode to begin comparison each the Crc32 queue
\param elementCrcQueue Container of Crc32 values in the order in which DataElementNodes should be matched as the DataElementNode tree is traversed
\return Vector of valid pointers to DataElementNodes which match the entire element Crc32 queue
*/
AZStd::vector<AZ::SerializeContext::DataElementNode*> FindDescendantElements(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement,
const AZStd::vector<AZ::Crc32>& elementCrcQueue)
{
AZStd::vector<AZ::SerializeContext::DataElementNode*> dataElementNodes;
FindDescendantElements(context, classElement, dataElementNodes, elementCrcQueue.begin(), elementCrcQueue.end());
return dataElementNodes;
}
/*!
\brief Finds any descendant DataElementNodes of the @classElement which match each Crc32 values in the supplied elementCrcQueue
\param context SerializeContext used for looking up ClassData
\param classElement The current DataElementNode which will be compared against be to current top Crc32 value in the Crc32 queue
\param dataElementNodes[out] Array to populate with a DataElementNode which was found by matching all Crc32 values in the Crc32 queue
\param first The current front of the Crc32 queue
\param last The end of the Crc32 queue
*/
void FindDescendantElements(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement,
AZStd::vector<AZ::SerializeContext::DataElementNode*>& dataElementNodes, AZStd::vector<AZ::Crc32>::const_iterator first, AZStd::vector<AZ::Crc32>::const_iterator last)
{
if (first == last)
{
return;
}
for (int i = 0; i < classElement.GetNumSubElements(); ++i)
{
auto& childElement = classElement.GetSubElement(i);
if (*first == AZ::Crc32(childElement.GetName()))
void* convertibleInstance{};
if (objectClassData->ConvertFromType(convertibleInstance, classId, targetPointer, *context))
{
if (AZStd::distance(first, last) == 1)
foundSuccess = true;
if (instance)
{
dataElementNodes.push_back(&childElement);
// The ObjectStream will ask us for the address of the target to load into, so provide it.
*instance = convertibleInstance;
}
if (classData)
{
// The ObjectStream will ask us for the class data of the target being loaded into, so provide it if needed.
// This allows us to load directly into a generic object (templated containers, strings, etc).
*classData = objectClassData;
}
}
};
bool readSuccess = ObjectStream::LoadBlocking(&stream, *context, ObjectStream::ClassReadyCB(), filterDesc, CreationCallback(handler, AZ::OSStdAllocator()));
AZ_Warning("Serialization", readSuccess, "LoadObjectFromStreamInPlace: Stream did not deserialize correctly");
AZ_Warning("Serialization", foundSuccess, "LoadObjectFromStreamInPlace: Did not find the expected type in the stream");
return readSuccess && foundSuccess;
}
bool LoadObjectFromStreamInPlace(IO::GenericStream& stream, AZ::SerializeContext* context, const Uuid& targetClassId, void* targetPointer, const FilterDescriptor& filterDesc)
{
AZ_PROFILE_FUNCTION(AzCore);
if (!context)
{
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialize context");
}
if (!context)
{
return false;
}
const SerializeContext::ClassData* classData = context->FindClassData(targetClassId);
if (!classData)
{
AZ_Error("Serialization", false,
"Unable to locate class data for uuid \"%s\". This object cannot be serialized as a root element. "
"Make sure the Uuid is valid, or if this is a generic type, use the override that takes a ClassData pointer instead.",
targetClassId.ToString<AZStd::string>().c_str());
return false;
}
return LoadObjectFromStreamInPlace(stream, context, classData, targetPointer, filterDesc);
}
bool LoadObjectFromFileInPlace(const AZStd::string& filePath, const Uuid& targetClassId, void* destination, AZ::SerializeContext* context /*= nullptr*/, const FilterDescriptor& filterDesc /*= FilterDescriptor()*/)
{
AZ::IO::FileIOStream fileStream;
if (!fileStream.Open(filePath.c_str(), IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary))
{
return false;
}
return LoadObjectFromStreamInPlace(fileStream, context, targetClassId, destination, filterDesc);
}
void* LoadObjectFromStream(IO::GenericStream& stream, AZ::SerializeContext* context, const Uuid* targetClassId, const FilterDescriptor& filterDesc)
{
AZ_PROFILE_FUNCTION(AzCore);
if (!context)
{
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialize context");
}
if (!context)
{
return nullptr;
}
void* loadedInstance = nullptr;
bool success = ObjectStream::LoadBlocking(&stream, *context,
[&loadedInstance, targetClassId](void* classPtr, const Uuid& classId, const SerializeContext* serializeContext)
{
if (targetClassId)
{
void* instance = serializeContext->DownCast(classPtr, classId, *targetClassId);
// Given a valid object
if (instance)
{
AZ_Assert(!loadedInstance, "loadedInstance must be NULL, otherwise we are being invoked with multiple valid objects");
loadedInstance = instance;
return;
}
}
else
{
FindDescendantElements(context, childElement, dataElementNodes, AZStd::next(first), last);
}
}
}
}
bool IsVectorContainerType(const AZ::Uuid& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
AZ::TypeId containerTypeId = type;
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
// This type is a container
containerTypeId = classInfo->GetGenericTypeId();
}
if (containerTypeId == AZ::GetGenericClassInfoVectorTypeId()
|| containerTypeId == AZ::GetGenericClassInfoFixedVectorTypeId()
|| containerTypeId == AZ::GetGenericClassInfoArrayTypeId()
)
{
return true;
}
}
return false;
}
bool IsSetContainerType(const AZ::Uuid& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
AZ::TypeId containerTypeId = type;
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
// This type is a container
containerTypeId = classInfo->GetGenericTypeId();
}
if (containerTypeId == AZ::GetGenericClassSetTypeId()
|| containerTypeId == AZ::GetGenericClassUnorderedSetTypeId()
)
{
return true;
}
}
return false;
}
bool IsMapContainerType(const AZ::Uuid& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
AZ::Uuid containerTypeId = type;
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
containerTypeId = classInfo->GetGenericTypeId();
}
if (containerTypeId == AZ::GetGenericClassMapTypeId()
|| containerTypeId == AZ::GetGenericClassUnorderedMapTypeId()
)
{
return true;
}
}
return false;
}
bool IsContainerType(const AZ::Uuid& type)
{
return IsVectorContainerType(type) || IsSetContainerType(type) || IsMapContainerType(type);
}
AZStd::vector<AZ::Uuid> GetContainedTypes(const AZ::Uuid& type)
{
AZStd::vector<AZ::Uuid> types;
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
for (int i = 0; i < classInfo->GetNumTemplatedArguments(); ++i)
{
types.push_back(classInfo->GetTemplatedTypeId(i));
}
}
}
return types;
}
bool IsOutcomeType(const AZ::Uuid& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type);
return classInfo && classInfo->GetGenericTypeId() == AZ::GetGenericOutcomeTypeId();
}
return false;
}
bool IsPairContainerType(const AZ::Uuid& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
AZ::Uuid containerTypeId = type;
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
containerTypeId = classInfo->GetGenericTypeId();
}
if (containerTypeId == AZ::GetGenericClassPairTypeId())
{
return true;
}
}
return false;
}
AZ::TypeId GetGenericContainerType(const AZ::TypeId& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
// This type is a container
return classInfo->GetGenericTypeId();
}
}
return azrtti_typeid<void>();
}
bool IsGenericContainerType(const AZ::TypeId& type)
{
return IsContainerType(type) && GetGenericContainerType(type) == azrtti_typeid<void>();
}
AZStd::pair<AZ::Uuid, AZ::Uuid> GetOutcomeTypes(const AZ::Uuid& type)
{
AZStd::vector<AZ::Uuid> types;
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
AZ_Assert(classInfo->GetNumTemplatedArguments() == 2, "Outcome template arguments must be 2, even if void, void");
return AZStd::make_pair(classInfo->GetTemplatedTypeId(0), classInfo->GetTemplatedTypeId(1));
}
}
return AZStd::make_pair(azrtti_typeid<void>(), azrtti_typeid<void>());
}
void* ResolvePointer(void* ptr, const SerializeContext::ClassElement& classElement, const SerializeContext& context)
{
if (classElement.m_flags & SerializeContext::ClassElement::FLG_POINTER)
{
// In the case of pointer-to-pointer, we'll deference.
ptr = *(void**)(ptr);
// Pointer-to-pointer fields may be base class / polymorphic, so cast pointer to actual type,
// safe for passing as 'this' to member functions.
if (ptr && classElement.m_azRtti)
{
Uuid actualClassId = classElement.m_azRtti->GetActualUuid(ptr);
if (actualClassId != classElement.m_typeId)
{
const SerializeContext::ClassData* classData = context.FindClassData(actualClassId);
if (classData)
if (!loadedInstance)
{
ptr = classElement.m_azRtti->Cast(ptr, classData->m_azRtti->GetTypeId());
loadedInstance = classPtr;
return;
}
}
}
}
return ptr;
auto classData = serializeContext->FindClassData(classId);
if (classData && classData->m_factory)
{
classData->m_factory->Destroy(classPtr);
}
},
filterDesc,
ObjectStream::InplaceLoadRootInfoCB()
);
if (!success)
{
return nullptr;
}
} // namespace Utils
} // namespace AZ
return loadedInstance;
}
void* LoadObjectFromFile(const AZStd::string& filePath, const Uuid& targetClassId, SerializeContext* context, const FilterDescriptor& filterDesc, int /*platformFlags*/)
{
AZ_PROFILE_FUNCTION(AzCore);
AZ::IO::FileIOStream fileStream;
if (!fileStream.Open(filePath.c_str(), IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary))
{
return nullptr;
}
void* loadedObject = LoadObjectFromStream(fileStream, context, &targetClassId, filterDesc);
return loadedObject;
}
bool SaveObjectToStream(IO::GenericStream& stream, DataStream::StreamType streamType, const void* classPtr, const Uuid& classId, SerializeContext* context, const SerializeContext::ClassData* classData)
{
AZ_PROFILE_FUNCTION(AzCore);
if (!context)
{
EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext);
if(!context)
{
AZ_Assert(false, "No serialize context");
return false;
}
}
if (!classPtr)
{
AZ_Assert(false, "SaveObjectToStream: classPtr is null, object cannot be serialized.");
return false;
}
AZ::ObjectStream* objectStream = AZ::ObjectStream::Create(&stream, *context, streamType);
if (!objectStream)
{
return false;
}
if (!objectStream->WriteClass(classPtr, classId, classData))
{
objectStream->Finalize();
return false;
}
if (!objectStream->Finalize())
{
return false;
}
return true;
}
bool SaveStreamToFile(const AZStd::string& filePath, const AZStd::vector<AZ::u8>& streamData, int platformFlags)
{
AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FixedMaxPathString resolvedPath;
if (fileIo == nullptr || !fileIo->ResolvePath(filePath.c_str(), resolvedPath.data(), resolvedPath.capacity() + 1))
{
resolvedPath = filePath;
}
if (AZ::IO::SystemFile fileHandle; fileHandle.Open(resolvedPath.c_str(),
AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY,
platformFlags))
{
AZ::IO::SizeType bytesWritten = fileHandle.Write(streamData.data(), streamData.size());
return bytesWritten == streamData.size();
}
return false;
}
bool SaveObjectToFile(const AZStd::string& filePath, DataStream::StreamType fileType, const void* classPtr, const Uuid& classId, SerializeContext* context, int platformFlags)
{
AZ_PROFILE_FUNCTION(AzCore);
// \note This is ok for tools, but we should use the streamer to write objects directly (no memory store)
AZStd::vector<AZ::u8> dstData;
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8> > dstByteStream(&dstData);
if (!SaveObjectToStream(dstByteStream, fileType, classPtr, classId, context))
{
return false;
}
return SaveStreamToFile(filePath, dstData, platformFlags);
}
/*!
\brief Finds any descendant DataElementNodes of the @classElement which match each Crc32 values in the supplied elementCrcQueue
\param context SerializeContext used for looking up ClassData
\param classElement Top level DataElementNode to begin comparison each the Crc32 queue
\param elementCrcQueue Container of Crc32 values in the order in which DataElementNodes should be matched as the DataElementNode tree is traversed
\return Vector of valid pointers to DataElementNodes which match the entire element Crc32 queue
*/
AZStd::vector<AZ::SerializeContext::DataElementNode*> FindDescendantElements(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement,
const AZStd::vector<AZ::Crc32>& elementCrcQueue)
{
AZStd::vector<AZ::SerializeContext::DataElementNode*> dataElementNodes;
FindDescendantElements(context, classElement, dataElementNodes, elementCrcQueue.begin(), elementCrcQueue.end());
return dataElementNodes;
}
/*!
\brief Finds any descendant DataElementNodes of the @classElement which match each Crc32 values in the supplied elementCrcQueue
\param context SerializeContext used for looking up ClassData
\param classElement The current DataElementNode which will be compared against be to current top Crc32 value in the Crc32 queue
\param dataElementNodes[out] Array to populate with a DataElementNode which was found by matching all Crc32 values in the Crc32 queue
\param first The current front of the Crc32 queue
\param last The end of the Crc32 queue
*/
void FindDescendantElements(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement,
AZStd::vector<AZ::SerializeContext::DataElementNode*>& dataElementNodes, AZStd::vector<AZ::Crc32>::const_iterator first, AZStd::vector<AZ::Crc32>::const_iterator last)
{
if (first == last)
{
return;
}
for (int i = 0; i < classElement.GetNumSubElements(); ++i)
{
auto& childElement = classElement.GetSubElement(i);
if (*first == AZ::Crc32(childElement.GetName()))
{
if (AZStd::distance(first, last) == 1)
{
dataElementNodes.push_back(&childElement);
}
else
{
FindDescendantElements(context, childElement, dataElementNodes, AZStd::next(first), last);
}
}
}
}
bool IsVectorContainerType(const AZ::Uuid& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
AZ::TypeId containerTypeId = type;
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
// This type is a container
containerTypeId = classInfo->GetGenericTypeId();
}
if (containerTypeId == AZ::GetGenericClassInfoVectorTypeId()
|| containerTypeId == AZ::GetGenericClassInfoFixedVectorTypeId()
|| containerTypeId == AZ::GetGenericClassInfoArrayTypeId()
)
{
return true;
}
}
return false;
}
bool IsSetContainerType(const AZ::Uuid& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
AZ::TypeId containerTypeId = type;
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
// This type is a container
containerTypeId = classInfo->GetGenericTypeId();
}
if (containerTypeId == AZ::GetGenericClassSetTypeId()
|| containerTypeId == AZ::GetGenericClassUnorderedSetTypeId()
)
{
return true;
}
}
return false;
}
bool IsMapContainerType(const AZ::Uuid& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
AZ::Uuid containerTypeId = type;
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
containerTypeId = classInfo->GetGenericTypeId();
}
if (containerTypeId == AZ::GetGenericClassMapTypeId()
|| containerTypeId == AZ::GetGenericClassUnorderedMapTypeId()
)
{
return true;
}
}
return false;
}
bool IsContainerType(const AZ::Uuid& type)
{
return IsVectorContainerType(type) || IsSetContainerType(type) || IsMapContainerType(type);
}
AZStd::vector<AZ::Uuid> GetContainedTypes(const AZ::Uuid& type)
{
AZStd::vector<AZ::Uuid> types;
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
for (int i = 0; i < classInfo->GetNumTemplatedArguments(); ++i)
{
types.push_back(classInfo->GetTemplatedTypeId(i));
}
}
}
return types;
}
bool IsOutcomeType(const AZ::Uuid& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type);
return classInfo && classInfo->GetGenericTypeId() == AZ::GetGenericOutcomeTypeId();
}
return false;
}
bool IsPairContainerType(const AZ::Uuid& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
AZ::Uuid containerTypeId = type;
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
containerTypeId = classInfo->GetGenericTypeId();
}
if (containerTypeId == AZ::GetGenericClassPairTypeId())
{
return true;
}
}
return false;
}
AZ::TypeId GetGenericContainerType(const AZ::TypeId& type)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
// This type is a container
return classInfo->GetGenericTypeId();
}
}
return azrtti_typeid<void>();
}
bool IsGenericContainerType(const AZ::TypeId& type)
{
return IsContainerType(type) && GetGenericContainerType(type) == azrtti_typeid<void>();
}
AZStd::pair<AZ::Uuid, AZ::Uuid> GetOutcomeTypes(const AZ::Uuid& type)
{
AZStd::vector<AZ::Uuid> types;
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
if (serializeContext)
{
if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type))
{
AZ_Assert(classInfo->GetNumTemplatedArguments() == 2, "Outcome template arguments must be 2, even if void, void");
return AZStd::make_pair(classInfo->GetTemplatedTypeId(0), classInfo->GetTemplatedTypeId(1));
}
}
return AZStd::make_pair(azrtti_typeid<void>(), azrtti_typeid<void>());
}
void* ResolvePointer(void* ptr, const SerializeContext::ClassElement& classElement, const SerializeContext& context)
{
if (classElement.m_flags & SerializeContext::ClassElement::FLG_POINTER)
{
// In the case of pointer-to-pointer, we'll deference.
ptr = *(void**)(ptr);
// Pointer-to-pointer fields may be base class / polymorphic, so cast pointer to actual type,
// safe for passing as 'this' to member functions.
if (ptr && classElement.m_azRtti)
{
Uuid actualClassId = classElement.m_azRtti->GetActualUuid(ptr);
if (actualClassId != classElement.m_typeId)
{
const SerializeContext::ClassData* classData = context.FindClassData(actualClassId);
if (classData)
{
ptr = classElement.m_azRtti->Cast(ptr, classData->m_azRtti->GetTypeId());
}
}
}
}
return ptr;
}
} // namespace AZ::Utils
@@ -2065,11 +2065,15 @@ namespace AZ
}
EnumerateInstanceCallContext callContext(
AZStd::bind(&SerializeContext::BeginCloneElement, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, &cloneData, &m_errorLogger, &scratchBuffer),
AZStd::bind(&SerializeContext::EndCloneElement, this, &cloneData),
this,
SerializeContext::ENUM_ACCESS_FOR_READ,
&m_errorLogger);
[&](void* ptr, const ClassData* classData, const ClassElement* elementData) -> bool
{
return BeginCloneElement(ptr, classData, elementData, &cloneData, &m_errorLogger, &scratchBuffer);
},
[&]() -> bool
{
return EndCloneElement(&cloneData);
},
this, SerializeContext::ENUM_ACCESS_FOR_READ, &m_errorLogger);
EnumerateInstance(
&callContext
@@ -2098,19 +2102,17 @@ namespace AZ
if (ptr)
{
EnumerateInstanceCallContext callContext(
AZStd::bind(&SerializeContext::BeginCloneElementInplace, this, dest, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, &cloneData, &m_errorLogger, &scratchBuffer),
AZStd::bind(&SerializeContext::EndCloneElement, this, &cloneData),
this,
SerializeContext::ENUM_ACCESS_FOR_READ,
&m_errorLogger);
[&](void* ptr, const ClassData* classData, const ClassElement* elementData) -> bool
{
return BeginCloneElementInplace(dest, ptr, classData, elementData, &cloneData, &m_errorLogger, &scratchBuffer);
},
[&]() -> bool
{
return EndCloneElement(&cloneData);
},
this, SerializeContext::ENUM_ACCESS_FOR_READ, &m_errorLogger);
EnumerateInstance(
&callContext
, const_cast<void*>(ptr)
, classId
, nullptr
, nullptr
);
EnumerateInstance(&callContext, const_cast<void*>(ptr), classId, nullptr, nullptr);
}
}
@@ -2941,14 +2943,10 @@ namespace AZ
{
m_errorHandler = errorHandler ? errorHandler : &m_defaultErrorHandler;
m_elementCallback = AZStd::bind(static_cast<bool (SerializeContext::*)(EnumerateInstanceCallContext*, void*, const Uuid&, const ClassData*, const ClassElement*) const>(&SerializeContext::EnumerateInstance)
, m_context
, this
, AZStd::placeholders::_1
, AZStd::placeholders::_2
, AZStd::placeholders::_3
, AZStd::placeholders::_4
);
m_elementCallback = [this](void* ptr, const Uuid& classId, const ClassData* classData, const ClassElement* classElement)->bool
{
return m_context->EnumerateInstance(this, ptr, classId, classData, classElement);
};
}
//=========================================================================
@@ -90,7 +90,7 @@ namespace AZ
using AttributePtr = AZStd::shared_ptr<Attribute>;
using AttributeSharedPair = AZStd::pair<AttributeId, AttributePtr>;
template <typename ContainerType, typename T>
template <typename T, typename ContainerType = AttributeContainerType<T>>
AttributePtr CreateModuleAttribute(T&& attrValue);
/**
@@ -540,6 +540,7 @@ namespace AZ
*/
struct ClassElement
{
AZ_TYPE_INFO(ClassElement, "{7D386902-A1D9-4525-8284-F68435FE1D05}");
enum Flags
{
FLG_POINTER = (1 << 0), ///< Element is stored as pointer (it's not a value).
@@ -563,22 +564,22 @@ namespace AZ
void ClearAttributes();
Attribute* FindAttribute(AttributeId attributeId) const;
const char* m_name; ///< Used in XML output and debugging purposes
u32 m_nameCrc; ///< CRC32 of m_name
Uuid m_typeId;
size_t m_dataSize;
size_t m_offset;
const char* m_name{ "" }; ///< Used in XML output and debugging purposes
u32 m_nameCrc{}; ///< CRC32 of m_name
Uuid m_typeId = AZ::TypeId::CreateNull();
size_t m_dataSize{};
size_t m_offset{};
IRttiHelper* m_azRtti; ///< Interface used to support RTTI.
IRttiHelper* m_azRtti{}; ///< Interface used to support RTTI.
GenericClassInfo* m_genericClassInfo = nullptr; ///< Valid when the generic class is set. So you don't search for the actual type in the class register.
Edit::ElementData* m_editData; ///< Pointer to edit data (generated by EditContext).
Edit::ElementData* m_editData{}; ///< Pointer to edit data (generated by EditContext).
AZStd::vector<AttributeSharedPair, AZStdFunctorAllocator> m_attributes{
AZStdFunctorAllocator([]() -> IAllocatorAllocate& { return AZ::AllocatorInstance<AZ::SystemAllocator>::Get(); })
}; ///< Attributes attached to ClassElement. Lambda is required here as AZStdFunctorAllocator expects a function pointer
///< that returns an IAllocatorAllocate& and the AZ::AllocatorInstance<AZ::SystemAllocator>::Get returns an AZ::SystemAllocator&
/// which while it inherits from IAllocatorAllocate, does not work as function pointers do not support covariant return types
AttributeOwnership m_attributeOwnership = AttributeOwnership::Parent;
int m_flags; ///<
int m_flags{}; ///<
};
typedef AZStd::vector<ClassElement> ClassElementArray;
@@ -589,6 +590,8 @@ namespace AZ
class ClassData
{
public:
AZ_TYPE_INFO(ClassData, "{20EB8E2E-D807-4039-84E2-CE37D7647CD4}");
ClassData();
~ClassData() { ClearAttributes(); }
ClassData(ClassData&&) = default;
@@ -1040,6 +1043,7 @@ namespace AZ
*/
struct EnumerateInstanceCallContext
{
AZ_TYPE_INFO(EnumerateInstanceCallContext, "{FCC1DB4B-72BD-4D78-9C23-C84B91589D33}");
EnumerateInstanceCallContext(const BeginElemEnumCB& beginElemCB, const EndElemEnumCB& endElemCB, const SerializeContext* context, unsigned int accessflags, ErrorHandler* errorHandler);
BeginElemEnumCB m_beginElemCB; ///< Optional callback when entering an element's hierarchy.
@@ -2539,7 +2543,7 @@ namespace AZ
/// associated with current module
/// @param attrValue value to store within the attribute
/// @param ContainerType second parameter which is used for function parameter deduction
template <typename ContainerType, typename T>
template <typename T, typename ContainerType>
AttributePtr CreateModuleAttribute(T&& attrValue)
{
IAllocatorAllocate& moduleAllocator = GetCurrentSerializeContextModule().GetAllocator();
@@ -433,9 +433,7 @@ namespace AZ
m_classData.m_attributes.set_allocator(AZStd::move(dllAllocator));
// Create the ObjectStreamWriteOverrideCB in the current module
using ContainerType = AttributeData<AZStd::function<void(SerializeContext::EnumerateInstanceCallContext&,
const void*, const SerializeContext::ClassData&, const SerializeContext::ClassElement*)>>;
m_classData.m_attributes.emplace_back(AZ_CRC("ObjectStreamWriteElementOverride", 0x35eb659f), CreateModuleAttribute<ContainerType>(&ObjectStreamWriter));
m_classData.m_attributes.emplace_back(AZ_CRC("ObjectStreamWriteElementOverride", 0x35eb659f), CreateModuleAttribute(&ObjectStreamWriter));
}
SerializeContext::ClassData* GetClassData() override
@@ -327,7 +327,7 @@ namespace AZ
while (!localNotifierQueue.empty())
{
for (SignalNotifierArgs notifierArgs : localNotifierQueue)
for (const SignalNotifierArgs& notifierArgs : localNotifierQueue)
{
localNotifierEvent.Signal(notifierArgs.m_jsonPath, notifierArgs.m_type);
}
@@ -266,7 +266,8 @@ namespace AZ::SettingsRegistryMergeUtils
// Step 3 locate the project root and attempt to find the engine root using the registered engine
// for the project in the project.json file
AZ::IO::FixedMaxPath projectRoot = FindProjectRoot(settingsRegistry);
AZ::IO::FixedMaxPath projectRoot;
settingsRegistry.Get(projectRoot.Native(), FilePathKey_ProjectPath);
if (projectRoot.empty())
{
return {};
@@ -668,7 +669,7 @@ namespace AZ::SettingsRegistryMergeUtils
// NOTE: We make the project-path in the BootstrapSettingsRootKey absolute first
AZ::IO::FixedMaxPath projectPath = FindProjectRoot(registry);
if (constexpr auto projectPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path";
if ([[maybe_unused]] constexpr auto projectPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path";
!projectPath.empty())
{
if (projectPath.IsRelative())
@@ -693,6 +694,7 @@ namespace AZ::SettingsRegistryMergeUtils
R"(Project path isn't set in the Settings Registry at "%.*s".)"
" Project-related filepaths will be set relative to the executable directory\n",
AZ_STRING_ARG(projectPathKey));
projectPath = exePath;
registry.Set(FilePathKey_ProjectPath, exePath.Native());
}
@@ -840,7 +840,7 @@ namespace AZ
}
SliceComponent::SliceInstance* SliceComponent::SliceReference::CreateInstanceFromExistingEntities(AZStd::vector<AZ::Entity*>& entities,
const EntityIdToEntityIdMap assetToLiveIdMap,
const EntityIdToEntityIdMap& assetToLiveIdMap,
SliceInstanceId sliceInstanceId)
{
AZ_PROFILE_FUNCTION(AzCore);
@@ -443,7 +443,7 @@ namespace AZ
* @return A pointer to the newly created slice instance. Returns nullptr on error or if the SliceComponent is not instantiated.
*/
SliceInstance* CreateInstanceFromExistingEntities(AZStd::vector<AZ::Entity*>& entities,
const EntityIdToEntityIdMap assetToLiveIdMap,
const EntityIdToEntityIdMap& assetToLiveIdMap,
SliceInstanceId sliceInstanceId = SliceInstanceId::CreateRandom());
/**
@@ -8,77 +8,74 @@
#include <AzCore/Socket/AzSocket.h>
namespace AZ
namespace AZ::AzSock
{
namespace AzSock
AzSocketAddress::AzSocketAddress()
{
AzSocketAddress::AzSocketAddress()
{
Reset();
}
AzSocketAddress& AzSocketAddress::operator=(const AZSOCKADDR& addr)
{
m_sockAddr = *reinterpret_cast<const AZSOCKADDR_IN*>(&addr);
return *this;
}
bool AzSocketAddress::operator==(const AzSocketAddress& rhs) const
{
return m_sockAddr.sin_family == rhs.m_sockAddr.sin_family
&& m_sockAddr.sin_addr.s_addr == rhs.m_sockAddr.sin_addr.s_addr
&& m_sockAddr.sin_port == rhs.m_sockAddr.sin_port;
}
const AZSOCKADDR* AzSocketAddress::GetTargetAddress() const
{
return reinterpret_cast<const AZSOCKADDR*>(&m_sockAddr);
}
AZStd::string AzSocketAddress::GetIP() const
{
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, const_cast<in_addr*>(&m_sockAddr.sin_addr), ip, AZ_ARRAY_SIZE(ip));
return AZStd::string(ip);
}
AZStd::string AzSocketAddress::GetAddress() const
{
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, const_cast<in_addr*>(&m_sockAddr.sin_addr), ip, AZ_ARRAY_SIZE(ip));
return AZStd::string::format("%s:%d", ip, AZ::AzSock::NetToHostShort(m_sockAddr.sin_port));
}
AZ::u16 AzSocketAddress::GetAddrPort() const
{
return AZ::AzSock::NetToHostShort(m_sockAddr.sin_port);
}
void AzSocketAddress::SetAddrPort(AZ::u16 port)
{
m_sockAddr.sin_port = AZ::AzSock::HostToNetShort(port);
}
bool AzSocketAddress::SetAddress(const AZStd::string& ip, AZ::u16 port)
{
AZ_Assert(!ip.empty(), "Invalid address string!");
Reset();
return AZ::AzSock::ResolveAddress(ip, port, m_sockAddr);
}
bool AzSocketAddress::SetAddress(AZ::u32 ip, AZ::u16 port)
{
Reset();
m_sockAddr.sin_addr.s_addr = AZ::AzSock::HostToNetLong(ip);
m_sockAddr.sin_port = AZ::AzSock::HostToNetShort(port);
return true;
}
void AzSocketAddress::Reset()
{
memset(&m_sockAddr, 0, sizeof(m_sockAddr));
m_sockAddr.sin_family = AF_INET;
m_sockAddr.sin_addr.s_addr = INADDR_ANY;
}
Reset();
}
}
AzSocketAddress& AzSocketAddress::operator=(const AZSOCKADDR& addr)
{
m_sockAddr = *reinterpret_cast<const AZSOCKADDR_IN*>(&addr);
return *this;
}
bool AzSocketAddress::operator==(const AzSocketAddress& rhs) const
{
return m_sockAddr.sin_family == rhs.m_sockAddr.sin_family
&& m_sockAddr.sin_addr.s_addr == rhs.m_sockAddr.sin_addr.s_addr
&& m_sockAddr.sin_port == rhs.m_sockAddr.sin_port;
}
const AZSOCKADDR* AzSocketAddress::GetTargetAddress() const
{
return reinterpret_cast<const AZSOCKADDR*>(&m_sockAddr);
}
AZStd::string AzSocketAddress::GetIP() const
{
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, const_cast<in_addr*>(&m_sockAddr.sin_addr), ip, AZ_ARRAY_SIZE(ip));
return AZStd::string(ip);
}
AZStd::string AzSocketAddress::GetAddress() const
{
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, const_cast<in_addr*>(&m_sockAddr.sin_addr), ip, AZ_ARRAY_SIZE(ip));
return AZStd::string::format("%s:%d", ip, AZ::AzSock::NetToHostShort(m_sockAddr.sin_port));
}
AZ::u16 AzSocketAddress::GetAddrPort() const
{
return AZ::AzSock::NetToHostShort(m_sockAddr.sin_port);
}
void AzSocketAddress::SetAddrPort(AZ::u16 port)
{
m_sockAddr.sin_port = AZ::AzSock::HostToNetShort(port);
}
bool AzSocketAddress::SetAddress(const AZStd::string& ip, AZ::u16 port)
{
AZ_Assert(!ip.empty(), "Invalid address string!");
Reset();
return AZ::AzSock::ResolveAddress(ip, port, m_sockAddr);
}
bool AzSocketAddress::SetAddress(AZ::u32 ip, AZ::u16 port)
{
Reset();
m_sockAddr.sin_addr.s_addr = AZ::AzSock::HostToNetLong(ip);
m_sockAddr.sin_port = AZ::AzSock::HostToNetShort(port);
return true;
}
void AzSocketAddress::Reset()
{
memset(&m_sockAddr, 0, sizeof(m_sockAddr));
m_sockAddr.sin_family = AF_INET;
m_sockAddr.sin_addr.s_addr = INADDR_ANY;
}
} // namespace AZ::AzSock
@@ -10,66 +10,63 @@
#include "RunningStatistic.h"
namespace AZ
namespace AZ::Statistics
{
namespace Statistics
void RunningStatistic::Reset()
{
void RunningStatistic::Reset()
m_numSamples = 0;
m_mostRecentSample = 0.0;
m_minimum = 0.0;
m_maximum = 0.0;
m_sum = 0.0;
m_average = 0.0;
m_varianceTracking = 0.0;
}
void RunningStatistic::PushSample(double value)
{
m_numSamples++;
m_mostRecentSample = value;
m_sum += value;
if (m_numSamples == 1)
{
m_numSamples = 0;
m_mostRecentSample = 0.0;
m_minimum = 0.0;
m_maximum = 0.0;
m_sum = 0.0;
m_average = 0.0;
m_varianceTracking = 0.0;
m_minimum = value;
m_maximum = value;
m_average = value;
return;
}
void RunningStatistic::PushSample(double value)
if (value < m_minimum)
{
m_numSamples++;
m_mostRecentSample = value;
m_sum += value;
if (m_numSamples == 1)
{
m_minimum = value;
m_maximum = value;
m_average = value;
return;
}
if (value < m_minimum)
{
m_minimum = value;
}
else if (value > m_maximum)
{
m_maximum = value;
}
//See header notes and references to understand this way of calculating
//running average & variance.
const double newAverage = m_average + (value - m_average) / m_numSamples;
m_varianceTracking = m_varianceTracking + (value - m_average)*(value - newAverage);
m_average = newAverage;
m_minimum = value;
}
else if (value > m_maximum)
{
m_maximum = value;
}
double RunningStatistic::GetVariance(VarianceType varianceType) const
{
if (m_numSamples > 1)
{
const AZ::u64 varianceDivisor = (varianceType == VarianceType::S) ? m_numSamples - 1 : m_numSamples;
return m_varianceTracking / varianceDivisor;
}
return 0.0;
}
//See header notes and references to understand this way of calculating
//running average & variance.
const double newAverage = m_average + (value - m_average) / m_numSamples;
m_varianceTracking = m_varianceTracking + (value - m_average)*(value - newAverage);
double RunningStatistic::GetStdev(VarianceType varianceType) const
m_average = newAverage;
}
double RunningStatistic::GetVariance(VarianceType varianceType) const
{
if (m_numSamples > 1)
{
return sqrt(GetVariance(varianceType));
const AZ::u64 varianceDivisor = (varianceType == VarianceType::S) ? m_numSamples - 1 : m_numSamples;
return m_varianceTracking / varianceDivisor;
}
}//namespace Statistics
}//namespace AZ
return 0.0;
}
double RunningStatistic::GetStdev(VarianceType varianceType) const
{
return sqrt(GetVariance(varianceType));
}
} // namespace AZ::Statistics
@@ -12,55 +12,52 @@
#include "StatisticalProfilerProxySystemComponent.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AZ
namespace AZ::Statistics
{
namespace Statistics
StatisticalProfilerProxy* StatisticalProfilerProxy::TimedScope::m_profilerProxy = nullptr;
////////////////////////////////////////////////////////////////////////////////////////////////
void StatisticalProfilerProxySystemComponent::Reflect(AZ::ReflectContext* context)
{
StatisticalProfilerProxy* StatisticalProfilerProxy::TimedScope::m_profilerProxy = nullptr;
////////////////////////////////////////////////////////////////////////////////////////////////
void StatisticalProfilerProxySystemComponent::Reflect(AZ::ReflectContext* context)
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<StatisticalProfilerProxySystemComponent, AZ::Component>()
->Version(1);
}
serializeContext->Class<StatisticalProfilerProxySystemComponent, AZ::Component>()
->Version(1);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void StatisticalProfilerProxySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("StatisticalProfilerService", 0x20066f73));
}
////////////////////////////////////////////////////////////////////////////////////////////////
void StatisticalProfilerProxySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("StatisticalProfilerService", 0x20066f73));
}
////////////////////////////////////////////////////////////////////////////////////////////////
void StatisticalProfilerProxySystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("StatisticalProfilerService", 0x20066f73));
}
////////////////////////////////////////////////////////////////////////////////////////////////
void StatisticalProfilerProxySystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("StatisticalProfilerService", 0x20066f73));
}
////////////////////////////////////////////////////////////////////////////////////////////////
StatisticalProfilerProxySystemComponent::StatisticalProfilerProxySystemComponent()
: m_StatisticalProfilerProxy(nullptr)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
StatisticalProfilerProxySystemComponent::StatisticalProfilerProxySystemComponent()
: m_StatisticalProfilerProxy(nullptr)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
StatisticalProfilerProxySystemComponent::~StatisticalProfilerProxySystemComponent()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
StatisticalProfilerProxySystemComponent::~StatisticalProfilerProxySystemComponent()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
void StatisticalProfilerProxySystemComponent::Activate()
{
m_StatisticalProfilerProxy = new StatisticalProfilerProxy;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void StatisticalProfilerProxySystemComponent::Activate()
{
m_StatisticalProfilerProxy = new StatisticalProfilerProxy;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void StatisticalProfilerProxySystemComponent::Deactivate()
{
delete m_StatisticalProfilerProxy;
}
} //namespace Statistics
} // namespace AZ
////////////////////////////////////////////////////////////////////////////////////////////////
void StatisticalProfilerProxySystemComponent::Deactivate()
{
delete m_StatisticalProfilerProxy;
}
} // namespace AZ::Statistics
File diff suppressed because it is too large Load Diff
@@ -485,10 +485,11 @@ namespace AZ
//! CalculateBranchToken
/*! Calculate the branch token that is used for asset processor connection negotiations
*
* \param appRootPath - The absolute path of the app root to base the token calculation on
* \param engineRootPath - The absolute path to the engine root to base the token calculation on
* \param token - The result of the branch token calculation
*/
void CalculateBranchToken(const AZStd::string& appRootPath, AZStd::string& token);
void CalculateBranchToken(AZStd::string_view engineRootPath, AZStd::string& token);
void CalculateBranchToken(AZStd::string_view engineRootPath, AZ::IO::FixedMaxPathString& token);
}
//////////////////////////////////////////////////////////////////////////
@@ -30,8 +30,13 @@ namespace AZ
if (Interface<TaskGraphActiveInterface>::Get() == nullptr)
{
#if (AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS)
const uint32_t numberOfWorkerThreads = AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS;
#else
const uint32_t numberOfWorkerThreads = Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved);
#endif // (AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS)
Interface<TaskGraphActiveInterface>::Register(this); // small window that another thread can try to use taskgraph between this line and the set instance.
m_taskExecutor = aznew TaskExecutor(Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved));
m_taskExecutor = aznew TaskExecutor(numberOfWorkerThreads);
TaskExecutor::SetInstance(m_taskExecutor);
}
}
+128 -9
View File
@@ -12,8 +12,8 @@
#include <AzCore/EBus/EBus.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
#include <AzCore/std/time.h>
#include <AzCore/std/chrono/chrono.h>
#include <AzCore/std/time.h>
namespace AZ
{
@@ -24,15 +24,21 @@ namespace AZ
//! Using int64_t as the underlying type, this is good to represent approximately 292,471 years
AZ_TYPE_SAFE_INTEGRAL(TimeUs, int64_t);
namespace Time
{
static const AZ::TimeMs ZeroTimeMs = AZ::TimeMs{ 0 };
static const AZ::TimeUs ZeroTimeUs = AZ::TimeUs{ 0 };
}
//! @class ITime
//! @brief This is an AZ::Interface<> for managing time related operations.
//! AZ::ITime and associated types may not operate in realtime. These abstractions are to allow our application
//! simulation to operate both slower and faster than realtime in a well defined and user controllable manner
//! The rate at which time passes for AZ::ITime is controlled by the cvar t_scale
//! t_scale == 0 means simulation time should halt
//! 0 < t_scale < 1 will cause time to pass slower than realtime, with t_scale 0.1 being roughly 1/10th realtime
//! t_scale == 1 will cause time to pass at roughly realtime
//! t_scale > 1 will cause time to pass faster than normal, with t_scale 10 being roughly 10x realtime
//! The rate at which time passes for AZ::ITime is controlled by the cvar t_simulationTickScale
//! t_simulationTickScale == 0 means simulation time should halt
//! 0 < t_simulationTickScale < 1 will cause time to pass slower than realtime, with t_simulationTickScale 0.1 being roughly 1/10th realtime
//! t_simulationTickScale == 1 will cause time to pass at roughly realtime
//! t_simulationTickScale > 1 will cause time to pass faster than normal, with t_simulationTickScale 10 being roughly 10x realtime
class ITime
{
public:
@@ -41,15 +47,72 @@ namespace AZ
ITime() = default;
virtual ~ITime() = default;
//! Returns the number of milliseconds since application start.
//! @return the number of milliseconds that have elapsed since application start
//! Returns the number of milliseconds since application start scaled by t_simulationTickScale.
//! @return The number of milliseconds that have elapsed since application start.
virtual TimeMs GetElapsedTimeMs() const = 0;
//! Returns the number of microseconds since application start.
//! Returns the number of microseconds since application start scaled by t_simulationTickScale.
//! @return the number of microseconds that have elapsed since application start
virtual TimeUs GetElapsedTimeUs() const = 0;
//! Returns the number of milliseconds since application start.
//! This value is not affected by the t_simulationTickScale cvar.
//! @return The number of milliseconds that have elapsed since application start.
virtual TimeMs GetRealElapsedTimeMs() const = 0;
//! Returns the number of microseconds since application start.
//! This value is not affected by the t_simulationTickScale cvar.
//! @return The number of microseconds that have elapsed since application start.
virtual TimeUs GetRealElapsedTimeUs() const = 0;
//! Returns the current simulation tick delta time.
//! This is affected by the cvars t_simulationTickScale, t_simulationTickDeltaOverride, and t_maxGameTickDelta.
//! @return The number of microseconds elapsed since the last game tick.
virtual TimeUs GetSimulationTickDeltaTimeUs() const = 0;
//! Returns the non-manipulated tick time.
//! @return The number of microseconds elapsed since the last game tick.
virtual TimeUs GetRealTickDeltaTimeUs() const = 0;
//! Returns the time since application start of when the last simulation tick was updated.
virtual TimeUs GetLastSimulationTickTime() const = 0;
//! If > 0 this will override the simulation tick delta time with the provided value.
//! When enabled this will ignore any set simulation tick scale.
//! Setting to 0 disables the override.
//! @param timeMs The time in milliseconds to use for the tick delta.
virtual void SetSimulationTickDeltaOverride(TimeMs timeMs) = 0;
//! Returns the current simulation tick override.
//! 0 means disabled.
//! @returns The current simulation tick override in milliseconds.
virtual TimeMs GetSimulationTickDeltaOverride() const = 0;
//! A scalar amount to adjust the passage of time by, 1.0 == realtime, 0.5 == half realtime, 2.0 == doubletime.
//! @param scale The scalar value to apply to the simulation time.
virtual void SetSimulationTickScale(float scale) = 0;
//! Returns the current simulation tick scale.
//! 1.0 == realtime, 0.5 == half realtime, 2.0 == doubletime.
//! @returns The simulation tick scale value.
virtual float GetSimulationTickScale() const = 0;
//! The minimum rate to force the simulation tick to run.
//! 0 for as fast as possible. 30 = ~33ms, 60 = ~16ms.
//! Setting to 0 will disable rate limiting.
//! @note It is not guaranteed to hit the requested tick rate exactly.
//! @param rate The rate in frames per second.
virtual void SetSimulationTickRate(int rate) = 0;
//! Return the current simulation tick rate.
//! 0 means disabled.
//! @return The rate in frames per second.
virtual int32_t GetSimulationTickRate() const = 0;
AZ_DISABLE_COPY_MOVE(ITime);
static const AZ::TimeMs ZeroTimeMs = AZ::TimeMs{ 0 };
static const AZ::TimeUs ZeroTimeUs = AZ::TimeUs{ 0 };
};
// EBus wrapper for ScriptCanvas
@@ -74,6 +137,36 @@ namespace AZ
return AZ::Interface<ITime>::Get()->GetElapsedTimeUs();
}
//! This is a simple convenience wrapper
inline TimeMs GetRealElapsedTimeMs()
{
return AZ::Interface<ITime>::Get()->GetRealElapsedTimeMs();
}
//! This is a simple convenience wrapper
inline TimeUs GetRealElapsedTimeUs()
{
return AZ::Interface<ITime>::Get()->GetRealElapsedTimeUs();
}
//! This is a simple convenience wrapper
inline TimeUs GetSimulationTickDeltaTimeUs()
{
return AZ::Interface<ITime>::Get()->GetSimulationTickDeltaTimeUs();
}
//! This is a simple convenience wrapper
inline TimeUs GetRealTickDeltaTimeUs()
{
return AZ::Interface<ITime>::Get()->GetRealTickDeltaTimeUs();
}
//! This is a simple convenience wrapper
inline TimeUs GetLastSimulationTickTime()
{
return AZ::Interface<ITime>::Get()->GetLastSimulationTickTime();
}
//! Converts from milliseconds to microseconds
inline TimeUs TimeMsToUs(TimeMs value)
{
@@ -92,12 +185,24 @@ namespace AZ
return static_cast<float>(value) / 1000.0f;
}
//! Converts from milliseconds to seconds
inline double TimeMsToSecondsDouble(TimeMs value)
{
return static_cast<double>(value) / 1000.0;
}
//! Converts from microseconds to seconds
inline float TimeUsToSeconds(TimeUs value)
{
return static_cast<float>(value) / 1000000.0f;
}
//! Converts from microseconds to seconds
inline double TimeUsToSecondsDouble(TimeUs value)
{
return static_cast<double>(value) / 1000000.0;
}
//! Converts from milliseconds to AZStd::chrono::time_point
inline auto TimeMsToChrono(TimeMs value)
{
@@ -113,6 +218,20 @@ namespace AZ
auto chronoValue = AZStd::chrono::microseconds(aznumeric_cast<int64_t>(value));
return epoch + chronoValue;
}
//! A utility function to convert from seconds to TimeMs
inline TimeMs SecondsToTimeMs(const double value)
{
const double valueMs = value * 1000.0;
return static_cast<TimeMs>(static_cast<int64_t>(valueMs));
}
//! A utility function to convert from seconds to TimeUs
inline TimeUs SecondsToTimeUs(const double value)
{
const double valueMs = value * 1000000.0;
return static_cast<TimeUs>(static_cast<int64_t>(valueMs));
}
} // namespace AZ
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AZ::TimeMs);
@@ -0,0 +1,213 @@
/*
* 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/Time/TimeSystem.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
namespace
{
void cvar_t_simulationTickScale_Changed(const float& value)
{
if (auto* timeSystem = AZ::Interface<ITime>::Get())
{
timeSystem->SetSimulationTickScale(value);
}
}
void cvar_t_simulationTickDeltaOverride_Changed(const float& value)
{
if (auto* timeSystem = AZ::Interface<ITime>::Get())
{
timeSystem->SetSimulationTickDeltaOverride(AZ::SecondsToTimeMs(value));
}
}
void cvar_t_simulationTickRate_Changed(const int& rate)
{
AZ_Warning("tick", false, "Simulation tick rate limiting is currently disabled. Setting will not be applied.");
if (auto* timeSystem = AZ::Interface<ITime>::Get())
{
timeSystem->SetSimulationTickRate(rate);
}
}
} // namespace
AZ_CVAR(float, t_simulationTickScale, 1.0f, cvar_t_simulationTickScale_Changed, AZ::ConsoleFunctorFlags::Null,
"A scalar amount to adjust time passage by, 1.0 == realtime, 0.5 == half realtime, 2.0 == doubletime");
AZ_CVAR(float, t_simulationTickDeltaOverride, 0.0f, cvar_t_simulationTickDeltaOverride_Changed, AZ::ConsoleFunctorFlags::Null,
"If > 0, overrides the simulation tick delta time with the provided value (Seconds) and ignores any t_simulationTickScale value.");
AZ_CVAR(int, t_simulationTickRate, 0, cvar_t_simulationTickRate_Changed, AZ::ConsoleFunctorFlags::Null,
"The minimum rate to force the game simulation tick to run. 0 for as fast as possible. 30 = ~33ms, 60 = ~16ms");
void TimeSystem::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<TimeSystem, ITime>()
->Version(1);
}
}
TimeSystem::TimeSystem()
{
m_lastInvokedTimeUs = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
AZ::Interface<ITime>::Register(this);
ITimeRequestBus::Handler::BusConnect();
}
TimeSystem::~TimeSystem()
{
AZ::Interface<ITime>::Unregister(this);
ITimeRequestBus::Handler::BusDisconnect();
}
TimeMs TimeSystem::GetElapsedTimeMs() const
{
return AZ::TimeUsToMs(GetElapsedTimeUs());
}
TimeUs TimeSystem::GetElapsedTimeUs() const
{
TimeUs currentTime = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
TimeUs deltaTime = currentTime - m_lastInvokedTimeUs;
if (t_simulationTickScale != 1.0f)
{
const float floatDelta = AZStd::GetMax(static_cast<float>(deltaTime) * t_simulationTickScale, 1.0f);
deltaTime = static_cast<TimeUs>(static_cast<int64_t>(floatDelta));
}
m_accumulatedTimeUs += deltaTime;
m_lastInvokedTimeUs = currentTime;
return m_accumulatedTimeUs;
}
TimeMs TimeSystem::GetRealElapsedTimeMs() const
{
return AZ::TimeUsToMs(GetRealElapsedTimeUs());
}
TimeUs TimeSystem::GetRealElapsedTimeUs() const
{
return static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
}
TimeUs TimeSystem::GetSimulationTickDeltaTimeUs() const
{
return m_simulationTickDeltaTimeUs;
}
TimeUs TimeSystem::GetRealTickDeltaTimeUs() const
{
return m_realTickDeltaTimeUs;
}
TimeUs TimeSystem::GetLastSimulationTickTime() const
{
return m_lastSimulationTickTimeUs;
}
TimeUs TimeSystem::AdvanceTickDeltaTimes()
{
const TimeUs currentTimeUs = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
//real time
m_realTickDeltaTimeUs = currentTimeUs - m_lastSimulationTickTimeUs;
//game time
if (m_simulationTickDeltaOverride > AZ::Time::ZeroTimeUs)
{
m_simulationTickDeltaTimeUs = m_simulationTickDeltaOverride;
m_lastSimulationTickTimeUs = currentTimeUs;
return m_simulationTickDeltaTimeUs;
}
m_simulationTickDeltaTimeUs = currentTimeUs - m_lastSimulationTickTimeUs;
if (!AZ::IsClose(t_simulationTickScale, 1.0f))
{
const double floatDelta = AZStd::GetMax(static_cast<double>(m_simulationTickDeltaTimeUs) * static_cast<double>(t_simulationTickScale), 1.0);
m_simulationTickDeltaTimeUs = static_cast<TimeUs>(static_cast<int64_t>(floatDelta));
}
m_lastSimulationTickTimeUs = currentTimeUs;
return m_simulationTickDeltaTimeUs;
}
void TimeSystem::ApplyTickRateLimiterIfNeeded()
{
// Currently disabling the Tick rate limiter as there are some reported issues when using it.
#ifdef ENABLE_TICK_RATE_LIMITER
// If tick rate limiting is on, ensure (1 / t_simulationTickRate) ms has elapsed since the last frame,
// sleeping if there's still time remaining.
if (t_simulationTickRate > 0)
{
const TimeUs currentTimeUs = AZ::GetRealElapsedTimeUs();
const TimeUs timeUntilNextTick = (m_lastSimulationTickTimeUs + m_simulationTickLimitTimeUs) - currentTimeUs;
if (timeUntilNextTick > AZ::Time::ZeroTimeUs)
{
AZ_TracePrintf("tick", "Sleeping for %.2f", AZ::TimeUsToSecondsDouble(timeUntilNextTick));
AZStd::this_thread::sleep_for(AZStd::chrono::microseconds(static_cast<int64_t>(timeUntilNextTick)));
}
}
#endif // #ifdef ENABLE_TICK_RATE_LIMITER
}
void TimeSystem::SetSimulationTickDeltaOverride(TimeMs timeMs)
{
const TimeUs timeUs = AZ::TimeMsToUs(timeMs);
if (timeUs != m_simulationTickDeltaOverride)
{
m_simulationTickDeltaOverride = timeUs;
t_simulationTickDeltaOverride = AZ::TimeUsToSeconds(timeUs); //update the cvar
}
}
TimeMs TimeSystem::GetSimulationTickDeltaOverride() const
{
return AZ::TimeUsToMs(m_simulationTickDeltaOverride);
}
void TimeSystem::SetSimulationTickScale(float scale)
{
if (!AZ::IsClose(scale, t_simulationTickScale))
{
t_simulationTickScale = scale;
}
}
float TimeSystem::GetSimulationTickScale() const
{
return t_simulationTickScale;
}
void TimeSystem::SetSimulationTickRate(int rate)
{
m_simulationTickLimitRate = AZStd::abs(rate);
if (m_simulationTickLimitRate != 0)
{
m_simulationTickLimitTimeUs = AZ::SecondsToTimeUs(1.0f / m_simulationTickLimitRate);
}
else
{
m_simulationTickLimitTimeUs = AZ::Time::ZeroTimeUs;
}
}
int32_t TimeSystem::GetSimulationTickRate() const
{
return m_simulationTickLimitRate;
}
} // namespace AZ
@@ -0,0 +1,83 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Time/ITime.h>
namespace AZ
{
class ReflectContext;
//! Implementation of the ITime system interface.
class TimeSystem
: public ITimeRequestBus::Handler
{
public:
AZ_RTTI(AZ::TimeSystem, "{CE1C5E4F-7DC1-4248-B10C-AC55E8924A48}", AZ::ITime);
static void Reflect(AZ::ReflectContext* context);
TimeSystem();
virtual ~TimeSystem();
//! ITime overrides.
//! @{
TimeMs GetElapsedTimeMs() const override;
TimeUs GetElapsedTimeUs() const override;
TimeMs GetRealElapsedTimeMs() const override;
TimeUs GetRealElapsedTimeUs() const override;
TimeUs GetSimulationTickDeltaTimeUs() const override;
TimeUs GetRealTickDeltaTimeUs() const override;
TimeUs GetLastSimulationTickTime() const override;
void SetSimulationTickDeltaOverride(TimeMs timeMs) override;
TimeMs GetSimulationTickDeltaOverride() const override;
void SetSimulationTickScale(float scale) override;
float GetSimulationTickScale() const override;
void SetSimulationTickRate(int rate) override;
int32_t GetSimulationTickRate() const override;
//! @}
//! Advances the Simulation and Real tick delta time counters.
//! This is called from the owner of the TimeSystem, ComponentApplication in Tick().
//! @return The delta in microseconds from the last call to AdvanceTickDeltaTimes(). Value will be the same as GetSimulationTickDeltaTimeUs().
TimeUs AdvanceTickDeltaTimes();
//! If t_simulationTickRate is >0 this will try to have the game delta time run at a maximum of the rate set.
//! This is called from the owner of the TimeSystem, ComponentApplication in Tick().
//! example. If t_simulationTickRate is set to 60Fps, and the game tick delta is <17ms(60fps), this will add a sleep for the remaining time.
//! example. If t_simulationTickRate is set to 60Fps, and the game tick delta is >=17ms(60fps), this will not sleep at all.
//! @note It is not guaranteed to hit the requested tick rate exactly.
void ApplyTickRateLimiterIfNeeded();
private:
//! Used to calculate the delta time between calls to GetElapsedTimeMs/TimeUs().
//! Mutable to allow GetElapsedTimeMs/TimeUs() to be a const functions.
mutable TimeUs m_lastInvokedTimeUs = AZ::Time::ZeroTimeUs;
//! Accumulates the delta time of GetElapsedTimeMs/TimeUs() calls.
//! Mutable to allow GetElapsedTimeMs/TimeUs() to be a const functions.
mutable TimeUs m_accumulatedTimeUs = AZ::Time::ZeroTimeUs;
//! The current game tick delta time.
//! Can be affected by time system cvars.
//! Updated in AdvanceTickDeltaTimes().
TimeUs m_simulationTickDeltaTimeUs = AZ::Time::ZeroTimeUs;
//! The current real tick delta time.
//! Will not be affected by time system cvars.
//! Updated in AdvanceTickDeltaTimes().
TimeUs m_realTickDeltaTimeUs = AZ::Time::ZeroTimeUs;
TimeUs m_lastSimulationTickTimeUs = AZ::Time::ZeroTimeUs; //!< Used to determine the game tick delta time.
TimeUs m_simulationTickDeltaOverride = AZ::Time::ZeroTimeUs; //<! Stores the TimeUs value of the t_simulationTickDeltaOverride cvar.
TimeUs m_simulationTickLimitTimeUs = AZ::Time::ZeroTimeUs; //<! Stores the TimeUs value of the t_simulationTickRate cvar.
int32_t m_simulationTickLimitRate = 0; //<! Stores the simulation rate limit in frames per second.
};
}
@@ -1,80 +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/Time/TimeSystemComponent.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AZ
{
AZ_CVAR(float, t_scale, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "A scalar amount to adjust time passage by, 1.0 == realtime, 0.5 == half realtime, 2.0 == doubletime");
void TimeSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<TimeSystemComponent, AZ::Component>()
->Version(1);
}
}
void TimeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("TimeService"));
}
void TimeSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("TimeService"));
}
TimeSystemComponent::TimeSystemComponent()
{
m_lastInvokedTimeUs = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
AZ::Interface<ITime>::Register(this);
ITimeRequestBus::Handler::BusConnect();
}
TimeSystemComponent::~TimeSystemComponent()
{
ITimeRequestBus::Handler::BusDisconnect();
AZ::Interface<ITime>::Unregister(this);
}
void TimeSystemComponent::Activate()
{
;
}
void TimeSystemComponent::Deactivate()
{
;
}
TimeMs TimeSystemComponent::GetElapsedTimeMs() const
{
return TimeUsToMs(GetElapsedTimeUs());
}
TimeUs TimeSystemComponent::GetElapsedTimeUs() const
{
TimeUs currentTime = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
TimeUs deltaTime = currentTime - m_lastInvokedTimeUs;
if (t_scale != 1.0f)
{
float floatDelta = static_cast<float>(deltaTime) * t_scale;
deltaTime = static_cast<TimeUs>(static_cast<int64_t>(floatDelta));
}
m_accumulatedTimeUs += deltaTime;
m_lastInvokedTimeUs = currentTime;
return m_accumulatedTimeUs;
}
}
@@ -1,50 +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/Time/ITime.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Console/IConsole.h>
namespace AZ
{
//! Implementation of the ITime system interface.
class TimeSystemComponent
: public AZ::Component
, public ITimeRequestBus::Handler
{
public:
AZ_COMPONENT(TimeSystemComponent, "{CE1C5E4F-7DC1-4248-B10C-AC55E8924A48}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
TimeSystemComponent();
virtual ~TimeSystemComponent();
//! AZ::Component overrides.
//! @{
void Activate() override;
void Deactivate() override;
//! @}
//! ITime overrides.
//! @{
TimeMs GetElapsedTimeMs() const override;
TimeUs GetElapsedTimeUs() const override;
//! @}
private:
mutable TimeUs m_lastInvokedTimeUs = TimeUs{0};
mutable TimeUs m_accumulatedTimeUs = TimeUs{0};
};
}
@@ -41,7 +41,6 @@ namespace UnitTest
MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ());
MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ());
MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ());
MOCK_CONST_METHOD0(GetAppRoot, const char* ());
MOCK_CONST_METHOD0(GetEngineRoot, const char* ());
MOCK_CONST_METHOD0(GetExecutableFolder, const char* ());
MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&));
@@ -0,0 +1,119 @@
/*
* 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/Time/TimeSystem.h>
#include <gmock/gmock.h>
namespace AZ
{
class MockTimeSystem;
using NiceTimeSystemMock =::testing::NiceMock<MockTimeSystem>;
//used if you wish to mock any of the Get time functions.
class MockTimeSystem
: public ITimeRequestBus::Handler
{
public:
MockTimeSystem()
{
AZ::Interface<ITime>::Register(this);
ITimeRequestBus::Handler::BusConnect();
}
virtual ~MockTimeSystem()
{
AZ::Interface<ITime>::Unregister(this);
ITimeRequestBus::Handler::BusDisconnect();
}
MOCK_CONST_METHOD0(GetElapsedTimeMs, TimeMs());
MOCK_CONST_METHOD0(GetElapsedTimeUs, TimeUs());
MOCK_CONST_METHOD0(GetRealElapsedTimeMs, TimeMs());
MOCK_CONST_METHOD0(GetRealElapsedTimeUs, TimeUs());
MOCK_CONST_METHOD0(GetSimulationTickDeltaTimeUs, TimeUs());
MOCK_CONST_METHOD0(GetRealTickDeltaTimeUs, TimeUs());
MOCK_CONST_METHOD0(GetLastSimulationTickTime, TimeUs());
MOCK_METHOD1(SetSimulationTickDeltaOverride, void(TimeMs));
MOCK_CONST_METHOD0(GetSimulationTickDeltaOverride, TimeMs());
MOCK_METHOD1(SetSimulationTickScale, void(float));
MOCK_CONST_METHOD0(GetSimulationTickScale, float());
MOCK_METHOD1(SetSimulationTickRate, void(int));
MOCK_CONST_METHOD0(GetSimulationTickRate, int32_t());
};
//used if you wish to override any of the Get time functions with specific functionality.
class StubTimeSystem
: public AZ::TimeSystem
{
public:
AZ_RTTI(AZ::StubTimeSystem, "{DD5D5A6A-345F-49FD-A61E-A40E63C49CFA}", AZ::TimeSystem);
virtual AZ::TimeMs GetElapsedTimeMs() const override
{
return AZ::Time::ZeroTimeMs;
}
virtual AZ::TimeUs GetElapsedTimeUs() const override
{
return AZ::Time::ZeroTimeUs;
}
virtual AZ::TimeMs GetRealElapsedTimeMs() const override
{
return AZ::Time::ZeroTimeMs;
}
virtual AZ::TimeUs GetRealElapsedTimeUs() const override
{
return AZ::Time::ZeroTimeUs;
}
virtual AZ::TimeUs GetSimulationTickDeltaTimeUs() const override
{
return AZ::Time::ZeroTimeUs;
}
virtual AZ::TimeUs GetRealTickDeltaTimeUs() const override
{
return AZ::Time::ZeroTimeUs;
}
virtual AZ::TimeUs GetLastSimulationTickTime() const override
{
return AZ::Time::ZeroTimeUs;
}
virtual void SetSimulationTickDeltaOverride([[maybe_unused]]TimeMs timeMs) override
{
}
virtual TimeMs GetSimulationTickDeltaOverride() const override
{
return AZ::Time::ZeroTimeMs;
}
virtual void SetSimulationTickScale([[maybe_unused]] float scale) override
{
}
virtual float GetSimulationTickScale() const override
{
return 1.0f;
}
virtual void SetSimulationTickRate([[maybe_unused]] int rate) override
{
}
virtual int32_t GetSimulationTickRate() const override
{
return 0;
}
};
} // namespace AZ
@@ -60,6 +60,7 @@ namespace UnitTest
m_isAssertTest = true;
m_numAssertsFailed = 0;
}
int StopAssertTests()
{
m_isAssertTest = false;
@@ -69,6 +70,11 @@ namespace UnitTest
}
bool m_isAssertTest;
bool m_suppressErrors = true;
bool m_suppressWarnings = true;
bool m_suppressAsserts = true;
bool m_suppressOutput = true;
bool m_suppressPrintf = true;
int m_numAssertsFailed;
};
@@ -114,7 +120,7 @@ namespace UnitTest
// utility classes that you can derive from or contain, which suppress AZ_Asserts
// and AZ_Errors to the below macros (processAssert, etc)
// If TraceBusHook or TraceBusRedirector have been started in your unit tests,
// If TraceBusHook or TraceBusRedirector have been started in your unit tests,
// use AZ_TEST_START_TRACE_SUPPRESSION and AZ_TEST_STOP_TRACE_SUPPRESSION(numExpectedAsserts) macros to perform AZ_Assert and AZ_Error suppression
class TraceBusRedirector
: public AZ::Debug::TraceMessageBus::Handler
@@ -124,16 +130,19 @@ namespace UnitTest
if (UnitTest::TestRunner::Instance().m_isAssertTest)
{
UnitTest::TestRunner::Instance().ProcessAssert(message, file, line, false);
return true;
}
else
else if (UnitTest::TestRunner::Instance().m_suppressAsserts)
{
GTEST_MESSAGE_AT_(file, line, message, ::testing::TestPartResult::kNonFatalFailure);
return true;
}
return true;
return false;
}
bool OnAssert(const char* /*message*/) override
{
return true; // stop processing
return UnitTest::TestRunner::Instance().m_suppressAsserts; // stop processing
}
bool OnPreError(const char* /*window*/, const char* file, int line, const char* /*func*/, const char* message) override
{
@@ -142,6 +151,7 @@ namespace UnitTest
UnitTest::TestRunner::Instance().ProcessAssert(message, file, line, false);
return true;
}
return false;
}
bool OnError(const char* /*window*/, const char* message) override
@@ -149,12 +159,15 @@ namespace UnitTest
if (UnitTest::TestRunner::Instance().m_isAssertTest)
{
UnitTest::TestRunner::Instance().ProcessAssert(message, __FILE__, __LINE__, UnitTest::AssertionExpr(false));
return true;
}
else
else if (UnitTest::TestRunner::Instance().m_suppressErrors)
{
GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure);
return true;
}
return true; // stop processing
return false;
}
bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override
{
@@ -163,21 +176,21 @@ namespace UnitTest
}
bool OnWarning(const char* /*window*/, const char* /*message*/) override
{
return true;
return UnitTest::TestRunner::Instance().m_suppressWarnings;
}
bool OnOutput(const char* /*window*/, const char* /*message*/) override
{
return true;
return UnitTest::TestRunner::Instance().m_suppressOutput;
}
bool OnPrintf(const char* window, const char* message) override
{
if (AZStd::string_view(window) == "Memory") // We want to print out the memory leak's stack traces
{
ColoredPrintf(COLOR_RED, "[ MEMORY ] %s", message);
ColoredPrintf(COLOR_RED, "[ MEMORY ] %s", message);
}
return true;
return UnitTest::TestRunner::Instance().m_suppressPrintf;
}
};
@@ -259,7 +272,6 @@ namespace UnitTest
bool m_environmentSetup = false;
bool m_createdAllocator = false;
};
}

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