Merge remote-tracking branch 'upstream/development' into nvsickle/GenericDomDocument
This commit is contained in:
@@ -37,7 +37,7 @@ namespace AZ
|
||||
using namespace AZ;
|
||||
|
||||
// Handle asserts
|
||||
class TraceDrillerHook
|
||||
class TestEnvironmentHook
|
||||
: public AZ::Test::ITestEnvironment
|
||||
, public UnitTest::TraceBusRedirector
|
||||
{
|
||||
@@ -57,5 +57,5 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
AZ_UNIT_TEST_HOOK(new TraceDrillerHook());
|
||||
AZ_UNIT_TEST_HOOK(new TestEnvironmentHook());
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/string/string_view.h>
|
||||
#include <AzCore/std/typetraits/is_base_of.h>
|
||||
#include <AzCore/Debug/AssetTracking.h>
|
||||
#include <AzCore/IO/Streamer/FileRequest.h>
|
||||
|
||||
namespace AZ
|
||||
@@ -169,7 +168,7 @@ namespace AZ
|
||||
virtual bool IsRegisterReadonlyAndShareable() { return true; }
|
||||
|
||||
/**
|
||||
* Override this function to control automatic reload behavior.
|
||||
* Override this function to control automatic reload behavior.
|
||||
* By default, the asset will reload automatically.
|
||||
* Return false to disable automatic reload. Potential use cases include:
|
||||
* 1, If an asset is dependent on a parent asset(i.e.both assets need to be reloaded as a group) the parent asset can explicitly reload the child.
|
||||
@@ -201,10 +200,10 @@ namespace AZ
|
||||
|
||||
AssetHandler* m_registeredHandler{ nullptr };
|
||||
|
||||
// This is used to identify a unique asset and should only be set by the asset manager
|
||||
// This is used to identify a unique asset and should only be set by the asset manager
|
||||
// and therefore does not need to be atomic.
|
||||
// All shared copy of an asset should have the same identifier and therefore
|
||||
// should not be modified while making copy of an existing asset.
|
||||
// should not be modified while making copy of an existing asset.
|
||||
int m_creationToken = s_defaultCreationToken;
|
||||
// General purpose flags that should only be accessed within the asset mutex
|
||||
AZStd::bitset<32> m_flags;
|
||||
@@ -325,13 +324,13 @@ namespace AZ
|
||||
|
||||
T& operator*() const
|
||||
{
|
||||
AZ_Assert(m_assetData, "Asset is not loaded");
|
||||
AZ_Assert(m_assetData, "Asset %s (%s) is not loaded", m_assetId.ToString<AZStd::string>().c_str(), m_assetHint.c_str());
|
||||
return *Get();
|
||||
}
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
AZ_Assert(m_assetData, "Asset is not loaded");
|
||||
AZ_Assert(m_assetData, "Asset %s (%s) is not loaded", m_assetId.ToString<AZStd::string>().c_str(), m_assetHint.c_str());
|
||||
return Get();
|
||||
}
|
||||
|
||||
@@ -431,7 +430,7 @@ namespace AZ
|
||||
*/
|
||||
void UpgradeAssetInfo();
|
||||
|
||||
/**
|
||||
/**
|
||||
* for debugging purposes - creates a string that represents the assets id, subid, hint, and name.
|
||||
* You should use this function for any time you want to show the full details of an asset in a log message
|
||||
* as it will always produce a consistent output string. By convention, don't surround the output of this call
|
||||
@@ -581,33 +580,32 @@ namespace AZ
|
||||
template<typename Bus>
|
||||
using ConnectionPolicy = AssetConnectionPolicy<Bus>;
|
||||
|
||||
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
virtual ~AssetEvents() {}
|
||||
|
||||
/// Called when an asset is loaded, patched and ready to be used.
|
||||
virtual void OnAssetReady(Asset<AssetData> asset) { (void)asset; }
|
||||
|
||||
|
||||
/// Called when an asset has been moved (usually due to de-fragmentation/compaction), if possible the only data pointer is provided otherwise NULL.
|
||||
virtual void OnAssetMoved(Asset<AssetData> asset, void* oldDataPointer) { (void)asset; (void)oldDataPointer; }
|
||||
|
||||
|
||||
/// Called before an asset reload has started.
|
||||
virtual void OnAssetPreReload(Asset<AssetData> asset) { (void)asset; }
|
||||
|
||||
|
||||
/// Called when an asset has been reloaded (usually in tool mode and loose more). It should not be called in final build.
|
||||
virtual void OnAssetReloaded(Asset<AssetData> asset) { (void)asset; }
|
||||
|
||||
|
||||
/// Called when an asset failed to reload.
|
||||
virtual void OnAssetReloadError(Asset<AssetData> asset) { (void)asset; }
|
||||
|
||||
|
||||
/// Called when an asset has been saved. In general most assets can't be saved (in a game) so make sure you check the flag.
|
||||
virtual void OnAssetSaved(Asset<AssetData> asset, bool isSuccessful) { (void)asset; (void)isSuccessful; }
|
||||
|
||||
|
||||
/// Called when an asset is unloaded.
|
||||
virtual void OnAssetUnloaded(const AssetId assetId, const AssetType assetType) { (void)assetId; (void)assetType; }
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Called when an error happened with an asset. When this message is received the asset should be considered broken by default.
|
||||
* Note that this can happen when the asset errors during load, but also happens when the asset is missing (not in catalog etc.)
|
||||
* in the case of an asset that is completely missing, the Asset<T> passed in here will have no hint or other information about
|
||||
@@ -1096,7 +1094,7 @@ namespace AZ
|
||||
// if we are a different asset (or being swapped with a empty) then we just swap as usual.
|
||||
AZStd::swap(m_assetHint, rhs.m_assetHint);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -1220,7 +1218,7 @@ namespace AZ
|
||||
/// Indiscriminately skips all asset references.
|
||||
bool AssetFilterNoAssetLoading(const AssetFilterInfo& filterInfo);
|
||||
|
||||
// Shared ProductDependency concepts between AP and LY
|
||||
// Shared ProductDependency concepts between AP and LY
|
||||
namespace ProductDependencyInfo
|
||||
{
|
||||
//! Corresponds to all ProductDependencyFlags, not just LoadBehaviors
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace AZ::Data
|
||||
AZ_PROFILE_SCOPE(AzCore, "AZ::Data::LoadAssetDataStreamCallback %s",
|
||||
m_filePath.c_str());
|
||||
|
||||
// Get the results
|
||||
// Get the results
|
||||
auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
|
||||
AZ::u64 bytesRead = 0;
|
||||
streamer->GetReadRequestResult(fileHandle, m_buffer, bytesRead,
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace AZ::Data
|
||||
//! The path and file name of the asset being loaded
|
||||
AZStd::string m_filePath;
|
||||
|
||||
//! The offset into the file to start loading at.
|
||||
//! The offset into the file to start loading at.
|
||||
size_t m_fileOffset{ 0 };
|
||||
|
||||
//! The amount of data that's expected to be loaded.
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <AzCore/Asset/AssetManager_private.h>
|
||||
#include <AzCore/Asset/AssetDataStream.h>
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzCore/Debug/AssetTracking.h>
|
||||
#include <AzCore/IO/IStreamer.h>
|
||||
#include <AzCore/Math/Crc.h>
|
||||
#include <AzCore/Math/MathUtils.h>
|
||||
@@ -164,8 +163,6 @@ namespace AZ::Data
|
||||
AZ_PROFILE_SCOPE(AzCore, "AZ::Data::LoadAssetJob::Process: %s",
|
||||
asset.GetHint().c_str());
|
||||
|
||||
AZ_ASSET_ATTACH_TO_SCOPE(this);
|
||||
|
||||
if (m_owner->ValidateAndRegisterAssetLoading(asset))
|
||||
{
|
||||
LoadAndSignal(asset);
|
||||
@@ -200,7 +197,6 @@ namespace AZ::Data
|
||||
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(cl_assetLoadDelay));
|
||||
}
|
||||
|
||||
AZ_ASSET_NAMED_SCOPE(asset.GetHint().c_str());
|
||||
bool loadedSuccessfully = false;
|
||||
|
||||
if (!cl_assetLoadError && m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Completed)
|
||||
@@ -982,7 +978,6 @@ namespace AZ::Data
|
||||
}
|
||||
|
||||
AZ_PROFILE_SCOPE(AzCore, "GetAsset: %s", assetInfo.m_relativePath.c_str());
|
||||
AZ_ASSET_NAMED_SCOPE("GetAsset: %s", assetInfo.m_relativePath.c_str());
|
||||
|
||||
AZStd::shared_ptr<AssetDataStream> dataStream;
|
||||
AssetStreamInfo loadInfo;
|
||||
|
||||
@@ -45,12 +45,9 @@
|
||||
#include <AzCore/Module/Module.h>
|
||||
#include <AzCore/Module/ModuleManager.h>
|
||||
|
||||
#include <AzCore/IO/Path/PathReflect.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
|
||||
#include <AzCore/Driller/Driller.h>
|
||||
#include <AzCore/Memory/MemoryDriller.h>
|
||||
#include <AzCore/Debug/TraceMessagesDriller.h>
|
||||
#include <AzCore/Debug/EventTraceDriller.h>
|
||||
#include <AzCore/Debug/Profiler.h>
|
||||
#include <AzCore/Script/ScriptSystemBus.h>
|
||||
|
||||
@@ -155,7 +152,6 @@ namespace AZ
|
||||
m_reservedDebug = 0;
|
||||
m_recordingMode = Debug::AllocationRecords::RECORD_STACK_IF_NO_FILE_LINE;
|
||||
m_stackRecordLevels = 5;
|
||||
m_enableDrilling = false;
|
||||
m_useOverrunDetection = false;
|
||||
m_useMalloc = false;
|
||||
}
|
||||
@@ -327,7 +323,6 @@ namespace AZ
|
||||
->Field("blockSize", &Descriptor::m_memoryBlocksByteSize)
|
||||
->Field("reservedOS", &Descriptor::m_reservedOS)
|
||||
->Field("reservedDebug", &Descriptor::m_reservedDebug)
|
||||
->Field("enableDrilling", &Descriptor::m_enableDrilling)
|
||||
->Field("useOverrunDetection", &Descriptor::m_useOverrunDetection)
|
||||
->Field("useMalloc", &Descriptor::m_useMalloc)
|
||||
->Field("allocatorRemappings", &Descriptor::m_allocatorRemappings)
|
||||
@@ -366,7 +361,6 @@ namespace AZ
|
||||
->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize)
|
||||
->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedOS, "OS reserved memory", "System memory reserved for OS (used only when 'Allocate all memory at startup' is true)")
|
||||
->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedDebug, "Memory reserved for debugger", "System memory reserved for Debug allocator, like memory tracking (used only when 'Allocate all memory at startup' is true)")
|
||||
->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_enableDrilling, "Enable Driller", "Enable Drilling support for the application (ignored in Release builds)")
|
||||
->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_useOverrunDetection, "Use Overrun Detection", "Use the overrun detection memory manager (only available on some platforms, ignored in Release builds)")
|
||||
->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_useMalloc, "Use Malloc", "Use malloc for memory allocations (for memory debugging only, ignored in Release builds)")
|
||||
;
|
||||
@@ -485,9 +479,11 @@ namespace AZ
|
||||
|
||||
// Merge Command Line arguments
|
||||
constexpr bool executeRegDumpCommands = false;
|
||||
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
|
||||
|
||||
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
|
||||
// Skip over merging the User Registry in non-debug and profile configurations
|
||||
SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(*m_settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});
|
||||
#endif
|
||||
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
|
||||
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry);
|
||||
|
||||
@@ -1547,7 +1543,7 @@ namespace AZ
|
||||
// reflect name dictionary.
|
||||
Name::Reflect(context);
|
||||
// reflect path
|
||||
IO::PathReflection::Reflect(context);
|
||||
IO::PathReflect(context);
|
||||
|
||||
// reflect the SettingsRegistryInterface, SettignsRegistryImpl and the global Settings Registry
|
||||
// instance (AZ::SettingsRegistry::Get()) into the Behavior Context
|
||||
|
||||
@@ -41,7 +41,6 @@ namespace AZ
|
||||
}
|
||||
namespace AZ::Debug
|
||||
{
|
||||
class DrillerManager;
|
||||
class LocalFileEventLogger;
|
||||
}
|
||||
|
||||
@@ -143,7 +142,6 @@ namespace AZ
|
||||
AZ::u64 m_reservedDebug; //!< Reserved memory for Debugging (allocation,etc.). Used only when m_grabAllMemory is set to true. (default: 0)
|
||||
Debug::AllocationRecords::Mode m_recordingMode; //!< When to record stack traces (default: AZ::Debug::AllocationRecords::RECORD_STACK_IF_NO_FILE_LINE)
|
||||
AZ::u64 m_stackRecordLevels; //!< If stack recording is enabled, how many stack levels to record. (default: 5)
|
||||
bool m_enableDrilling; //!< True to enabled drilling support for the application. RegisterDrillers will be called. Ignored in release. (default: true)
|
||||
bool m_useOverrunDetection; //!< True to use the overrun detection memory management scheme. Only available on some platforms; greatly increases memory consumption.
|
||||
bool m_useMalloc; //!< True to use malloc instead of the internal memory manager. Intended for debugging purposes only.
|
||||
|
||||
|
||||
@@ -37,11 +37,6 @@ namespace AZ
|
||||
class ComponentFactoryInterface;
|
||||
}
|
||||
|
||||
namespace Debug
|
||||
{
|
||||
class DrillerManager;
|
||||
}
|
||||
|
||||
struct ApplicationTypeQuery
|
||||
{
|
||||
bool IsEditor() const;
|
||||
|
||||
@@ -811,12 +811,12 @@ namespace AZ
|
||||
if (behaviorContext)
|
||||
{
|
||||
behaviorContext->Class<EntityId>()
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "entity")
|
||||
->Method("IsValid", &EntityId::IsValid)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method("ToString", &EntityId::ToString)
|
||||
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#define AZCORE_COMPONENT_TICK_BUS_H
|
||||
|
||||
#include <AzCore/Component/ComponentBus.h>
|
||||
#include <AzCore/Debug/AssetTracking.h>
|
||||
#include <AzCore/std/chrono/chrono.h>
|
||||
#include <AzCore/std/parallel/mutex.h> // For TickBus thread events.
|
||||
#include <AzCore/Script/ScriptTimePoint.h>
|
||||
@@ -112,10 +111,6 @@ namespace AZ
|
||||
AZ_FORCE_INLINE bool operator()(TickEvents* left, TickEvents* right) const { return left->GetTickOrder() < right->GetTickOrder(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable tick bus to work with the AssetTracking
|
||||
*/
|
||||
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
@@ -217,10 +212,6 @@ namespace AZ
|
||||
*/
|
||||
typedef AZStd::mutex EventQueueMutexType;
|
||||
|
||||
/**
|
||||
* Enable tick bus to work with the AssetTracking
|
||||
*/
|
||||
using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace AZ
|
||||
|
||||
if (StringFunc::StartsWith(curr->m_name, command, false))
|
||||
{
|
||||
AZLOG_INFO("- %s : %s\n", curr->m_name, curr->m_desc);
|
||||
AZLOG_INFO("- %s : %s", curr->m_name, curr->m_desc);
|
||||
|
||||
if (commandSubset.size() < MaxConsoleCommandPlusArgsLength)
|
||||
{
|
||||
@@ -433,29 +433,29 @@ namespace AZ
|
||||
{
|
||||
if ((curr->GetFlags() & requiredSet) != requiredSet)
|
||||
{
|
||||
AZLOG_WARN("%s failed required set flag check\n", curr->m_name);
|
||||
AZLOG_WARN("%s failed required set flag check", curr->m_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((curr->GetFlags() & requiredClear) != ConsoleFunctorFlags::Null)
|
||||
{
|
||||
AZLOG_WARN("%s failed required clear flag check\n", curr->m_name);
|
||||
AZLOG_WARN("%s failed required clear flag check", curr->m_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((curr->GetFlags() & ConsoleFunctorFlags::IsCheat) != ConsoleFunctorFlags::Null)
|
||||
{
|
||||
AZLOG_WARN("%s is marked as a cheat\n", curr->m_name);
|
||||
AZLOG_WARN("%s is marked as a cheat", curr->m_name);
|
||||
}
|
||||
|
||||
if ((curr->GetFlags() & ConsoleFunctorFlags::IsDeprecated) != ConsoleFunctorFlags::Null)
|
||||
{
|
||||
AZLOG_WARN("%s is marked as deprecated\n", curr->m_name);
|
||||
AZLOG_WARN("%s is marked as deprecated", curr->m_name);
|
||||
}
|
||||
|
||||
if ((curr->GetFlags() & ConsoleFunctorFlags::NeedsReload) != ConsoleFunctorFlags::Null)
|
||||
{
|
||||
AZLOG_WARN("Changes to %s will only take effect after level reload\n", curr->m_name);
|
||||
AZLOG_WARN("Changes to %s will only take effect after level reload", curr->m_name);
|
||||
}
|
||||
|
||||
// Letting this intentionally fall-through, since in editor we can register common variables multiple times
|
||||
@@ -468,7 +468,7 @@ namespace AZ
|
||||
{
|
||||
CVarFixedString value;
|
||||
curr->GetValue(value);
|
||||
AZLOG_INFO("> %s : %s\n", curr->GetName(), value.empty() ? "<empty>" : value.c_str());
|
||||
AZLOG_INFO("> %s : %s", curr->GetName(), value.empty() ? "<empty>" : value.c_str());
|
||||
}
|
||||
flags = curr->GetFlags();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,16 @@ namespace AZ
|
||||
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
|
||||
inline void ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator =(const BASE_TYPE& rhs)
|
||||
{
|
||||
const BASE_TYPE currentValue = this->m_value;
|
||||
// Do the value assignment outside new value check.
|
||||
// Client code can supply a type for m_value that overrides the operator= function and trigger side effects
|
||||
// in the operator= function body. Doing the assignment outside the value change check avoids those side
|
||||
// effects not being triggered because AzCore believes the value wouldn't change.
|
||||
this->m_value = rhs;
|
||||
if (currentValue != rhs)
|
||||
{
|
||||
InvokeCallback();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
|
||||
|
||||
@@ -119,25 +119,21 @@ namespace AZ
|
||||
void LoggerSystemComponent::LogInternalV(LogLevel level, const char* format, const char* file, const char* function, int32_t line, va_list args)
|
||||
{
|
||||
constexpr AZStd::size_t MaxLogBufferSize = 1000;
|
||||
char buffer[MaxLogBufferSize];
|
||||
auto buffer = AZStd::fixed_string<MaxLogBufferSize>::format_arg(format, args);
|
||||
m_logEvent.Signal(level, buffer.c_str(), file, function, line);
|
||||
buffer += '\n';
|
||||
|
||||
const AZStd::size_t length = azvsnprintf(buffer, MaxLogBufferSize, format, args);
|
||||
buffer[AZStd::min<AZStd::size_t>(length + 1, MaxLogBufferSize - 1)] = '\0';
|
||||
m_logEvent.Signal(level, buffer, file, function, line);
|
||||
|
||||
// Force a new-line before calling the AZ::Debug::Trace functions, as they assume a newline is present
|
||||
buffer[AZStd::min<AZStd::size_t>(length + 1, MaxLogBufferSize - 2)] = '\n';
|
||||
switch (level)
|
||||
{
|
||||
case LogLevel::Warn:
|
||||
AZ_Warning("Logger", true, buffer);
|
||||
AZ_Warning("Logger", true, buffer.c_str());
|
||||
break;
|
||||
case LogLevel::Error:
|
||||
AZ_Error("Logger", true, buffer);
|
||||
AZ_Error("Logger", true, buffer.c_str());
|
||||
break;
|
||||
default:
|
||||
// Catch all else with trace
|
||||
AZ::Debug::Trace::Output("Logger", buffer);
|
||||
AZ::Debug::Trace::Output("Logger", buffer.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,326 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AssetTracking.h"
|
||||
|
||||
#include <AzCore/Debug/AssetTrackingTypes.h>
|
||||
#include <AzCore/Memory/AllocatorManager.h>
|
||||
#include <AzCore/Memory/HphaSchema.h>
|
||||
#include <AzCore/std/containers/map.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
|
||||
namespace AZ::Debug
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct AssetTreeNode;
|
||||
|
||||
// Per-thread data that needs to be stored.
|
||||
struct ThreadData
|
||||
{
|
||||
AZStd::vector<AssetTreeNodeBase*, AZStdAssetTrackingAllocator> m_currentAssetStack;
|
||||
};
|
||||
|
||||
// Access thread data through a virtual function to ensure that the same thread-local data is being shared across DLLs.
|
||||
// Otherwise, the thread_local variables are replicated across DLLs that link the AzCore library, and you'll get a
|
||||
// different version in each module.
|
||||
class ThreadDataProvider
|
||||
{
|
||||
public:
|
||||
virtual ThreadData& GetThreadData() = 0;
|
||||
};
|
||||
}
|
||||
|
||||
class AssetTrackingImpl final :
|
||||
public ThreadDataProvider
|
||||
{
|
||||
public:
|
||||
AZ_TYPE_INFO(AssetTrackingImpl, "{01E2A099-3523-40BE-80E0-E0ADD861BEE1}");
|
||||
AZ_CLASS_ALLOCATOR(AssetTrackingImpl, OSAllocator, 0);
|
||||
|
||||
AssetTrackingImpl(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable);
|
||||
~AssetTrackingImpl();
|
||||
|
||||
void AssetBegin(const char* id, const char* file, int line);
|
||||
void AssetAttach(void* otherAllocation, const char* file, int line);
|
||||
void AssetEnd();
|
||||
|
||||
ThreadData& GetThreadData() override;
|
||||
|
||||
private:
|
||||
static EnvironmentVariable<AssetTrackingImpl*>& GetEnvironmentVariable();
|
||||
static AssetTrackingImpl* GetSharedInstance();
|
||||
static ThreadData& GetSharedThreadData();
|
||||
|
||||
using PrimaryAssets = AZStd::unordered_map<AssetTrackingId, AssetPrimaryInfo, AZStd::hash<AssetTrackingId>, AZStd::equal_to<AssetTrackingId>, AZStdAssetTrackingAllocator>;
|
||||
using ThreadData = ThreadData;
|
||||
using mutex_type = AZStd::mutex;
|
||||
using lock_type = AZStd::lock_guard<mutex_type>;
|
||||
|
||||
mutex_type m_mutex;
|
||||
PrimaryAssets m_primaryAssets;
|
||||
AssetTreeNodeBase* m_assetRoot = nullptr;
|
||||
AssetAllocationTableBase* m_allocationTable = nullptr;
|
||||
bool m_performingAnalysis = false;
|
||||
|
||||
friend class AssetTracking;
|
||||
friend class AssetTracking::Scope;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// AssetTrackingImpl methods
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AssetTrackingImpl::AssetTrackingImpl(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable) :
|
||||
m_assetRoot(&assetTree->GetRoot()),
|
||||
m_allocationTable(allocationTable)
|
||||
{
|
||||
AZ_Assert(!GetSharedInstance(), "Only one AssetTrackingImpl can exist!");
|
||||
|
||||
GetEnvironmentVariable().Set(this);
|
||||
AllocatorManager::Instance().EnterProfilingMode();
|
||||
}
|
||||
|
||||
AssetTrackingImpl::~AssetTrackingImpl()
|
||||
{
|
||||
AllocatorManager::Instance().ExitProfilingMode();
|
||||
GetEnvironmentVariable().Reset();
|
||||
}
|
||||
|
||||
void AssetTrackingImpl::AssetBegin(const char* id, const char* file, int line)
|
||||
{
|
||||
// In the future it may be desirable to organize assets based on where in code the asset was entered into.
|
||||
// For now these are ignored.
|
||||
AZ_UNUSED(file);
|
||||
AZ_UNUSED(line);
|
||||
|
||||
using namespace Internal;
|
||||
|
||||
AssetTrackingId assetId(id);
|
||||
auto& threadData = GetSharedThreadData();
|
||||
AssetTreeNodeBase* parentAsset = threadData.m_currentAssetStack.empty() ? nullptr : threadData.m_currentAssetStack.back();
|
||||
AssetTreeNodeBase* childAsset;
|
||||
AssetPrimaryInfo* assetPrimaryInfo;
|
||||
|
||||
if (!parentAsset)
|
||||
{
|
||||
parentAsset = m_assetRoot;
|
||||
}
|
||||
|
||||
{
|
||||
lock_type lock(m_mutex);
|
||||
|
||||
// Locate or create the primary record for this asset
|
||||
auto primaryItr = m_primaryAssets.find(assetId);
|
||||
|
||||
if (primaryItr != m_primaryAssets.end())
|
||||
{
|
||||
assetPrimaryInfo = &primaryItr->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto insertResult = m_primaryAssets.emplace(assetId, AssetPrimaryInfo());
|
||||
assetPrimaryInfo = &insertResult.first->second;
|
||||
assetPrimaryInfo->m_id = &insertResult.first->first;
|
||||
}
|
||||
|
||||
// Add this asset to the stack for this thread's context
|
||||
childAsset = parentAsset->FindOrAddChild(assetId, assetPrimaryInfo);
|
||||
}
|
||||
|
||||
threadData.m_currentAssetStack.push_back(childAsset);
|
||||
}
|
||||
|
||||
void AssetTrackingImpl::AssetAttach(void* otherAllocation, const char* file, int line)
|
||||
{
|
||||
AZ_UNUSED(file);
|
||||
AZ_UNUSED(line);
|
||||
|
||||
using namespace Internal;
|
||||
|
||||
AssetTreeNodeBase* assetInfo = m_allocationTable->FindAllocation(otherAllocation);
|
||||
|
||||
// We will push back a nullptr if there is no asset, this is necessary to balance the call to AssetEnd()
|
||||
GetSharedThreadData().m_currentAssetStack.push_back(assetInfo);
|
||||
}
|
||||
|
||||
void AssetTrackingImpl::AssetEnd()
|
||||
{
|
||||
AZ_Assert(!GetSharedThreadData().m_currentAssetStack.empty(), "AssetEnd() called without matching AssetBegin() or AssetAttach. Use the AZ_ASSET_NAMED_SCOPE and AZ_ASSET_ATTACH_TO_SCOPE macros to avoid this!");
|
||||
GetSharedThreadData().m_currentAssetStack.pop_back();
|
||||
}
|
||||
|
||||
AssetTrackingImpl* AssetTrackingImpl::GetSharedInstance()
|
||||
{
|
||||
auto environmentVariable = GetEnvironmentVariable();
|
||||
|
||||
if(environmentVariable)
|
||||
{
|
||||
return *environmentVariable;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ThreadData& AssetTrackingImpl::GetSharedThreadData()
|
||||
{
|
||||
// Cast to the base type so our virtual call doesn't get optimized away. We require GetThreadData() to be executed in the same DLL every time.
|
||||
return static_cast<ThreadDataProvider*>(GetSharedInstance())->GetThreadData();
|
||||
}
|
||||
|
||||
AssetTrackingImpl::ThreadData& AssetTrackingImpl::GetThreadData()
|
||||
{
|
||||
static thread_local ThreadData* data = nullptr;
|
||||
static thread_local typename AZStd::aligned_storage_t<sizeof(ThreadData), alignof(ThreadData)> storage;
|
||||
|
||||
if (!data)
|
||||
{
|
||||
data = new (&storage) ThreadData;
|
||||
}
|
||||
|
||||
return *data;
|
||||
}
|
||||
|
||||
EnvironmentVariable<AssetTrackingImpl*>& AssetTrackingImpl::GetEnvironmentVariable()
|
||||
{
|
||||
static EnvironmentVariable<AssetTrackingImpl*> assetTrackingImpl = Environment::CreateVariable<AssetTrackingImpl*>(AzTypeInfo<AssetTrackingImpl*>::Name());
|
||||
|
||||
return assetTrackingImpl;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// AssetTracking::Scope functions
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AssetTracking::Scope AssetTracking::Scope::ScopeFromAssetId(const char* file, int line, const char* fmt, ...)
|
||||
{
|
||||
if (auto impl = AssetTrackingImpl::GetSharedInstance())
|
||||
{
|
||||
static const int BUFFER_SIZE = 1024;
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
azvsnprintf(buffer, BUFFER_SIZE, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
impl->AssetBegin(buffer, file, line);
|
||||
}
|
||||
|
||||
return Scope();
|
||||
}
|
||||
|
||||
AssetTracking::Scope AssetTracking::Scope::ScopeFromAttachment(void* attachTo, const char* file, int line)
|
||||
{
|
||||
if (auto impl = AssetTrackingImpl::GetSharedInstance())
|
||||
{
|
||||
impl->AssetAttach(attachTo, file, line);
|
||||
}
|
||||
|
||||
return Scope();
|
||||
}
|
||||
|
||||
AssetTracking::Scope::~Scope()
|
||||
{
|
||||
if (auto impl = AssetTrackingImpl::GetSharedInstance())
|
||||
{
|
||||
impl->AssetEnd();
|
||||
}
|
||||
}
|
||||
|
||||
AssetTracking::Scope::Scope()
|
||||
{
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// AssetTracking functions
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void AssetTracking::EnterScopeByAssetId(const char* file, int line, const char* fmt, ...)
|
||||
{
|
||||
if (auto impl = AssetTrackingImpl::GetSharedInstance())
|
||||
{
|
||||
static const int BUFFER_SIZE = 1024;
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
azvsnprintf(buffer, BUFFER_SIZE, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
impl->AssetBegin(buffer, file, line);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetTracking::EnterScopeByAttachment(void* attachTo, const char* file, int line)
|
||||
{
|
||||
if (auto impl = AssetTrackingImpl::GetSharedInstance())
|
||||
{
|
||||
impl->AssetAttach(attachTo, file, line);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetTracking::ExitScope()
|
||||
{
|
||||
if (auto impl = AssetTrackingImpl::GetSharedInstance())
|
||||
{
|
||||
impl->AssetEnd();
|
||||
}
|
||||
}
|
||||
|
||||
const char* AssetTracking::GetDebugScope()
|
||||
{
|
||||
// Output debug information about the current asset scope in the current thread.
|
||||
// Do not use in production code.
|
||||
#ifndef RELEASE
|
||||
static const int BUFFER_SIZE = 1024;
|
||||
static char buffer[BUFFER_SIZE];
|
||||
const auto& assetStack = AssetTrackingImpl::GetSharedInstance()->GetThreadData().m_currentAssetStack;
|
||||
|
||||
if (assetStack.empty())
|
||||
{
|
||||
azsnprintf(buffer, BUFFER_SIZE, "<none>");
|
||||
}
|
||||
else
|
||||
{
|
||||
char* pos = buffer;
|
||||
for (auto itr = assetStack.rbegin(); itr != assetStack.rend(); ++itr)
|
||||
{
|
||||
pos += azsnprintf(pos, BUFFER_SIZE - (pos - buffer), "%s\n", (*itr)->GetAssetPrimaryInfo()->m_id->m_id.c_str());
|
||||
|
||||
if (pos >= buffer + BUFFER_SIZE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
AssetTracking::AssetTracking(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable)
|
||||
{
|
||||
m_impl.reset(aznew AssetTrackingImpl(assetTree, allocationTable));
|
||||
}
|
||||
|
||||
AssetTracking::~AssetTracking()
|
||||
{
|
||||
}
|
||||
|
||||
AssetTreeNodeBase* AssetTracking::GetCurrentThreadAsset() const
|
||||
{
|
||||
const auto& assetStack = m_impl->GetThreadData().m_currentAssetStack;
|
||||
AssetTreeNodeBase* result = assetStack.empty() ? nullptr : assetStack.back();
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Memory/OSAllocator.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzCore/EBus/Policies.h>
|
||||
|
||||
#ifndef AZ_TRACK_ASSET_SCOPES
|
||||
// You may manually uncomment this to enable asset tracking.
|
||||
//# define AZ_TRACK_ASSET_SCOPES
|
||||
#endif
|
||||
|
||||
#if !defined(AZ_TRACK_ASSET_SCOPES)
|
||||
// Default to enabling asset tracking when memory tracking is enabled
|
||||
# define AZ_TRACK_ASSET_SCOPES
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef AZ_TRACK_ASSET_SCOPES
|
||||
#define AZ_ASSET_SCOPE_VARIABLE_NAME(line) AZ_JOIN(_az_assettracking_scope_, line)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Preferred macros to use at the top of a scope you want to to track asset memory for.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Creates a new scope with a name, usually the name of an asset being loaded. (This may be a format-string, e.g. "Foo: %s", bar.c_str())
|
||||
# define AZ_ASSET_NAMED_SCOPE(...) AZ::Debug::AssetTracking::Scope AZ_ASSET_SCOPE_VARIABLE_NAME(__LINE__) (AZ::Debug::AssetTracking::Scope::ScopeFromAssetId(__FILE__, __LINE__, __VA_ARGS__))
|
||||
|
||||
// Attempts to enter an existing scope that already owns some other allocation.
|
||||
# define AZ_ASSET_ATTACH_TO_SCOPE(other) AZ::Debug::AssetTracking::Scope AZ_ASSET_SCOPE_VARIABLE_NAME(__LINE__) (AZ::Debug::AssetTracking::Scope::ScopeFromAttachment((other), __FILE__, __LINE__))
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Optional macros to manually enter and exit a scope.
|
||||
// It is the responsibility of the user to make sure every call to AZ_ASSET_ENTER_SCOPE_* is matched by a corresponding call to AZ_ASSET_EXIT_SCOPE.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
# define AZ_ASSET_ENTER_SCOPE_BY_ASSET_ID(...) AZ::Debug::AssetTracking::EnterScopeByAssetId(__FILE__, __LINE__, __VA_ARGS__)
|
||||
# define AZ_ASSET_ENTER_SCOPE_BY_ATTACHMENT(other) AZ::Debug::AssetTracking::EnterScopeByAttachment((other), __FILE__, __LINE__)
|
||||
# define AZ_ASSET_EXIT_SCOPE AZ::Debug::AssetTracking::ExitScope()
|
||||
|
||||
#else
|
||||
# define AZ_ASSET_NAMED_SCOPE(...) (void)0
|
||||
# define AZ_ASSET_ATTACH_TO_SCOPE(other) (void)0
|
||||
|
||||
# define AZ_ASSET_ENTER_SCOPE_BY_ASSET_ID(...) (void)0
|
||||
# define AZ_ASSET_ENTER_SCOPE_BY_ATTACHMENT(other) (void)0
|
||||
# define AZ_ASSET_EXIT_SCOPE (void)0
|
||||
|
||||
#endif
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class ReflectContext;
|
||||
|
||||
namespace Debug
|
||||
{
|
||||
class AssetTrackingImpl;
|
||||
class AssetTreeBase;
|
||||
class AssetTreeNodeBase;
|
||||
class AssetAllocationTableBase;
|
||||
|
||||
class AssetTracking
|
||||
{
|
||||
public:
|
||||
AZ_TYPE_INFO(AssetTracking, "{D4335180-09A2-415A-8B50-9B734E7CE1E6}");
|
||||
AZ_CLASS_ALLOCATOR(AssetTracking, OSAllocator, 0);
|
||||
|
||||
// Provide RAII method for entering and exiting scopes.
|
||||
// Generally you will want to use the macros at the top of this file rather than instantiating this object directly.
|
||||
class Scope
|
||||
{
|
||||
public:
|
||||
static Scope ScopeFromAssetId(const char* file, int line, const char* fmt, ...);
|
||||
static Scope ScopeFromAttachment(void* attachTo, const char* file, int line);
|
||||
|
||||
Scope(Scope&&) = default;
|
||||
~Scope();
|
||||
|
||||
private:
|
||||
Scope();
|
||||
};
|
||||
|
||||
// Generally you will want to use the macros at the top of this file rather than calling these functions directly.
|
||||
static void EnterScopeByAssetId(const char* file, int line, const char* fmt, ...);
|
||||
static void EnterScopeByAttachment(void* attachTo, const char* file, int line);
|
||||
static void ExitScope();
|
||||
static const char* GetDebugScope();
|
||||
|
||||
AssetTracking(AssetTreeBase* assetTree, AssetAllocationTableBase* allocationTable);
|
||||
~AssetTracking();
|
||||
|
||||
AssetTreeNodeBase* GetCurrentThreadAsset() const;
|
||||
|
||||
private:
|
||||
AZStd::unique_ptr<AssetTrackingImpl> m_impl;
|
||||
};
|
||||
|
||||
// An EBus processing policy that attempts to attach to an existing scope before calling a handler.
|
||||
//
|
||||
// Use this on EBuses where you want the callees to track asset memory during their event handlers.
|
||||
// This will work so long as the callees were themselves allocated inside an existing asset scope.
|
||||
//
|
||||
// May be added to an existing EBus with the following code:
|
||||
// using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy;
|
||||
//
|
||||
template<typename Parent = EBusEventProcessingPolicy>
|
||||
struct AssetTrackingEventProcessingPolicy
|
||||
{
|
||||
template<class Results, class Function, class Interface, class... InputArgs>
|
||||
static void CallResult(Results& results, Function&& func, Interface&& iface, InputArgs&&... args)
|
||||
{
|
||||
AZ_ASSET_ATTACH_TO_SCOPE(iface);
|
||||
Parent::CallResult(results, AZStd::forward<Function>(func), AZStd::forward<Interface>(iface), AZStd::forward<InputArgs>(args)...);
|
||||
}
|
||||
|
||||
template<class Function, class Interface, class... InputArgs>
|
||||
static void Call(Function&& func, Interface&& iface, InputArgs&&... args)
|
||||
{
|
||||
AZ_ASSET_ATTACH_TO_SCOPE(iface);
|
||||
Parent::Call(AZStd::forward<Function>(func), AZStd::forward<Interface>(iface), AZStd::forward<InputArgs>(args)...);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace AzFramework
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Memory/HphaSchema.h>
|
||||
#include <AzCore/Memory/SimpleSchemaAllocator.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
struct AssetTrackingId;
|
||||
}
|
||||
}
|
||||
|
||||
namespace AZStd
|
||||
{
|
||||
// Declare hash specializations for types that need them; implementations will have to come after the classes are fully defined
|
||||
template<>
|
||||
struct hash<AZ::Debug::AssetTrackingId>
|
||||
{
|
||||
size_t operator()(const AZ::Debug::AssetTrackingId& id) const;
|
||||
};
|
||||
}
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
class AssetTrackingImpl;
|
||||
|
||||
// Custom allocator for the Analyzer that doesn't go through profiling tools and cannot be overridden
|
||||
class AssetTrackingAllocator : public AZ::SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false>
|
||||
{
|
||||
public:
|
||||
AZ_TYPE_INFO(AssetTrackingAllocator, "{F6C08E92-559C-4153-9620-6A8491F78F10}");
|
||||
|
||||
using Base = AZ::SimpleSchemaAllocator<AZ::HphaSchema, AZ::HphaSchema::Descriptor, false>;
|
||||
using Descriptor = Base::Descriptor;
|
||||
|
||||
AssetTrackingAllocator()
|
||||
: Base("AssetTrackingAllocator", "Allocator for the AssetTracking")
|
||||
{
|
||||
DisableOverriding();
|
||||
}
|
||||
};
|
||||
|
||||
using AZStdAssetTrackingAllocator = AZ::AZStdAlloc<AssetTrackingAllocator>;
|
||||
using AssetTrackingString = AZStd::basic_string<char, AZStd::char_traits<char>, AZStdAssetTrackingAllocator>;
|
||||
|
||||
template<typename Key, typename MappedType>
|
||||
using AssetTrackingMap = AZStd::unordered_map<Key, MappedType, AZStd::hash<Key>, AZStd::equal_to<Key>, AZStdAssetTrackingAllocator>;
|
||||
|
||||
|
||||
// ID for an asset that is hashable.
|
||||
// Currently only contains one string identifier, but we may want to store a more sophisticated ID in the future.
|
||||
struct AssetTrackingId
|
||||
{
|
||||
AssetTrackingId(const char* id) : m_id(id)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const AssetTrackingId& other) const
|
||||
{
|
||||
return m_id == other.m_id;
|
||||
}
|
||||
|
||||
AssetTrackingString m_id;
|
||||
};
|
||||
|
||||
// Primary information about an asset.
|
||||
// Currently just contains the ID of the asset, but in the future may carry additional information about that asset (such as where in code it was initialized).
|
||||
struct AssetPrimaryInfo
|
||||
{
|
||||
const AssetTrackingId* m_id;
|
||||
};
|
||||
|
||||
// Base class for a node in the asset tree. Implemented by the template AssetTreeNode<>.
|
||||
class AssetTreeNodeBase
|
||||
{
|
||||
public:
|
||||
virtual ~AssetTreeNodeBase() = default;
|
||||
virtual const AssetPrimaryInfo* GetAssetPrimaryInfo() const = 0;
|
||||
virtual AssetTreeNodeBase* FindOrAddChild(const AssetTrackingId& id, const AssetPrimaryInfo* info) = 0;
|
||||
};
|
||||
|
||||
// Base class for an asset tree. Implemented by the template AssetTree<>.
|
||||
class AssetTreeBase
|
||||
{
|
||||
public:
|
||||
virtual ~AssetTreeBase() = default;
|
||||
virtual AssetTreeNodeBase& GetRoot() = 0;
|
||||
};
|
||||
|
||||
// Base class for an asset allocation table. Implemented by the template AssetAllocationTable<>.
|
||||
class AssetAllocationTableBase
|
||||
{
|
||||
public:
|
||||
virtual ~AssetAllocationTableBase() = default;
|
||||
virtual AssetTreeNodeBase* FindAllocation(void* ptr) const = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Hash functions for map support
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
inline size_t AZStd::hash<AZ::Debug::AssetTrackingId>::operator()(const AZ::Debug::AssetTrackingId& info) const
|
||||
{
|
||||
return AZStd::hash<AZ::Debug::AssetTrackingString>()(info.m_id);
|
||||
}
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Debug/AssetTrackingTypes.h>
|
||||
|
||||
#include <AzCore/std/containers/map.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
// A node in the current asset state tree.
|
||||
// Each thread maintains a stack of currently in-scope assets. As this stack changes the asset tree forms.
|
||||
// The same asset may appear in multiple places in the tree, e.g. if asset A is a common asset loaded by both asset B and asset C, the tree may look like:
|
||||
// Root -> B -> A
|
||||
// \--> C -> A
|
||||
template<typename AssetDataT>
|
||||
class AssetTreeNode : public AssetTreeNodeBase
|
||||
{
|
||||
public:
|
||||
AssetTreeNode(const AssetPrimaryInfo* primaryInfo = nullptr, AssetTreeNode* parent = nullptr) :
|
||||
m_primaryinfo(primaryInfo),
|
||||
m_parent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
~AssetTreeNode() override = default;
|
||||
|
||||
const AssetPrimaryInfo* GetAssetPrimaryInfo() const override
|
||||
{
|
||||
return m_primaryinfo;
|
||||
}
|
||||
|
||||
AssetTreeNodeBase* FindOrAddChild(const AssetTrackingId& id, const AssetPrimaryInfo* info) override
|
||||
{
|
||||
AssetTreeNodeBase* result = nullptr;
|
||||
auto childItr = m_children.find(id);
|
||||
|
||||
if (childItr != m_children.end())
|
||||
{
|
||||
result = &childItr->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto childResult = m_children.emplace(id, AssetTreeNode(info, this));
|
||||
result = &childResult.first->second;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
using AssetMap = AssetTrackingMap<AssetTrackingId, AssetTreeNode>;
|
||||
|
||||
const AssetPrimaryInfo* m_primaryinfo;
|
||||
AssetTreeNode* m_parent;
|
||||
AssetMap m_children;
|
||||
AssetDataT m_data;
|
||||
};
|
||||
|
||||
template<typename AssetDataT>
|
||||
class AssetTree : public AssetTreeBase
|
||||
{
|
||||
public:
|
||||
~AssetTree() override = default;
|
||||
|
||||
AssetTreeNodeBase& GetRoot() override
|
||||
{
|
||||
return m_rootAssets;
|
||||
}
|
||||
|
||||
using NodeType = AssetTreeNode<AssetDataT>;
|
||||
|
||||
NodeType m_rootAssets;
|
||||
};
|
||||
|
||||
|
||||
template<typename AllocationDataT>
|
||||
struct AllocationRecord
|
||||
{
|
||||
AssetTreeNodeBase* m_asset;
|
||||
uint32_t m_size;
|
||||
AllocationDataT m_data;
|
||||
};
|
||||
|
||||
|
||||
template<typename AllocationDataT>
|
||||
class AllocationTable : public AssetAllocationTableBase
|
||||
{
|
||||
public:
|
||||
using RecordType = AllocationRecord<AllocationDataT>;
|
||||
using AllocationReverseMap = AZStd::map<void*, RecordType, AZStd::greater<void*>, AZStdAssetTrackingAllocator>;
|
||||
using mutex_type = AZStd::mutex;
|
||||
using lock_type = AZStd::lock_guard<mutex_type>;
|
||||
|
||||
AllocationTable(mutex_type& mutex) : m_mutex(mutex)
|
||||
{
|
||||
}
|
||||
~AllocationTable() override = default;
|
||||
|
||||
AssetTreeNodeBase* FindAllocation(void* ptr) const override
|
||||
{
|
||||
// Note that ptr is not guaranteed to have an exact entry in the map. For instance, ptr may point to a member of the original object that was allocated, or
|
||||
// ptr may be a different "this" pointer in the case of multiple inheritance.
|
||||
//
|
||||
// To solve this, we use lower_bound() and check to see if ptr falls in the range of the nearest allocation. Our map uses AZStd::greater instead of
|
||||
// AZStd::less as its sorting function, and thus sorts largest-to-smallest instead of smallest-to-largest. This causes lower_bound() to return the first
|
||||
// iterator that is not greater than otherAllocation, i.e. less than or equal to ptr.
|
||||
lock_type lock(m_mutex);
|
||||
auto itr = m_allocationTable.lower_bound(ptr);
|
||||
AssetTreeNodeBase* result = nullptr;
|
||||
|
||||
if (itr != m_allocationTable.end())
|
||||
{
|
||||
// Check if otherAllocation is within the size range of the allocation we found
|
||||
if (reinterpret_cast<uintptr_t>(ptr) <= reinterpret_cast<uintptr_t>(itr->first) + itr->second.m_size)
|
||||
{
|
||||
result = itr->second.m_asset;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ReallocateAllocation(void* prevAddress, void* newAddress, size_t newByteSize)
|
||||
{
|
||||
lock_type lock(m_mutex);
|
||||
auto itr = m_allocationTable.find(prevAddress);
|
||||
|
||||
if (itr != m_allocationTable.end())
|
||||
{
|
||||
RecordType newAllocation = itr->second;
|
||||
newAllocation.m_size = (uint32_t)newByteSize;
|
||||
|
||||
m_allocationTable.erase(itr);
|
||||
m_allocationTable.emplace(newAddress, AZStd::move(newAllocation));
|
||||
}
|
||||
}
|
||||
|
||||
void ResizeAllocation(void* address, size_t newSize)
|
||||
{
|
||||
// Resize an existing allocation if we can find it
|
||||
lock_type lock(m_mutex);
|
||||
auto itr = m_allocationTable.find(address);
|
||||
|
||||
if (itr != m_allocationTable.end())
|
||||
{
|
||||
itr->second.m_size = (uint32_t)newSize;
|
||||
}
|
||||
}
|
||||
|
||||
AllocationReverseMap& Get()
|
||||
{
|
||||
return m_allocationTable;
|
||||
}
|
||||
|
||||
const AllocationReverseMap& Get() const
|
||||
{
|
||||
return m_allocationTable;
|
||||
}
|
||||
|
||||
private:
|
||||
AllocationReverseMap m_allocationTable;
|
||||
mutex_type& m_mutex;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AzCore/Debug/EventTraceDrillerBus.h>
|
||||
#include <AzCore/std/time.h>
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
|
||||
namespace AZ::Debug
|
||||
{
|
||||
EventTrace::ScopedSlice::ScopedSlice(const char* name, const char* category)
|
||||
: m_Name(name)
|
||||
, m_Category(category)
|
||||
, m_Time(AZStd::GetTimeNowMicroSecond())
|
||||
{
|
||||
}
|
||||
|
||||
EventTrace::ScopedSlice::~ScopedSlice()
|
||||
{
|
||||
EventTraceDrillerBus::TryQueueBroadcast(
|
||||
&EventTraceDrillerInterface::RecordSlice, m_Name, m_Category, AZStd::this_thread::get_id(), m_Time,
|
||||
(uint32_t)(AZStd::GetTimeNowMicroSecond() - m_Time));
|
||||
}
|
||||
} // namespace AZ::Debug
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/PlatformDef.h>
|
||||
#include <AzCore/Debug/Profiler.h>
|
||||
|
||||
namespace AZStd
|
||||
{
|
||||
struct thread_id;
|
||||
}
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
namespace EventTrace
|
||||
{
|
||||
class ScopedSlice
|
||||
{
|
||||
public:
|
||||
ScopedSlice(const char* name, const char* category);
|
||||
~ScopedSlice();
|
||||
|
||||
private:
|
||||
const char* m_Name;
|
||||
const char* m_Category;
|
||||
u64 m_Time;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define AZ_TRACE_METHOD_NAME_CATEGORY(name, category)
|
||||
#define AZ_TRACE_METHOD_NAME(name) AZ_TRACE_METHOD_NAME_CATEGORY(name, "")
|
||||
#define AZ_TRACE_METHOD() AZ_TRACE_METHOD_NAME(AZ_FUNCTION_SIGNATURE)
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Debug/EventTraceDriller.h>
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AzCore/std/containers/array.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace AZ::Debug
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Main bus mutex guards m_output.
|
||||
auto& context = EventTraceDrillerBus::GetOrCreateContext();
|
||||
|
||||
AZStd::scoped_lock<decltype(context.m_contextMutex), decltype(m_ThreadMutex)> lock(context.m_contextMutex, m_ThreadMutex);
|
||||
for (const auto& keyValue : m_Threads)
|
||||
{
|
||||
m_output->BeginTag(Crc::EventTraceDriller);
|
||||
m_output->BeginTag(Crc::ThreadInfo);
|
||||
m_output->Write(Crc::ThreadId, keyValue.first);
|
||||
m_output->Write(Crc::Name, keyValue.second.name);
|
||||
m_output->EndTag(Crc::ThreadInfo);
|
||||
m_output->EndTag(Crc::EventTraceDriller);
|
||||
}
|
||||
}
|
||||
|
||||
void EventTraceDriller::RecordSlice(
|
||||
const char* name,
|
||||
const char* category,
|
||||
const AZStd::thread_id threadId,
|
||||
AZ::u64 timestamp,
|
||||
AZ::u32 duration)
|
||||
{
|
||||
m_output->BeginTag(Crc::EventTraceDriller);
|
||||
m_output->BeginTag(Crc::Slice);
|
||||
m_output->Write(Crc::Name, name);
|
||||
m_output->Write(Crc::Category, category);
|
||||
m_output->Write(Crc::ThreadId, (size_t)threadId.m_id);
|
||||
m_output->Write(Crc::Timestamp, timestamp);
|
||||
m_output->Write(Crc::Duration, std::max(duration, 1u));
|
||||
m_output->EndTag(Crc::Slice);
|
||||
m_output->EndTag(Crc::EventTraceDriller);
|
||||
}
|
||||
|
||||
void EventTraceDriller::RecordInstantGlobal(
|
||||
const char* name,
|
||||
const char* category,
|
||||
AZ::u64 timestamp)
|
||||
{
|
||||
m_output->BeginTag(Crc::EventTraceDriller);
|
||||
m_output->BeginTag(Crc::Instant);
|
||||
m_output->Write(Crc::Name, name);
|
||||
m_output->Write(Crc::Category, category);
|
||||
m_output->Write(Crc::Timestamp, timestamp);
|
||||
m_output->EndTag(Crc::Instant);
|
||||
m_output->EndTag(Crc::EventTraceDriller);
|
||||
}
|
||||
|
||||
void EventTraceDriller::RecordInstantThread(
|
||||
const char* name,
|
||||
const char* category,
|
||||
const AZStd::thread_id threadId,
|
||||
AZ::u64 timestamp)
|
||||
{
|
||||
m_output->BeginTag(Crc::EventTraceDriller);
|
||||
m_output->BeginTag(Crc::Instant);
|
||||
m_output->Write(Crc::Name, name);
|
||||
m_output->Write(Crc::Category, category);
|
||||
m_output->Write(Crc::ThreadId, (size_t)threadId.m_id);
|
||||
m_output->Write(Crc::Timestamp, timestamp);
|
||||
m_output->EndTag(Crc::Instant);
|
||||
m_output->EndTag(Crc::EventTraceDriller);
|
||||
}
|
||||
} // namespace AZ::Debug
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Driller/Driller.h>
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
#include <AzCore/std/parallel/threadbus.h>
|
||||
#include <AzCore/std/containers/array.h>
|
||||
#include <AzCore/Debug/EventTraceDrillerBus.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
class EventTraceDriller
|
||||
: public Driller
|
||||
, public EventTraceDrillerBus::Handler
|
||||
, public EventTraceDrillerSetupBus::Handler
|
||||
, public AZStd::ThreadDrillerEventBus::Handler
|
||||
, public AZ::TickBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(EventTraceDriller, OSAllocator, 0)
|
||||
|
||||
EventTraceDriller();
|
||||
virtual ~EventTraceDriller();
|
||||
|
||||
private:
|
||||
// Driller
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
const char* GroupName() const override { return "SystemDrillers"; }
|
||||
const char* GetName() const override { return "EventTraceDriller"; }
|
||||
const char* GetDescription() const override { return "Handles timed events for a Chrome Tracing."; }
|
||||
void Start(const Param* params = NULL, int numParams = 0) override;
|
||||
void Stop() override;
|
||||
|
||||
// ThreadBus
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void OnThreadEnter(const AZStd::thread::id& id, const AZStd::thread_desc* desc) override;
|
||||
void OnThreadExit(const AZStd::thread::id& id) override;
|
||||
|
||||
// TickBus
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void OnTick(float deltaTime, ScriptTimePoint time) override;
|
||||
|
||||
// EventTraceDrillerSetupBus
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void SetThreadName(const AZStd::thread_id& threadId, const char* name) override;
|
||||
|
||||
// EventTraceDrillerBus
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void RecordSlice(
|
||||
const char* name,
|
||||
const char* category,
|
||||
const AZStd::thread_id threadId,
|
||||
AZ::u64 timestamp,
|
||||
AZ::u32 duration) override;
|
||||
|
||||
void RecordInstantGlobal(
|
||||
const char* name,
|
||||
const char* category,
|
||||
AZ::u64 timestamp) override;
|
||||
|
||||
void RecordInstantThread(
|
||||
const char* name,
|
||||
const char* category,
|
||||
const AZStd::thread_id threadId,
|
||||
AZ::u64 timestamp) override;
|
||||
|
||||
void RecordThreads();
|
||||
|
||||
struct ThreadData
|
||||
{
|
||||
AZStd::string name;
|
||||
};
|
||||
|
||||
AZStd::recursive_mutex m_ThreadMutex;
|
||||
AZStd::unordered_map<size_t, ThreadData, AZStd::hash<size_t>, AZStd::equal_to<size_t>, OSStdAllocator> m_Threads;
|
||||
};
|
||||
}
|
||||
} // namespace AZ
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Driller/DrillerBus.h>
|
||||
#include <AzCore/std/time.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace AZStd
|
||||
{
|
||||
struct thread_id;
|
||||
}
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
class EventTraceDrillerInterface
|
||||
: public DrillerEBusTraits
|
||||
{
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const bool EnableEventQueue = true;
|
||||
static const bool EventQueueingActiveByDefault = false;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
virtual ~EventTraceDrillerInterface() {}
|
||||
|
||||
virtual void RecordSlice(
|
||||
const char* name,
|
||||
const char* category,
|
||||
const AZStd::thread_id threadId,
|
||||
AZ::u64 timestamp,
|
||||
AZ::u32 duration) = 0;
|
||||
|
||||
virtual void RecordInstantThread(
|
||||
const char* name,
|
||||
const char* category,
|
||||
const AZStd::thread_id threadId,
|
||||
AZ::u64 timestamp) = 0;
|
||||
|
||||
virtual void RecordInstantGlobal(
|
||||
const char* name,
|
||||
const char* category,
|
||||
AZ::u64 timestamp) = 0;
|
||||
};
|
||||
|
||||
typedef AZ::EBus<EventTraceDrillerInterface> EventTraceDrillerBus;
|
||||
|
||||
class EventTraceDrillerSetupInterface
|
||||
: public DrillerEBusTraits
|
||||
{
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
virtual ~EventTraceDrillerSetupInterface() {}
|
||||
|
||||
virtual void SetThreadName(const AZStd::thread_id& threadId, const char* name) = 0;
|
||||
};
|
||||
|
||||
typedef AZ::EBus<EventTraceDrillerSetupInterface> EventTraceDrillerSetupBus;
|
||||
}
|
||||
}
|
||||
|
||||
#define AZ_TRACE_INSTANT_GLOBAL_CATEGORY(name, category) \
|
||||
EBUS_QUEUE_EVENT(AZ::Debug::EventTraceDrillerBus, RecordInstantGlobal, name, category, AZStd::GetTimeNowMicroSecond())
|
||||
#define AZ_TRACE_INSTANT_GLOBAL(name) AZ_TRACE_INSTANT_GLOBAL_CATEGORY(name, "")
|
||||
|
||||
#define AZ_TRACE_INSTANT_THREAD_CATEGORY(name, category) \
|
||||
EBUS_QUEUE_EVENT(AZ::Debug::EventTraceDrillerBus, RecordInstantThread, name, category, AZStd::this_thread::get_id(), AZStd::GetTimeNowMicroSecond())
|
||||
#define AZ_TRACE_INSTANT_THREAD(name) AZ_TRACE_INSTANT_THREAD_CATEGORY(name, "")
|
||||
@@ -8,7 +8,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef AZ_PROFILE_MEMORY_ALLOC
|
||||
// No other profiler has defined the performance markers AZ_PROFILE_MEMORY_ALLOC (and friends), fall back to a Driller implementation (currently empty)
|
||||
// No other profiler has defined the performance markers AZ_PROFILE_MEMORY_ALLOC (and friends), fall back to current implementation (empty)
|
||||
# define AZ_PROFILE_MEMORY_ALLOC(category, address, size, context)
|
||||
# define AZ_PROFILE_MEMORY_ALLOC_EX(category, filename, lineNumber, address, size, context)
|
||||
# define AZ_PROFILE_MEMORY_FREE(category, address)
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include <AzCore/Debug/StackTracer.h>
|
||||
#include <AzCore/Debug/TraceMessageBus.h>
|
||||
#include <AzCore/Debug/TraceMessagesDrillerBus.h>
|
||||
#include <AzCore/Debug/IEventLogger.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
|
||||
@@ -276,8 +275,6 @@ namespace AZ::Debug
|
||||
logger->Flush(); // Flush as an assert may indicate a crash is imminent.
|
||||
}
|
||||
|
||||
EBUS_EVENT(TraceMessageDrillerBus, OnPreAssert, fileName, line, funcName, message);
|
||||
|
||||
TraceMessageResult result;
|
||||
EBUS_EVENT_RESULT(result, TraceMessageBus, OnPreAssert, fileName, line, funcName, message);
|
||||
|
||||
@@ -302,7 +299,6 @@ namespace AZ::Debug
|
||||
azstrcat(message, g_maxMessageLength, "\n");
|
||||
Output(g_dbgSystemWnd, message);
|
||||
|
||||
EBUS_EVENT(TraceMessageDrillerBus, OnAssert, message);
|
||||
EBUS_EVENT_RESULT(result, TraceMessageBus, OnAssert, message);
|
||||
if (result.m_value)
|
||||
{
|
||||
@@ -405,8 +401,6 @@ namespace AZ::Debug
|
||||
logger->RecordStringEvent(ErrorEventId, message);
|
||||
}
|
||||
|
||||
EBUS_EVENT(TraceMessageDrillerBus, OnPreError, window, fileName, line, funcName, message);
|
||||
|
||||
TraceMessageResult result;
|
||||
EBUS_EVENT_RESULT(result, TraceMessageBus, OnPreError, window, fileName, line, funcName, message);
|
||||
if (result.m_value)
|
||||
@@ -421,7 +415,6 @@ namespace AZ::Debug
|
||||
azstrcat(message, g_maxMessageLength, "\n");
|
||||
Output(window, message);
|
||||
|
||||
EBUS_EVENT(TraceMessageDrillerBus, OnError, window, message);
|
||||
EBUS_EVENT_RESULT(result, TraceMessageBus, OnError, window, message);
|
||||
Output(window, "==================================================================\n");
|
||||
if (result.m_value)
|
||||
@@ -457,8 +450,6 @@ namespace AZ::Debug
|
||||
logger->RecordStringEvent(WarningEventId, message);
|
||||
}
|
||||
|
||||
EBUS_EVENT(TraceMessageDrillerBus, OnPreWarning, window, fileName, line, funcName, message);
|
||||
|
||||
TraceMessageResult result;
|
||||
EBUS_EVENT_RESULT(result, TraceMessageBus, OnPreWarning, window, fileName, line, funcName, message);
|
||||
if (result.m_value)
|
||||
@@ -472,7 +463,6 @@ namespace AZ::Debug
|
||||
azstrcat(message, g_maxMessageLength, "\n");
|
||||
Output(window, message);
|
||||
|
||||
EBUS_EVENT(TraceMessageDrillerBus, OnWarning, window, message);
|
||||
EBUS_EVENT_RESULT(result, TraceMessageBus, OnWarning, window, message);
|
||||
Output(window, "==================================================================\n");
|
||||
}
|
||||
@@ -501,8 +491,6 @@ namespace AZ::Debug
|
||||
logger->RecordStringEvent(PrintfEventId, message);
|
||||
}
|
||||
|
||||
EBUS_EVENT(TraceMessageDrillerBus, OnPrintf, window, message);
|
||||
|
||||
TraceMessageResult result;
|
||||
EBUS_EVENT_RESULT(result, TraceMessageBus, OnPrintf, window, message);
|
||||
if (result.m_value)
|
||||
@@ -531,7 +519,6 @@ namespace AZ::Debug
|
||||
// only call into Ebusses if we are not in a recursive-exception situation as that
|
||||
// would likely just lead to even more exceptions.
|
||||
|
||||
EBUS_EVENT(TraceMessageDrillerBus, OnOutput, window, message);
|
||||
TraceMessageResult result;
|
||||
EBUS_EVENT_RESULT(result, TraceMessageBus, OnOutput, window, message);
|
||||
if (result.m_value)
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Debug/TraceMessagesDriller.h>
|
||||
#include <AzCore/Math/Crc.h>
|
||||
|
||||
namespace AZ::Debug
|
||||
{
|
||||
//=========================================================================
|
||||
// Start
|
||||
// [2/6/2013]
|
||||
//=========================================================================
|
||||
void TraceMessagesDriller::Start(const Param* params, int numParams)
|
||||
{
|
||||
(void)params;
|
||||
(void)numParams;
|
||||
BusConnect();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Stop
|
||||
// [2/6/2013]
|
||||
//=========================================================================
|
||||
void TraceMessagesDriller::Stop()
|
||||
{
|
||||
BusDisconnect();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// OnAssert
|
||||
// [2/6/2013]
|
||||
//=========================================================================
|
||||
void TraceMessagesDriller::OnAssert(const char* message)
|
||||
{
|
||||
// Not sure if we can really capture assert since the code will stop executing very soon.
|
||||
m_output->BeginTag(AZ_CRC_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_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_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_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_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
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Driller/Driller.h>
|
||||
#include <AzCore/Debug/TraceMessagesDrillerBus.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
/**
|
||||
* Trace messages driller class
|
||||
*/
|
||||
class TraceMessagesDriller
|
||||
: public Driller
|
||||
, public TraceMessageDrillerBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(TraceMessagesDriller, OSAllocator, 0)
|
||||
|
||||
protected:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Driller
|
||||
const char* GroupName() const override { return "SystemDrillers"; }
|
||||
const char* GetName() const override { return "TraceMessagesDriller"; }
|
||||
const char* GetDescription() const override { return "Handles all system messages like Assert, Exception, Error, Warning, Printf, etc."; }
|
||||
void Start(const Param* params = NULL, int numParams = 0) override;
|
||||
void Stop() override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TraceMessagesDrillerBus
|
||||
/// Triggered when a AZ_Assert failed. This is terminating event! (the code will break, crash).
|
||||
void OnAssert(const char* message) override;
|
||||
void OnException(const char* message) override;
|
||||
void OnError(const char* window, const char* message) override;
|
||||
void OnWarning(const char* window, const char* message) override;
|
||||
void OnPrintf(const char* window, const char* message) override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
};
|
||||
} // namespace Debug
|
||||
} // namespace AZ
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Driller/DrillerBus.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
/**
|
||||
* Trace messages event handle.
|
||||
* All messages are optional (they have default implementation) and you can handle only one at a time.
|
||||
* Driller messages are similar to TraceMessages, but do not provide a return value,
|
||||
* as we only care about collecting driller messages, not operating on them.
|
||||
*
|
||||
* We use a driller bus so all messages are sending in exclusive matter no other driller messages
|
||||
* can be triggered at that moment, so we already preserve the calling order. You can assume
|
||||
* all access code in the driller framework in guarded. You can manually lock the driller mutex are you
|
||||
* use by using \ref AZ::Debug::DrillerEBusMutex.
|
||||
*/
|
||||
class TraceMessageDrillerEvents
|
||||
: public DrillerEBusTraits
|
||||
{
|
||||
public:
|
||||
virtual ~TraceMessageDrillerEvents() {}
|
||||
|
||||
/// Triggered when a AZ_Assert failed. This is terminating event! (the code will break, crash).
|
||||
virtual void OnPreAssert(const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) {}
|
||||
virtual void OnAssert(const char* /*message*/) {}
|
||||
virtual void OnException(const char* /*message*/) {}
|
||||
virtual void OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) {}
|
||||
virtual void OnError(const char* /*window*/, const char* /*message*/) {}
|
||||
virtual void OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) {}
|
||||
virtual void OnWarning(const char* /*window*/, const char* /*message*/) {}
|
||||
virtual void OnPrintf(const char* /*window*/, const char* /*message*/) {}
|
||||
/**
|
||||
* All trace functions you output to anything. So if you want to handle all the output this is the place.
|
||||
* You are not given the choice to disable the system output as if you listen at that level you can't make
|
||||
* that decision. Otherwise we can trigger an assert without even one line of message send to the console/debugger.
|
||||
*/
|
||||
virtual void OnOutput(const char* /*window*/, const char* /*message*/) {}
|
||||
};
|
||||
|
||||
typedef AZ::EBus<TraceMessageDrillerEvents> TraceMessageDrillerBus;
|
||||
} // namespace Debug
|
||||
} // namespace AZ
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_DRILLER_DEFAULT_STRING_POOL_H
|
||||
#define AZCORE_DRILLER_DEFAULT_STRING_POOL_H
|
||||
|
||||
#include <AzCore/Driller/Stream.h>
|
||||
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
template<class Key, class Mapped>
|
||||
struct unordered_map
|
||||
{
|
||||
typedef AZStd::unordered_map<Key, Mapped, AZStd::hash<Key>, AZStd::equal_to<Key>, OSStdAllocator> type;
|
||||
};
|
||||
|
||||
template<class Key>
|
||||
struct unordered_set
|
||||
{
|
||||
typedef AZStd::unordered_set<Key, AZStd::hash<Key>, AZStd::equal_to<Key>, OSStdAllocator> type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default implementation of a string pool.
|
||||
*/
|
||||
class DrillerDefaultStringPool
|
||||
: public DrillerStringPool
|
||||
{
|
||||
public:
|
||||
virtual ~DrillerDefaultStringPool()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
typedef unordered_map<AZ::u32, const char*>::type CrcToStringMapType;
|
||||
typedef unordered_set<const char*>::type OwnedStringsMapType;
|
||||
|
||||
/**
|
||||
* Add a copy of the string to the pool. If we return true the string was added otherwise it was already in the bool.
|
||||
* In both cases the crc32 of the string and the pointer to the shared copy is returned (optional for poolStringAddress)!
|
||||
*/
|
||||
virtual bool InsertCopy(const char* string, unsigned int length, AZ::u32& crc32, const char** poolStringAddress = nullptr)
|
||||
{
|
||||
crc32 = AZ::Crc32(string, length);
|
||||
CrcToStringMapType::pair_iter_bool insertIt = m_crcToStringMap.insert_key(crc32);
|
||||
if (insertIt.second)
|
||||
{
|
||||
char* newString = reinterpret_cast<char*>(azmalloc(length + 1, 1, AZ::OSAllocator));
|
||||
memcpy(newString, string, length);
|
||||
newString[length] = '\0'; // terminate
|
||||
m_ownedStrings.insert(newString);
|
||||
insertIt.first->second = newString;
|
||||
}
|
||||
if (poolStringAddress)
|
||||
{
|
||||
*poolStringAddress = insertIt.first->second;
|
||||
}
|
||||
return insertIt.second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as the InsertCopy above without actually coping the string into the pool. The pool assumes that
|
||||
* none of the strings added to the pool will be deleted.
|
||||
*/
|
||||
virtual bool Insert(const char* string, unsigned int length, AZ::u32& crc32)
|
||||
{
|
||||
crc32 = AZ::Crc32(string, length);
|
||||
return m_crcToStringMap.insert(AZStd::make_pair(crc32, string)).second;
|
||||
}
|
||||
|
||||
/// Finds a string in the pool by crc32.
|
||||
virtual const char* Find(AZ::u32 crc32)
|
||||
{
|
||||
CrcToStringMapType::iterator it = m_crcToStringMap.find(crc32);
|
||||
if (it != m_crcToStringMap.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
virtual void Erase(AZ::u32 crc32)
|
||||
{
|
||||
CrcToStringMapType::iterator it = m_crcToStringMap.find(crc32);
|
||||
if (it != m_crcToStringMap.end())
|
||||
{
|
||||
OwnedStringsMapType::iterator ownerIt = m_ownedStrings.find(it->second);
|
||||
if (ownerIt != m_ownedStrings.end())
|
||||
{
|
||||
azfree(const_cast<char*>(it->second), AZ::OSAllocator);
|
||||
m_ownedStrings.erase(ownerIt);
|
||||
}
|
||||
m_crcToStringMap.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Reset()
|
||||
{
|
||||
for (OwnedStringsMapType::iterator it = m_ownedStrings.begin(); it != m_ownedStrings.end(); ++it)
|
||||
{
|
||||
azfree(const_cast<char*>(*it), AZ::OSAllocator);
|
||||
}
|
||||
m_crcToStringMap.clear();
|
||||
m_ownedStrings.clear();
|
||||
}
|
||||
protected:
|
||||
CrcToStringMapType m_crcToStringMap;
|
||||
OwnedStringsMapType m_ownedStrings;
|
||||
};
|
||||
} // namespace Debug
|
||||
} // namespace AZ
|
||||
|
||||
#endif // AZCORE_DRILLER_DEFAULT_STRING_POOL_H
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,301 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Driller/Driller.h>
|
||||
#include <AzCore/Driller/DrillerBus.h>
|
||||
|
||||
#include <AzCore/std/parallel/mutex.h>
|
||||
#include <AzCore/std/parallel/lock.h>
|
||||
#include <AzCore/Math/Crc.h>
|
||||
|
||||
|
||||
namespace AZ::Debug
|
||||
{
|
||||
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)
|
||||
{
|
||||
AZ::AllocatorInstance<OSAllocator>::Create();
|
||||
}
|
||||
|
||||
DrillerManagerImpl* impl = aznew DrillerManagerImpl;
|
||||
impl->m_ownsOSAllocator = createAllocator;
|
||||
return impl;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Register
|
||||
// [3/17/2011]
|
||||
//=========================================================================
|
||||
void DrillerManager::Destroy(DrillerManager* manager)
|
||||
{
|
||||
const bool allocatorCreated = manager->m_ownsOSAllocator;
|
||||
delete manager;
|
||||
if (allocatorCreated)
|
||||
{
|
||||
AZ::AllocatorInstance<OSAllocator>::Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// DrillerManagerImpl
|
||||
|
||||
//=========================================================================
|
||||
// ~DrillerManagerImpl
|
||||
// [3/17/2011]
|
||||
//=========================================================================
|
||||
DrillerManagerImpl::~DrillerManagerImpl()
|
||||
{
|
||||
while (!m_sessions.empty())
|
||||
{
|
||||
Stop(&m_sessions.front());
|
||||
}
|
||||
|
||||
while (!m_drillers.empty())
|
||||
{
|
||||
Driller* driller = m_drillers[0];
|
||||
Unregister(driller);
|
||||
delete driller;
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Register
|
||||
// [3/17/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
DrillerManagerImpl::Register(Driller* driller)
|
||||
{
|
||||
AZ_Assert(driller, "You must provide a valid factory!");
|
||||
for (size_t i = 0; i < m_drillers.size(); ++i)
|
||||
{
|
||||
if (m_drillers[i]->GetId() == driller->GetId())
|
||||
{
|
||||
AZ_Error("Debug", false, "Driller with id %08x has already been registered! You can't have two factory instances for the same driller type", driller->GetId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_drillers.push_back(driller);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Unregister
|
||||
// [3/17/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
DrillerManagerImpl::Unregister(Driller* driller)
|
||||
{
|
||||
AZ_Assert(driller, "You must provide a valid factory!");
|
||||
for (DrillerArrayType::iterator iter = m_drillers.begin(); iter != m_drillers.end(); ++iter)
|
||||
{
|
||||
if ((*iter)->GetId() == driller->GetId())
|
||||
{
|
||||
m_drillers.erase(iter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AZ_Error("Debug", false, "Failed to find driller factory with id %08x", driller->GetId());
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// FrameUpdate
|
||||
// [3/17/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
DrillerManagerImpl::FrameUpdate()
|
||||
{
|
||||
if (m_sessions.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AZStd::lock_guard<DrillerEBusMutex::MutexType> lock(DrillerEBusMutex::GetMutex()); ///< Make sure no driller is writing to the stream
|
||||
for (SessionListType::iterator sessionIter = m_sessions.begin(); sessionIter != m_sessions.end(); )
|
||||
{
|
||||
DrillerSession& s = *sessionIter;
|
||||
|
||||
// tick the drillers directly if they care.
|
||||
for (size_t i = 0; i < s.drillers.size(); ++i)
|
||||
{
|
||||
s.drillers[i]->Update();
|
||||
}
|
||||
|
||||
s.output->EndTag(AZ_CRC("Frame", 0xb5f83ccd));
|
||||
|
||||
s.output->OnEndOfFrame();
|
||||
|
||||
s.curFrame++;
|
||||
|
||||
if (s.numFrames != -1)
|
||||
{
|
||||
if (s.curFrame == s.numFrames)
|
||||
{
|
||||
Stop(&s);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
s.output->BeginTag(AZ_CRC("Frame", 0xb5f83ccd));
|
||||
s.output->Write(AZ_CRC("FrameNum", 0x85a1a919), s.curFrame);
|
||||
|
||||
++sessionIter;
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Start
|
||||
// [3/17/2011]
|
||||
//=========================================================================
|
||||
DrillerSession*
|
||||
DrillerManagerImpl::Start(DrillerOutputStream& output, const DrillerListType& drillerList, int numFrames)
|
||||
{
|
||||
if (drillerList.empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_sessions.push_back();
|
||||
DrillerSession& s = m_sessions.back();
|
||||
s.curFrame = 0;
|
||||
s.numFrames = numFrames;
|
||||
s.output = &output;
|
||||
|
||||
s.output->WriteHeader(); // first write the header in the stream
|
||||
|
||||
s.output->BeginTag(AZ_CRC("StartData", 0xecf3f53f));
|
||||
s.output->Write(AZ_CRC("Platform", 0x3952d0cb), (unsigned int)g_currentPlatform);
|
||||
for (DrillerListType::const_iterator iDriller = drillerList.begin(); iDriller != drillerList.end(); ++iDriller)
|
||||
{
|
||||
const DrillerInfo& di = *iDriller;
|
||||
s.output->BeginTag(AZ_CRC("Driller", 0xa6e1fb73));
|
||||
s.output->Write(AZ_CRC("Name", 0x5e237e06), di.id);
|
||||
for (int iParam = 0; iParam < (int)di.params.size(); ++iParam)
|
||||
{
|
||||
s.output->BeginTag(AZ_CRC("Param", 0xa4fa7c89));
|
||||
s.output->Write(AZ_CRC("Name", 0x5e237e06), di.params[iParam].name);
|
||||
s.output->Write(AZ_CRC("Description", 0x6de44026), di.params[iParam].desc);
|
||||
s.output->Write(AZ_CRC("Type", 0x8cde5729), di.params[iParam].type);
|
||||
s.output->Write(AZ_CRC("Value", 0x1d775834), di.params[iParam].value);
|
||||
s.output->EndTag(AZ_CRC("Param", 0xa4fa7c89));
|
||||
}
|
||||
s.output->EndTag(AZ_CRC("Driller", 0xa6e1fb73));
|
||||
}
|
||||
s.output->EndTag(AZ_CRC("StartData", 0xecf3f53f));
|
||||
|
||||
s.output->BeginTag(AZ_CRC("Frame", 0xb5f83ccd));
|
||||
s.output->Write(AZ_CRC("FrameNum", 0x85a1a919), s.curFrame);
|
||||
|
||||
{
|
||||
AZStd::lock_guard<DrillerEBusMutex::MutexType> lock(DrillerEBusMutex::GetMutex()); ///< Make sure no driller is writing to the stream
|
||||
for (DrillerListType::const_iterator iDriller = drillerList.begin(); iDriller != drillerList.end(); ++iDriller)
|
||||
{
|
||||
Driller* driller = nullptr;
|
||||
const DrillerInfo& di = *iDriller;
|
||||
for (size_t iDesc = 0; iDesc < m_drillers.size(); ++iDesc)
|
||||
{
|
||||
if (m_drillers[iDesc]->GetId() == di.id)
|
||||
{
|
||||
driller = m_drillers[iDesc];
|
||||
AZ_Assert(driller->m_output == nullptr, "Driller with id %08x is already have an output stream %p (currently we support only 1 at a time)", di.id, driller->m_output);
|
||||
driller->m_output = &output;
|
||||
driller->Start(di.params.data(), static_cast<unsigned int>(di.params.size()));
|
||||
s.drillers.push_back(driller);
|
||||
break;
|
||||
}
|
||||
}
|
||||
AZ_Warning("Driller", driller != nullptr, "We can't start a driller with id %d!", di.id);
|
||||
}
|
||||
}
|
||||
return &s;
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// Stop
|
||||
// [3/17/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
DrillerManagerImpl::Stop(DrillerSession* session)
|
||||
{
|
||||
SessionListType::iterator iter;
|
||||
for (iter = m_sessions.begin(); iter != m_sessions.end(); ++iter)
|
||||
{
|
||||
if (&*iter == session)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AZ_Assert(iter != m_sessions.end(), "We did not find session ID 0x%08x in the list!", session);
|
||||
if (iter != m_sessions.end())
|
||||
{
|
||||
DrillerSession& s = *session;
|
||||
|
||||
{
|
||||
AZStd::lock_guard<DrillerEBusMutex::MutexType> lock(DrillerEBusMutex::GetMutex());
|
||||
for (size_t i = 0; i < s.drillers.size(); ++i)
|
||||
{
|
||||
s.drillers[i]->Stop();
|
||||
s.drillers[i]->m_output = nullptr;
|
||||
}
|
||||
}
|
||||
s.output->EndTag(AZ_CRC("Frame", 0xb5f83ccd));
|
||||
m_sessions.erase(iter);
|
||||
}
|
||||
}
|
||||
} // namespace AZ::Debug
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_DRILLER_H
|
||||
#define AZCORE_DRILLER_H
|
||||
|
||||
#include <AzCore/Driller/Stream.h>
|
||||
namespace AZStd
|
||||
{
|
||||
class mutex;
|
||||
}
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
class DrillerOutputStream;
|
||||
|
||||
/**
|
||||
* Driller base class. Every driller should inherit from this class.
|
||||
* When a driller is need to start outputting data
|
||||
* the DrillerManager will call Driller::Start() so the driller
|
||||
* can output the initial state for all reported entities.
|
||||
* The same applies for the Stop.
|
||||
* Depending on the type of your driller you might choose to collect state
|
||||
* even before the driller has started. This of course should be a fast as
|
||||
* possible, as we don't want to burden engine systems and it's highly recommended
|
||||
* that you use configuration parameters to change that behavior as not all drillers
|
||||
* are used on a daily basis.
|
||||
* All drillers should use DebugAllocators (AZ_CLASS_ALLOCATOR(Driller,OSAllocator,0))
|
||||
* and they should use 'aznew' to create one, as by default if you don't unregister a
|
||||
* a driller, the manager will use "delete" to delete them.
|
||||
*
|
||||
* IMPORTANT: Driller systems works OUTSIDE engine systems, you should NOT use SystemAllocator or any other engine systems
|
||||
* as they might be drilled or not available at the moment.
|
||||
*/
|
||||
class Driller
|
||||
{
|
||||
friend class DrillerManagerImpl;
|
||||
|
||||
public:
|
||||
struct Param
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
PT_BOOL,
|
||||
PT_INT,
|
||||
PT_FLOAT
|
||||
};
|
||||
const char* desc;
|
||||
u32 name;
|
||||
int type;
|
||||
int value;
|
||||
};
|
||||
|
||||
Driller()
|
||||
: m_output(NULL) {}
|
||||
virtual ~Driller() {}
|
||||
|
||||
/// Returns the driller ID Crc32 of the name (Crc32(GetName())
|
||||
AZ::u32 GetId() const;
|
||||
/// Driller group name, used only for organizational purpose
|
||||
virtual const char* GroupName() const = 0;
|
||||
/// Unique name of the Driller, driller ID is the Crc of the name
|
||||
virtual const char* GetName() const = 0;
|
||||
virtual const char* GetDescription() const = 0;
|
||||
// @{ Managing the list of supported driller parameters.
|
||||
virtual int GetNumParams() const { return 0; }
|
||||
virtual const Param* GetParam(int index) const { (void)index; return NULL; }
|
||||
|
||||
protected:
|
||||
Driller& operator=(const Driller&);
|
||||
|
||||
/// Called by DrillerManager
|
||||
virtual void Start(const Param* params = NULL, int numParams = 0) { (void)params; (void)numParams; }
|
||||
/// Called by DrillerManager
|
||||
virtual void Stop() {}
|
||||
/// Called every frame by DrillerManger (while the driller is started)
|
||||
virtual void Update() {}
|
||||
|
||||
DrillerOutputStream* m_output; ///< Session output stream.
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores the information while an active
|
||||
* driller(s) session is running.
|
||||
*/
|
||||
struct DrillerSession
|
||||
{
|
||||
int numFrames;
|
||||
int curFrame;
|
||||
typedef vector<Driller*>::type DrillerArrayType;
|
||||
DrillerArrayType drillers;
|
||||
DrillerOutputStream* output;
|
||||
};
|
||||
|
||||
/**
|
||||
* Driller manager will manage all active driller sessions and driller factories. Generally you will never
|
||||
* need more than one driller manger.
|
||||
* IMPORTANT: Driller systems works OUTSIDE engine systems, you should NOT use SystemAllocator or any other engine systems
|
||||
* as they might be drilled or not available at the moment.
|
||||
*/
|
||||
class DrillerManager
|
||||
{
|
||||
friend class DrillerRemoteServer;
|
||||
public:
|
||||
struct DrillerInfo
|
||||
{
|
||||
AZ::u32 id;
|
||||
vector<Driller::Param>::type params;
|
||||
};
|
||||
typedef forward_list<DrillerInfo>::type DrillerListType;
|
||||
|
||||
virtual ~DrillerManager() {}
|
||||
|
||||
static DrillerManager* Create(/*const Descriptor& desc*/);
|
||||
static void Destroy(DrillerManager* manager);
|
||||
|
||||
virtual void Register(Driller* driller) = 0;
|
||||
virtual void Unregister(Driller* driller) = 0;
|
||||
|
||||
virtual void FrameUpdate() = 0;
|
||||
|
||||
virtual DrillerSession* Start(DrillerOutputStream& output, const DrillerListType& drillerList, int numFrames = -1) = 0;
|
||||
virtual void Stop(DrillerSession* session) = 0;
|
||||
|
||||
virtual int GetNumDrillers() const = 0;
|
||||
virtual Driller* GetDriller(int index) = 0;
|
||||
|
||||
private:
|
||||
// If the manager created the allocator, it should destroy it when it gets destroyed
|
||||
bool m_ownsOSAllocator = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // AZCORE_DRILLER_H
|
||||
#pragma once
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Driller/DrillerBus.h>
|
||||
|
||||
#include <AzCore/Driller/Driller.h>
|
||||
#include <AzCore/Module/Environment.h>
|
||||
#include <AzCore/std/parallel/mutex.h>
|
||||
|
||||
namespace AZ::Debug
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Globals
|
||||
// We need to synchronize all driller evens, so we have proper order, and access to the data
|
||||
// We use a global mutex which should be used for all driller operations.
|
||||
// The mutex is held in an environment variable so it works across DLLs.
|
||||
EnvironmentVariable<AZStd::recursive_mutex> s_drillerGlobalMutex;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// lock
|
||||
// [4/11/2011]
|
||||
//=========================================================================
|
||||
void DrillerEBusMutex::lock()
|
||||
{
|
||||
GetMutex().lock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// try_lock
|
||||
// [4/11/2011]
|
||||
//=========================================================================
|
||||
bool DrillerEBusMutex::try_lock()
|
||||
{
|
||||
return GetMutex().try_lock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// unlock
|
||||
// [4/11/2011]
|
||||
//=========================================================================
|
||||
void DrillerEBusMutex::unlock()
|
||||
{
|
||||
GetMutex().unlock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// unlock
|
||||
// [4/11/2011]
|
||||
//=========================================================================
|
||||
AZStd::recursive_mutex& DrillerEBusMutex::GetMutex()
|
||||
{
|
||||
if (!s_drillerGlobalMutex)
|
||||
{
|
||||
s_drillerGlobalMutex = Environment::CreateVariable<AZStd::recursive_mutex>(AZ_FUNCTION_SIGNATURE);
|
||||
}
|
||||
return *s_drillerGlobalMutex;
|
||||
}
|
||||
} // namespace AZ::Debug
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_DRILLER_BUS_H
|
||||
#define AZCORE_DRILLER_BUS_H
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/Memory/OSAllocator.h>
|
||||
|
||||
namespace AZStd
|
||||
{
|
||||
class mutex;
|
||||
}
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
class DrillerEBusMutex
|
||||
{
|
||||
public:
|
||||
typedef AZStd::recursive_mutex MutexType;
|
||||
|
||||
static MutexType& GetMutex();
|
||||
void lock();
|
||||
bool try_lock();
|
||||
void unlock();
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialization of the EBusTraits for a driller bus. We make sure
|
||||
* all allocation are made using DebugAllocation (so no engine systems are involved).
|
||||
* In addition we make sure all driller buses use the same Mutex to synchronize data across
|
||||
* threads (so all events came in order all the time), they are still executed in the context of
|
||||
* the thread.
|
||||
*/
|
||||
struct DrillerEBusTraits
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
typedef DrillerEBusMutex MutexType;
|
||||
typedef OSStdAllocator AllocatorType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // AZCORE_DRILLER_BUS_H
|
||||
#pragma once
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_DRILLER_ROOT_HANDLER_H
|
||||
#define AZCORE_DRILLER_ROOT_HANDLER_H
|
||||
|
||||
#include <AzCore/Driller/Stream.h>
|
||||
#include <AzCore/Math/Crc.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
// Please check DrillerRootHandler class... this is the one for direct use.
|
||||
|
||||
/**
|
||||
* Handler for the <Frame><StartData><Driller></Driller></StartData></Frame> tag.
|
||||
*/
|
||||
class DrillerDrillerdataHandler
|
||||
: public DrillerHandlerParser
|
||||
{
|
||||
public:
|
||||
class ParamHandler
|
||||
: public DrillerHandlerParser
|
||||
{
|
||||
public:
|
||||
virtual void OnData(const DrillerSAXParser::Data& dataNode)
|
||||
{
|
||||
if (dataNode.m_name == AZ_CRC("Name", 0x5e237e06))
|
||||
{
|
||||
dataNode.Read(m_param->name);
|
||||
}
|
||||
else if (dataNode.m_name == AZ_CRC("Description", 0x6de44026))
|
||||
{
|
||||
m_param->desc = NULL; // ignored
|
||||
}
|
||||
else if (dataNode.m_name == AZ_CRC("Type", 0x8cde5729))
|
||||
{
|
||||
dataNode.Read(m_param->type);
|
||||
}
|
||||
else if (dataNode.m_name == AZ_CRC("Value", 0x1d775834))
|
||||
{
|
||||
dataNode.Read(m_param->value);
|
||||
}
|
||||
}
|
||||
Driller::Param* m_param;
|
||||
};
|
||||
|
||||
virtual DrillerHandlerParser* OnEnterTag(u32 tagName)
|
||||
{
|
||||
if (tagName == AZ_CRC("Param", 0xa4fa7c89))
|
||||
{
|
||||
m_drillerInfo->params.push_back();
|
||||
m_paramHandler.m_param = &m_drillerInfo->params.back();
|
||||
return &m_paramHandler;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
virtual void OnData(const DrillerSAXParser::Data& dataNode)
|
||||
{
|
||||
if (dataNode.m_name == AZ_CRC("Name", 0x5e237e06))
|
||||
{
|
||||
dataNode.Read(m_drillerInfo->id);
|
||||
}
|
||||
}
|
||||
|
||||
DrillerManager::DrillerInfo* m_drillerInfo;
|
||||
ParamHandler m_paramHandler;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the <Frame><StartData></StartData></Frame> tag
|
||||
*/
|
||||
class DrillerStartdataHandler
|
||||
: public DrillerHandlerParser
|
||||
{
|
||||
public:
|
||||
virtual DrillerHandlerParser* OnEnterTag(u32 tagName)
|
||||
{
|
||||
if (tagName == AZ_CRC("Driller", 0xa6e1fb73))
|
||||
{
|
||||
m_drillers.push_back();
|
||||
m_drillerDataHandler.m_drillerInfo = &m_drillers.back();
|
||||
return &m_drillerDataHandler;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
virtual void OnData(const DrillerSAXParser::Data& dataNode)
|
||||
{
|
||||
if (dataNode.m_name == AZ_CRC("Platform", 0x3952d0cb))
|
||||
{
|
||||
dataNode.Read(m_platform);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int m_platform;
|
||||
DrillerManager::DrillerListType m_drillers;
|
||||
DrillerDrillerdataHandler m_drillerDataHandler;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler for the <Frame></Frame> tag
|
||||
*/
|
||||
template<class DrillerContainer>
|
||||
class FrameHandler
|
||||
: public DrillerHandlerParser
|
||||
{
|
||||
public:
|
||||
FrameHandler()
|
||||
: DrillerHandlerParser(DrillerContainer::s_isWarnOnMissingDrillers)
|
||||
, m_currentFrame(-1) {}
|
||||
|
||||
virtual DrillerHandlerParser* OnEnterTag(u32 tagName) { return m_drillersContainer.FindDrillerHandler(tagName); }
|
||||
virtual void OnData(const DrillerSAXParser::Data& dataNode)
|
||||
{
|
||||
if (dataNode.m_name == AZ_CRC("FrameNum", 0x85a1a919))
|
||||
{
|
||||
dataNode.Read(m_currentFrame);
|
||||
}
|
||||
}
|
||||
DrillerContainer m_drillersContainer;
|
||||
int m_currentFrame;
|
||||
};
|
||||
|
||||
/**
|
||||
* Use this class a input parameter to DrillerSAXParserHandler::DrillerSAXParserHandler(). It will handle all root level
|
||||
* tags for a standard driller input stream stream.
|
||||
*
|
||||
* DrillerContainer should comply to the following requirements:
|
||||
* - default constructible
|
||||
* - has a static const bool s_isWarnOnMissingDrillers member to indicate if you want to
|
||||
* trigger a warning when a driller is not found in the class.
|
||||
* - implement a function DrillerHandlerParser* DrillerContainer::FindDrillerHandler(u32 drillerName)
|
||||
*
|
||||
*/
|
||||
template<class DrillerContainer>
|
||||
class DrillerRootHandler
|
||||
: public DrillerHandlerParser
|
||||
{
|
||||
public:
|
||||
DrillerContainer* GetDrillerContainer() { return m_frameHandler.m_drillersContainer; }
|
||||
|
||||
virtual DrillerHandlerParser* OnEnterTag(u32 tagName)
|
||||
{
|
||||
if (tagName == AZ_CRC("StartData", 0xecf3f53f))
|
||||
{
|
||||
return &m_drillerSessionInfo;
|
||||
}
|
||||
if (tagName == AZ_CRC("Frame", 0xb5f83ccd))
|
||||
{
|
||||
return &m_frameHandler;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
DrillerStartdataHandler m_drillerSessionInfo;
|
||||
FrameHandler<DrillerContainer> m_frameHandler;
|
||||
};
|
||||
} // namespace Debug
|
||||
} // namespace AZ
|
||||
|
||||
#endif // AZCORE_DRILLER_ROOT_HANDLER_H
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,893 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Driller/Stream.h>
|
||||
|
||||
#include <AzCore/Math/Quaternion.h>
|
||||
#include <AzCore/Math/Matrix3x3.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
#include <AzCore/Math/Obb.h>
|
||||
#include <AzCore/Math/Plane.h>
|
||||
|
||||
#include <AzCore/std/time.h>
|
||||
|
||||
#if !defined(AZCORE_EXCLUDE_ZLIB)
|
||||
# define AZ_FILE_STREAM_COMPRESSION
|
||||
#endif // AZCORE_EXCLUDE_ZLIB
|
||||
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
# include <AzCore/Compression/Compression.h>
|
||||
#endif // AZ_FILE_STREAM_COMPRESSION
|
||||
|
||||
namespace AZ::Debug
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Driller output stream
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void DrillerOutputStream::Write(u32 name, const AZ::Vector3& v)
|
||||
{
|
||||
float data[4];
|
||||
unsigned int dataSize = 3 * sizeof(float);
|
||||
v.StoreToFloat4(data);
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = dataSize;
|
||||
WriteBinary(de);
|
||||
WriteBinary(data, dataSize);
|
||||
}
|
||||
void DrillerOutputStream::Write(u32 name, const AZ::Vector4& v)
|
||||
{
|
||||
float data[4];
|
||||
unsigned int dataSize = 4 * sizeof(float);
|
||||
v.StoreToFloat4(data);
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = dataSize;
|
||||
WriteBinary(de);
|
||||
WriteBinary(data, dataSize);
|
||||
}
|
||||
void DrillerOutputStream::Write(u32 name, const AZ::Aabb& aabb)
|
||||
{
|
||||
float data[7];
|
||||
unsigned int dataSize = 6 * sizeof(float);
|
||||
aabb.GetMin().StoreToFloat4(data);
|
||||
aabb.GetMax().StoreToFloat4(&data[3]);
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = dataSize;
|
||||
WriteBinary(de);
|
||||
WriteBinary(data, dataSize);
|
||||
}
|
||||
void DrillerOutputStream::Write(u32 name, const AZ::Obb& obb)
|
||||
{
|
||||
float data[10];
|
||||
unsigned int dataSize = 10 * sizeof(float); // position (Vector3), rotation (Quaternion) and halfLengths (Vector3)
|
||||
obb.GetPosition().StoreToFloat3(data);
|
||||
obb.GetRotation().StoreToFloat4(&data[3]);
|
||||
obb.GetHalfLengths().StoreToFloat3(&data[7]);
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = dataSize;
|
||||
WriteBinary(de);
|
||||
WriteBinary(data, dataSize);
|
||||
}
|
||||
void DrillerOutputStream::Write(u32 name, const AZ::Transform& tm)
|
||||
{
|
||||
float data[12];
|
||||
unsigned int dataSize = 12 * sizeof(float);
|
||||
const Matrix3x4 matrix3x4 = Matrix3x4::CreateFromTransform(tm);
|
||||
matrix3x4.StoreToRowMajorFloat12(data);
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = dataSize;
|
||||
WriteBinary(de);
|
||||
WriteBinary(data, dataSize);
|
||||
}
|
||||
void DrillerOutputStream::Write(u32 name, const AZ::Matrix3x3& tm)
|
||||
{
|
||||
float data[9];
|
||||
unsigned int dataSize = 9 * sizeof(float);
|
||||
tm.StoreToRowMajorFloat9(data);
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = dataSize;
|
||||
WriteBinary(de);
|
||||
WriteBinary(data, dataSize);
|
||||
}
|
||||
void DrillerOutputStream::Write(u32 name, const AZ::Matrix4x4& tm)
|
||||
{
|
||||
float data[16];
|
||||
unsigned int dataSize = 16 * sizeof(float);
|
||||
tm.StoreToRowMajorFloat16(data);
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = dataSize;
|
||||
WriteBinary(de);
|
||||
WriteBinary(data, dataSize);
|
||||
}
|
||||
void DrillerOutputStream::Write(u32 name, const AZ::Quaternion& tm)
|
||||
{
|
||||
float data[4];
|
||||
unsigned int dataSize = 4 * sizeof(float);
|
||||
tm.StoreToFloat4(data);
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = dataSize;
|
||||
WriteBinary(de);
|
||||
WriteBinary(data, dataSize);
|
||||
}
|
||||
void DrillerOutputStream::Write(u32 name, const AZ::Plane& plane)
|
||||
{
|
||||
Write(name, plane.GetPlaneEquationCoefficients());
|
||||
}
|
||||
void DrillerOutputStream::WriteHeader()
|
||||
{
|
||||
StreamHeader sh; // StreamHeader should be endianess independent.
|
||||
WriteBinary(&sh, sizeof(sh));
|
||||
}
|
||||
|
||||
void DrillerOutputStream::WriteTimeUTC(u32 name)
|
||||
{
|
||||
AZStd::sys_time_t now = AZStd::GetTimeUTCMilliSecond();
|
||||
Write(name, now);
|
||||
}
|
||||
|
||||
void DrillerOutputStream::WriteTimeMicrosecond(u32 name)
|
||||
{
|
||||
AZStd::sys_time_t now = AZStd::GetTimeNowMicroSecond();
|
||||
Write(name, now);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Driller Input Stream
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
bool DrillerInputStream::ReadHeader()
|
||||
{
|
||||
DrillerOutputStream::StreamHeader sh; // StreamHeader should be endianess independent.
|
||||
unsigned int numRead = ReadBinary(&sh, sizeof(sh));
|
||||
(void)numRead;
|
||||
AZ_Error("IO", numRead == sizeof(sh), "We should have atleast %d bytes in the stream to read the header!", sizeof(sh));
|
||||
if (numRead != sizeof(sh))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_isEndianSwap = AZ::IsBigEndian(static_cast<AZ::PlatformID>(sh.platform)) != AZ::IsBigEndian(AZ::g_currentPlatform);
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Driller file stream
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//=========================================================================
|
||||
// DrillerOutputFileStream::DrillerOutputFileStream
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
DrillerOutputFileStream::DrillerOutputFileStream()
|
||||
{
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
m_zlib = azcreate(ZLib, (&AllocatorInstance<OSAllocator>::GetAllocator()), OSAllocator);
|
||||
m_zlib->StartCompressor(2);
|
||||
#endif
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// DrillerOutputFileStream::~DrillerOutputFileStream
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
DrillerOutputFileStream::~DrillerOutputFileStream()
|
||||
{
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
azdestroy(m_zlib, OSAllocator);
|
||||
#endif
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// DrillerOutputFileStream::Open
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
bool DrillerOutputFileStream::Open(const char* fileName, int mode, int platformFlags)
|
||||
{
|
||||
if (IO::SystemFile::Open(fileName, mode, platformFlags))
|
||||
{
|
||||
m_dataBuffer.reserve(100 * 1024);
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
// // Enable optional: encode the file in the same format as the streamer so they are interchangeable
|
||||
// IO::CompressorHeader ch;
|
||||
// ch.SetAZCS();
|
||||
// ch.m_compressorId = IO::CompressorZLib::TypeId();
|
||||
// ch.m_uncompressedSize = 0; // will be updated later
|
||||
// AZStd::endian_swap(ch.m_compressorId);
|
||||
// AZStd::endian_swap(ch.m_uncompressedSize);
|
||||
// IO::SystemFile::Write(&ch,sizeof(ch));
|
||||
// IO::CompressorZLibHeader zlibHdr;
|
||||
// zlibHdr.m_numSeekPoints = 0;
|
||||
// IO::SystemFile::Write(&zlibHdr,sizeof(zlibHdr));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// DrillerOutputFileStream::Close
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
void DrillerOutputFileStream::Close()
|
||||
{
|
||||
unsigned int dataSizeInBuffer = static_cast<unsigned int>(m_dataBuffer.size());
|
||||
{
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
unsigned int minCompressBufferSize = m_zlib->GetMinCompressedBufferSize(dataSizeInBuffer);
|
||||
if (m_compressionBuffer.size() < minCompressBufferSize) // grow compression buffer if needed
|
||||
{
|
||||
m_compressionBuffer.clear();
|
||||
m_compressionBuffer.resize(minCompressBufferSize);
|
||||
}
|
||||
unsigned int compressedSize;
|
||||
do
|
||||
{
|
||||
compressedSize = m_zlib->Compress(m_dataBuffer.data(), dataSizeInBuffer, m_compressionBuffer.data(), (unsigned)m_compressionBuffer.size(), ZLib::FT_FINISH);
|
||||
if (compressedSize)
|
||||
{
|
||||
IO::SystemFile::Write(m_compressionBuffer.data(), compressedSize);
|
||||
}
|
||||
} while (compressedSize > 0);
|
||||
m_zlib->ResetCompressor();
|
||||
#else
|
||||
if (dataSizeInBuffer)
|
||||
{
|
||||
IO::SystemFile::Write(m_dataBuffer.data(), m_dataBuffer.size());
|
||||
}
|
||||
#endif
|
||||
m_dataBuffer.clear();
|
||||
}
|
||||
IO::SystemFile::Close();
|
||||
}
|
||||
//=========================================================================
|
||||
// DrillerOutputFileStream::WriteBinary
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
void DrillerOutputFileStream::WriteBinary(const void* data, unsigned int dataSize)
|
||||
{
|
||||
size_t dataSizeInBuffer = m_dataBuffer.size();
|
||||
if (dataSizeInBuffer + dataSize > m_dataBuffer.capacity())
|
||||
{
|
||||
if (dataSizeInBuffer > 0)
|
||||
{
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
// we need to flush the data
|
||||
unsigned int dataToCompress = static_cast<unsigned int>(dataSizeInBuffer);
|
||||
unsigned int minCompressBufferSize = m_zlib->GetMinCompressedBufferSize(dataToCompress);
|
||||
if (m_compressionBuffer.size() < minCompressBufferSize) // grow compression buffer if needed
|
||||
{
|
||||
m_compressionBuffer.clear();
|
||||
m_compressionBuffer.resize(minCompressBufferSize);
|
||||
}
|
||||
while (dataToCompress > 0)
|
||||
{
|
||||
unsigned int compressedSize = m_zlib->Compress(m_dataBuffer.data(), dataToCompress, m_compressionBuffer.data(), (unsigned)m_compressionBuffer.size());
|
||||
if (compressedSize)
|
||||
{
|
||||
IO::SystemFile::Write(m_compressionBuffer.data(), compressedSize);
|
||||
}
|
||||
}
|
||||
#else
|
||||
IO::SystemFile::Write(m_dataBuffer.data(), m_dataBuffer.size());
|
||||
#endif
|
||||
m_dataBuffer.clear();
|
||||
}
|
||||
}
|
||||
m_dataBuffer.insert(m_dataBuffer.end(), reinterpret_cast<const unsigned char*>(data), reinterpret_cast<const unsigned char*>(data) + dataSize);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Driller file input stream
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//=========================================================================
|
||||
// DrillerInputFileStream::DrillerInputFileStream
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
DrillerInputFileStream::DrillerInputFileStream()
|
||||
{
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
m_zlib = azcreate(ZLib, (&AllocatorInstance<OSAllocator>::GetAllocator()), OSAllocator);
|
||||
m_zlib->StartDecompressor();
|
||||
#endif
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// DrillerInputFileStream::DrillerInputFileStream
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
DrillerInputFileStream::~DrillerInputFileStream()
|
||||
{
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
azdestroy(m_zlib, OSAllocator);
|
||||
#endif
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// DrillerInputFileStream::Open
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
bool DrillerInputFileStream::Open(const char* fileName, int mode, int platformFlags)
|
||||
{
|
||||
if (IO::SystemFile::Open(fileName, mode, platformFlags))
|
||||
{
|
||||
DrillerOutputStream::StreamHeader sh;
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
// TODO: optional encode the file in the same format as the streamer so they are interchangeable
|
||||
#endif
|
||||
// first read the header of the stream file.
|
||||
return ReadHeader();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
//=========================================================================
|
||||
// DrillerInputFileStream::ReadBinary
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
unsigned int DrillerInputFileStream::ReadBinary(void* data, unsigned int maxDataSize)
|
||||
{
|
||||
// make sure the compressed buffer if full enough...
|
||||
size_t dataToLoad = maxDataSize * 2;
|
||||
m_compressedData.reserve(dataToLoad);
|
||||
while (m_compressedData.size() < dataToLoad)
|
||||
{
|
||||
unsigned char buffer[10 * 1024];
|
||||
IO::SystemFile::SizeType bytesRead = Read(AZ_ARRAY_SIZE(buffer), buffer);
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
m_compressedData.insert(m_compressedData.end(), (unsigned char*)buffer, buffer + bytesRead);
|
||||
}
|
||||
if (bytesRead < AZ_ARRAY_SIZE(buffer))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
unsigned int dataSize = maxDataSize;
|
||||
unsigned int bytesProcessed = m_zlib->Decompress(m_compressedData.data(), (unsigned)m_compressedData.size(), data, dataSize);
|
||||
unsigned int readSize = maxDataSize - dataSize; // Zlib::Decompress decrements the dataSize parameter by the amount uncompressed
|
||||
#else
|
||||
unsigned int bytesProcessed = AZStd::GetMin((unsigned int)m_compressedData.size(), maxDataSize);
|
||||
unsigned int readSize = bytesProcessed;
|
||||
memcpy(data, m_compressedData.data(), readSize);
|
||||
#endif
|
||||
m_compressedData.erase(m_compressedData.begin(), m_compressedData.begin() + bytesProcessed);
|
||||
return readSize;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// DrillerInputFileStream::Close
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
void DrillerInputFileStream::Close()
|
||||
{
|
||||
#if defined(AZ_FILE_STREAM_COMPRESSION)
|
||||
if (m_zlib)
|
||||
{
|
||||
m_zlib->ResetDecompressor();
|
||||
}
|
||||
#endif // AZ_FILE_STREAM_COMPRESSION
|
||||
AZ::IO::SystemFile::Close();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// DrillerSAXParser
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//=========================================================================
|
||||
// DrillerSAXParser
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
DrillerSAXParser::DrillerSAXParser(const TagCallbackType& tcb, const DataCallbackType& dcb)
|
||||
: m_tagCallback(tcb)
|
||||
, m_dataCallback(dcb)
|
||||
{
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ProcessStream
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
DrillerSAXParser::ProcessStream(DrillerInputStream& stream)
|
||||
{
|
||||
static const int processChunkSize = 15 * 1024;
|
||||
char buffer[processChunkSize];
|
||||
unsigned int dataSize;
|
||||
bool isEndianSwap = stream.IsEndianSwap();
|
||||
while ((dataSize = stream.ReadBinary(buffer, processChunkSize)) > 0)
|
||||
{
|
||||
char* dataStart = buffer;
|
||||
char* dataEnd = dataStart + dataSize;
|
||||
bool dataInBuffer = false;
|
||||
if (!m_buffer.empty())
|
||||
{
|
||||
m_buffer.insert(m_buffer.end(), dataStart, dataEnd);
|
||||
dataStart = m_buffer.data();
|
||||
dataEnd = dataStart + m_buffer.size();
|
||||
dataInBuffer = true;
|
||||
}
|
||||
const int entrySize = sizeof(DrillerOutputStream::StreamEntry);
|
||||
while (dataStart != dataEnd)
|
||||
{
|
||||
if ((dataEnd - dataStart) < entrySize) // we need at least one entry to proceed
|
||||
{
|
||||
// not enough data to process, buffer it.
|
||||
if (!dataInBuffer)
|
||||
{
|
||||
m_buffer.insert(m_buffer.end(), dataStart, dataEnd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
DrillerOutputStream::StreamEntry* se = reinterpret_cast<DrillerOutputStream::StreamEntry*>(dataStart);
|
||||
if (isEndianSwap)
|
||||
{
|
||||
// endian swap
|
||||
AZStd::endian_swap(se->name);
|
||||
AZStd::endian_swap(se->sizeAndFlags);
|
||||
}
|
||||
|
||||
u32 dataType = (se->sizeAndFlags & DrillerOutputStream::StreamEntry::dataInternalMask) >> DrillerOutputStream::StreamEntry::dataInternalShift;
|
||||
u32 value = se->sizeAndFlags & DrillerOutputStream::StreamEntry::dataSizeMask;
|
||||
Data de;
|
||||
de.m_name = se->name;
|
||||
de.m_stringPool = stream.GetStringPool();
|
||||
de.m_isPooledString = false;
|
||||
de.m_isPooledStringCrc32 = false;
|
||||
switch (dataType)
|
||||
{
|
||||
case DrillerOutputStream::StreamEntry::INT_TAG:
|
||||
{
|
||||
bool isStart = (value != 0);
|
||||
m_tagCallback(se->name, isStart);
|
||||
dataStart += entrySize;
|
||||
} break;
|
||||
case DrillerOutputStream::StreamEntry::INT_DATA_U8:
|
||||
{
|
||||
u8 value8 = static_cast<u8>(value);
|
||||
de.m_data = &value8;
|
||||
de.m_dataSize = 1;
|
||||
de.m_isEndianSwap = false;
|
||||
m_dataCallback(de);
|
||||
dataStart += entrySize;
|
||||
} break;
|
||||
case DrillerOutputStream::StreamEntry::INT_DATA_U16:
|
||||
{
|
||||
u16 value16 = static_cast<u16>(value);
|
||||
de.m_data = &value16;
|
||||
de.m_dataSize = 2;
|
||||
de.m_isEndianSwap = false;
|
||||
m_dataCallback(de);
|
||||
dataStart += entrySize;
|
||||
} break;
|
||||
case DrillerOutputStream::StreamEntry::INT_DATA_U29:
|
||||
{
|
||||
de.m_data = &value;
|
||||
de.m_dataSize = 4;
|
||||
de.m_isEndianSwap = false;
|
||||
m_dataCallback(de);
|
||||
dataStart += entrySize;
|
||||
} break;
|
||||
case DrillerOutputStream::StreamEntry::INT_POOLED_STRING:
|
||||
{
|
||||
unsigned int userDataSize = value;
|
||||
if ((userDataSize + entrySize) <= (unsigned)(dataEnd - dataStart))
|
||||
{
|
||||
// Add string to the pool
|
||||
AZ_Assert(de.m_stringPool != nullptr, "We require a string pool to parse this stream");
|
||||
AZ::u32 crc32;
|
||||
const char* stringPtr;
|
||||
dataStart += entrySize;
|
||||
de.m_stringPool->InsertCopy(reinterpret_cast<const char*>(dataStart), userDataSize, crc32, &stringPtr);
|
||||
de.m_dataSize = userDataSize;
|
||||
de.m_isEndianSwap = isEndianSwap;
|
||||
de.m_isPooledString = true;
|
||||
de.m_data = const_cast<void*>(static_cast<const void*>(stringPtr));
|
||||
m_dataCallback(de);
|
||||
dataStart += userDataSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we can't process data right now add it to the buffer (if we have not done that already)
|
||||
if (!dataInBuffer)
|
||||
{
|
||||
m_buffer.insert(m_buffer.end(), dataStart, dataEnd);
|
||||
}
|
||||
dataEnd = dataStart; // exit the loop
|
||||
}
|
||||
} break;
|
||||
case DrillerOutputStream::StreamEntry::INT_POOLED_STRING_CRC32:
|
||||
{
|
||||
de.m_isPooledStringCrc32 = true;
|
||||
AZ_Assert(value == 4, "The data size for a pooled string crc32 should be 4 bytes!");
|
||||
} // continue to INT_SIZE
|
||||
case DrillerOutputStream::StreamEntry::INT_SIZE:
|
||||
{
|
||||
unsigned int userDataSize = value;
|
||||
if ((userDataSize + entrySize) <= (unsigned)(dataEnd - dataStart)) // do we have all the date we need to process...
|
||||
{
|
||||
dataStart += entrySize;
|
||||
de.m_data = dataStart;
|
||||
de.m_dataSize = userDataSize;
|
||||
de.m_isEndianSwap = isEndianSwap;
|
||||
m_dataCallback(de);
|
||||
dataStart += userDataSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we can't process data right now add it to the buffer (if we have not done that already)
|
||||
if (!dataInBuffer)
|
||||
{
|
||||
m_buffer.insert(m_buffer.end(), dataStart, dataEnd);
|
||||
}
|
||||
dataEnd = dataStart; // exit the loop
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
AZ_Error("DrillerSAXParser",false,"Encounted unknown symbol (%i) while processing stream (%s). Aborting stream.\n",dataType, stream.GetIdentifier());
|
||||
|
||||
// If we can't process anything, we want to just escape the loop, to avoid spinning infinitely
|
||||
dataEnd = dataStart;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
if (dataInBuffer) // if the data was in the buffer remove the processed data!
|
||||
{
|
||||
m_buffer.erase(m_buffer.begin(), m_buffer.begin() + (dataStart - m_buffer.data()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrillerSAXParser::Data::Read(AZ::Vector3& v) const
|
||||
{
|
||||
AZ_Assert(m_dataSize == sizeof(float) * 3, "We are expecting 3 floats for Vector3 element 0x%08x with size %d bytes", m_name, m_dataSize);
|
||||
float* data = reinterpret_cast<float*>(m_data);
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(data, data + 3);
|
||||
m_isEndianSwap = false;
|
||||
}
|
||||
v = Vector3::CreateFromFloat3(data);
|
||||
}
|
||||
void DrillerSAXParser::Data::Read(AZ::Vector4& v) const
|
||||
{
|
||||
AZ_Assert(m_dataSize == sizeof(float) * 4, "We are expecting 4 floats for Vector4 element 0x%08x with size %d bytes", m_name, m_dataSize);
|
||||
float* data = reinterpret_cast<float*>(m_data);
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(data, data + 4);
|
||||
m_isEndianSwap = false;
|
||||
}
|
||||
v = Vector4::CreateFromFloat4(data);
|
||||
}
|
||||
void DrillerSAXParser::Data::Read(AZ::Aabb& aabb) const
|
||||
{
|
||||
AZ_Assert(m_dataSize == sizeof(float) * 6, "We are expecting 6 floats for Aabb element 0x%08x with size %d bytes", m_name, m_dataSize);
|
||||
float* data = reinterpret_cast<float*>(m_data);
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(data, data + 6);
|
||||
m_isEndianSwap = false;
|
||||
}
|
||||
Vector3 min = Vector3::CreateFromFloat3(data);
|
||||
Vector3 max = Vector3::CreateFromFloat3(&data[3]);
|
||||
aabb = Aabb::CreateFromMinMax(min, max);
|
||||
}
|
||||
void DrillerSAXParser::Data::Read(AZ::Obb& obb) const
|
||||
{
|
||||
AZ_Assert(m_dataSize == sizeof(float) * 10, "We are expecting 10 floats for Obb element 0x%08x with size %d bytes", m_name, m_dataSize);
|
||||
float* data = reinterpret_cast<float*>(m_data);
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(data, data + 10);
|
||||
m_isEndianSwap = false;
|
||||
}
|
||||
Vector3 position = Vector3::CreateFromFloat3(data);
|
||||
Quaternion rotation = Quaternion::CreateFromFloat4(&data[3]);
|
||||
Vector3 halfLengths = Vector3::CreateFromFloat3(&data[7]);
|
||||
obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths);
|
||||
}
|
||||
void DrillerSAXParser::Data::Read(AZ::Transform& tm) const
|
||||
{
|
||||
AZ_Assert(m_dataSize == sizeof(float) * 12, "We are expecting 12 floats for Transform element 0x%08x with size %d bytes", m_name, m_dataSize);
|
||||
float* data = reinterpret_cast<float*>(m_data);
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(data, data + 12);
|
||||
m_isEndianSwap = false;
|
||||
}
|
||||
const Matrix3x4 matrix3x4 = Matrix3x4::CreateFromRowMajorFloat12(data);
|
||||
tm = Transform::CreateFromMatrix3x4(matrix3x4);
|
||||
}
|
||||
void DrillerSAXParser::Data::Read(AZ::Matrix3x3& tm) const
|
||||
{
|
||||
AZ_Assert(m_dataSize == sizeof(float) * 9, "We are expecting 9 floats for Matrix3x3 element 0x%08x with size %d bytes", m_name, m_dataSize);
|
||||
float* data = reinterpret_cast<float*>(m_data);
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(data, data + 9);
|
||||
m_isEndianSwap = false;
|
||||
}
|
||||
tm = Matrix3x3::CreateFromRowMajorFloat9(data);
|
||||
}
|
||||
void DrillerSAXParser::Data::Read(AZ::Matrix4x4& tm) const
|
||||
{
|
||||
AZ_Assert(m_dataSize == sizeof(float) * 16, "We are expecting 16 floats for Matrix4x4 element 0x%08x with size %d bytes", m_name, m_dataSize);
|
||||
float* data = reinterpret_cast<float*>(m_data);
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(data, data + 16);
|
||||
m_isEndianSwap = false;
|
||||
}
|
||||
tm = Matrix4x4::CreateFromRowMajorFloat16(data);
|
||||
}
|
||||
void DrillerSAXParser::Data::Read(AZ::Quaternion& tm) const
|
||||
{
|
||||
AZ_Assert(m_dataSize == sizeof(float) * 4, "We are expecting 4 floats for Quaternion element 0x%08x with size %d bytes", m_name, m_dataSize);
|
||||
float* data = reinterpret_cast<float*>(m_data);
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(data, data + 4);
|
||||
m_isEndianSwap = false;
|
||||
}
|
||||
tm = Quaternion::CreateFromFloat4(data);
|
||||
}
|
||||
void DrillerSAXParser::Data::Read(AZ::Plane& plane) const
|
||||
{
|
||||
AZ::Vector4 coeff;
|
||||
Read(coeff);
|
||||
plane = Plane::CreateFromCoefficients(coeff.GetX(), coeff.GetY(), coeff.GetZ(), coeff.GetW());
|
||||
}
|
||||
|
||||
const char* DrillerSAXParser::Data::PrepareString(unsigned int& stringLength) const
|
||||
{
|
||||
const char* srcData = reinterpret_cast<const char*>(m_data);
|
||||
stringLength = m_dataSize;
|
||||
if (m_stringPool)
|
||||
{
|
||||
AZ::u32 crc32;
|
||||
const char* stringPtr;
|
||||
if (m_isPooledStringCrc32)
|
||||
{
|
||||
crc32 = *reinterpret_cast<AZ::u32*>(m_data);
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(crc32);
|
||||
}
|
||||
stringPtr = m_stringPool->Find(crc32);
|
||||
AZ_Assert(stringPtr != nullptr, "Failed to find string with id 0x%08x in the string pool, proper stream read is impossible!", crc32);
|
||||
stringLength = static_cast<unsigned int>(strlen(stringPtr));
|
||||
}
|
||||
else if (m_isPooledString)
|
||||
{
|
||||
stringPtr = srcData; // already stored in the pool just transfer the pointer
|
||||
}
|
||||
else
|
||||
{
|
||||
// Store copy of the string in the pool to save memory (keep only one reference of the string).
|
||||
m_stringPool->InsertCopy(reinterpret_cast<const char*>(srcData), stringLength, crc32, &stringPtr);
|
||||
}
|
||||
srcData = stringPtr;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(m_isPooledString == false && m_isPooledStringCrc32 == false, "This stream requires using of a string pool as the string is send only once and afterwards only the Crc32 is used!");
|
||||
}
|
||||
return srcData;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// DrillerDOMParser
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//=========================================================================
|
||||
// Node::GetTag
|
||||
// [1/23/2013]
|
||||
//=========================================================================
|
||||
const DrillerDOMParser::Node* DrillerDOMParser::Node::GetTag(u32 tagName) const
|
||||
{
|
||||
const Node* tagNode = nullptr;
|
||||
for (Node::NodeListType::const_iterator i = m_tags.begin(); i != m_tags.end(); ++i)
|
||||
{
|
||||
if ((*i).m_name == tagName)
|
||||
{
|
||||
tagNode = &*i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return tagNode;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Node::GetData
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
const DrillerDOMParser::Data* DrillerDOMParser::Node::GetData(u32 dataName) const
|
||||
{
|
||||
const Data* dataNode = nullptr;
|
||||
for (Node::DataListType::const_iterator i = m_data.begin(); i != m_data.end(); ++i)
|
||||
{
|
||||
if (i->m_name == dataName)
|
||||
{
|
||||
dataNode = &*i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return dataNode;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// DrillerDOMParser
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
DrillerDOMParser::DrillerDOMParser(bool isPersistentInputData)
|
||||
: DrillerSAXParser(TagCallbackType(this, &DrillerDOMParser::OnTag), DataCallbackType(this, &DrillerDOMParser::OnData))
|
||||
, m_isPersistentInputData(isPersistentInputData)
|
||||
{
|
||||
m_root.m_name = 0;
|
||||
m_root.m_parent = nullptr;
|
||||
m_topNode = &m_root;
|
||||
}
|
||||
static int g_numFree = 0;
|
||||
//=========================================================================
|
||||
// ~DrillerDOMParser
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
DrillerDOMParser::~DrillerDOMParser()
|
||||
{
|
||||
DeleteNode(m_root);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// OnTag
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
DrillerDOMParser::OnTag(AZ::u32 name, bool isOpen)
|
||||
{
|
||||
if (isOpen)
|
||||
{
|
||||
m_topNode->m_tags.push_back();
|
||||
Node& node = m_topNode->m_tags.back();
|
||||
node.m_name = name;
|
||||
node.m_parent = m_topNode;
|
||||
|
||||
m_topNode = &node;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(m_topNode->m_name == name, "We have opened tag with name 0x%08x and closing with name 0x%08x", m_topNode->m_name, name);
|
||||
m_topNode = m_topNode->m_parent;
|
||||
}
|
||||
}
|
||||
//=========================================================================
|
||||
// OnData
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
DrillerDOMParser::OnData(const Data& data)
|
||||
{
|
||||
Data de = data;
|
||||
if (!m_isPersistentInputData)
|
||||
{
|
||||
de.m_data = azmalloc(data.m_dataSize, 1, OSAllocator);
|
||||
memcpy(const_cast<void*>(de.m_data), data.m_data, data.m_dataSize);
|
||||
}
|
||||
m_topNode->m_data.push_back(de);
|
||||
}
|
||||
//=========================================================================
|
||||
// DeleteNode
|
||||
// [3/23/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
DrillerDOMParser::DeleteNode(Node& node)
|
||||
{
|
||||
if (!m_isPersistentInputData)
|
||||
{
|
||||
for (Node::DataListType::iterator iter = node.m_data.begin(); iter != node.m_data.end(); ++iter)
|
||||
{
|
||||
azfree(iter->m_data, OSAllocator, iter->m_dataSize);
|
||||
++g_numFree;
|
||||
}
|
||||
node.m_data.clear();
|
||||
}
|
||||
for (Node::NodeListType::iterator iter = node.m_tags.begin(); iter != node.m_tags.end(); ++iter)
|
||||
{
|
||||
DeleteNode(*iter);
|
||||
}
|
||||
node.m_tags.clear();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// DrillerSAXParserHandler
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//=========================================================================
|
||||
// DrillerSAXParserHandler
|
||||
// [3/14/2013]
|
||||
//=========================================================================
|
||||
DrillerSAXParserHandler::DrillerSAXParserHandler(DrillerHandlerParser* rootHandler)
|
||||
: DrillerSAXParser(TagCallbackType(this, &DrillerSAXParserHandler::OnTag), DataCallbackType(this, &DrillerSAXParserHandler::OnData))
|
||||
{
|
||||
// Push the root element
|
||||
m_stack.push_back(rootHandler);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// OnTag
|
||||
// [3/14/2013]
|
||||
//=========================================================================
|
||||
void DrillerSAXParserHandler::OnTag(u32 name, bool isOpen)
|
||||
{
|
||||
if (m_stack.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrillerHandlerParser* childHandler = nullptr;
|
||||
DrillerHandlerParser* currentHandler = m_stack.back();
|
||||
if (isOpen)
|
||||
{
|
||||
if (currentHandler != nullptr)
|
||||
{
|
||||
childHandler = currentHandler->OnEnterTag(name);
|
||||
AZ_Warning("Driller", !currentHandler->IsWarnOnUnsupportedTags() || childHandler != nullptr, "Could not find handler for tag 0x%08x", name);
|
||||
}
|
||||
m_stack.push_back(childHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_stack.pop_back();
|
||||
if (!m_stack.empty())
|
||||
{
|
||||
DrillerHandlerParser* parentHandler = m_stack.back();
|
||||
if (parentHandler)
|
||||
{
|
||||
parentHandler->OnExitTag(currentHandler, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// OnData
|
||||
// [3/14/2013]
|
||||
//=========================================================================
|
||||
void DrillerSAXParserHandler::OnData(const DrillerSAXParser::Data& data)
|
||||
{
|
||||
if (m_stack.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrillerHandlerParser* currentHandler = m_stack.back();
|
||||
if (currentHandler)
|
||||
{
|
||||
currentHandler->OnData(data);
|
||||
}
|
||||
}
|
||||
} // namespace AZ::Debug
|
||||
@@ -1,848 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_DRILLER_STREAM_H
|
||||
#define AZCORE_DRILLER_STREAM_H
|
||||
|
||||
#include <AzCore/Memory/OSAllocator.h>
|
||||
|
||||
#include <AzCore/std/delegate/delegate.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/containers/forward_list.h>
|
||||
#include <AzCore/std/typetraits/is_signed.h>
|
||||
#include <AzCore/std/typetraits/is_pod.h>
|
||||
|
||||
#include <AzCore/IO/SystemFile.h> // for the Driller direct file stream
|
||||
#include <AzCore/PlatformId/PlatformId.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class Vector3;
|
||||
class Vector4;
|
||||
class Aabb;
|
||||
class Obb;
|
||||
class Transform;
|
||||
class Matrix3x3;
|
||||
class Matrix4x4;
|
||||
class Quaternion;
|
||||
class Plane;
|
||||
class ZLib;
|
||||
|
||||
namespace IO
|
||||
{
|
||||
class Stream;
|
||||
}
|
||||
|
||||
namespace Debug
|
||||
{
|
||||
template<class T>
|
||||
struct vector
|
||||
{
|
||||
typedef AZStd::vector<T, OSStdAllocator> type;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct forward_list
|
||||
{
|
||||
typedef AZStd::forward_list<T, OSStdAllocator> type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for a string pool which can be used by input/output streams to avoid storing multiple copies of the same
|
||||
* string in the stream. Of course this comes at the bookkeeping cost of the table.
|
||||
*/
|
||||
class DrillerStringPool
|
||||
{
|
||||
public:
|
||||
virtual ~DrillerStringPool() {}
|
||||
|
||||
/**
|
||||
* Add a copy of the string to the pool. If we return true the string was added otherwise it was already in the bool.
|
||||
* In both cases the crc32 of the string and the pointer to the shared copy is returned (optional for poolStringAddress)!
|
||||
*/
|
||||
virtual bool InsertCopy(const char* string, unsigned int length, AZ::u32& crc32, const char** poolStringAddress = NULL) = 0;
|
||||
|
||||
/**
|
||||
* Same as the InsertCopy above without actually coping the string into the pool. The pool assumes that
|
||||
* none of the strings added to the pool will be deleted.
|
||||
*/
|
||||
virtual bool Insert(const char* string, unsigned int length, AZ::u32& crc32) = 0;
|
||||
|
||||
/// Finds a string in the pool by crc32.
|
||||
virtual const char* Find(AZ::u32 crc32) = 0;
|
||||
|
||||
virtual void Erase(AZ::u32 crc32) = 0;
|
||||
|
||||
/// Clears all the strings in the pool, make sure you don't reference any strings before you call that function.
|
||||
virtual void Reset() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class DrillerOutputStream
|
||||
{
|
||||
protected:
|
||||
friend class DrillerManagerImpl;
|
||||
friend class DrillerSAXParser;
|
||||
struct StreamEntry
|
||||
{
|
||||
enum InternalDataSize // max 8 values as we use 3 bit to store them
|
||||
{
|
||||
INT_SIZE = 0, ///< No internal data, we store the data size. IMPORTANT: INT_SIZE should be 0 the code makes assumptions based on that
|
||||
INT_TAG, ///< True if this entry is tag
|
||||
INT_DATA_U8, ///< Internal data u8 stored (1 byte)
|
||||
INT_DATA_U16, ///< Internal data u16 stored (2 bytes)
|
||||
INT_DATA_U29, ///< Internal data u32 stored (4 bytes) for which we use only the first 29 bits.
|
||||
INT_POOLED_STRING_CRC32, ///< Data size should be 4 bytes crc32 that a string CRC and it require string pool.
|
||||
INT_POOLED_STRING, ///< This data contains a string which should be inserted in the string pool.
|
||||
};
|
||||
static const u32 dataSizeMask = 0x1fffffff;
|
||||
static const u32 dataInternalMask = 0xE0000000;
|
||||
static const u32 dataInternalShift = 29;
|
||||
|
||||
u32 name; ///< data or tag name
|
||||
u32 sizeAndFlags; ///<
|
||||
};
|
||||
|
||||
template<class T, size_t Size, bool isIntegralType>
|
||||
struct IntergralType;
|
||||
|
||||
template<class T>
|
||||
struct IntergralType<T, 1, true>
|
||||
{
|
||||
static void Write(DrillerOutputStream& stream, u32 name, const T& data)
|
||||
{
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U8) << StreamEntry::dataInternalShift;
|
||||
de.sizeAndFlags |= *reinterpret_cast<const u8*>(&data);
|
||||
stream.WriteBinary(de);
|
||||
}
|
||||
};
|
||||
template<class T>
|
||||
struct IntergralType<T, 2, true>
|
||||
{
|
||||
static void Write(DrillerOutputStream& stream, u32 name, const T& data)
|
||||
{
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U16) << StreamEntry::dataInternalShift;
|
||||
de.sizeAndFlags |= *reinterpret_cast<const u16*>(&data);
|
||||
stream.WriteBinary(de);
|
||||
}
|
||||
};
|
||||
template<class T>
|
||||
struct IntergralType<T, 4, true>
|
||||
{
|
||||
static void Write(DrillerOutputStream& stream, u32 name, const T& data)
|
||||
{
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
const u32* uintData = reinterpret_cast<const u32*>(&data);
|
||||
if (((*uintData) & StreamEntry::dataSizeMask) == *uintData) // check if we can store it internally
|
||||
{
|
||||
de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U29) << StreamEntry::dataInternalShift;
|
||||
de.sizeAndFlags |= *uintData;
|
||||
stream.WriteBinary(de);
|
||||
}
|
||||
else
|
||||
{
|
||||
de.sizeAndFlags = 4;
|
||||
stream.WriteBinary(de);
|
||||
stream.WriteBinary(&data, de.sizeAndFlags);
|
||||
}
|
||||
}
|
||||
};
|
||||
template<class T>
|
||||
struct IntergralType<T, 8, true>
|
||||
{
|
||||
static void Write(DrillerOutputStream& stream, u32 name, const T& data)
|
||||
{
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
const u64* uintData = reinterpret_cast<const u64*>(&data);
|
||||
if (((*uintData) & static_cast<u64>(StreamEntry::dataSizeMask)) == *uintData) // check if we can store it internally
|
||||
{
|
||||
de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U29) << StreamEntry::dataInternalShift;
|
||||
de.sizeAndFlags |= static_cast<u32>(*uintData);
|
||||
stream.WriteBinary(de);
|
||||
}
|
||||
else
|
||||
{
|
||||
de.sizeAndFlags = 8;
|
||||
stream.WriteBinary(de);
|
||||
stream.WriteBinary(&data, de.sizeAndFlags);
|
||||
}
|
||||
}
|
||||
};
|
||||
template<class T>
|
||||
struct IntergralType<T*, sizeof(void*), false>
|
||||
{
|
||||
static void Write(DrillerOutputStream& stream, u32 name, const T* pointer)
|
||||
{
|
||||
size_t id = reinterpret_cast<size_t>(pointer);
|
||||
IntergralType<size_t, sizeof(id), true>::Write(stream, name, id);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* Each stream with start with this header, before anything else.
|
||||
*/
|
||||
struct StreamHeader
|
||||
{
|
||||
StreamHeader()
|
||||
: platform((u8)g_currentPlatform) {}
|
||||
u8 platform;
|
||||
};
|
||||
|
||||
DrillerOutputStream(DrillerStringPool* stringPool = NULL)
|
||||
: m_stringPool(stringPool) { }
|
||||
virtual ~DrillerOutputStream() {}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Write
|
||||
inline void BeginTag(u32 name)
|
||||
{
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = (u32)(StreamEntry::INT_TAG) << StreamEntry::dataInternalShift;
|
||||
de.sizeAndFlags |= 1; // true - open tag
|
||||
WriteBinary(de);
|
||||
}
|
||||
inline void EndTag(u32 name)
|
||||
{
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = (u32)(StreamEntry::INT_TAG) << StreamEntry::dataInternalShift;
|
||||
WriteBinary(de);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Generic
|
||||
template<class T>
|
||||
inline void Write(u32 name, const T& data)
|
||||
{
|
||||
// User should handle non specialized non integral types.
|
||||
IntergralType<T, sizeof(T), AZStd::is_integral<T>::value || AZStd::is_enum<T>::value>::Write(*this, name, data);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Binary and strings
|
||||
inline void Write(u32 name, const void* data, unsigned int dataSize)
|
||||
{
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = dataSize;
|
||||
WriteBinary(de);
|
||||
WriteBinary(data, dataSize);
|
||||
}
|
||||
inline void Write(u32 name, const char* string, bool isCopyString = true)
|
||||
{
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = static_cast<unsigned int>(strlen(string));
|
||||
AZ_Assert(de.sizeAndFlags <= StreamEntry::dataSizeMask, "Invalid string length! String is too long, length is limited to %u bytes!", StreamEntry::dataSizeMask);
|
||||
;
|
||||
if (m_stringPool)
|
||||
{
|
||||
AZ::u32 crc;
|
||||
bool isInserted = isCopyString ? m_stringPool->InsertCopy(string, de.sizeAndFlags, crc) : m_stringPool->Insert(string, de.sizeAndFlags, crc);
|
||||
if (!isInserted) // if already inserted, it means it's in the stream, so store the crc only.
|
||||
{
|
||||
de.sizeAndFlags = (u32)(StreamEntry::INT_POOLED_STRING_CRC32) << StreamEntry::dataInternalShift;
|
||||
de.sizeAndFlags |= sizeof(crc);
|
||||
WriteBinary(de);
|
||||
WriteBinary(&crc, sizeof(crc));
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ::u32 stringSize = de.sizeAndFlags;
|
||||
de.sizeAndFlags |= (u32)(StreamEntry::INT_POOLED_STRING) << StreamEntry::dataInternalShift;
|
||||
WriteBinary(de);
|
||||
WriteBinary(string, stringSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteBinary(de);
|
||||
WriteBinary(string, de.sizeAndFlags);
|
||||
}
|
||||
}
|
||||
template<class Allocator>
|
||||
inline void Write(u32 name, const AZStd::basic_string<AZStd::string::value_type, AZStd::string::traits_type, Allocator>& str, bool isCopyString = true)
|
||||
{
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = static_cast<AZ::u32>(str.size());
|
||||
AZ_Assert(de.sizeAndFlags <= StreamEntry::dataSizeMask, "Invalid string length! String is too long, length is limited to %u bytes!", StreamEntry::dataSizeMask);
|
||||
if (m_stringPool)
|
||||
{
|
||||
AZ::u32 crc;
|
||||
bool isInserted = isCopyString ? m_stringPool->InsertCopy(str.c_str(), de.sizeAndFlags, crc) : m_stringPool->Insert(str.c_str(), de.sizeAndFlags, crc);
|
||||
if (!isInserted) // if already inserted, it means it's in the stream, so store the crc only.
|
||||
{
|
||||
de.sizeAndFlags = (u32)(StreamEntry::INT_POOLED_STRING_CRC32) << StreamEntry::dataInternalShift;
|
||||
de.sizeAndFlags |= sizeof(crc);
|
||||
WriteBinary(de);
|
||||
WriteBinary(&crc, sizeof(crc));
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ::u32 stringSize = de.sizeAndFlags;
|
||||
de.sizeAndFlags |= (u32)(StreamEntry::INT_POOLED_STRING) << StreamEntry::dataInternalShift;
|
||||
WriteBinary(de);
|
||||
WriteBinary(str.data(), stringSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteBinary(de);
|
||||
WriteBinary(str.data(), de.sizeAndFlags);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
inline void Write(u32 name, const AZStd::basic_string<AZStd::wstring::value_type, AZStd::wstring::traits_type, Allocator>& str)
|
||||
{
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = static_cast<AZ::u32>(str.size());
|
||||
AZ_Assert(de.sizeAndFlags <= StreamEntry::dataSizeMask, "Invalid string length! String is too long, length is limited to %u bytes!", StreamEntry::dataSizeMask);
|
||||
WriteBinary(de);
|
||||
WriteBinary(str.data(), de.sizeAndFlags * sizeof(AZStd::wstring::value_type));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// math types
|
||||
inline void Write(u32 name, float f)
|
||||
{
|
||||
Write(name, &f, static_cast<unsigned int>(sizeof(float)));
|
||||
}
|
||||
inline void Write(u32 name, double d)
|
||||
{
|
||||
Write(name, &d, static_cast<unsigned int>(sizeof(double)));
|
||||
}
|
||||
|
||||
void Write(u32 name, const AZ::Vector3& v);
|
||||
void Write(u32 name, const AZ::Vector4& v);
|
||||
void Write(u32 name, const AZ::Aabb& aabb);
|
||||
void Write(u32 name, const AZ::Obb& obb);
|
||||
void Write(u32 name, const AZ::Transform& tm);
|
||||
void Write(u32 name, const AZ::Matrix3x3& tm);
|
||||
void Write(u32 name, const AZ::Matrix4x4& tm);
|
||||
void Write(u32 name, const AZ::Quaternion& tm);
|
||||
void Write(u32 name, const AZ::Plane& plane);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// containers
|
||||
template<class InputIterator>
|
||||
inline void Write(u32 name, InputIterator first, InputIterator last)
|
||||
{
|
||||
// we can specialize for contiguous_iterator_tag so have only 1 write for all elements
|
||||
size_t numElements = AZStd::distance(first, last);
|
||||
size_t elementSize = sizeof(typename AZStd::iterator_traits<InputIterator>::value_type);
|
||||
unsigned int dataSize = static_cast<unsigned int>(numElements * elementSize);
|
||||
StreamEntry de;
|
||||
de.name = name;
|
||||
de.sizeAndFlags = dataSize;
|
||||
AZ_Assert(dataSize < StreamEntry::dataSizeMask, "Invalid data size, size is limited to %d bytes!", StreamEntry::dataSizeMask - 1);
|
||||
WriteBinary(de);
|
||||
//WriteBinary(data,dataSize); for contiguous_iterator_tag
|
||||
for (; first != last; ++first)
|
||||
{
|
||||
WriteBinary(&*first, static_cast<unsigned int>(elementSize));
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Raw data to the output
|
||||
template<class T>
|
||||
inline void WriteBinary(const T& data)
|
||||
{
|
||||
WriteBinary(&data, sizeof(T));
|
||||
}
|
||||
|
||||
virtual void WriteBinary(const void* data, unsigned int dataSize) = 0;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Write a time stamp (AZStd::sys_time_t) in millisecond since 1970/01/01 00:00:00 UTC.
|
||||
* On older windows this function can have ~15 ms resolution, in such cases use \ref GetTimeNowMicroSecond
|
||||
*/
|
||||
void WriteTimeUTC(u32 name);
|
||||
|
||||
/**
|
||||
* Write a time stamp (AZStd::sys_time_t) in micriseconds. This function is inaccurate for long periods but it has ms resolution.
|
||||
* For long periods use \ref WriteTimeUTC.
|
||||
*/
|
||||
void WriteTimeMicrosecond(u32 name);
|
||||
|
||||
/// Called when the driller is moving on the next frame, so you can flush you current buffer to network/disk.
|
||||
virtual void OnEndOfFrame() {}
|
||||
|
||||
/// Sets the string pool used for this stream. To disable the pool just set it to NULL.
|
||||
void SetStringPool(DrillerStringPool* stringPool) { m_stringPool = stringPool; }
|
||||
|
||||
protected:
|
||||
/// Write the Stream header structure (should be endianess independent).
|
||||
void WriteHeader();
|
||||
|
||||
DrillerStringPool* m_stringPool; ///< Optional pointer to a string pool.
|
||||
};
|
||||
|
||||
/**
|
||||
* For efficiency all data read functions are placed with the parsers.
|
||||
*/
|
||||
class DrillerInputStream
|
||||
{
|
||||
public:
|
||||
DrillerInputStream(DrillerStringPool* stringPool = NULL)
|
||||
: m_isEndianSwap(false)
|
||||
, m_stringPool(stringPool) {}
|
||||
virtual ~DrillerInputStream() {}
|
||||
|
||||
bool IsEndianSwap() const { return m_isEndianSwap; }
|
||||
/// Reads binary data from a stream to to maxDataSize. Returns 0 if no more data.
|
||||
virtual unsigned int ReadBinary(void* data, unsigned int maxDataSize) = 0;
|
||||
|
||||
/// Sets the string pool used for this stream. To disable the pool just set it to NULL.
|
||||
void SetStringPool(DrillerStringPool* stringPool) { m_stringPool = stringPool; }
|
||||
DrillerStringPool* GetStringPool() const { return m_stringPool; }
|
||||
|
||||
void SetIdentifier(const char* identifier) { m_streamIdentifier = identifier; }
|
||||
const char* GetIdentifier() const { return m_streamIdentifier.c_str(); }
|
||||
|
||||
protected:
|
||||
/// Read the Stream header structure
|
||||
bool ReadHeader();
|
||||
|
||||
bool m_isEndianSwap;
|
||||
DrillerStringPool* m_stringPool; ///< Optional pointer to a string pool.
|
||||
AZStd::string m_streamIdentifier;
|
||||
};
|
||||
|
||||
/**
|
||||
* Outputs all stream data into a memory buffer. It will grow automatically.
|
||||
*/
|
||||
class DrillerOutputMemoryStream
|
||||
: public DrillerOutputStream
|
||||
{
|
||||
protected:
|
||||
vector<unsigned char>::type m_data;
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(DrillerOutputMemoryStream, OSAllocator, 0)
|
||||
DrillerOutputMemoryStream(size_t memorySize = 2048) { m_data.reserve(memorySize); }
|
||||
const unsigned char* GetData() const { return m_data.data(); }
|
||||
unsigned int GetDataSize() const { return static_cast<unsigned int>(m_data.size()); }
|
||||
inline void Reset() { m_data.clear(); }
|
||||
void WriteBinary(const void* data, unsigned int dataSize) override
|
||||
{
|
||||
m_data.insert(m_data.end(), reinterpret_cast<const unsigned char*>(data), reinterpret_cast<const unsigned char*>(data) + dataSize);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Reads data from a memory stream. Data is NOT copied and must be persistent while we are using it.
|
||||
*/
|
||||
class DrillerInputMemoryStream
|
||||
: public DrillerInputStream
|
||||
{
|
||||
const unsigned char* m_data;
|
||||
const unsigned char* m_dataEnd;
|
||||
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(DrillerInputMemoryStream, OSAllocator, 0)
|
||||
DrillerInputMemoryStream(const char* streamIdentifier = "", const void* data = nullptr, unsigned int dataSize = 0)
|
||||
: DrillerInputStream()
|
||||
, m_data(nullptr)
|
||||
, m_dataEnd(nullptr)
|
||||
{
|
||||
if (data != nullptr)
|
||||
{
|
||||
SetData(streamIdentifier, data, dataSize);
|
||||
}
|
||||
}
|
||||
|
||||
void SetData(const char* streamIdentifier, const void* data, unsigned int dataSize)
|
||||
{
|
||||
SetIdentifier(streamIdentifier);
|
||||
|
||||
AZ_Assert(data != nullptr && dataSize > 0, "We must have a valid pointer %p and data size %d !", data, dataSize);
|
||||
if (m_data == nullptr) // this is the first data chuck, read the platform
|
||||
{
|
||||
m_data = reinterpret_cast<const unsigned char*>(data);
|
||||
m_dataEnd = m_data + dataSize;
|
||||
ReadHeader();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data = reinterpret_cast<const unsigned char*>(data);
|
||||
m_dataEnd = m_data + dataSize;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int GetDataLeft() const { return static_cast<unsigned int>(m_dataEnd - m_data); }
|
||||
unsigned int ReadBinary(void* data, unsigned int maxDataSize) override
|
||||
{
|
||||
AZ_Assert(m_data != nullptr, "You must call SetData function, before you can read data!");
|
||||
AZ_Assert(data != nullptr && maxDataSize > 0, "We must have a valid pointer and max data size!");
|
||||
unsigned int dataToCopy = AZStd::GetMin(static_cast<unsigned int>(m_dataEnd - m_data), maxDataSize);
|
||||
if (dataToCopy)
|
||||
{
|
||||
memcpy(data, m_data, dataToCopy);
|
||||
}
|
||||
m_data += dataToCopy;
|
||||
return dataToCopy;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Outputs driller data to a file (buffered)
|
||||
* IMPORTANT: We provide direct IO classes (instead trough Streamer), because the driller
|
||||
* framework should NOT use engine systems (for example imagine we are drilling the Streamer, using it to
|
||||
* write the drilled data will invalidate all the results as the streamer is unaware which data is driller data and which not)
|
||||
*/
|
||||
class DrillerOutputFileStream
|
||||
: public IO::SystemFile
|
||||
, public DrillerOutputStream
|
||||
{
|
||||
ZLib* m_zlib;
|
||||
vector<unsigned char>::type m_compressionBuffer;
|
||||
vector<unsigned char>::type m_dataBuffer;
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(DrillerOutputFileStream, OSAllocator, 0)
|
||||
DrillerOutputFileStream();
|
||||
~DrillerOutputFileStream();
|
||||
bool Open(const char* fileName, int mode, int platformFlags = 0);
|
||||
void Close();
|
||||
|
||||
void WriteBinary(const void* data, unsigned int dataSize) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads driller data from a file.
|
||||
*/
|
||||
class DrillerInputFileStream
|
||||
: public AZ::IO::SystemFile
|
||||
, public DrillerInputStream
|
||||
{
|
||||
ZLib* m_zlib;
|
||||
vector<unsigned char>::type m_compressedData;
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(DrillerInputFileStream, OSAllocator, 0)
|
||||
DrillerInputFileStream();
|
||||
~DrillerInputFileStream();
|
||||
bool Open(const char* fileName, int mode, int platformFlags = 0);
|
||||
unsigned int ReadBinary(void* data, unsigned int maxDataSize) override;
|
||||
void Close();
|
||||
};
|
||||
|
||||
/**
|
||||
* SAX like stream parser for driller data. We can stream the data
|
||||
* and we will trigger events as tags and data (attributes) arrive. We use less memory this way.
|
||||
* \note SAX is used as reference name, we are NOT trying to compatible with
|
||||
* any specs. (not that SAX has specs)
|
||||
* IMPORTANT: All data callbacks (tag and data) are called in the order they were at store. You can
|
||||
* use this order as event index.
|
||||
*/
|
||||
class DrillerSAXParser
|
||||
{
|
||||
public:
|
||||
struct Data
|
||||
{
|
||||
u32 m_name; ///< Crc name of the data entry.
|
||||
void* m_data; ///< Pointer to copy if the loaded data.
|
||||
unsigned int m_dataSize; ///< Data size in bytes.
|
||||
mutable bool m_isEndianSwap; ///< True if the user will need to swap the endian when he access the data. We swap the data is the storage so we can read it multiple times without swap.
|
||||
DrillerStringPool* m_stringPool; ///< Pointer to optional data string pool.
|
||||
bool m_isPooledString; ///< True if we have a pooled string (stored in the stringPool already).
|
||||
bool m_isPooledStringCrc32; ///< True is we have stored a crc32 (4 bytes) which refers to a string from the String Pool.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Generic
|
||||
template<class T>
|
||||
inline void Read(T& t) const
|
||||
{
|
||||
static_assert(AZStd::is_pod<T>::value, "T must be plain-old-data");
|
||||
|
||||
AZ_Assert(sizeof(t) >= m_dataSize, "You are about to lose some data, this is wrong.");
|
||||
if (m_dataSize == sizeof(t))
|
||||
{
|
||||
// do a memcpy as alignment might be required for some data types! This is not performance critical as we usually load drill files on x86/x64
|
||||
// which doesn't care about alignment.
|
||||
memcpy(&t, m_data, m_dataSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(AZStd::is_pointer<T>::value || AZStd::is_integral<T>::value, "We support extending only for integral types, float and pointers up to 8 bytes!");
|
||||
|
||||
if (AZStd::is_signed<T>::value)
|
||||
{
|
||||
switch (m_dataSize)
|
||||
{
|
||||
case 1:
|
||||
t = static_cast<T>(*reinterpret_cast<s8*>(m_data));
|
||||
break;
|
||||
case 2:
|
||||
t = static_cast<T>(*reinterpret_cast<s16*>(m_data));
|
||||
break;
|
||||
case 4:
|
||||
t = static_cast<T>(*reinterpret_cast<s32*>(m_data));
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "Source data size unsupported... we can extend only 1,2,4 bytes into 2,4,8 bytes integrals");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (m_dataSize)
|
||||
{
|
||||
case 1:
|
||||
t = static_cast<T>(*reinterpret_cast<u8*>(m_data));
|
||||
break;
|
||||
case 2:
|
||||
t = static_cast<T>(*reinterpret_cast<u16*>(m_data));
|
||||
break;
|
||||
case 4:
|
||||
t = static_cast<T>(*reinterpret_cast<u32*>(m_data));
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "Source data size unsupported... we can extend only 1,2,4 bytes into 2,4,8 bytes integrals");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(t);
|
||||
}
|
||||
}
|
||||
|
||||
inline void Read(bool& b) const
|
||||
{
|
||||
u8* data = reinterpret_cast<u8*>(m_data);
|
||||
b = false;
|
||||
for (unsigned int i = 0; i < m_dataSize; ++i)
|
||||
{
|
||||
if (data[i] != 0)
|
||||
{
|
||||
b = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Binary and strings
|
||||
inline unsigned int Read(void* buffer, unsigned int bufferSize) const
|
||||
{
|
||||
unsigned int dataToCopy = AZStd::GetMin(m_dataSize, bufferSize);
|
||||
memcpy(buffer, m_data, dataToCopy);
|
||||
// no data swap
|
||||
return dataToCopy;
|
||||
}
|
||||
// a call avilable only when we use a string pool, it will return the pointer of string in the pool, so you don't need to copy it or do any fancy procedures.
|
||||
inline const char* ReadPooledString() const
|
||||
{
|
||||
AZ_Assert(m_stringPool != nullptr, "This read type is supported only when we use string pool!");
|
||||
unsigned int srcDataSize;
|
||||
return PrepareString(srcDataSize);
|
||||
}
|
||||
inline unsigned int Read(char* string, unsigned int maxNumChars) const
|
||||
{
|
||||
unsigned int srcDataSize;
|
||||
const char* srcData = PrepareString(srcDataSize);
|
||||
unsigned int dataToCopy = AZStd::GetMin(maxNumChars - 1, srcDataSize);
|
||||
memcpy(string, srcData, dataToCopy);
|
||||
string[dataToCopy] = '\0';
|
||||
return dataToCopy;
|
||||
}
|
||||
template<class Allocator>
|
||||
inline unsigned int Read(AZStd::basic_string<AZStd::string::value_type, AZStd::string::traits_type, Allocator>& str) const
|
||||
{
|
||||
unsigned int srcDataSize;
|
||||
const char* srcData = PrepareString(srcDataSize);
|
||||
str = AZStd::basic_string<AZStd::string::value_type, AZStd::string::traits_type, Allocator>(static_cast<const AZStd::string::value_type*>(srcData), srcDataSize);
|
||||
return m_dataSize;
|
||||
}
|
||||
template<class Allocator>
|
||||
inline unsigned int Read(AZStd::basic_string<AZStd::wstring::value_type, AZStd::wstring::traits_type, Allocator>& str) const
|
||||
{
|
||||
// wstring pooling not supported yet
|
||||
str = AZStd::basic_string<AZStd::wstring::value_type, AZStd::wstring::traits_type, Allocator>(static_cast<const AZStd::wstring::value_type*>(m_data), m_dataSize / 2);
|
||||
if (m_isEndianSwap)
|
||||
{
|
||||
AZStd::endian_swap(str.begin(), str.end());
|
||||
}
|
||||
return m_dataSize;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// math types
|
||||
void Read(AZ::Vector3& v) const;
|
||||
void Read(AZ::Vector4& v) const;
|
||||
void Read(AZ::Aabb& aabb) const;
|
||||
void Read(AZ::Obb& obb) const;
|
||||
void Read(AZ::Transform& tm) const;
|
||||
void Read(AZ::Matrix3x3& tm) const;
|
||||
void Read(AZ::Matrix4x4& tm) const;
|
||||
void Read(AZ::Quaternion& tm) const;
|
||||
void Read(AZ::Plane& plane) const;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// containers
|
||||
template<class Container>
|
||||
inline void Read(AZStd::insert_iterator<Container>& iter) const
|
||||
{
|
||||
typedef typename AZStd::insert_iterator<Container> InsertIterator;
|
||||
// we can specialize for contiguous_iterator_tag so have only 1 write for all elements
|
||||
const size_t elementSize = sizeof(InsertIterator::container_type::value_type);
|
||||
size_t numElements = m_dataSize / elementSize;
|
||||
AZ_Assert(m_dataSize % elementSize == 0, "Stored elements size doesn't match the read parameters!");
|
||||
Data elementEntry = *this;
|
||||
elementEntry.m_dataSize = elementSize;
|
||||
char* dataPtr = reinterpret_cast<char*>(m_data);
|
||||
for (size_t i = 0; i < numElements; ++i, ++iter)
|
||||
{
|
||||
typename InsertIterator::container_type::value_type value;
|
||||
elementEntry.m_data = dataPtr;
|
||||
Read(elementEntry, value);
|
||||
iter = value;
|
||||
dataPtr += elementSize;
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
private:
|
||||
const char* PrepareString(unsigned int& stringLength) const;
|
||||
};
|
||||
|
||||
typedef AZStd::delegate<void (u32 /*name*/, bool /*isOpen*/)> TagCallbackType;
|
||||
typedef AZStd::delegate<void (const Data&)> DataCallbackType;
|
||||
|
||||
AZ_CLASS_ALLOCATOR(DrillerSAXParser, OSAllocator, 0)
|
||||
DrillerSAXParser(const TagCallbackType& tcb, const DataCallbackType& dcb);
|
||||
/// Processes an input stream until all data is consumed (read returns 0 bytes).
|
||||
void ProcessStream(DrillerInputStream& stream);
|
||||
protected:
|
||||
|
||||
typedef vector<char>::type BufferType;
|
||||
BufferType m_buffer;
|
||||
TagCallbackType m_tagCallback;
|
||||
DataCallbackType m_dataCallback;
|
||||
};
|
||||
|
||||
/**
|
||||
* DOM like parser, we will load the entire stream in memory (ProcessStream function).
|
||||
* Depending on the data size this can be very memory consuming.
|
||||
* \note DOM is used as reference we are NOT compliant with the DOM specs in any way.
|
||||
* IMPORTANT: All data is stored (for parsing) in the same order the events occurred
|
||||
* or the remote machine. Each next tad or data was recorded in the way. You can use
|
||||
* this as an event index.
|
||||
*/
|
||||
class DrillerDOMParser
|
||||
: public DrillerSAXParser
|
||||
{
|
||||
public:
|
||||
struct Node
|
||||
{
|
||||
typedef forward_list<Data>::type DataListType;
|
||||
typedef forward_list<Node>::type NodeListType;
|
||||
|
||||
u32 m_name;
|
||||
Node* m_parent;
|
||||
DataListType m_data;
|
||||
NodeListType m_tags;
|
||||
|
||||
/// Return a pointer to the first tag with specific name.
|
||||
const Node* GetTag(u32 tagName) const;
|
||||
/// Returns pointer to the first data entry with specific name. NULL if not data has been found.
|
||||
const Data* GetData(u32 dataName) const;
|
||||
/// Returns pointer to the first data entry with specific name. If it can't be found it will assert
|
||||
const Data* GetDataRequired(u32 dataName) const
|
||||
{
|
||||
const Data* dataNode = GetData(dataName);
|
||||
AZ_Assert(dataNode != NULL, "Data node in tag 0x%08x with name 0x%08x is required but missing!", m_name, dataName);
|
||||
return dataNode;
|
||||
}
|
||||
};
|
||||
|
||||
AZ_CLASS_ALLOCATOR(DrillerDOMParser, OSAllocator, 0)
|
||||
|
||||
DrillerDOMParser(bool isPersistentInputData = false);
|
||||
~DrillerDOMParser();
|
||||
/// return true if we are at top level of the tree and we can parse the data safely (there may be still more data, but it's top level only).
|
||||
bool CanParse() const { return m_topNode == &m_root; }
|
||||
|
||||
const Node* GetRootNode() const { return &m_root; }
|
||||
protected:
|
||||
Node m_root;
|
||||
Node* m_topNode;
|
||||
bool m_isPersistentInputData; ///< true if data that we process is persistent so we don't need to copy it internally, false otherwise.
|
||||
|
||||
void OnTag(u32 name, bool isOpen);
|
||||
void OnData(const Data& data);
|
||||
void DeleteNode(Node& node);
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for handling a Tag with a specific name. Handlers are kept in a hierarchy
|
||||
* with one required by DrillerSAXParserHandler to be able to handle tags at a root
|
||||
* level for the driller data stream.
|
||||
*/
|
||||
class DrillerHandlerParser
|
||||
{
|
||||
public:
|
||||
DrillerHandlerParser(bool isWarnOnUnsupportedTags = true)
|
||||
: m_isWarnOnUnsupportedTags(isWarnOnUnsupportedTags) {}
|
||||
|
||||
virtual ~DrillerHandlerParser() {}
|
||||
/// Enumerate all the child tags that we support for the tag we are handling. If the tag is not know you should return NULL
|
||||
virtual DrillerHandlerParser* OnEnterTag(u32 tagName) { (void)tagName; return NULL; }
|
||||
/// Exit tag you are not required to implement this, we always exist tags in order FILO.
|
||||
virtual void OnExitTag(DrillerHandlerParser* handler, u32 tagName) { (void)handler; (void)tagName; }
|
||||
/// Handle that data for the tag we are handling.
|
||||
virtual void OnData(const DrillerSAXParser::Data& dataNode) { (void)dataNode; }
|
||||
/// Return the warning state on unsupported tags (sometime you might want to warn usually) and sometimes not (if you load newer drills, etc.)
|
||||
inline bool IsWarnOnUnsupportedTags() const { return m_isWarnOnUnsupportedTags; }
|
||||
|
||||
protected:
|
||||
bool m_isWarnOnUnsupportedTags;
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes a driller driller and dispatches the data based on the
|
||||
* the DrillerHandlerParser (handlers) and their ability to handle specific tags.
|
||||
* If a tag is NOT found as a child of the current one it will display a warning with the tag name
|
||||
* (useless it's allowed by DrillerHandlerParser::IsWarnOnUnsupportedTags) and process the stream
|
||||
* is a safe manner by skipping all the data and tags we can't handle.
|
||||
*/
|
||||
class DrillerSAXParserHandler
|
||||
: public DrillerSAXParser
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(DrillerSAXParserHandler, OSAllocator, 0)
|
||||
|
||||
DrillerSAXParserHandler(DrillerHandlerParser* rootHandler);
|
||||
|
||||
protected:
|
||||
|
||||
/// Called from DrillerSAXParser when we have an open tag.
|
||||
void OnTag(u32 name, bool isOpen);
|
||||
/// Called from DrillerSAXParser when we have data, which will be forwarded to the handler.
|
||||
void OnData(const DrillerSAXParser::Data& data);
|
||||
|
||||
typedef vector<DrillerHandlerParser*>::type DrillerHandlerStackType;
|
||||
DrillerHandlerStackType m_stack;
|
||||
};
|
||||
} // namespace Debug
|
||||
} // namespace AZ
|
||||
|
||||
#endif // AZCORE_DRILLER_STREAM_H
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_SYSTEM_FILE_BUS_H
|
||||
#define AZCORE_SYSTEM_FILE_BUS_H
|
||||
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/std/parallel/mutex.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace IO
|
||||
{
|
||||
/**
|
||||
* File IO interface. All events return true if we executed the
|
||||
* specific operation and no other code will be executed. If we return false
|
||||
* the normal code for the specific event will be executed.
|
||||
* IMPORTANT: We support multiple listeners with the idea that many systems can listen
|
||||
* for event. This interface allows to actually perform the operations, in such cases make
|
||||
* sure only one of the listeners provides this service (otherwise depending on registration
|
||||
* order service providers may change)
|
||||
* IMPORTANT: We don't provide any sync for the FileIOBus. We do that for a couple of reasons.
|
||||
* 1. If you will handle file IO youself or keeptrack of statistics you code will most likely already do that
|
||||
* 2. It is NOT safe to BusConnect/BusDisconnect while the FileIO is in use (this is why you should connect in advance)
|
||||
* otherwise if you provide service you can end up connecting in a middle of reads/writes/etc.
|
||||
*/
|
||||
class FileIO
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
virtual ~FileIO() {}
|
||||
virtual bool OnOpen(SystemFile& file, const char* fileName, int mode, int platformFlags, bool& isFileOpened) = 0;
|
||||
virtual bool OnClose(SystemFile& file) = 0;
|
||||
virtual bool OnSeek(SystemFile& file, SystemFile::SizeType offset, SystemFile::SeekMode mode) = 0;
|
||||
virtual bool OnRead(SystemFile& file, SystemFile::SizeType byteSize, void* buffer, SystemFile::SizeType& numRead) = 0;
|
||||
virtual bool OnWrite(SystemFile& file, const void* buffer, SystemFile::SizeType byteSize, SystemFile::SizeType& numWritten) = 0;
|
||||
};
|
||||
|
||||
typedef AZ::EBus<FileIO> FileIOBus;
|
||||
|
||||
/**
|
||||
* Interface for handling file io events. All events are syncronized
|
||||
*/
|
||||
class FileIOEvents
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
virtual ~FileIOEvents() {}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
//TODO rbbaklov or zolniery look into why a recursive lock was not needed previously
|
||||
typedef AZStd::recursive_mutex MutexType; //< make sure all file events are thread safe as they will called from many threads
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* You will either have a file (SystemFile) pointer or fileName pointer to the file name.
|
||||
* \param fileName is provided when there is NO SystemFile object (when you call static functions).
|
||||
*/
|
||||
virtual void OnError(const SystemFile* file, const char* fileName, int errorCode) = 0;
|
||||
};
|
||||
|
||||
typedef AZ::EBus<FileIOEvents> FileIOEventBus;
|
||||
}
|
||||
}
|
||||
#endif // AZCORE_SYSTEM_FILE_BUS_H
|
||||
#pragma once
|
||||
@@ -50,13 +50,4 @@ namespace AZ::IO
|
||||
const PathIterator<Path>& rhs);
|
||||
template bool operator!=<FixedMaxPath>(const PathIterator<FixedMaxPath>& lhs,
|
||||
const PathIterator<FixedMaxPath>& rhs);
|
||||
|
||||
void PathReflection::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<AZ::IO::Path>()
|
||||
->Field("m_path", &AZ::IO::Path::m_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,8 @@ namespace AZ::IO
|
||||
|
||||
// native format observers
|
||||
//! Returns string_view stored within the PathView
|
||||
constexpr AZStd::string_view Native() const noexcept;
|
||||
constexpr const AZStd::string_view& Native() const noexcept;
|
||||
constexpr AZStd::string_view& Native() noexcept;
|
||||
//! Conversion operator to retrieve string_view stored within the PathView
|
||||
constexpr explicit operator AZStd::string_view() const noexcept;
|
||||
|
||||
@@ -321,7 +322,6 @@ namespace AZ::IO
|
||||
using const_iterator = const PathIterator<BasicPath>;
|
||||
using iterator = const_iterator;
|
||||
friend PathIterator<BasicPath>;
|
||||
friend struct PathReflection;
|
||||
|
||||
// constructors and destructor
|
||||
constexpr BasicPath() = default;
|
||||
@@ -665,6 +665,7 @@ namespace AZ::IO
|
||||
namespace AZ
|
||||
{
|
||||
AZ_TYPE_INFO_SPECIALIZE(AZ::IO::Path, "{88E0A40F-3085-4CAB-8B11-EF5A2659C71A}");
|
||||
AZ_TYPE_INFO_SPECIALIZE(AZ::IO::FixedMaxPath, "{FA6CA49F-376A-417C-9767-DD50744DF203}");
|
||||
}
|
||||
|
||||
namespace AZ::IO
|
||||
|
||||
@@ -101,7 +101,11 @@ namespace AZ::IO
|
||||
}
|
||||
|
||||
// native format observers
|
||||
constexpr auto PathView::Native() const noexcept -> AZStd::string_view
|
||||
constexpr auto PathView::Native() const noexcept -> const AZStd::string_view&
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
constexpr auto PathView::Native() noexcept -> AZStd::string_view&
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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/IO/Path/Path.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
|
||||
namespace AZ::IO
|
||||
{
|
||||
template <typename PathType>
|
||||
struct PathSerializer
|
||||
: public SerializeContext::IDataSerializer
|
||||
{
|
||||
public:
|
||||
/// Convert binary data to text
|
||||
size_t DataToText(IO::GenericStream& in, IO::GenericStream& out, bool) override
|
||||
{
|
||||
PathType outPath;
|
||||
outPath.Native().resize_no_construct(in.GetLength());
|
||||
in.Read(outPath.Native().size(), outPath.Native().data());
|
||||
|
||||
return static_cast<size_t>(out.Write(outPath.Native().size(), outPath.Native().c_str()));
|
||||
}
|
||||
|
||||
size_t TextToData(const char* text, unsigned int, IO::GenericStream& stream, bool) override
|
||||
{
|
||||
return static_cast<size_t>(stream.Write(strlen(text), reinterpret_cast<const void*>(text)));
|
||||
}
|
||||
|
||||
size_t Save(const void* classPtr, IO::GenericStream& stream, bool) override
|
||||
{
|
||||
/// Save paths out using the PosixPathSeparator
|
||||
PathType path(reinterpret_cast<const PathType*>(classPtr)->Native(), AZ::IO::PosixPathSeparator);
|
||||
path.MakePreferred();
|
||||
|
||||
return static_cast<size_t>(stream.Write(path.Native().size(), path.c_str()));
|
||||
}
|
||||
|
||||
bool Load(void* classPtr, IO::GenericStream& stream, unsigned int, bool) override
|
||||
{
|
||||
// Normalize the path load
|
||||
auto path = reinterpret_cast<PathType*>(classPtr);
|
||||
|
||||
path->Native().resize_no_construct(stream.GetLength());
|
||||
stream.Read(path->Native().size(), path->Native().data());
|
||||
*path = path->LexicallyNormal();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompareValueData(const void* lhs, const void* rhs) override
|
||||
{
|
||||
return SerializeContext::EqualityCompareHelper<Path>::CompareValues(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
void PathReflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
serializeContext->Class<Path>()
|
||||
->Serializer(AZ::SerializeContext::IDataSerializerPtr{ new PathSerializer<Path>{},
|
||||
AZ::SerializeContext::IDataSerializer::CreateDefaultDeleteDeleter() })
|
||||
;
|
||||
|
||||
serializeContext->Class<FixedMaxPath>()
|
||||
->Serializer(AZ::SerializeContext::IDataSerializerPtr{ new PathSerializer<FixedMaxPath>{},
|
||||
AZ::SerializeContext::IDataSerializer::CreateDefaultDeleteDeleter() })
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-1
@@ -8,4 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define STREAM_CACHE_DEFAULT 0
|
||||
namespace AZ
|
||||
{
|
||||
class ReflectContext;
|
||||
}
|
||||
|
||||
namespace AZ::IO
|
||||
{
|
||||
void PathReflect(AZ::ReflectContext* context);
|
||||
}
|
||||
@@ -57,11 +57,6 @@ namespace AZ::IO
|
||||
// It depends on the path type
|
||||
template <typename PathType>
|
||||
class PathIterator;
|
||||
|
||||
struct PathReflection
|
||||
{
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
};
|
||||
}
|
||||
|
||||
namespace AZStd
|
||||
|
||||
@@ -19,144 +19,144 @@
|
||||
#include <AzCore/std/containers/deque.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
|
||||
namespace AZ::IO
|
||||
{
|
||||
struct BlockCacheConfig final :
|
||||
public IStreamerStackConfig
|
||||
{
|
||||
AZ_RTTI(AZ::IO::BlockCacheConfig, "{70120525-88A4-40B6-A75B-BAA7E8FD77F3}", IStreamerStackConfig);
|
||||
AZ_CLASS_ALLOCATOR(BlockCacheConfig, AZ::SystemAllocator, 0);
|
||||
|
||||
~BlockCacheConfig() override = default;
|
||||
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
|
||||
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
//! Dynamic options for the blocks size.
|
||||
//! It's possible to set static sizes or use the names from this enum to have AZ::IO::Streamer automatically fill in the sizes.
|
||||
//! Fixed sizes are set through the Settings Registry with "BlockSize": 524288, while dynamic values are set like
|
||||
//! "BlockSize": "MemoryAlignment". In the latter case AZ::IO::Streamer will use the available hardware information and fill
|
||||
//! in the actual value.
|
||||
enum BlockSize : u32
|
||||
{
|
||||
MaxTransfer = AZStd::numeric_limits<u32>::max(), //!< The largest possible block size.
|
||||
MemoryAlignment = MaxTransfer - 1, //!< The size of the minimal memory requirement of the storage device.
|
||||
SizeAlignment = MemoryAlignment - 1 //!< The minimal read size required by the storage device.
|
||||
};
|
||||
|
||||
//! The overall size of the cache in megabytes.
|
||||
u32 m_cacheSizeMib{ 8 };
|
||||
//! The size of the individual blocks inside the cache.
|
||||
BlockSize m_blockSize{ BlockSize::MemoryAlignment };
|
||||
};
|
||||
|
||||
class BlockCache
|
||||
: public StreamStackEntry
|
||||
{
|
||||
public:
|
||||
BlockCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites);
|
||||
BlockCache(BlockCache&& rhs) = delete;
|
||||
BlockCache(const BlockCache& rhs) = delete;
|
||||
~BlockCache() override;
|
||||
|
||||
BlockCache& operator=(BlockCache&& rhs) = delete;
|
||||
BlockCache& operator=(const BlockCache& rhs) = delete;
|
||||
|
||||
void QueueRequest(FileRequest* request) override;
|
||||
bool ExecuteRequests() override;
|
||||
|
||||
void UpdateStatus(Status& status) const override;
|
||||
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
|
||||
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
|
||||
void AddDelayedRequests(AZStd::vector<FileRequest*>& internalPending);
|
||||
void UpdatePendingRequestEstimations();
|
||||
|
||||
void FlushCache(const RequestPath& filePath);
|
||||
void FlushEntireCache();
|
||||
|
||||
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
|
||||
|
||||
double CalculateHitRatePercentage() const;
|
||||
double CalculateCacheableRatePercentage() const;
|
||||
s32 CalculateAvailableRequestSlots() const;
|
||||
|
||||
protected:
|
||||
static constexpr u32 s_fileNotCached = static_cast<u32>(-1);
|
||||
|
||||
enum class CacheResult
|
||||
{
|
||||
ReadFromCache, //!< Data was found in the cache and reused.
|
||||
CacheMiss, //!< Data wasn't found in the cache and no sub request was queued.
|
||||
Queued, //!< A sub request was created or appended and queued for processing on the next entry in the streamer stack.
|
||||
Delayed //!< There's no more room to queue a new request, so delay the request until a slot becomes available.
|
||||
};
|
||||
|
||||
struct Section
|
||||
{
|
||||
u8* m_output{ nullptr }; //!< The buffer to write the data to.
|
||||
FileRequest* m_parent{ nullptr }; //!< If set, the file request that is split up by this section.
|
||||
FileRequest* m_wait{ nullptr }; //!< If set, this contains a "wait"-operation that blocks an operation chain from continuing until this section has been loaded.
|
||||
u64 m_readOffset{ 0 }; //!< Offset into the file to start reading from.
|
||||
u64 m_readSize{ 0 }; //!< Number of bytes to read from file.
|
||||
u64 m_blockOffset{ 0 }; //!< Offset into the cache block to start copying from.
|
||||
u64 m_copySize{ 0 }; //!< Number of bytes to copy from cache.
|
||||
u32 m_cacheBlockIndex{ s_fileNotCached }; //!< If assigned, the index of the cache block assigned to this section.
|
||||
bool m_used{ false }; //!< Whether or not this section is used in further processing.
|
||||
|
||||
// Add the provided section in front of this one.
|
||||
void Prefix(const Section& section);
|
||||
};
|
||||
|
||||
using TimePoint = AZStd::chrono::system_clock::time_point;
|
||||
|
||||
void ReadFile(FileRequest* request, FileRequest::ReadData& data);
|
||||
void ContinueReadFile(FileRequest* request, u64 fileLength);
|
||||
CacheResult ReadFromCache(FileRequest* request, Section& section, const RequestPath& filePath);
|
||||
CacheResult ReadFromCache(FileRequest* request, Section& section, u32 cacheBlock);
|
||||
CacheResult ServiceFromCache(FileRequest* request, Section& section, const RequestPath& filePath, bool sharedRead);
|
||||
void CompleteRead(FileRequest& request);
|
||||
bool SplitRequest(Section& prolog, Section& main, Section& epilog, const RequestPath& filePath, u64 fileLength,
|
||||
u64 offset, u64 size, u8* buffer) const;
|
||||
|
||||
u8* GetCacheBlockData(u32 index);
|
||||
void TouchBlock(u32 index);
|
||||
AZ::u32 RecycleOldestBlock(const RequestPath& filePath, u64 offset);
|
||||
u32 FindInCache(const RequestPath& filePath, u64 offset) const;
|
||||
bool IsCacheBlockInFlight(u32 index) const;
|
||||
void ResetCacheEntry(u32 index);
|
||||
void ResetCache();
|
||||
|
||||
//! Map of the file requests that are being processed and the sections of the parent requests they'll complete.
|
||||
AZStd::unordered_multimap<FileRequest*, Section> m_pendingRequests;
|
||||
//! List of file sections that were delayed because the cache was full.
|
||||
AZStd::deque<Section> m_delayedSections;
|
||||
|
||||
AZ::Statistics::RunningStatistic m_hitRateStat;
|
||||
AZ::Statistics::RunningStatistic m_cacheableStat;
|
||||
|
||||
u8* m_cache;
|
||||
u64 m_cacheSize;
|
||||
u32 m_blockSize;
|
||||
u32 m_alignment;
|
||||
u32 m_numBlocks;
|
||||
s32 m_numInFlightRequests{ 0 };
|
||||
//! The file path associated with a cache block.
|
||||
AZStd::unique_ptr<RequestPath[]> m_cachedPaths; // Array of m_numBlocks size.
|
||||
//! The offset into the file the cache blocks starts at.
|
||||
AZStd::unique_ptr<u64[]> m_cachedOffsets; // Array of m_numBlocks size.
|
||||
//! The last time the cache block was read from.
|
||||
AZStd::unique_ptr<TimePoint[]> m_blockLastTouched; // Array of m_numBlocks size.
|
||||
//! The file request that's currently read data into the cache block. If null, the block has been read.
|
||||
AZStd::unique_ptr<FileRequest*[]> m_inFlightRequests; // Array of m_numbBlocks size.
|
||||
|
||||
//! The number of requests waiting for meta data to be retrieved.
|
||||
s32 m_numMetaDataRetrievalInProgress{ 0 };
|
||||
//! Whether or not only the epilog ever writes to the cache.
|
||||
bool m_onlyEpilogWrites;
|
||||
};
|
||||
} // namespace AZ::IO
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace IO
|
||||
{
|
||||
struct BlockCacheConfig final :
|
||||
public IStreamerStackConfig
|
||||
{
|
||||
AZ_RTTI(AZ::IO::BlockCacheConfig, "{70120525-88A4-40B6-A75B-BAA7E8FD77F3}", IStreamerStackConfig);
|
||||
AZ_CLASS_ALLOCATOR(BlockCacheConfig, AZ::SystemAllocator, 0);
|
||||
|
||||
~BlockCacheConfig() override = default;
|
||||
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
|
||||
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
//! Dynamic options for the blocks size.
|
||||
//! It's possible to set static sizes or use the names from this enum to have AZ::IO::Streamer automatically fill in the sizes.
|
||||
//! Fixed sizes are set through the Settings Registry with "BlockSize": 524288, while dynamic values are set like
|
||||
//! "BlockSize": "MemoryAlignment". In the latter case AZ::IO::Streamer will use the available hardware information and fill
|
||||
//! in the actual value.
|
||||
enum BlockSize : u32
|
||||
{
|
||||
MaxTransfer = AZStd::numeric_limits<u32>::max(), //!< The largest possible block size.
|
||||
MemoryAlignment = MaxTransfer - 1, //!< The size of the minimal memory requirement of the storage device.
|
||||
SizeAlignment = MemoryAlignment - 1 //!< The minimal read size required by the storage device.
|
||||
};
|
||||
|
||||
//! The overall size of the cache in megabytes.
|
||||
u32 m_cacheSizeMib{ 8 };
|
||||
//! The size of the individual blocks inside the cache.
|
||||
BlockSize m_blockSize{ BlockSize::MemoryAlignment };
|
||||
};
|
||||
|
||||
class BlockCache
|
||||
: public StreamStackEntry
|
||||
{
|
||||
public:
|
||||
BlockCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites);
|
||||
BlockCache(BlockCache&& rhs) = delete;
|
||||
BlockCache(const BlockCache& rhs) = delete;
|
||||
~BlockCache() override;
|
||||
|
||||
BlockCache& operator=(BlockCache&& rhs) = delete;
|
||||
BlockCache& operator=(const BlockCache& rhs) = delete;
|
||||
|
||||
void QueueRequest(FileRequest* request) override;
|
||||
bool ExecuteRequests() override;
|
||||
|
||||
void UpdateStatus(Status& status) const override;
|
||||
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
|
||||
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
|
||||
void AddDelayedRequests(AZStd::vector<FileRequest*>& internalPending);
|
||||
void UpdatePendingRequestEstimations();
|
||||
|
||||
void FlushCache(const RequestPath& filePath);
|
||||
void FlushEntireCache();
|
||||
|
||||
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
|
||||
|
||||
double CalculateHitRatePercentage() const;
|
||||
double CalculateCacheableRatePercentage() const;
|
||||
s32 CalculateAvailableRequestSlots() const;
|
||||
|
||||
protected:
|
||||
static constexpr u32 s_fileNotCached = static_cast<u32>(-1);
|
||||
|
||||
enum class CacheResult
|
||||
{
|
||||
ReadFromCache, //!< Data was found in the cache and reused.
|
||||
CacheMiss, //!< Data wasn't found in the cache and no sub request was queued.
|
||||
Queued, //!< A sub request was created or appended and queued for processing on the next entry in the streamer stack.
|
||||
Delayed //!< There's no more room to queue a new request, so delay the request until a slot becomes available.
|
||||
};
|
||||
|
||||
struct Section
|
||||
{
|
||||
u8* m_output{ nullptr }; //!< The buffer to write the data to.
|
||||
FileRequest* m_parent{ nullptr }; //!< If set, the file request that is split up by this section.
|
||||
FileRequest* m_wait{ nullptr }; //!< If set, this contains a "wait"-operation that blocks an operation chain from continuing until this section has been loaded.
|
||||
u64 m_readOffset{ 0 }; //!< Offset into the file to start reading from.
|
||||
u64 m_readSize{ 0 }; //!< Number of bytes to read from file.
|
||||
u64 m_blockOffset{ 0 }; //!< Offset into the cache block to start copying from.
|
||||
u64 m_copySize{ 0 }; //!< Number of bytes to copy from cache.
|
||||
u32 m_cacheBlockIndex{ s_fileNotCached }; //!< If assigned, the index of the cache block assigned to this section.
|
||||
bool m_used{ false }; //!< Whether or not this section is used in further processing.
|
||||
|
||||
// Add the provided section in front of this one.
|
||||
void Prefix(const Section& section);
|
||||
};
|
||||
|
||||
using TimePoint = AZStd::chrono::system_clock::time_point;
|
||||
|
||||
void ReadFile(FileRequest* request, FileRequest::ReadData& data);
|
||||
void ContinueReadFile(FileRequest* request, u64 fileLength);
|
||||
CacheResult ReadFromCache(FileRequest* request, Section& section, const RequestPath& filePath);
|
||||
CacheResult ReadFromCache(FileRequest* request, Section& section, u32 cacheBlock);
|
||||
CacheResult ServiceFromCache(FileRequest* request, Section& section, const RequestPath& filePath, bool sharedRead);
|
||||
void CompleteRead(FileRequest& request);
|
||||
bool SplitRequest(Section& prolog, Section& main, Section& epilog, const RequestPath& filePath, u64 fileLength,
|
||||
u64 offset, u64 size, u8* buffer) const;
|
||||
|
||||
u8* GetCacheBlockData(u32 index);
|
||||
void TouchBlock(u32 index);
|
||||
AZ::u32 RecycleOldestBlock(const RequestPath& filePath, u64 offset);
|
||||
u32 FindInCache(const RequestPath& filePath, u64 offset) const;
|
||||
bool IsCacheBlockInFlight(u32 index) const;
|
||||
void ResetCacheEntry(u32 index);
|
||||
void ResetCache();
|
||||
|
||||
//! Map of the file requests that are being processed and the sections of the parent requests they'll complete.
|
||||
AZStd::unordered_multimap<FileRequest*, Section> m_pendingRequests;
|
||||
//! List of file sections that were delayed because the cache was full.
|
||||
AZStd::deque<Section> m_delayedSections;
|
||||
|
||||
AZ::Statistics::RunningStatistic m_hitRateStat;
|
||||
AZ::Statistics::RunningStatistic m_cacheableStat;
|
||||
|
||||
u8* m_cache;
|
||||
u64 m_cacheSize;
|
||||
u32 m_blockSize;
|
||||
u32 m_alignment;
|
||||
u32 m_numBlocks;
|
||||
s32 m_numInFlightRequests{ 0 };
|
||||
//! The file path associated with a cache block.
|
||||
AZStd::unique_ptr<RequestPath[]> m_cachedPaths; // Array of m_numBlocks size.
|
||||
//! The offset into the file the cache blocks starts at.
|
||||
AZStd::unique_ptr<u64[]> m_cachedOffsets; // Array of m_numBlocks size.
|
||||
//! The last time the cache block was read from.
|
||||
AZStd::unique_ptr<TimePoint[]> m_blockLastTouched; // Array of m_numBlocks size.
|
||||
//! The file request that's currently read data into the cache block. If null, the block has been read.
|
||||
AZStd::unique_ptr<FileRequest*[]> m_inFlightRequests; // Array of m_numbBlocks size.
|
||||
|
||||
//! The number of requests waiting for meta data to be retrieved.
|
||||
s32 m_numMetaDataRetrievalInProgress{ 0 };
|
||||
//! Whether or not only the epilog ever writes to the cache.
|
||||
bool m_onlyEpilogWrites;
|
||||
};
|
||||
} // namespace IO
|
||||
|
||||
AZ_TYPE_INFO_SPECIALIZE(AZ::IO::BlockCacheConfig::BlockSize, "{5D4D597D-4605-462D-A27D-8046115C5381}");
|
||||
} // namespace AZ
|
||||
|
||||
@@ -18,77 +18,74 @@
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
|
||||
namespace AZ
|
||||
namespace AZ::IO
|
||||
{
|
||||
namespace IO
|
||||
struct DedicatedCacheConfig final :
|
||||
public IStreamerStackConfig
|
||||
{
|
||||
struct DedicatedCacheConfig final :
|
||||
public IStreamerStackConfig
|
||||
{
|
||||
AZ_RTTI(AZ::IO::DedicatedCacheConfig, "{DF0F6029-02B0-464C-9846-524654335BCC}", IStreamerStackConfig);
|
||||
AZ_CLASS_ALLOCATOR(DedicatedCacheConfig, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(AZ::IO::DedicatedCacheConfig, "{DF0F6029-02B0-464C-9846-524654335BCC}", IStreamerStackConfig);
|
||||
AZ_CLASS_ALLOCATOR(DedicatedCacheConfig, AZ::SystemAllocator, 0);
|
||||
|
||||
~DedicatedCacheConfig() override = default;
|
||||
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
|
||||
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
~DedicatedCacheConfig() override = default;
|
||||
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
|
||||
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
//! The size of the individual blocks inside the cache.
|
||||
BlockCacheConfig::BlockSize m_blockSize{ BlockCacheConfig::BlockSize::MemoryAlignment };
|
||||
//! The overall size of the cache in megabytes.
|
||||
u32 m_cacheSizeMib{ 8 };
|
||||
//! If true, only the epilog is written otherwise the prolog and epilog are written. In either case both prolog and epilog are read.
|
||||
//! For uses of the cache that read mostly sequentially this flag should be set to true. If reads are more random than it's better
|
||||
//! to set this flag to false.
|
||||
bool m_writeOnlyEpilog{ true };
|
||||
};
|
||||
//! The size of the individual blocks inside the cache.
|
||||
BlockCacheConfig::BlockSize m_blockSize{ BlockCacheConfig::BlockSize::MemoryAlignment };
|
||||
//! The overall size of the cache in megabytes.
|
||||
u32 m_cacheSizeMib{ 8 };
|
||||
//! If true, only the epilog is written otherwise the prolog and epilog are written. In either case both prolog and epilog are read.
|
||||
//! For uses of the cache that read mostly sequentially this flag should be set to true. If reads are more random than it's better
|
||||
//! to set this flag to false.
|
||||
bool m_writeOnlyEpilog{ true };
|
||||
};
|
||||
|
||||
class DedicatedCache
|
||||
: public StreamStackEntry
|
||||
{
|
||||
public:
|
||||
DedicatedCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites);
|
||||
|
||||
void SetNext(AZStd::shared_ptr<StreamStackEntry> next) override;
|
||||
void SetContext(StreamerContext& context) override;
|
||||
class DedicatedCache
|
||||
: public StreamStackEntry
|
||||
{
|
||||
public:
|
||||
DedicatedCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites);
|
||||
|
||||
void PrepareRequest(FileRequest* request) override;
|
||||
void QueueRequest(FileRequest* request) override;
|
||||
bool ExecuteRequests() override;
|
||||
void SetNext(AZStd::shared_ptr<StreamStackEntry> next) override;
|
||||
void SetContext(StreamerContext& context) override;
|
||||
|
||||
void UpdateStatus(Status& status) const override;
|
||||
void PrepareRequest(FileRequest* request) override;
|
||||
void QueueRequest(FileRequest* request) override;
|
||||
bool ExecuteRequests() override;
|
||||
|
||||
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
|
||||
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
|
||||
void UpdateStatus(Status& status) const override;
|
||||
|
||||
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
|
||||
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
|
||||
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
|
||||
|
||||
private:
|
||||
void CreateDedicatedCache(FileRequest* request, FileRequest::CreateDedicatedCacheData& data);
|
||||
void DestroyDedicatedCache(FileRequest* request, FileRequest::DestroyDedicatedCacheData& data);
|
||||
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
|
||||
|
||||
void ReadFile(FileRequest* request, FileRequest::ReadData& data);
|
||||
size_t FindCache(const RequestPath& filename, FileRange range);
|
||||
size_t FindCache(const RequestPath& filename, u64 offset);
|
||||
private:
|
||||
void CreateDedicatedCache(FileRequest* request, FileRequest::CreateDedicatedCacheData& data);
|
||||
void DestroyDedicatedCache(FileRequest* request, FileRequest::DestroyDedicatedCacheData& data);
|
||||
|
||||
void FlushCache(const RequestPath& filePath);
|
||||
void FlushEntireCache();
|
||||
void ReadFile(FileRequest* request, FileRequest::ReadData& data);
|
||||
size_t FindCache(const RequestPath& filename, FileRange range);
|
||||
size_t FindCache(const RequestPath& filename, u64 offset);
|
||||
|
||||
AZStd::vector<RequestPath> m_cachedFileNames;
|
||||
AZStd::vector<FileRange> m_cachedFileRanges;
|
||||
AZStd::vector<AZStd::unique_ptr<BlockCache>> m_cachedFileCaches;
|
||||
AZStd::vector<size_t> m_cachedFileRefCounts;
|
||||
void FlushCache(const RequestPath& filePath);
|
||||
void FlushEntireCache();
|
||||
|
||||
AZ::Statistics::RunningStatistic m_usagePercentageStat;
|
||||
AZStd::vector<RequestPath> m_cachedFileNames;
|
||||
AZStd::vector<FileRange> m_cachedFileRanges;
|
||||
AZStd::vector<AZStd::unique_ptr<BlockCache>> m_cachedFileCaches;
|
||||
AZStd::vector<size_t> m_cachedFileRefCounts;
|
||||
|
||||
AZ::Statistics::RunningStatistic m_usagePercentageStat;
|
||||
#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
|
||||
AZ::Statistics::RunningStatistic m_overallHitRateStat;
|
||||
AZ::Statistics::RunningStatistic m_overallCacheableRateStat;
|
||||
AZ::Statistics::RunningStatistic m_overallHitRateStat;
|
||||
AZ::Statistics::RunningStatistic m_overallCacheableRateStat;
|
||||
#endif
|
||||
|
||||
u64 m_cacheSize;
|
||||
u32 m_alignment;
|
||||
u32 m_blockSize;
|
||||
bool m_onlyEpilogWrites;
|
||||
};
|
||||
} // namespace IO
|
||||
} // namespace AZ
|
||||
u64 m_cacheSize;
|
||||
u32 m_alignment;
|
||||
u32 m_blockSize;
|
||||
bool m_onlyEpilogWrites;
|
||||
};
|
||||
} // namespace AZ::IO
|
||||
|
||||
@@ -21,403 +21,401 @@
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <AzCore/std/string/string_view.h>
|
||||
|
||||
namespace AZ
|
||||
namespace AZ::IO
|
||||
{
|
||||
namespace IO
|
||||
class StreamStackEntry;
|
||||
class ExternalFileRequest;
|
||||
|
||||
using FileRequestPtr = AZStd::intrusive_ptr<ExternalFileRequest>;
|
||||
|
||||
class FileRequest final
|
||||
{
|
||||
class StreamStackEntry;
|
||||
class ExternalFileRequest;
|
||||
public:
|
||||
inline constexpr static AZStd::chrono::system_clock::time_point s_noDeadlineTime = AZStd::chrono::system_clock::time_point::max();
|
||||
|
||||
using FileRequestPtr = AZStd::intrusive_ptr<ExternalFileRequest>;
|
||||
|
||||
class FileRequest final
|
||||
friend class StreamerContext;
|
||||
friend class ExternalFileRequest;
|
||||
|
||||
//! Stores a reference to the external request so it stays alive while the request is being processed.
|
||||
//! This is needed because Streamer supports fire-and-forget requests since completion can be handled by
|
||||
//! registering a callback.
|
||||
struct ExternalRequestData
|
||||
{
|
||||
public:
|
||||
inline constexpr static AZStd::chrono::system_clock::time_point s_noDeadlineTime = AZStd::chrono::system_clock::time_point::max();
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
|
||||
friend class StreamerContext;
|
||||
friend class ExternalFileRequest;
|
||||
explicit ExternalRequestData(FileRequestPtr&& request);
|
||||
|
||||
//! Stores a reference to the external request so it stays alive while the request is being processed.
|
||||
//! This is needed because Streamer supports fire-and-forget requests since completion can be handled by
|
||||
//! registering a callback.
|
||||
struct ExternalRequestData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
FileRequestPtr m_request; //!< The request that was send to Streamer.
|
||||
};
|
||||
|
||||
explicit ExternalRequestData(FileRequestPtr&& request);
|
||||
|
||||
FileRequestPtr m_request; //!< The request that was send to Streamer.
|
||||
};
|
||||
//! Stores an instance of a RequestPath. To reduce copying instances of a RequestPath functions that
|
||||
//! need a path take them by reference to the original request. In some cases a path originates from
|
||||
//! within in the stack and temporary storage is needed. This struct allows for that temporary storage
|
||||
//! so it can be safely referenced later.
|
||||
struct RequestPathStoreData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
|
||||
//! Stores an instance of a RequestPath. To reduce copying instances of a RequestPath functions that
|
||||
//! need a path take them by reference to the original request. In some cases a path originates from
|
||||
//! within in the stack and temporary storage is needed. This struct allows for that temporary storage
|
||||
//! so it can be safely referenced later.
|
||||
struct RequestPathStoreData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
explicit RequestPathStoreData(RequestPath path);
|
||||
|
||||
explicit RequestPathStoreData(RequestPath path);
|
||||
RequestPath m_path;
|
||||
};
|
||||
|
||||
RequestPath m_path;
|
||||
};
|
||||
//! Request to read data. This is an untranslated request and holds a relative path. The Scheduler
|
||||
//! will translate this to the appropriate ReadData or CompressedReadData.
|
||||
struct ReadRequestData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
|
||||
//! Request to read data. This is an untranslated request and holds a relative path. The Scheduler
|
||||
//! will translate this to the appropriate ReadData or CompressedReadData.
|
||||
struct ReadRequestData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
|
||||
ReadRequestData(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
|
||||
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
|
||||
ReadRequestData(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
|
||||
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
|
||||
~ReadRequestData();
|
||||
|
||||
RequestPath m_path; //!< Relative path to the target file.
|
||||
IStreamerTypes::RequestMemoryAllocator* m_allocator; //!< Allocator used to manage the memory for this request.
|
||||
AZStd::chrono::system_clock::time_point m_deadline; //!< Time by which this request should have been completed.
|
||||
void* m_output; //!< The memory address assigned (during processing) to store the read data to.
|
||||
u64 m_outputSize; //!< The memory size of the addressed used to store the read data.
|
||||
u64 m_offset; //!< The offset in bytes into the file.
|
||||
u64 m_size; //!< The number of bytes to read from the file.
|
||||
IStreamerTypes::Priority m_priority; //!< Priority used for ordering requests. This is used when requests have the same deadline.
|
||||
IStreamerTypes::MemoryType m_memoryType; //!< The type of memory provided by the allocator if used.
|
||||
};
|
||||
|
||||
//! Request to read data. This is a translated request and holds an absolute path and has been
|
||||
//! resolved to the archive file if needed.
|
||||
struct ReadData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
|
||||
ReadData(void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead);
|
||||
|
||||
const RequestPath& m_path; //!< The path to the file that contains the requested data.
|
||||
void* m_output; //!< Target output to write the read data to.
|
||||
u64 m_outputSize; //!< Size of memory m_output points to. This needs to be at least as big as m_size, but can be bigger.
|
||||
u64 m_offset; //!< The offset in bytes into the file.
|
||||
u64 m_size; //!< The number of bytes to read from the file.
|
||||
bool m_sharedRead; //!< True if other code will be reading from the file or the stack entry can exclusively lock.
|
||||
};
|
||||
|
||||
//! Request to read and decompress data.
|
||||
struct CompressedReadData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
|
||||
CompressedReadData(CompressionInfo&& compressionInfo, void* output, u64 readOffset, u64 readSize);
|
||||
|
||||
CompressionInfo m_compressionInfo;
|
||||
void* m_output; //!< Target output to write the read data to.
|
||||
u64 m_readOffset; //!< The offset into the decompressed to start copying from.
|
||||
u64 m_readSize; //!< Number of bytes to read from the decompressed file.
|
||||
};
|
||||
|
||||
//! Holds the progress of an operation chain until this request is explicitly completed.
|
||||
struct WaitData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
};
|
||||
|
||||
//! Checks to see if any node in the stack can find a file at the provided path.
|
||||
struct FileExistsCheckData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
explicit FileExistsCheckData(const RequestPath& path);
|
||||
|
||||
const RequestPath& m_path;
|
||||
bool m_found{ false };
|
||||
};
|
||||
|
||||
//! Searches for a file in the stack and retrieves the meta data. This may be slower than a file exists
|
||||
//! check.
|
||||
struct FileMetaDataRetrievalData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
explicit FileMetaDataRetrievalData(const RequestPath& path);
|
||||
|
||||
const RequestPath& m_path;
|
||||
u64 m_fileSize{ 0 };
|
||||
bool m_found{ false };
|
||||
};
|
||||
|
||||
//! Cancels a request in the stream stack, if possible.
|
||||
struct CancelData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHighest;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
explicit CancelData(FileRequestPtr target);
|
||||
|
||||
FileRequestPtr m_target; //!< The request that will be canceled.
|
||||
};
|
||||
|
||||
//! Updates the priority and deadline of a request that has not been queued yet.
|
||||
struct RescheduleData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
RescheduleData(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, IStreamerTypes::Priority newPriority);
|
||||
|
||||
FileRequestPtr m_target; //!< The request that will be rescheduled.
|
||||
AZStd::chrono::system_clock::time_point m_newDeadline; //!< The new deadline for the request.
|
||||
IStreamerTypes::Priority m_newPriority; //!< The new priority for the request.
|
||||
};
|
||||
|
||||
//! Flushes all references to the provided file in the streaming stack.
|
||||
struct FlushData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
explicit FlushData(RequestPath path);
|
||||
|
||||
RequestPath m_path;
|
||||
};
|
||||
|
||||
//! Flushes all caches in the streaming stack.
|
||||
struct FlushAllData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
};
|
||||
|
||||
//! Creates a cache dedicated to a single file. This is best used for files where blocks are read from
|
||||
//! periodically such as audio banks of video files.
|
||||
struct CreateDedicatedCacheData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
CreateDedicatedCacheData(RequestPath path, const FileRange& range);
|
||||
|
||||
RequestPath m_path;
|
||||
FileRange m_range;
|
||||
};
|
||||
|
||||
//! Destroys a cache dedicated to a single file that was previously created by CreateDedicatedCache
|
||||
struct DestroyDedicatedCacheData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
DestroyDedicatedCacheData(RequestPath path, const FileRange& range);
|
||||
|
||||
RequestPath m_path;
|
||||
FileRange m_range;
|
||||
};
|
||||
|
||||
struct ReportData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityLow;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
enum class ReportType
|
||||
{
|
||||
FileLocks
|
||||
};
|
||||
|
||||
explicit ReportData(ReportType reportType);
|
||||
|
||||
ReportType m_reportType;
|
||||
};
|
||||
|
||||
//! Data for a custom command. This can be used by nodes added extensions that need data that can't be stored
|
||||
//! in the already provided data.
|
||||
struct CustomData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
|
||||
CustomData(AZStd::any data, bool failWhenUnhandled);
|
||||
|
||||
AZStd::any m_data; //!< The data for the custom request.
|
||||
bool m_failWhenUnhandled; //!< Whether or not the request is marked as failed or success when no node process it.
|
||||
};
|
||||
|
||||
using CommandVariant = AZStd::variant<AZStd::monostate, ExternalRequestData, RequestPathStoreData, ReadRequestData, ReadData,
|
||||
CompressedReadData, WaitData, FileExistsCheckData, FileMetaDataRetrievalData, CancelData, RescheduleData, FlushData,
|
||||
FlushAllData, CreateDedicatedCacheData, DestroyDedicatedCacheData, ReportData, CustomData>;
|
||||
using OnCompletionCallback = AZStd::function<void(FileRequest& request)>;
|
||||
|
||||
AZ_CLASS_ALLOCATOR(FileRequest, SystemAllocator, 0);
|
||||
|
||||
enum class Usage : u8
|
||||
{
|
||||
Internal,
|
||||
External
|
||||
};
|
||||
|
||||
void CreateRequestLink(FileRequestPtr&& request);
|
||||
void CreateRequestPathStore(FileRequest* parent, RequestPath path);
|
||||
void CreateReadRequest(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
|
||||
ReadRequestData(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
|
||||
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
|
||||
void CreateReadRequest(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
|
||||
ReadRequestData(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
|
||||
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
|
||||
void CreateRead(FileRequest* parent, void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead = false);
|
||||
void CreateCompressedRead(FileRequest* parent, const CompressionInfo& compressionInfo, void* output,
|
||||
u64 readOffset, u64 readSize);
|
||||
void CreateCompressedRead(FileRequest* parent, CompressionInfo&& compressionInfo, void* output,
|
||||
u64 readOffset, u64 readSize);
|
||||
void CreateWait(FileRequest* parent);
|
||||
void CreateFileExistsCheck(const RequestPath& path);
|
||||
void CreateFileMetaDataRetrieval(const RequestPath& path);
|
||||
void CreateCancel(FileRequestPtr target);
|
||||
void CreateReschedule(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, IStreamerTypes::Priority newPriority);
|
||||
void CreateFlush(RequestPath path);
|
||||
void CreateFlushAll();
|
||||
void CreateDedicatedCacheCreation(RequestPath path, const FileRange& range = {}, FileRequest* parent = nullptr);
|
||||
void CreateDedicatedCacheDestruction(RequestPath path, const FileRange& range = {}, FileRequest* parent = nullptr);
|
||||
void CreateReport(ReportData::ReportType reportType);
|
||||
void CreateCustom(AZStd::any data, bool failWhenUnhandled = true, FileRequest* parent = nullptr);
|
||||
~ReadRequestData();
|
||||
|
||||
void SetCompletionCallback(OnCompletionCallback callback);
|
||||
|
||||
CommandVariant& GetCommand();
|
||||
const CommandVariant& GetCommand() const;
|
||||
|
||||
IStreamerTypes::RequestStatus GetStatus() const;
|
||||
void SetStatus(IStreamerTypes::RequestStatus newStatus);
|
||||
FileRequest* GetParent();
|
||||
const FileRequest* GetParent() const;
|
||||
size_t GetNumDependencies() const;
|
||||
static constexpr size_t GetMaxNumDependencies();
|
||||
//! Whether or not this request should fail if no node in the chain has picked up the request.
|
||||
bool FailsWhenUnhandled() const;
|
||||
|
||||
//! Checks the chain of request for the provided command. Returns the command if found, otherwise null.
|
||||
template<typename T> T* GetCommandFromChain();
|
||||
//! Checks the chain of request for the provided command. Returns the command if found, otherwise null.
|
||||
template<typename T> const T* GetCommandFromChain() const;
|
||||
|
||||
//! Determines if this request is contributing to the external request.
|
||||
bool WorksOn(FileRequestPtr& request) const;
|
||||
|
||||
//! Returns the id that's assigned to the request when it was added to the pending queue.
|
||||
//! The id will always increment so a smaller id means it was originally queued earlier.
|
||||
size_t GetPendingId() const;
|
||||
|
||||
//! Set the estimated completion time for this request and it's immediate parent. The general approach
|
||||
//! to getting the final estimation is to bubble up the estimation, with ever entry in the stack adding
|
||||
//! it's own additional delay.
|
||||
void SetEstimatedCompletion(AZStd::chrono::system_clock::time_point time);
|
||||
AZStd::chrono::system_clock::time_point GetEstimatedCompletion() const;
|
||||
|
||||
private:
|
||||
explicit FileRequest(Usage usage = Usage::Internal);
|
||||
~FileRequest();
|
||||
|
||||
void Reset();
|
||||
void SetOptionalParent(FileRequest* parent);
|
||||
|
||||
inline static void OnCompletionPlaceholder(const FileRequest& /*request*/) {}
|
||||
|
||||
//! Command and parameters for the request.
|
||||
CommandVariant m_command;
|
||||
|
||||
//! Status of the request.
|
||||
AZStd::atomic<IStreamerTypes::RequestStatus> m_status{ IStreamerTypes::RequestStatus::Pending };
|
||||
|
||||
//! Called once the request has completed. This will always be called from the Streamer thread
|
||||
//! and thread safety is the responsibility of called function. When assigning a lambda avoid
|
||||
//! capturing a FileRequestPtr by value as this will cause a circular reference which causes
|
||||
//! the FileRequestPtr to never be released and causes a memory leak. This call will
|
||||
//! block the main Streamer thread until it returns so callbacks should be kept short. If
|
||||
//! a longer running task is needed consider using a job to do the work.
|
||||
OnCompletionCallback m_onCompletion;
|
||||
|
||||
//! Estimated time this request will complete. This is an estimation and depends on many
|
||||
//! factors which can cause it to change drastically from moment to moment.
|
||||
AZStd::chrono::system_clock::time_point m_estimatedCompletion;
|
||||
|
||||
//! The file request that has a dependency on this one. This can be null if there are no
|
||||
//! other request depending on this one to complete.
|
||||
FileRequest* m_parent{ nullptr };
|
||||
|
||||
//! Id assigned when the request is added to the pending queue.
|
||||
size_t m_pendingId{ 0 };
|
||||
|
||||
//! The number of dependent file request that need to complete before this one is done.
|
||||
u16 m_dependencies{ 0 };
|
||||
|
||||
//! Internal request. If this is true the request is created inside the streaming stack and never
|
||||
//! leaves it. If true it will automatically be maintained by the scheduler, if false than it's
|
||||
//! up to the owner to recycle this request.
|
||||
Usage m_usage{ Usage::Internal };
|
||||
|
||||
//! Whether or not this request is currently in a recycle bin. This allows detecting double deletes.
|
||||
bool m_inRecycleBin{ false };
|
||||
RequestPath m_path; //!< Relative path to the target file.
|
||||
IStreamerTypes::RequestMemoryAllocator* m_allocator; //!< Allocator used to manage the memory for this request.
|
||||
AZStd::chrono::system_clock::time_point m_deadline; //!< Time by which this request should have been completed.
|
||||
void* m_output; //!< The memory address assigned (during processing) to store the read data to.
|
||||
u64 m_outputSize; //!< The memory size of the addressed used to store the read data.
|
||||
u64 m_offset; //!< The offset in bytes into the file.
|
||||
u64 m_size; //!< The number of bytes to read from the file.
|
||||
IStreamerTypes::Priority m_priority; //!< Priority used for ordering requests. This is used when requests have the same deadline.
|
||||
IStreamerTypes::MemoryType m_memoryType; //!< The type of memory provided by the allocator if used.
|
||||
};
|
||||
|
||||
class StreamerContext;
|
||||
class FileRequestHandle;
|
||||
|
||||
//! ExternalFileRequest is a wrapper around the FileRequest so it's safe to use outside the
|
||||
//! Streaming Stack. The main differences are that ExternalFileRequest is used in a thread-safe
|
||||
//! context and it doesn't get automatically destroyed upon completion. Instead intrusive_ptr is
|
||||
//! used to handle clean up.
|
||||
class ExternalFileRequest final
|
||||
//! Request to read data. This is a translated request and holds an absolute path and has been
|
||||
//! resolved to the archive file if needed.
|
||||
struct ReadData
|
||||
{
|
||||
friend struct AZStd::IntrusivePtrCountPolicy<ExternalFileRequest>;
|
||||
friend class FileRequestHandle;
|
||||
friend class FileRequest;
|
||||
friend class Streamer;
|
||||
friend class StreamerContext;
|
||||
friend class Scheduler;
|
||||
friend class Device;
|
||||
friend bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(ExternalFileRequest, SystemAllocator, 0);
|
||||
ReadData(void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead);
|
||||
|
||||
explicit ExternalFileRequest(StreamerContext* owner);
|
||||
|
||||
private:
|
||||
void add_ref();
|
||||
void release();
|
||||
|
||||
FileRequest m_request;
|
||||
AZStd::atomic_uint64_t m_refCount{ 0 };
|
||||
StreamerContext* m_owner;
|
||||
const RequestPath& m_path; //!< The path to the file that contains the requested data.
|
||||
void* m_output; //!< Target output to write the read data to.
|
||||
u64 m_outputSize; //!< Size of memory m_output points to. This needs to be at least as big as m_size, but can be bigger.
|
||||
u64 m_offset; //!< The offset in bytes into the file.
|
||||
u64 m_size; //!< The number of bytes to read from the file.
|
||||
bool m_sharedRead; //!< True if other code will be reading from the file or the stack entry can exclusively lock.
|
||||
};
|
||||
|
||||
class FileRequestHandle
|
||||
//! Request to read and decompress data.
|
||||
struct CompressedReadData
|
||||
{
|
||||
public:
|
||||
friend class Streamer;
|
||||
friend bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
|
||||
// Intentional cast operator.
|
||||
FileRequestHandle(FileRequest& request)
|
||||
: m_request(&request)
|
||||
{}
|
||||
CompressedReadData(CompressionInfo&& compressionInfo, void* output, u64 readOffset, u64 readSize);
|
||||
|
||||
// Intentional cast operator.
|
||||
FileRequestHandle(const FileRequestPtr& request)
|
||||
: m_request(request ? &request->m_request : nullptr)
|
||||
{}
|
||||
|
||||
private:
|
||||
FileRequest* m_request;
|
||||
CompressionInfo m_compressionInfo;
|
||||
void* m_output; //!< Target output to write the read data to.
|
||||
u64 m_readOffset; //!< The offset into the decompressed to start copying from.
|
||||
u64 m_readSize; //!< Number of bytes to read from the decompressed file.
|
||||
};
|
||||
|
||||
bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
|
||||
bool operator==(const FileRequestPtr& lhs, const FileRequestHandle& rhs);
|
||||
bool operator!=(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
|
||||
bool operator!=(const FileRequestPtr& lhs, const FileRequestHandle& rhs);
|
||||
} // namespace IO
|
||||
} // namespace AZ
|
||||
//! Holds the progress of an operation chain until this request is explicitly completed.
|
||||
struct WaitData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
inline constexpr static bool s_failWhenUnhandled = true;
|
||||
};
|
||||
|
||||
//! Checks to see if any node in the stack can find a file at the provided path.
|
||||
struct FileExistsCheckData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
explicit FileExistsCheckData(const RequestPath& path);
|
||||
|
||||
const RequestPath& m_path;
|
||||
bool m_found{ false };
|
||||
};
|
||||
|
||||
//! Searches for a file in the stack and retrieves the meta data. This may be slower than a file exists
|
||||
//! check.
|
||||
struct FileMetaDataRetrievalData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
explicit FileMetaDataRetrievalData(const RequestPath& path);
|
||||
|
||||
const RequestPath& m_path;
|
||||
u64 m_fileSize{ 0 };
|
||||
bool m_found{ false };
|
||||
};
|
||||
|
||||
//! Cancels a request in the stream stack, if possible.
|
||||
struct CancelData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHighest;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
explicit CancelData(FileRequestPtr target);
|
||||
|
||||
FileRequestPtr m_target; //!< The request that will be canceled.
|
||||
};
|
||||
|
||||
//! Updates the priority and deadline of a request that has not been queued yet.
|
||||
struct RescheduleData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
RescheduleData(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, IStreamerTypes::Priority newPriority);
|
||||
|
||||
FileRequestPtr m_target; //!< The request that will be rescheduled.
|
||||
AZStd::chrono::system_clock::time_point m_newDeadline; //!< The new deadline for the request.
|
||||
IStreamerTypes::Priority m_newPriority; //!< The new priority for the request.
|
||||
};
|
||||
|
||||
//! Flushes all references to the provided file in the streaming stack.
|
||||
struct FlushData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
explicit FlushData(RequestPath path);
|
||||
|
||||
RequestPath m_path;
|
||||
};
|
||||
|
||||
//! Flushes all caches in the streaming stack.
|
||||
struct FlushAllData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
};
|
||||
|
||||
//! Creates a cache dedicated to a single file. This is best used for files where blocks are read from
|
||||
//! periodically such as audio banks of video files.
|
||||
struct CreateDedicatedCacheData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
CreateDedicatedCacheData(RequestPath path, const FileRange& range);
|
||||
|
||||
RequestPath m_path;
|
||||
FileRange m_range;
|
||||
};
|
||||
|
||||
//! Destroys a cache dedicated to a single file that was previously created by CreateDedicatedCache
|
||||
struct DestroyDedicatedCacheData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
DestroyDedicatedCacheData(RequestPath path, const FileRange& range);
|
||||
|
||||
RequestPath m_path;
|
||||
FileRange m_range;
|
||||
};
|
||||
|
||||
struct ReportData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityLow;
|
||||
inline constexpr static bool s_failWhenUnhandled = false;
|
||||
|
||||
enum class ReportType
|
||||
{
|
||||
FileLocks
|
||||
};
|
||||
|
||||
explicit ReportData(ReportType reportType);
|
||||
|
||||
ReportType m_reportType;
|
||||
};
|
||||
|
||||
//! Data for a custom command. This can be used by nodes added extensions that need data that can't be stored
|
||||
//! in the already provided data.
|
||||
struct CustomData
|
||||
{
|
||||
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
|
||||
|
||||
CustomData(AZStd::any data, bool failWhenUnhandled);
|
||||
|
||||
AZStd::any m_data; //!< The data for the custom request.
|
||||
bool m_failWhenUnhandled; //!< Whether or not the request is marked as failed or success when no node process it.
|
||||
};
|
||||
|
||||
using CommandVariant = AZStd::variant<AZStd::monostate, ExternalRequestData, RequestPathStoreData, ReadRequestData, ReadData,
|
||||
CompressedReadData, WaitData, FileExistsCheckData, FileMetaDataRetrievalData, CancelData, RescheduleData, FlushData,
|
||||
FlushAllData, CreateDedicatedCacheData, DestroyDedicatedCacheData, ReportData, CustomData>;
|
||||
using OnCompletionCallback = AZStd::function<void(FileRequest& request)>;
|
||||
|
||||
AZ_CLASS_ALLOCATOR(FileRequest, SystemAllocator, 0);
|
||||
|
||||
enum class Usage : u8
|
||||
{
|
||||
Internal,
|
||||
External
|
||||
};
|
||||
|
||||
void CreateRequestLink(FileRequestPtr&& request);
|
||||
void CreateRequestPathStore(FileRequest* parent, RequestPath path);
|
||||
void CreateReadRequest(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
|
||||
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
|
||||
void CreateReadRequest(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
|
||||
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
|
||||
void CreateRead(FileRequest* parent, void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead = false);
|
||||
void CreateCompressedRead(FileRequest* parent, const CompressionInfo& compressionInfo, void* output,
|
||||
u64 readOffset, u64 readSize);
|
||||
void CreateCompressedRead(FileRequest* parent, CompressionInfo&& compressionInfo, void* output,
|
||||
u64 readOffset, u64 readSize);
|
||||
void CreateWait(FileRequest* parent);
|
||||
void CreateFileExistsCheck(const RequestPath& path);
|
||||
void CreateFileMetaDataRetrieval(const RequestPath& path);
|
||||
void CreateCancel(FileRequestPtr target);
|
||||
void CreateReschedule(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, IStreamerTypes::Priority newPriority);
|
||||
void CreateFlush(RequestPath path);
|
||||
void CreateFlushAll();
|
||||
void CreateDedicatedCacheCreation(RequestPath path, const FileRange& range = {}, FileRequest* parent = nullptr);
|
||||
void CreateDedicatedCacheDestruction(RequestPath path, const FileRange& range = {}, FileRequest* parent = nullptr);
|
||||
void CreateReport(ReportData::ReportType reportType);
|
||||
void CreateCustom(AZStd::any data, bool failWhenUnhandled = true, FileRequest* parent = nullptr);
|
||||
|
||||
void SetCompletionCallback(OnCompletionCallback callback);
|
||||
|
||||
CommandVariant& GetCommand();
|
||||
const CommandVariant& GetCommand() const;
|
||||
|
||||
IStreamerTypes::RequestStatus GetStatus() const;
|
||||
void SetStatus(IStreamerTypes::RequestStatus newStatus);
|
||||
FileRequest* GetParent();
|
||||
const FileRequest* GetParent() const;
|
||||
size_t GetNumDependencies() const;
|
||||
static constexpr size_t GetMaxNumDependencies();
|
||||
//! Whether or not this request should fail if no node in the chain has picked up the request.
|
||||
bool FailsWhenUnhandled() const;
|
||||
|
||||
//! Checks the chain of request for the provided command. Returns the command if found, otherwise null.
|
||||
template<typename T> T* GetCommandFromChain();
|
||||
//! Checks the chain of request for the provided command. Returns the command if found, otherwise null.
|
||||
template<typename T> const T* GetCommandFromChain() const;
|
||||
|
||||
//! Determines if this request is contributing to the external request.
|
||||
bool WorksOn(FileRequestPtr& request) const;
|
||||
|
||||
//! Returns the id that's assigned to the request when it was added to the pending queue.
|
||||
//! The id will always increment so a smaller id means it was originally queued earlier.
|
||||
size_t GetPendingId() const;
|
||||
|
||||
//! Set the estimated completion time for this request and it's immediate parent. The general approach
|
||||
//! to getting the final estimation is to bubble up the estimation, with ever entry in the stack adding
|
||||
//! it's own additional delay.
|
||||
void SetEstimatedCompletion(AZStd::chrono::system_clock::time_point time);
|
||||
AZStd::chrono::system_clock::time_point GetEstimatedCompletion() const;
|
||||
|
||||
private:
|
||||
explicit FileRequest(Usage usage = Usage::Internal);
|
||||
~FileRequest();
|
||||
|
||||
void Reset();
|
||||
void SetOptionalParent(FileRequest* parent);
|
||||
|
||||
inline static void OnCompletionPlaceholder(const FileRequest& /*request*/) {}
|
||||
|
||||
//! Command and parameters for the request.
|
||||
CommandVariant m_command;
|
||||
|
||||
//! Status of the request.
|
||||
AZStd::atomic<IStreamerTypes::RequestStatus> m_status{ IStreamerTypes::RequestStatus::Pending };
|
||||
|
||||
//! Called once the request has completed. This will always be called from the Streamer thread
|
||||
//! and thread safety is the responsibility of called function. When assigning a lambda avoid
|
||||
//! capturing a FileRequestPtr by value as this will cause a circular reference which causes
|
||||
//! the FileRequestPtr to never be released and causes a memory leak. This call will
|
||||
//! block the main Streamer thread until it returns so callbacks should be kept short. If
|
||||
//! a longer running task is needed consider using a job to do the work.
|
||||
OnCompletionCallback m_onCompletion;
|
||||
|
||||
//! Estimated time this request will complete. This is an estimation and depends on many
|
||||
//! factors which can cause it to change drastically from moment to moment.
|
||||
AZStd::chrono::system_clock::time_point m_estimatedCompletion;
|
||||
|
||||
//! The file request that has a dependency on this one. This can be null if there are no
|
||||
//! other request depending on this one to complete.
|
||||
FileRequest* m_parent{ nullptr };
|
||||
|
||||
//! Id assigned when the request is added to the pending queue.
|
||||
size_t m_pendingId{ 0 };
|
||||
|
||||
//! The number of dependent file request that need to complete before this one is done.
|
||||
u16 m_dependencies{ 0 };
|
||||
|
||||
//! Internal request. If this is true the request is created inside the streaming stack and never
|
||||
//! leaves it. If true it will automatically be maintained by the scheduler, if false than it's
|
||||
//! up to the owner to recycle this request.
|
||||
Usage m_usage{ Usage::Internal };
|
||||
|
||||
//! Whether or not this request is currently in a recycle bin. This allows detecting double deletes.
|
||||
bool m_inRecycleBin{ false };
|
||||
};
|
||||
|
||||
class StreamerContext;
|
||||
class FileRequestHandle;
|
||||
|
||||
//! ExternalFileRequest is a wrapper around the FileRequest so it's safe to use outside the
|
||||
//! Streaming Stack. The main differences are that ExternalFileRequest is used in a thread-safe
|
||||
//! context and it doesn't get automatically destroyed upon completion. Instead intrusive_ptr is
|
||||
//! used to handle clean up.
|
||||
class ExternalFileRequest final
|
||||
{
|
||||
friend struct AZStd::IntrusivePtrCountPolicy<ExternalFileRequest>;
|
||||
friend class FileRequestHandle;
|
||||
friend class FileRequest;
|
||||
friend class Streamer;
|
||||
friend class StreamerContext;
|
||||
friend class Scheduler;
|
||||
friend class Device;
|
||||
friend bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
|
||||
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(ExternalFileRequest, SystemAllocator, 0);
|
||||
|
||||
explicit ExternalFileRequest(StreamerContext* owner);
|
||||
|
||||
private:
|
||||
void add_ref();
|
||||
void release();
|
||||
|
||||
FileRequest m_request;
|
||||
AZStd::atomic_uint64_t m_refCount{ 0 };
|
||||
StreamerContext* m_owner;
|
||||
};
|
||||
|
||||
class FileRequestHandle
|
||||
{
|
||||
public:
|
||||
friend class Streamer;
|
||||
friend bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
|
||||
|
||||
// Intentional cast operator.
|
||||
FileRequestHandle(FileRequest& request)
|
||||
: m_request(&request)
|
||||
{}
|
||||
|
||||
// Intentional cast operator.
|
||||
FileRequestHandle(const FileRequestPtr& request)
|
||||
: m_request(request ? &request->m_request : nullptr)
|
||||
{}
|
||||
|
||||
private:
|
||||
FileRequest* m_request;
|
||||
};
|
||||
|
||||
bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
|
||||
bool operator==(const FileRequestPtr& lhs, const FileRequestHandle& rhs);
|
||||
bool operator!=(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
|
||||
bool operator!=(const FileRequestPtr& lhs, const FileRequestHandle& rhs);
|
||||
|
||||
} // namespace AZ::IO
|
||||
|
||||
#include <AzCore/IO/Streamer/FileRequest.inl>
|
||||
|
||||
@@ -19,118 +19,115 @@
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzCore/Statistics/RunningStatistic.h>
|
||||
|
||||
namespace AZ
|
||||
namespace AZ::IO
|
||||
{
|
||||
namespace IO
|
||||
struct FullFileDecompressorConfig final :
|
||||
public IStreamerStackConfig
|
||||
{
|
||||
struct FullFileDecompressorConfig final :
|
||||
public IStreamerStackConfig
|
||||
AZ_RTTI(AZ::IO::FullFileDecompressorConfig, "{C96B7EC1-8C73-4493-A7CB-66F5D550FC3A}", IStreamerStackConfig);
|
||||
AZ_CLASS_ALLOCATOR(FullFileDecompressorConfig, AZ::SystemAllocator, 0);
|
||||
|
||||
~FullFileDecompressorConfig() override = default;
|
||||
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
|
||||
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
//! Maximum number of reads that are kept in flight.
|
||||
u32 m_maxNumReads{ 2 };
|
||||
//! Maximum number of decompression jobs that can run simultaneously.
|
||||
u32 m_maxNumJobs{ 2 };
|
||||
};
|
||||
|
||||
//! Entry in the streaming stack that decompresses files from an archive that are stored
|
||||
//! as single files and without equally distributed seek points.
|
||||
//! Because the target archive has compressed the entire file, it needs to be decompressed
|
||||
//! completely, so even if the file is partially read, it needs to be fully loaded. This
|
||||
//! also means that there's no upper limit to the memory so every decompression job will
|
||||
//! need to allocate memory as a temporary buffer (in-place decompression is not supported).
|
||||
//! Finally, the lack of an upper limit also means that the duration of the decompression job
|
||||
//! can vary largely so a dedicated job system is used to decompress on to avoid blocking
|
||||
//! the main job system from working.
|
||||
class FullFileDecompressor
|
||||
: public StreamStackEntry
|
||||
{
|
||||
public:
|
||||
FullFileDecompressor(u32 maxNumReads, u32 maxNumJobs, u32 alignment);
|
||||
~FullFileDecompressor() override = default;
|
||||
|
||||
void PrepareRequest(FileRequest* request) override;
|
||||
void QueueRequest(FileRequest* request) override;
|
||||
bool ExecuteRequests() override;
|
||||
|
||||
void UpdateStatus(Status& status) const override;
|
||||
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
|
||||
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
|
||||
|
||||
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
|
||||
|
||||
private:
|
||||
using Buffer = u8*;
|
||||
|
||||
enum class ReadBufferStatus : uint8_t
|
||||
{
|
||||
AZ_RTTI(AZ::IO::FullFileDecompressorConfig, "{C96B7EC1-8C73-4493-A7CB-66F5D550FC3A}", IStreamerStackConfig);
|
||||
AZ_CLASS_ALLOCATOR(FullFileDecompressorConfig, AZ::SystemAllocator, 0);
|
||||
|
||||
~FullFileDecompressorConfig() override = default;
|
||||
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
|
||||
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
//! Maximum number of reads that are kept in flight.
|
||||
u32 m_maxNumReads{ 2 };
|
||||
//! Maximum number of decompression jobs that can run simultaneously.
|
||||
u32 m_maxNumJobs{ 2 };
|
||||
Unused,
|
||||
ReadInFlight,
|
||||
PendingDecompression
|
||||
};
|
||||
|
||||
//! Entry in the streaming stack that decompresses files from an archive that are stored
|
||||
//! as single files and without equally distributed seek points.
|
||||
//! Because the target archive has compressed the entire file, it needs to be decompressed
|
||||
//! completely, so even if the file is partially read, it needs to be fully loaded. This
|
||||
//! also means that there's no upper limit to the memory so every decompression job will
|
||||
//! need to allocate memory as a temporary buffer (in-place decompression is not supported).
|
||||
//! Finally, the lack of an upper limit also means that the duration of the decompression job
|
||||
//! can vary largely so a dedicated job system is used to decompress on to avoid blocking
|
||||
//! the main job system from working.
|
||||
class FullFileDecompressor
|
||||
: public StreamStackEntry
|
||||
struct DecompressionInformation
|
||||
{
|
||||
public:
|
||||
FullFileDecompressor(u32 maxNumReads, u32 maxNumJobs, u32 alignment);
|
||||
~FullFileDecompressor() override = default;
|
||||
bool IsProcessing() const;
|
||||
|
||||
void PrepareRequest(FileRequest* request) override;
|
||||
void QueueRequest(FileRequest* request) override;
|
||||
bool ExecuteRequests() override;
|
||||
|
||||
void UpdateStatus(Status& status) const override;
|
||||
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
|
||||
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
|
||||
|
||||
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
|
||||
AZStd::chrono::high_resolution_clock::time_point m_queueStartTime;
|
||||
AZStd::chrono::high_resolution_clock::time_point m_jobStartTime;
|
||||
Buffer m_compressedData{ nullptr };
|
||||
FileRequest* m_waitRequest{ nullptr };
|
||||
u32 m_alignmentOffset{ 0 };
|
||||
};
|
||||
|
||||
private:
|
||||
using Buffer = u8*;
|
||||
bool IsIdle() const;
|
||||
|
||||
enum class ReadBufferStatus : uint8_t
|
||||
{
|
||||
Unused,
|
||||
ReadInFlight,
|
||||
PendingDecompression
|
||||
};
|
||||
void PrepareReadRequest(FileRequest* request, FileRequest::ReadRequestData& data);
|
||||
void PrepareDedicatedCache(FileRequest* request, const RequestPath& path);
|
||||
void FileExistsCheck(FileRequest* checkRequest);
|
||||
|
||||
struct DecompressionInformation
|
||||
{
|
||||
bool IsProcessing() const;
|
||||
void EstimateCompressedReadRequest(FileRequest* request, AZStd::chrono::microseconds& cumulativeDelay,
|
||||
AZStd::chrono::microseconds decompressionDelay, double totalDecompressionDurationUs, double totalBytesDecompressed) const;
|
||||
|
||||
AZStd::chrono::high_resolution_clock::time_point m_queueStartTime;
|
||||
AZStd::chrono::high_resolution_clock::time_point m_jobStartTime;
|
||||
Buffer m_compressedData{ nullptr };
|
||||
FileRequest* m_waitRequest{ nullptr };
|
||||
u32 m_alignmentOffset{ 0 };
|
||||
};
|
||||
void StartArchiveRead(FileRequest* compressedReadRequest);
|
||||
void FinishArchiveRead(FileRequest* readRequest, u32 readSlot);
|
||||
bool StartDecompressions();
|
||||
void FinishDecompression(FileRequest* waitRequest, u32 jobSlot);
|
||||
|
||||
bool IsIdle() const;
|
||||
static void FullDecompression(StreamerContext* context, DecompressionInformation& info);
|
||||
static void PartialDecompression(StreamerContext* context, DecompressionInformation& info);
|
||||
|
||||
void PrepareReadRequest(FileRequest* request, FileRequest::ReadRequestData& data);
|
||||
void PrepareDedicatedCache(FileRequest* request, const RequestPath& path);
|
||||
void FileExistsCheck(FileRequest* checkRequest);
|
||||
AZStd::deque<FileRequest*> m_pendingReads;
|
||||
AZStd::deque<FileRequest*> m_pendingFileExistChecks;
|
||||
|
||||
void EstimateCompressedReadRequest(FileRequest* request, AZStd::chrono::microseconds& cumulativeDelay,
|
||||
AZStd::chrono::microseconds decompressionDelay, double totalDecompressionDurationUs, double totalBytesDecompressed) const;
|
||||
|
||||
void StartArchiveRead(FileRequest* compressedReadRequest);
|
||||
void FinishArchiveRead(FileRequest* readRequest, u32 readSlot);
|
||||
bool StartDecompressions();
|
||||
void FinishDecompression(FileRequest* waitRequest, u32 jobSlot);
|
||||
|
||||
static void FullDecompression(StreamerContext* context, DecompressionInformation& info);
|
||||
static void PartialDecompression(StreamerContext* context, DecompressionInformation& info);
|
||||
|
||||
AZStd::deque<FileRequest*> m_pendingReads;
|
||||
AZStd::deque<FileRequest*> m_pendingFileExistChecks;
|
||||
|
||||
AverageWindow<size_t, double, s_statisticsWindowSize> m_decompressionJobDelayMicroSec;
|
||||
AverageWindow<size_t, double, s_statisticsWindowSize> m_decompressionDurationMicroSec;
|
||||
AverageWindow<size_t, double, s_statisticsWindowSize> m_bytesDecompressed;
|
||||
AverageWindow<size_t, double, s_statisticsWindowSize> m_decompressionJobDelayMicroSec;
|
||||
AverageWindow<size_t, double, s_statisticsWindowSize> m_decompressionDurationMicroSec;
|
||||
AverageWindow<size_t, double, s_statisticsWindowSize> m_bytesDecompressed;
|
||||
#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
|
||||
AZ::Statistics::RunningStatistic m_decompressionBoundStat;
|
||||
AZ::Statistics::RunningStatistic m_readBoundStat;
|
||||
AZ::Statistics::RunningStatistic m_decompressionBoundStat;
|
||||
AZ::Statistics::RunningStatistic m_readBoundStat;
|
||||
#endif
|
||||
|
||||
AZStd::unique_ptr<Buffer[]> m_readBuffers;
|
||||
// Nullptr if not reading, the read request if reading the file and the wait request for decompression when waiting on decompression.
|
||||
AZStd::unique_ptr<FileRequest*[]> m_readRequests;
|
||||
AZStd::unique_ptr<ReadBufferStatus[]> m_readBufferStatus;
|
||||
|
||||
AZStd::unique_ptr<DecompressionInformation[]> m_processingJobs;
|
||||
AZStd::unique_ptr<JobManager> m_decompressionJobManager;
|
||||
AZStd::unique_ptr<JobContext> m_decompressionjobContext;
|
||||
AZStd::unique_ptr<Buffer[]> m_readBuffers;
|
||||
// Nullptr if not reading, the read request if reading the file and the wait request for decompression when waiting on decompression.
|
||||
AZStd::unique_ptr<FileRequest*[]> m_readRequests;
|
||||
AZStd::unique_ptr<ReadBufferStatus[]> m_readBufferStatus;
|
||||
|
||||
size_t m_memoryUsage{ 0 }; //!< Amount of memory used for buffers by the decompressor.
|
||||
u32 m_maxNumReads{ 2 };
|
||||
u32 m_numInFlightReads{ 0 };
|
||||
u32 m_numPendingDecompression{ 0 };
|
||||
u32 m_maxNumJobs{ 1 };
|
||||
u32 m_numRunningJobs{ 0 };
|
||||
u32 m_alignment{ 0 };
|
||||
};
|
||||
} // namespace IO
|
||||
} // namespace AZ
|
||||
AZStd::unique_ptr<DecompressionInformation[]> m_processingJobs;
|
||||
AZStd::unique_ptr<JobManager> m_decompressionJobManager;
|
||||
AZStd::unique_ptr<JobContext> m_decompressionjobContext;
|
||||
|
||||
size_t m_memoryUsage{ 0 }; //!< Amount of memory used for buffers by the decompressor.
|
||||
u32 m_maxNumReads{ 2 };
|
||||
u32 m_numInFlightReads{ 0 };
|
||||
u32 m_numPendingDecompression{ 0 };
|
||||
u32 m_maxNumJobs{ 1 };
|
||||
u32 m_numRunningJobs{ 0 };
|
||||
u32 m_alignment{ 0 };
|
||||
};
|
||||
} // namespace AZ::IO
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace AZ::IO
|
||||
{
|
||||
auto parentReadRequest = next->GetCommandFromChain<FileRequest::ReadRequestData>();
|
||||
AZ_Assert(parentReadRequest != nullptr, "The issued read request can't be found for the (compressed) read command.");
|
||||
|
||||
|
||||
size_t size = parentReadRequest->m_size;
|
||||
if (parentReadRequest->m_output == nullptr)
|
||||
{
|
||||
@@ -266,7 +266,7 @@ namespace AZ::IO
|
||||
m_processingStartTime = AZStd::chrono::system_clock::now();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData>)
|
||||
{
|
||||
m_threadData.m_lastFilePath = args.m_path;
|
||||
@@ -411,7 +411,7 @@ namespace AZ::IO
|
||||
++pendingIt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
m_threadData.m_streamStack->QueueRequest(request);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
namespace AZ::IO
|
||||
{
|
||||
class FileRequest;
|
||||
|
||||
|
||||
class Scheduler final
|
||||
{
|
||||
public:
|
||||
@@ -63,7 +63,7 @@ namespace AZ::IO
|
||||
void Thread_ProcessTillIdle();
|
||||
void Thread_ProcessCancelRequest(FileRequest* request, FileRequest::CancelData& data);
|
||||
void Thread_ProcessRescheduleRequest(FileRequest* request, FileRequest::RescheduleData& data);
|
||||
|
||||
|
||||
enum class Order
|
||||
{
|
||||
FirstRequest, //< The first request is the most important to process next.
|
||||
|
||||
@@ -16,454 +16,451 @@
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzCore/std/typetraits/decay.h>
|
||||
|
||||
namespace AZ
|
||||
namespace AZ::IO
|
||||
{
|
||||
namespace IO
|
||||
AZStd::shared_ptr<StreamStackEntry> StorageDriveConfig::AddStreamStackEntry(
|
||||
[[maybe_unused]] const HardwareInformation& hardware, [[maybe_unused]] AZStd::shared_ptr<StreamStackEntry> parent)
|
||||
{
|
||||
AZStd::shared_ptr<StreamStackEntry> StorageDriveConfig::AddStreamStackEntry(
|
||||
[[maybe_unused]] const HardwareInformation& hardware, [[maybe_unused]] AZStd::shared_ptr<StreamStackEntry> parent)
|
||||
{
|
||||
return AZStd::make_shared<StorageDrive>(m_maxFileHandles);
|
||||
}
|
||||
return AZStd::make_shared<StorageDrive>(m_maxFileHandles);
|
||||
}
|
||||
|
||||
void StorageDriveConfig::Reflect(AZ::ReflectContext* context)
|
||||
void StorageDriveConfig::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
serializeContext->Class<StorageDriveConfig, IStreamerStackConfig>()
|
||||
->Version(1)
|
||||
->Field("MaxFileHandles", &StorageDriveConfig::m_maxFileHandles);
|
||||
}
|
||||
}
|
||||
|
||||
const AZStd::chrono::microseconds StorageDrive::s_averageSeekTime =
|
||||
AZStd::chrono::milliseconds(9) + // Common average seek time for desktop hdd drives.
|
||||
AZStd::chrono::milliseconds(3); // Rotational latency for a 7200RPM disk
|
||||
|
||||
StorageDrive::StorageDrive(u32 maxFileHandles)
|
||||
: StreamStackEntry("Storage drive (generic)")
|
||||
{
|
||||
m_fileLastUsed.resize(maxFileHandles, AZStd::chrono::system_clock::time_point::min());
|
||||
m_filePaths.resize(maxFileHandles);
|
||||
m_fileHandles.resize(maxFileHandles);
|
||||
|
||||
// Add initial dummy values to the stats to avoid division by zero later on and avoid needing branches.
|
||||
m_readSizeAverage.PushEntry(1);
|
||||
m_readTimeAverage.PushEntry(AZStd::chrono::microseconds(1));
|
||||
}
|
||||
|
||||
void StorageDrive::SetNext(AZStd::shared_ptr<StreamStackEntry> /*next*/)
|
||||
{
|
||||
AZ_Assert(false, "StorageDrive isn't allowed to have a node to forward requests to.");
|
||||
}
|
||||
|
||||
void StorageDrive::PrepareRequest(FileRequest* request)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzCore);
|
||||
AZ_Assert(request, "PrepareRequest was provided a null request.");
|
||||
|
||||
if (AZStd::holds_alternative<FileRequest::ReadRequestData>(request->GetCommand()))
|
||||
{
|
||||
auto& readRequest = AZStd::get<FileRequest::ReadRequestData>(request->GetCommand());
|
||||
|
||||
FileRequest* read = m_context->GetNewInternalRequest();
|
||||
read->CreateRead(request, readRequest.m_output, readRequest.m_outputSize, readRequest.m_path,
|
||||
readRequest.m_offset, readRequest.m_size);
|
||||
m_context->PushPreparedRequest(read);
|
||||
return;
|
||||
}
|
||||
StreamStackEntry::PrepareRequest(request);
|
||||
}
|
||||
|
||||
void StorageDrive::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> ||
|
||||
AZStd::is_same_v<Command, FileRequest::FileExistsCheckData> ||
|
||||
AZStd::is_same_v<Command, FileRequest::FileMetaDataRetrievalData>)
|
||||
{
|
||||
serializeContext->Class<StorageDriveConfig, IStreamerStackConfig>()
|
||||
->Version(1)
|
||||
->Field("MaxFileHandles", &StorageDriveConfig::m_maxFileHandles);
|
||||
}
|
||||
}
|
||||
|
||||
const AZStd::chrono::microseconds StorageDrive::s_averageSeekTime =
|
||||
AZStd::chrono::milliseconds(9) + // Common average seek time for desktop hdd drives.
|
||||
AZStd::chrono::milliseconds(3); // Rotational latency for a 7200RPM disk
|
||||
|
||||
StorageDrive::StorageDrive(u32 maxFileHandles)
|
||||
: StreamStackEntry("Storage drive (generic)")
|
||||
{
|
||||
m_fileLastUsed.resize(maxFileHandles, AZStd::chrono::system_clock::time_point::min());
|
||||
m_filePaths.resize(maxFileHandles);
|
||||
m_fileHandles.resize(maxFileHandles);
|
||||
|
||||
// Add initial dummy values to the stats to avoid division by zero later on and avoid needing branches.
|
||||
m_readSizeAverage.PushEntry(1);
|
||||
m_readTimeAverage.PushEntry(AZStd::chrono::microseconds(1));
|
||||
}
|
||||
|
||||
void StorageDrive::SetNext(AZStd::shared_ptr<StreamStackEntry> /*next*/)
|
||||
{
|
||||
AZ_Assert(false, "StorageDrive isn't allowed to have a node to forward requests to.");
|
||||
}
|
||||
|
||||
void StorageDrive::PrepareRequest(FileRequest* request)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzCore);
|
||||
AZ_Assert(request, "PrepareRequest was provided a null request.");
|
||||
|
||||
if (AZStd::holds_alternative<FileRequest::ReadRequestData>(request->GetCommand()))
|
||||
{
|
||||
auto& readRequest = AZStd::get<FileRequest::ReadRequestData>(request->GetCommand());
|
||||
|
||||
FileRequest* read = m_context->GetNewInternalRequest();
|
||||
read->CreateRead(request, readRequest.m_output, readRequest.m_outputSize, readRequest.m_path,
|
||||
readRequest.m_offset, readRequest.m_size);
|
||||
m_context->PushPreparedRequest(read);
|
||||
m_pendingRequests.push_back(request);
|
||||
return;
|
||||
}
|
||||
StreamStackEntry::PrepareRequest(request);
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::CancelData>)
|
||||
{
|
||||
CancelRequest(request, args.m_target);
|
||||
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();
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::ReportData>)
|
||||
{
|
||||
Report(args);
|
||||
}
|
||||
StreamStackEntry::QueueRequest(request);
|
||||
}
|
||||
}, request->GetCommand());
|
||||
}
|
||||
|
||||
void StorageDrive::QueueRequest(FileRequest* request)
|
||||
bool StorageDrive::ExecuteRequests()
|
||||
{
|
||||
if (!m_pendingRequests.empty())
|
||||
{
|
||||
AZ_Assert(request, "QueueRequest was provided a null request.");
|
||||
FileRequest* request = m_pendingRequests.front();
|
||||
AZStd::visit([this, request](auto&& args)
|
||||
{
|
||||
using Command = AZStd::decay_t<decltype(args)>;
|
||||
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData> ||
|
||||
AZStd::is_same_v<Command, FileRequest::FileExistsCheckData> ||
|
||||
AZStd::is_same_v<Command, FileRequest::FileMetaDataRetrievalData>)
|
||||
{
|
||||
m_pendingRequests.push_back(request);
|
||||
return;
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::CancelData>)
|
||||
{
|
||||
CancelRequest(request, args.m_target);
|
||||
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();
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::ReportData>)
|
||||
{
|
||||
Report(args);
|
||||
}
|
||||
StreamStackEntry::QueueRequest(request);
|
||||
}
|
||||
}, request->GetCommand());
|
||||
}
|
||||
|
||||
bool StorageDrive::ExecuteRequests()
|
||||
{
|
||||
if (!m_pendingRequests.empty())
|
||||
{
|
||||
FileRequest* request = m_pendingRequests.front();
|
||||
AZStd::visit([this, request](auto&& args)
|
||||
{
|
||||
using Command = AZStd::decay_t<decltype(args)>;
|
||||
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData>)
|
||||
{
|
||||
ReadFile(request);
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileExistsCheckData>)
|
||||
{
|
||||
FileExistsRequest(request);
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileMetaDataRetrievalData>)
|
||||
{
|
||||
FileMetaDataRetrievalRequest(request);
|
||||
}
|
||||
}, request->GetCommand());
|
||||
m_pendingRequests.pop_front();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void StorageDrive::UpdateStatus(Status& status) const
|
||||
{
|
||||
// Only participate if there are actually any reads done.
|
||||
if (m_fileOpenCloseTimeAverage.GetNumRecorded() > 0)
|
||||
{
|
||||
s32 availableSlots = s_maxRequests - aznumeric_cast<s32>(m_pendingRequests.size());
|
||||
StreamStackEntry::UpdateStatus(status);
|
||||
status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, availableSlots);
|
||||
status.m_isIdle = status.m_isIdle && m_pendingRequests.empty();
|
||||
}
|
||||
else
|
||||
{
|
||||
status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, s_maxRequests);
|
||||
}
|
||||
}
|
||||
|
||||
void StorageDrive::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now,
|
||||
AZStd::vector<FileRequest*>& internalPending, StreamerContext::PreparedQueue::iterator pendingBegin,
|
||||
StreamerContext::PreparedQueue::iterator pendingEnd)
|
||||
{
|
||||
StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd);
|
||||
|
||||
const RequestPath* activeFile = nullptr;
|
||||
if (m_activeCacheSlot != s_fileNotFound)
|
||||
{
|
||||
activeFile = &m_filePaths[m_activeCacheSlot];
|
||||
}
|
||||
u64 activeOffset = m_activeOffset;
|
||||
|
||||
// Estimate requests in this stack entry.
|
||||
for (FileRequest* request : m_pendingRequests)
|
||||
{
|
||||
EstimateCompletionTimeForRequest(request, now, activeFile, activeOffset);
|
||||
}
|
||||
|
||||
// Estimate internally pending requests. Because this call will go from the top of the stack to the bottom,
|
||||
// but estimation is calculated from the bottom to the top, this list should be processed in reverse order.
|
||||
for (auto requestIt = internalPending.rbegin(); requestIt != internalPending.rend(); ++requestIt)
|
||||
{
|
||||
EstimateCompletionTimeForRequest(*requestIt, now, activeFile, activeOffset);
|
||||
}
|
||||
|
||||
// Estimate pending requests that have not been queued yet.
|
||||
for (auto requestIt = pendingBegin; requestIt != pendingEnd; ++requestIt)
|
||||
{
|
||||
EstimateCompletionTimeForRequest(*requestIt, now, activeFile, activeOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void StorageDrive::EstimateCompletionTimeForRequest(FileRequest* request, AZStd::chrono::system_clock::time_point& startTime,
|
||||
const RequestPath*& activeFile, u64& activeOffset) const
|
||||
{
|
||||
u64 readSize = 0;
|
||||
u64 offset = 0;
|
||||
const RequestPath* targetFile = nullptr;
|
||||
|
||||
AZStd::visit([&](auto&& args)
|
||||
{
|
||||
using Command = AZStd::decay_t<decltype(args)>;
|
||||
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData>)
|
||||
{
|
||||
targetFile = &args.m_path;
|
||||
readSize = args.m_size;
|
||||
offset = args.m_offset;
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::CompressedReadData>)
|
||||
{
|
||||
targetFile = &args.m_compressionInfo.m_archiveFilename;
|
||||
readSize = args.m_compressionInfo.m_compressedSize;
|
||||
offset = args.m_compressionInfo.m_offset;
|
||||
ReadFile(request);
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileExistsCheckData>)
|
||||
{
|
||||
readSize = 0;
|
||||
AZStd::chrono::microseconds averageTime = m_getFileExistsTimeAverage.CalculateAverage();
|
||||
startTime += averageTime;
|
||||
FileExistsRequest(request);
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileMetaDataRetrievalData>)
|
||||
{
|
||||
readSize = 0;
|
||||
AZStd::chrono::microseconds averageTime = m_getFileMetaDataTimeAverage.CalculateAverage();
|
||||
startTime += averageTime;
|
||||
FileMetaDataRetrievalRequest(request);
|
||||
}
|
||||
}, request->GetCommand());
|
||||
m_pendingRequests.pop_front();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (readSize > 0)
|
||||
{
|
||||
if (activeFile && activeFile != targetFile)
|
||||
{
|
||||
if (FindFileInCache(*targetFile) == s_fileNotFound)
|
||||
{
|
||||
AZStd::chrono::microseconds fileOpenCloseTimeAverage = m_fileOpenCloseTimeAverage.CalculateAverage();
|
||||
startTime += fileOpenCloseTimeAverage;
|
||||
}
|
||||
startTime += s_averageSeekTime;
|
||||
activeOffset = std::numeric_limits<u64>::max();
|
||||
}
|
||||
else if (activeOffset != offset)
|
||||
{
|
||||
startTime += s_averageSeekTime;
|
||||
}
|
||||
void StorageDrive::UpdateStatus(Status& status) const
|
||||
{
|
||||
// Only participate if there are actually any reads done.
|
||||
if (m_fileOpenCloseTimeAverage.GetNumRecorded() > 0)
|
||||
{
|
||||
s32 availableSlots = s_maxRequests - aznumeric_cast<s32>(m_pendingRequests.size());
|
||||
StreamStackEntry::UpdateStatus(status);
|
||||
status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, availableSlots);
|
||||
status.m_isIdle = status.m_isIdle && m_pendingRequests.empty();
|
||||
}
|
||||
else
|
||||
{
|
||||
status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, s_maxRequests);
|
||||
}
|
||||
}
|
||||
|
||||
u64 totalBytesRead = m_readSizeAverage.GetTotal();
|
||||
double totalReadTimeUSec = aznumeric_caster(m_readTimeAverage.GetTotal().count());
|
||||
startTime += AZStd::chrono::microseconds(aznumeric_cast<u64>((readSize * totalReadTimeUSec) / totalBytesRead));
|
||||
activeOffset = offset + readSize;
|
||||
}
|
||||
request->SetEstimatedCompletion(startTime);
|
||||
void StorageDrive::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now,
|
||||
AZStd::vector<FileRequest*>& internalPending, StreamerContext::PreparedQueue::iterator pendingBegin,
|
||||
StreamerContext::PreparedQueue::iterator pendingEnd)
|
||||
{
|
||||
StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd);
|
||||
|
||||
const RequestPath* activeFile = nullptr;
|
||||
if (m_activeCacheSlot != s_fileNotFound)
|
||||
{
|
||||
activeFile = &m_filePaths[m_activeCacheSlot];
|
||||
}
|
||||
u64 activeOffset = m_activeOffset;
|
||||
|
||||
// Estimate requests in this stack entry.
|
||||
for (FileRequest* request : m_pendingRequests)
|
||||
{
|
||||
EstimateCompletionTimeForRequest(request, now, activeFile, activeOffset);
|
||||
}
|
||||
|
||||
void StorageDrive::ReadFile(FileRequest* request)
|
||||
// Estimate internally pending requests. Because this call will go from the top of the stack to the bottom,
|
||||
// but estimation is calculated from the bottom to the top, this list should be processed in reverse order.
|
||||
for (auto requestIt = internalPending.rbegin(); requestIt != internalPending.rend(); ++requestIt)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzCore);
|
||||
|
||||
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
|
||||
AZ_Assert(data, "FileRequest queued on StorageDrive to be read didn't contain read data.");
|
||||
|
||||
SystemFile* file = nullptr;
|
||||
|
||||
// If the file is already open, use that file handle and update it's last touched time.
|
||||
size_t cacheIndex = FindFileInCache(data->m_path);
|
||||
if (cacheIndex != s_fileNotFound)
|
||||
{
|
||||
file = m_fileHandles[cacheIndex].get();
|
||||
m_fileLastUsed[cacheIndex] = AZStd::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
// If the file is not open, eject the entry from the cache that hasn't been used for the longest time
|
||||
// and open the file for reading.
|
||||
if (!file)
|
||||
{
|
||||
AZStd::chrono::system_clock::time_point oldest = m_fileLastUsed[0];
|
||||
cacheIndex = 0;
|
||||
size_t numFiles = m_filePaths.size();
|
||||
for (size_t i = 1; i < numFiles; ++i)
|
||||
{
|
||||
if (m_fileLastUsed[i] < oldest)
|
||||
{
|
||||
oldest = m_fileLastUsed[i];
|
||||
cacheIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
TIMED_AVERAGE_WINDOW_SCOPE(m_fileOpenCloseTimeAverage);
|
||||
AZStd::unique_ptr<SystemFile> newFile = AZStd::make_unique<SystemFile>();
|
||||
bool isOpen = newFile->Open(data->m_path.GetAbsolutePath(), SystemFile::OpenMode::SF_OPEN_READ_ONLY);
|
||||
if (!isOpen)
|
||||
{
|
||||
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
|
||||
m_context->MarkRequestAsCompleted(request);
|
||||
return;
|
||||
}
|
||||
|
||||
file = newFile.get();
|
||||
m_fileLastUsed[cacheIndex] = AZStd::chrono::high_resolution_clock::now();
|
||||
m_fileHandles[cacheIndex] = AZStd::move(newFile);
|
||||
m_filePaths[cacheIndex] = data->m_path;
|
||||
}
|
||||
|
||||
AZ_Assert(file, "While searching for file '%s' StorageDevice::ReadFile failed to detect a problem.", data->m_path.GetRelativePath());
|
||||
u64 bytesRead = 0;
|
||||
{
|
||||
TIMED_AVERAGE_WINDOW_SCOPE(m_readTimeAverage);
|
||||
if (file->Tell() != data->m_offset)
|
||||
{
|
||||
file->Seek(data->m_offset, SystemFile::SeekMode::SF_SEEK_BEGIN);
|
||||
}
|
||||
bytesRead = file->Read(data->m_size, data->m_output);
|
||||
}
|
||||
m_readSizeAverage.PushEntry(bytesRead);
|
||||
|
||||
m_activeCacheSlot = cacheIndex;
|
||||
m_activeOffset = data->m_offset + bytesRead;
|
||||
|
||||
request->SetStatus(bytesRead == data->m_size ? IStreamerTypes::RequestStatus::Completed : IStreamerTypes::RequestStatus::Failed);
|
||||
m_context->MarkRequestAsCompleted(request);
|
||||
EstimateCompletionTimeForRequest(*requestIt, now, activeFile, activeOffset);
|
||||
}
|
||||
|
||||
void StorageDrive::CancelRequest(FileRequest* cancelRequest, FileRequestPtr& target)
|
||||
// Estimate pending requests that have not been queued yet.
|
||||
for (auto requestIt = pendingBegin; requestIt != pendingEnd; ++requestIt)
|
||||
{
|
||||
for (auto it = m_pendingRequests.begin(); it != m_pendingRequests.end();)
|
||||
EstimateCompletionTimeForRequest(*requestIt, now, activeFile, activeOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void StorageDrive::EstimateCompletionTimeForRequest(FileRequest* request, AZStd::chrono::system_clock::time_point& startTime,
|
||||
const RequestPath*& activeFile, u64& activeOffset) const
|
||||
{
|
||||
u64 readSize = 0;
|
||||
u64 offset = 0;
|
||||
const RequestPath* targetFile = nullptr;
|
||||
|
||||
AZStd::visit([&](auto&& args)
|
||||
{
|
||||
using Command = AZStd::decay_t<decltype(args)>;
|
||||
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData>)
|
||||
{
|
||||
if ((*it)->WorksOn(target))
|
||||
{
|
||||
(*it)->SetStatus(IStreamerTypes::RequestStatus::Canceled);
|
||||
m_context->MarkRequestAsCompleted(*it);
|
||||
it = m_pendingRequests.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
targetFile = &args.m_path;
|
||||
readSize = args.m_size;
|
||||
offset = args.m_offset;
|
||||
}
|
||||
cancelRequest->SetStatus(IStreamerTypes::RequestStatus::Completed);
|
||||
m_context->MarkRequestAsCompleted(cancelRequest);
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::CompressedReadData>)
|
||||
{
|
||||
targetFile = &args.m_compressionInfo.m_archiveFilename;
|
||||
readSize = args.m_compressionInfo.m_compressedSize;
|
||||
offset = args.m_compressionInfo.m_offset;
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileExistsCheckData>)
|
||||
{
|
||||
readSize = 0;
|
||||
AZStd::chrono::microseconds averageTime = m_getFileExistsTimeAverage.CalculateAverage();
|
||||
startTime += averageTime;
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileMetaDataRetrievalData>)
|
||||
{
|
||||
readSize = 0;
|
||||
AZStd::chrono::microseconds averageTime = m_getFileMetaDataTimeAverage.CalculateAverage();
|
||||
startTime += averageTime;
|
||||
}
|
||||
}, request->GetCommand());
|
||||
|
||||
if (readSize > 0)
|
||||
{
|
||||
if (activeFile && activeFile != targetFile)
|
||||
{
|
||||
if (FindFileInCache(*targetFile) == s_fileNotFound)
|
||||
{
|
||||
AZStd::chrono::microseconds fileOpenCloseTimeAverage = m_fileOpenCloseTimeAverage.CalculateAverage();
|
||||
startTime += fileOpenCloseTimeAverage;
|
||||
}
|
||||
startTime += s_averageSeekTime;
|
||||
activeOffset = std::numeric_limits<u64>::max();
|
||||
}
|
||||
else if (activeOffset != offset)
|
||||
{
|
||||
startTime += s_averageSeekTime;
|
||||
}
|
||||
|
||||
u64 totalBytesRead = m_readSizeAverage.GetTotal();
|
||||
double totalReadTimeUSec = aznumeric_caster(m_readTimeAverage.GetTotal().count());
|
||||
startTime += AZStd::chrono::microseconds(aznumeric_cast<u64>((readSize * totalReadTimeUSec) / totalBytesRead));
|
||||
activeOffset = offset + readSize;
|
||||
}
|
||||
request->SetEstimatedCompletion(startTime);
|
||||
}
|
||||
|
||||
void StorageDrive::ReadFile(FileRequest* request)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzCore);
|
||||
|
||||
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
|
||||
AZ_Assert(data, "FileRequest queued on StorageDrive to be read didn't contain read data.");
|
||||
|
||||
SystemFile* file = nullptr;
|
||||
|
||||
// If the file is already open, use that file handle and update it's last touched time.
|
||||
size_t cacheIndex = FindFileInCache(data->m_path);
|
||||
if (cacheIndex != s_fileNotFound)
|
||||
{
|
||||
file = m_fileHandles[cacheIndex].get();
|
||||
m_fileLastUsed[cacheIndex] = AZStd::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void StorageDrive::FileExistsRequest(FileRequest* request)
|
||||
// If the file is not open, eject the entry from the cache that hasn't been used for the longest time
|
||||
// and open the file for reading.
|
||||
if (!file)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzCore);
|
||||
TIMED_AVERAGE_WINDOW_SCOPE(m_getFileExistsTimeAverage);
|
||||
|
||||
auto& fileExists = AZStd::get<FileRequest::FileExistsCheckData>(request->GetCommand());
|
||||
size_t cacheIndex = FindFileInCache(fileExists.m_path);
|
||||
if (cacheIndex != s_fileNotFound)
|
||||
AZStd::chrono::system_clock::time_point oldest = m_fileLastUsed[0];
|
||||
cacheIndex = 0;
|
||||
size_t numFiles = m_filePaths.size();
|
||||
for (size_t i = 1; i < numFiles; ++i)
|
||||
{
|
||||
fileExists.m_found = true;
|
||||
if (m_fileLastUsed[i] < oldest)
|
||||
{
|
||||
oldest = m_fileLastUsed[i];
|
||||
cacheIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
TIMED_AVERAGE_WINDOW_SCOPE(m_fileOpenCloseTimeAverage);
|
||||
AZStd::unique_ptr<SystemFile> newFile = AZStd::make_unique<SystemFile>();
|
||||
bool isOpen = newFile->Open(data->m_path.GetAbsolutePath(), SystemFile::OpenMode::SF_OPEN_READ_ONLY);
|
||||
if (!isOpen)
|
||||
{
|
||||
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
|
||||
m_context->MarkRequestAsCompleted(request);
|
||||
return;
|
||||
}
|
||||
|
||||
file = newFile.get();
|
||||
m_fileLastUsed[cacheIndex] = AZStd::chrono::high_resolution_clock::now();
|
||||
m_fileHandles[cacheIndex] = AZStd::move(newFile);
|
||||
m_filePaths[cacheIndex] = data->m_path;
|
||||
}
|
||||
|
||||
AZ_Assert(file, "While searching for file '%s' StorageDevice::ReadFile failed to detect a problem.", data->m_path.GetRelativePath());
|
||||
u64 bytesRead = 0;
|
||||
{
|
||||
TIMED_AVERAGE_WINDOW_SCOPE(m_readTimeAverage);
|
||||
if (file->Tell() != data->m_offset)
|
||||
{
|
||||
file->Seek(data->m_offset, SystemFile::SeekMode::SF_SEEK_BEGIN);
|
||||
}
|
||||
bytesRead = file->Read(data->m_size, data->m_output);
|
||||
}
|
||||
m_readSizeAverage.PushEntry(bytesRead);
|
||||
|
||||
m_activeCacheSlot = cacheIndex;
|
||||
m_activeOffset = data->m_offset + bytesRead;
|
||||
|
||||
request->SetStatus(bytesRead == data->m_size ? IStreamerTypes::RequestStatus::Completed : IStreamerTypes::RequestStatus::Failed);
|
||||
m_context->MarkRequestAsCompleted(request);
|
||||
}
|
||||
|
||||
void StorageDrive::CancelRequest(FileRequest* cancelRequest, FileRequestPtr& target)
|
||||
{
|
||||
for (auto it = m_pendingRequests.begin(); it != m_pendingRequests.end();)
|
||||
{
|
||||
if ((*it)->WorksOn(target))
|
||||
{
|
||||
(*it)->SetStatus(IStreamerTypes::RequestStatus::Canceled);
|
||||
m_context->MarkRequestAsCompleted(*it);
|
||||
it = m_pendingRequests.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileExists.m_found = SystemFile::Exists(fileExists.m_path.GetAbsolutePath());
|
||||
++it;
|
||||
}
|
||||
m_context->MarkRequestAsCompleted(request);
|
||||
}
|
||||
cancelRequest->SetStatus(IStreamerTypes::RequestStatus::Completed);
|
||||
m_context->MarkRequestAsCompleted(cancelRequest);
|
||||
}
|
||||
|
||||
void StorageDrive::FileMetaDataRetrievalRequest(FileRequest* request)
|
||||
void StorageDrive::FileExistsRequest(FileRequest* request)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzCore);
|
||||
TIMED_AVERAGE_WINDOW_SCOPE(m_getFileExistsTimeAverage);
|
||||
|
||||
auto& fileExists = AZStd::get<FileRequest::FileExistsCheckData>(request->GetCommand());
|
||||
size_t cacheIndex = FindFileInCache(fileExists.m_path);
|
||||
if (cacheIndex != s_fileNotFound)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzCore);
|
||||
TIMED_AVERAGE_WINDOW_SCOPE(m_getFileMetaDataTimeAverage);
|
||||
fileExists.m_found = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileExists.m_found = SystemFile::Exists(fileExists.m_path.GetAbsolutePath());
|
||||
}
|
||||
m_context->MarkRequestAsCompleted(request);
|
||||
}
|
||||
|
||||
auto& command = AZStd::get<FileRequest::FileMetaDataRetrievalData>(request->GetCommand());
|
||||
// If the file is already open, use the file handle which usually is cheaper than asking for the file by name.
|
||||
size_t cacheIndex = FindFileInCache(command.m_path);
|
||||
if (cacheIndex != s_fileNotFound)
|
||||
void StorageDrive::FileMetaDataRetrievalRequest(FileRequest* request)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzCore);
|
||||
TIMED_AVERAGE_WINDOW_SCOPE(m_getFileMetaDataTimeAverage);
|
||||
|
||||
auto& command = AZStd::get<FileRequest::FileMetaDataRetrievalData>(request->GetCommand());
|
||||
// If the file is already open, use the file handle which usually is cheaper than asking for the file by name.
|
||||
size_t cacheIndex = FindFileInCache(command.m_path);
|
||||
if (cacheIndex != s_fileNotFound)
|
||||
{
|
||||
AZ_Assert(m_fileHandles[cacheIndex],
|
||||
"File path '%s' doesn't have an associated file handle.", m_filePaths[cacheIndex].GetRelativePath());
|
||||
command.m_fileSize = m_fileHandles[cacheIndex]->Length();
|
||||
command.m_found = true;
|
||||
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The file is not open yet, so try to get the file size by name.
|
||||
u64 size = SystemFile::Length(command.m_path.GetAbsolutePath());
|
||||
if (size != 0) // SystemFile::Length doesn't allow telling a zero-sized file apart from a invalid path.
|
||||
{
|
||||
AZ_Assert(m_fileHandles[cacheIndex],
|
||||
"File path '%s' doesn't have an associated file handle.", m_filePaths[cacheIndex].GetRelativePath());
|
||||
command.m_fileSize = m_fileHandles[cacheIndex]->Length();
|
||||
command.m_fileSize = size;
|
||||
command.m_found = true;
|
||||
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The file is not open yet, so try to get the file size by name.
|
||||
u64 size = SystemFile::Length(command.m_path.GetAbsolutePath());
|
||||
if (size != 0) // SystemFile::Length doesn't allow telling a zero-sized file apart from a invalid path.
|
||||
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
|
||||
}
|
||||
}
|
||||
|
||||
m_context->MarkRequestAsCompleted(request);
|
||||
}
|
||||
|
||||
void StorageDrive::FlushCache(const RequestPath& filePath)
|
||||
{
|
||||
size_t cacheIndex = FindFileInCache(filePath);
|
||||
if (cacheIndex != s_fileNotFound)
|
||||
{
|
||||
m_fileLastUsed[cacheIndex] = AZStd::chrono::system_clock::time_point();
|
||||
m_fileHandles[cacheIndex].reset();
|
||||
m_filePaths[cacheIndex].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void StorageDrive::FlushEntireCache()
|
||||
{
|
||||
size_t numFiles = m_filePaths.size();
|
||||
for (size_t i = 0; i < numFiles; ++i)
|
||||
{
|
||||
m_fileLastUsed[i] = AZStd::chrono::system_clock::time_point();
|
||||
m_fileHandles[i].reset();
|
||||
m_filePaths[i].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
size_t StorageDrive::FindFileInCache(const RequestPath& filePath) const
|
||||
{
|
||||
size_t numFiles = m_filePaths.size();
|
||||
for (size_t i = 0; i < numFiles; ++i)
|
||||
{
|
||||
if (m_filePaths[i] == filePath)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return s_fileNotFound;
|
||||
}
|
||||
|
||||
void StorageDrive::CollectStatistics(AZStd::vector<Statistic>& statistics) const
|
||||
{
|
||||
constexpr double bytesToMB = (1024.0 * 1024.0);
|
||||
using DoubleSeconds = AZStd::chrono::duration<double>;
|
||||
|
||||
double totalBytesReadMB = m_readSizeAverage.GetTotal() / bytesToMB;
|
||||
double totalReadTimeSec = AZStd::chrono::duration_cast<DoubleSeconds>(m_readTimeAverage.GetTotal()).count();
|
||||
if (m_readSizeAverage.GetTotal() > 1) // A default value is always added.
|
||||
{
|
||||
statistics.push_back(Statistic::CreateFloat(m_name, "Read Speed (avg. mbps)", totalBytesReadMB / totalReadTimeSec));
|
||||
}
|
||||
|
||||
if (m_fileOpenCloseTimeAverage.GetNumRecorded() > 0)
|
||||
{
|
||||
statistics.push_back(Statistic::CreateInteger(m_name, "File Open & Close (avg. us)", m_fileOpenCloseTimeAverage.CalculateAverage().count()));
|
||||
statistics.push_back(Statistic::CreateInteger(m_name, "Get file exists (avg. us)", m_getFileExistsTimeAverage.CalculateAverage().count()));
|
||||
statistics.push_back(Statistic::CreateInteger(m_name, "Get file meta data (avg. us)", m_getFileMetaDataTimeAverage.CalculateAverage().count()));
|
||||
statistics.push_back(Statistic::CreateInteger(m_name, "Available slots", s64{ s_maxRequests } - m_pendingRequests.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void StorageDrive::Report(const FileRequest::ReportData& data) const
|
||||
{
|
||||
switch (data.m_reportType)
|
||||
{
|
||||
case FileRequest::ReportData::ReportType::FileLocks:
|
||||
for (u32 i = 0; i < m_fileHandles.size(); ++i)
|
||||
{
|
||||
if (m_fileHandles[i] != nullptr)
|
||||
{
|
||||
command.m_fileSize = size;
|
||||
command.m_found = true;
|
||||
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
|
||||
}
|
||||
else
|
||||
{
|
||||
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
|
||||
AZ_Printf("Streamer", "File lock in %s : '%s'.\n", m_name.c_str(), m_filePaths[i].GetRelativePath());
|
||||
}
|
||||
}
|
||||
|
||||
m_context->MarkRequestAsCompleted(request);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
void StorageDrive::FlushCache(const RequestPath& filePath)
|
||||
{
|
||||
size_t cacheIndex = FindFileInCache(filePath);
|
||||
if (cacheIndex != s_fileNotFound)
|
||||
{
|
||||
m_fileLastUsed[cacheIndex] = AZStd::chrono::system_clock::time_point();
|
||||
m_fileHandles[cacheIndex].reset();
|
||||
m_filePaths[cacheIndex].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void StorageDrive::FlushEntireCache()
|
||||
{
|
||||
size_t numFiles = m_filePaths.size();
|
||||
for (size_t i = 0; i < numFiles; ++i)
|
||||
{
|
||||
m_fileLastUsed[i] = AZStd::chrono::system_clock::time_point();
|
||||
m_fileHandles[i].reset();
|
||||
m_filePaths[i].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
size_t StorageDrive::FindFileInCache(const RequestPath& filePath) const
|
||||
{
|
||||
size_t numFiles = m_filePaths.size();
|
||||
for (size_t i = 0; i < numFiles; ++i)
|
||||
{
|
||||
if (m_filePaths[i] == filePath)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return s_fileNotFound;
|
||||
}
|
||||
|
||||
void StorageDrive::CollectStatistics(AZStd::vector<Statistic>& statistics) const
|
||||
{
|
||||
constexpr double bytesToMB = (1024.0 * 1024.0);
|
||||
using DoubleSeconds = AZStd::chrono::duration<double>;
|
||||
|
||||
double totalBytesReadMB = m_readSizeAverage.GetTotal() / bytesToMB;
|
||||
double totalReadTimeSec = AZStd::chrono::duration_cast<DoubleSeconds>(m_readTimeAverage.GetTotal()).count();
|
||||
if (m_readSizeAverage.GetTotal() > 1) // A default value is always added.
|
||||
{
|
||||
statistics.push_back(Statistic::CreateFloat(m_name, "Read Speed (avg. mbps)", totalBytesReadMB / totalReadTimeSec));
|
||||
}
|
||||
|
||||
if (m_fileOpenCloseTimeAverage.GetNumRecorded() > 0)
|
||||
{
|
||||
statistics.push_back(Statistic::CreateInteger(m_name, "File Open & Close (avg. us)", m_fileOpenCloseTimeAverage.CalculateAverage().count()));
|
||||
statistics.push_back(Statistic::CreateInteger(m_name, "Get file exists (avg. us)", m_getFileExistsTimeAverage.CalculateAverage().count()));
|
||||
statistics.push_back(Statistic::CreateInteger(m_name, "Get file meta data (avg. us)", m_getFileMetaDataTimeAverage.CalculateAverage().count()));
|
||||
statistics.push_back(Statistic::CreateInteger(m_name, "Available slots", s64{ s_maxRequests } - m_pendingRequests.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void StorageDrive::Report(const FileRequest::ReportData& data) const
|
||||
{
|
||||
switch (data.m_reportType)
|
||||
{
|
||||
case FileRequest::ReportData::ReportType::FileLocks:
|
||||
for (u32 i = 0; i < m_fileHandles.size(); ++i)
|
||||
{
|
||||
if (m_fileHandles[i] != nullptr)
|
||||
{
|
||||
AZ_Printf("Streamer", "File lock in %s : '%s'.\n", m_name.c_str(), m_filePaths[i].GetRelativePath());
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace IO
|
||||
} // namespace AZ
|
||||
}
|
||||
} // namespace AZ::IO
|
||||
|
||||
@@ -16,85 +16,82 @@
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/chrono/clocks.h>
|
||||
|
||||
namespace AZ
|
||||
namespace AZ::IO
|
||||
{
|
||||
namespace IO
|
||||
struct StorageDriveConfig final :
|
||||
public IStreamerStackConfig
|
||||
{
|
||||
struct StorageDriveConfig final :
|
||||
public IStreamerStackConfig
|
||||
{
|
||||
AZ_RTTI(AZ::IO::StorageDriveConfig, "{3D568902-6C09-4E9E-A4DB-8B561481D298}", IStreamerStackConfig);
|
||||
AZ_CLASS_ALLOCATOR(StorageDriveConfig, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(AZ::IO::StorageDriveConfig, "{3D568902-6C09-4E9E-A4DB-8B561481D298}", IStreamerStackConfig);
|
||||
AZ_CLASS_ALLOCATOR(StorageDriveConfig, AZ::SystemAllocator, 0);
|
||||
|
||||
~StorageDriveConfig() override = default;
|
||||
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
|
||||
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
~StorageDriveConfig() override = default;
|
||||
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
|
||||
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
u32 m_maxFileHandles{1024};
|
||||
};
|
||||
u32 m_maxFileHandles{1024};
|
||||
};
|
||||
|
||||
//! Platform agnostic version of a storage drive, such as hdd, ssd, dvd, etc.
|
||||
//! This stream stack entry is responsible for accessing a storage drive to
|
||||
//! retrieve file information and data.
|
||||
//! This entry is designed as a catch-all for any reads that weren't handled
|
||||
//! by platform specific implementations or the virtual file system. It should
|
||||
//! by the last entry in the stack as it will not forward calls to the next entry.
|
||||
class StorageDrive
|
||||
: public StreamStackEntry
|
||||
{
|
||||
public:
|
||||
explicit StorageDrive(u32 maxFileHandles);
|
||||
~StorageDrive() override = default;
|
||||
//! Platform agnostic version of a storage drive, such as hdd, ssd, dvd, etc.
|
||||
//! This stream stack entry is responsible for accessing a storage drive to
|
||||
//! retrieve file information and data.
|
||||
//! This entry is designed as a catch-all for any reads that weren't handled
|
||||
//! by platform specific implementations or the virtual file system. It should
|
||||
//! by the last entry in the stack as it will not forward calls to the next entry.
|
||||
class StorageDrive
|
||||
: public StreamStackEntry
|
||||
{
|
||||
public:
|
||||
explicit StorageDrive(u32 maxFileHandles);
|
||||
~StorageDrive() override = default;
|
||||
|
||||
void SetNext(AZStd::shared_ptr<StreamStackEntry> next) override;
|
||||
void SetNext(AZStd::shared_ptr<StreamStackEntry> next) override;
|
||||
|
||||
void PrepareRequest(FileRequest* request) override;
|
||||
void QueueRequest(FileRequest* request) override;
|
||||
bool ExecuteRequests() override;
|
||||
void PrepareRequest(FileRequest* request) override;
|
||||
void QueueRequest(FileRequest* request) override;
|
||||
bool ExecuteRequests() override;
|
||||
|
||||
void UpdateStatus(Status& status) const override;
|
||||
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
|
||||
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
|
||||
void UpdateStatus(Status& status) const override;
|
||||
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
|
||||
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
|
||||
|
||||
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
|
||||
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
|
||||
|
||||
protected:
|
||||
static const AZStd::chrono::microseconds s_averageSeekTime;
|
||||
static constexpr s32 s_maxRequests = 1;
|
||||
protected:
|
||||
static const AZStd::chrono::microseconds s_averageSeekTime;
|
||||
static constexpr s32 s_maxRequests = 1;
|
||||
|
||||
size_t FindFileInCache(const RequestPath& filePath) const;
|
||||
void ReadFile(FileRequest* request);
|
||||
void CancelRequest(FileRequest* cancelRequest, FileRequestPtr& target);
|
||||
void FileExistsRequest(FileRequest* request);
|
||||
void FileMetaDataRetrievalRequest(FileRequest* request);
|
||||
void FlushCache(const RequestPath& filePath);
|
||||
void FlushEntireCache();
|
||||
size_t FindFileInCache(const RequestPath& filePath) const;
|
||||
void ReadFile(FileRequest* request);
|
||||
void CancelRequest(FileRequest* cancelRequest, FileRequestPtr& target);
|
||||
void FileExistsRequest(FileRequest* request);
|
||||
void FileMetaDataRetrievalRequest(FileRequest* request);
|
||||
void FlushCache(const RequestPath& filePath);
|
||||
void FlushEntireCache();
|
||||
|
||||
void EstimateCompletionTimeForRequest(FileRequest* request, AZStd::chrono::system_clock::time_point& startTime,
|
||||
const RequestPath*& activeFile, u64& activeOffset) const;
|
||||
void EstimateCompletionTimeForRequest(FileRequest* request, AZStd::chrono::system_clock::time_point& startTime,
|
||||
const RequestPath*& activeFile, u64& activeOffset) const;
|
||||
|
||||
void Report(const FileRequest::ReportData& data) const;
|
||||
void Report(const FileRequest::ReportData& data) const;
|
||||
|
||||
TimedAverageWindow<s_statisticsWindowSize> m_fileOpenCloseTimeAverage;
|
||||
TimedAverageWindow<s_statisticsWindowSize> m_getFileExistsTimeAverage;
|
||||
TimedAverageWindow<s_statisticsWindowSize> m_getFileMetaDataTimeAverage;
|
||||
TimedAverageWindow<s_statisticsWindowSize> m_readTimeAverage;
|
||||
AverageWindow<u64, float, s_statisticsWindowSize> m_readSizeAverage;
|
||||
//! File requests that are queued for processing.
|
||||
AZStd::deque<FileRequest*> m_pendingRequests;
|
||||
TimedAverageWindow<s_statisticsWindowSize> m_fileOpenCloseTimeAverage;
|
||||
TimedAverageWindow<s_statisticsWindowSize> m_getFileExistsTimeAverage;
|
||||
TimedAverageWindow<s_statisticsWindowSize> m_getFileMetaDataTimeAverage;
|
||||
TimedAverageWindow<s_statisticsWindowSize> m_readTimeAverage;
|
||||
AverageWindow<u64, float, s_statisticsWindowSize> m_readSizeAverage;
|
||||
//! File requests that are queued for processing.
|
||||
AZStd::deque<FileRequest*> m_pendingRequests;
|
||||
|
||||
//! The last time a file handle was used to access a file. The handle is stored in m_fileHandles.
|
||||
AZStd::vector<AZStd::chrono::system_clock::time_point> m_fileLastUsed;
|
||||
//! The file path to the file handle. The handle is stored in m_fileHandles.
|
||||
AZStd::vector<RequestPath> m_filePaths;
|
||||
//! A list of file handles that's being cached in case they're needed again in the future.
|
||||
AZStd::vector<AZStd::unique_ptr<SystemFile>> m_fileHandles;
|
||||
//! The last time a file handle was used to access a file. The handle is stored in m_fileHandles.
|
||||
AZStd::vector<AZStd::chrono::system_clock::time_point> m_fileLastUsed;
|
||||
//! The file path to the file handle. The handle is stored in m_fileHandles.
|
||||
AZStd::vector<RequestPath> m_filePaths;
|
||||
//! A list of file handles that's being cached in case they're needed again in the future.
|
||||
AZStd::vector<AZStd::unique_ptr<SystemFile>> m_fileHandles;
|
||||
|
||||
//! The offset into the file that's cached by the active cache slot.
|
||||
u64 m_activeOffset = 0;
|
||||
//! The index into m_fileHandles for the file that's currently being read.
|
||||
size_t m_activeCacheSlot = s_fileNotFound;
|
||||
};
|
||||
} // namespace IO
|
||||
} // namespace AZ
|
||||
//! The offset into the file that's cached by the active cache slot.
|
||||
u64 m_activeOffset = 0;
|
||||
//! The index into m_fileHandles for the file that's currently being read.
|
||||
size_t m_activeCacheSlot = s_fileNotFound;
|
||||
};
|
||||
} // namespace AZ::IO
|
||||
|
||||
@@ -219,7 +219,7 @@ namespace AZ::IO
|
||||
{
|
||||
AZ_Assert(HasRequestCompleted(request), "Claiming memory from a read request that's still in progress. "
|
||||
"This can lead to crashing if data is still being streamed to the request's buffer.");
|
||||
// The caller has claimed the buffer and is now responsible for clearing it.
|
||||
// The caller has claimed the buffer and is now responsible for clearing it.
|
||||
readRequest->m_allocator->UnlockAllocator();
|
||||
readRequest->m_allocator = nullptr;
|
||||
}
|
||||
@@ -293,7 +293,7 @@ namespace AZ::IO
|
||||
request->m_request.CreateReport(reportType);
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
Streamer::Streamer(const AZStd::thread_desc& threadDesc, AZStd::unique_ptr<Scheduler> streamStack)
|
||||
: m_streamStack(AZStd::move(streamStack))
|
||||
{
|
||||
|
||||
@@ -17,116 +17,113 @@
|
||||
#include <AzCore/std/containers/queue.h>
|
||||
#include <AzCore/Statistics/RunningStatistic.h>
|
||||
|
||||
namespace AZ
|
||||
namespace AZ::IO
|
||||
{
|
||||
namespace IO
|
||||
class StreamerContext
|
||||
{
|
||||
class StreamerContext
|
||||
{
|
||||
public:
|
||||
using PreparedQueue = AZStd::deque<FileRequest*>;
|
||||
public:
|
||||
using PreparedQueue = AZStd::deque<FileRequest*>;
|
||||
|
||||
~StreamerContext();
|
||||
~StreamerContext();
|
||||
|
||||
//! Gets a new file request, either by creating a new instance or
|
||||
//! picking one from the recycle bin. This version should only be used
|
||||
//! by nodes on the streaming stack as it's not thread safe, but faster.
|
||||
//! The scheduler will automatically recycle these requests.
|
||||
FileRequest* GetNewInternalRequest();
|
||||
//! Gets a new file request, either by creating a new instance or
|
||||
//! picking one from the recycle bin. This version is for use by
|
||||
//! any system outside the stream stack and is thread safe. Once the
|
||||
//! reference count in the request hits zero it will automatically be recycled.
|
||||
FileRequestPtr GetNewExternalRequest();
|
||||
//! Gets a batch of new file requests, either by creating new instances or
|
||||
//! picking from the recycle bin. This version is for use by
|
||||
//! any system outside the stream stack and is thread safe. The owner
|
||||
//! needs to manually recycle these requests once they're done. Requests
|
||||
//! with a reference count of zero will automatically be recycled.
|
||||
//! If multiple requests need to be create this is preferable as it only locks the
|
||||
//! recycle bin once.
|
||||
void GetNewExternalRequestBatch(AZStd::vector<FileRequestPtr>& requests, size_t count);
|
||||
//! Gets a new file request, either by creating a new instance or
|
||||
//! picking one from the recycle bin. This version should only be used
|
||||
//! by nodes on the streaming stack as it's not thread safe, but faster.
|
||||
//! The scheduler will automatically recycle these requests.
|
||||
FileRequest* GetNewInternalRequest();
|
||||
//! Gets a new file request, either by creating a new instance or
|
||||
//! picking one from the recycle bin. This version is for use by
|
||||
//! any system outside the stream stack and is thread safe. Once the
|
||||
//! reference count in the request hits zero it will automatically be recycled.
|
||||
FileRequestPtr GetNewExternalRequest();
|
||||
//! Gets a batch of new file requests, either by creating new instances or
|
||||
//! picking from the recycle bin. This version is for use by
|
||||
//! any system outside the stream stack and is thread safe. The owner
|
||||
//! needs to manually recycle these requests once they're done. Requests
|
||||
//! with a reference count of zero will automatically be recycled.
|
||||
//! If multiple requests need to be create this is preferable as it only locks the
|
||||
//! recycle bin once.
|
||||
void GetNewExternalRequestBatch(AZStd::vector<FileRequestPtr>& requests, size_t count);
|
||||
|
||||
//! Gets the number of prepared requests. Prepared requests are requests
|
||||
//! that are ready to be queued up for further processing.
|
||||
size_t GetNumPreparedRequests() const;
|
||||
//! Gets the next prepared request that should be queued. Prepared requests
|
||||
//! are requests that are ready to be queued up for further processing.
|
||||
FileRequest* PopPreparedRequest();
|
||||
//! Adds a prepared request for later queuing and processing.
|
||||
void PushPreparedRequest(FileRequest* request);
|
||||
//! Gets the prepared requests that are queued to be processed.
|
||||
PreparedQueue& GetPreparedRequests();
|
||||
//! Gets the prepared requests that are queued to be processed.
|
||||
const PreparedQueue& GetPreparedRequests() const;
|
||||
//! Gets the number of prepared requests. Prepared requests are requests
|
||||
//! that are ready to be queued up for further processing.
|
||||
size_t GetNumPreparedRequests() const;
|
||||
//! Gets the next prepared request that should be queued. Prepared requests
|
||||
//! are requests that are ready to be queued up for further processing.
|
||||
FileRequest* PopPreparedRequest();
|
||||
//! Adds a prepared request for later queuing and processing.
|
||||
void PushPreparedRequest(FileRequest* request);
|
||||
//! Gets the prepared requests that are queued to be processed.
|
||||
PreparedQueue& GetPreparedRequests();
|
||||
//! Gets the prepared requests that are queued to be processed.
|
||||
const PreparedQueue& GetPreparedRequests() const;
|
||||
|
||||
//! Marks a request as completed so the main thread in Streamer can close it out.
|
||||
//! This can be safely called from multiple threads.
|
||||
void MarkRequestAsCompleted(FileRequest* request);
|
||||
//! Rejects a request by removing it from the chain and recycling it.
|
||||
//! Only requests without children can be rejected. If the rejected request has a parent it might need to be processed
|
||||
//! further.
|
||||
//! @param request The request to remove and recycle.
|
||||
//! @return The parent request of the rejected request or null if there was no parent.
|
||||
FileRequest* RejectRequest(FileRequest* request);
|
||||
//! Adds an old request to the recycle bin so it can be reused later.
|
||||
void RecycleRequest(FileRequest* request);
|
||||
//! Adds an old external request to the recycle bin so it can be reused later.
|
||||
void RecycleRequest(ExternalFileRequest* request);
|
||||
//! Marks a request as completed so the main thread in Streamer can close it out.
|
||||
//! This can be safely called from multiple threads.
|
||||
void MarkRequestAsCompleted(FileRequest* request);
|
||||
//! Rejects a request by removing it from the chain and recycling it.
|
||||
//! Only requests without children can be rejected. If the rejected request has a parent it might need to be processed
|
||||
//! further.
|
||||
//! @param request The request to remove and recycle.
|
||||
//! @return The parent request of the rejected request or null if there was no parent.
|
||||
FileRequest* RejectRequest(FileRequest* request);
|
||||
//! Adds an old request to the recycle bin so it can be reused later.
|
||||
void RecycleRequest(FileRequest* request);
|
||||
//! Adds an old external request to the recycle bin so it can be reused later.
|
||||
void RecycleRequest(ExternalFileRequest* request);
|
||||
|
||||
//! Does the FinalizeRequest callback where appropriate and does some bookkeeping to finalize requests.
|
||||
//! @return True if any requests were finalized, otherwise false.
|
||||
bool FinalizeCompletedRequests();
|
||||
//! Does the FinalizeRequest callback where appropriate and does some bookkeeping to finalize requests.
|
||||
//! @return True if any requests were finalized, otherwise false.
|
||||
bool FinalizeCompletedRequests();
|
||||
|
||||
//! Causes the main thread for streamer to wake up and process any pending requests. If the thread
|
||||
//! is already awake, nothing happens.
|
||||
void WakeUpSchedulingThread();
|
||||
//! If there's no pending messages this will cause the main thread for streamer to go to sleep.
|
||||
void SuspendSchedulingThread();
|
||||
//! Returns the native primitive(s) used to suspend and wake up the scheduling thread and possibly other threads.
|
||||
AZ::Platform::StreamerContextThreadSync& GetStreamerThreadSynchronizer();
|
||||
//! Causes the main thread for streamer to wake up and process any pending requests. If the thread
|
||||
//! is already awake, nothing happens.
|
||||
void WakeUpSchedulingThread();
|
||||
//! If there's no pending messages this will cause the main thread for streamer to go to sleep.
|
||||
void SuspendSchedulingThread();
|
||||
//! Returns the native primitive(s) used to suspend and wake up the scheduling thread and possibly other threads.
|
||||
AZ::Platform::StreamerContextThreadSync& GetStreamerThreadSynchronizer();
|
||||
|
||||
//! Collects statistics recorded during processing. This will only return statistics for the
|
||||
//! context. Use the CollectStatistics on AZ::IO::Streamer to get all statistics.
|
||||
void CollectStatistics(AZStd::vector<Statistic>& statistics);
|
||||
//! Collects statistics recorded during processing. This will only return statistics for the
|
||||
//! context. Use the CollectStatistics on AZ::IO::Streamer to get all statistics.
|
||||
void CollectStatistics(AZStd::vector<Statistic>& statistics);
|
||||
|
||||
private:
|
||||
//! Gets a new FileRequestPtr. This version is for internal use only and is not thread-safe.
|
||||
//! This will be called by GetNewExternalRequest or GetNewExternalRequestBatch which are responsible
|
||||
//! for managing the lock to the recycle bin.
|
||||
FileRequestPtr GetNewExternalRequestUnguarded();
|
||||
private:
|
||||
//! Gets a new FileRequestPtr. This version is for internal use only and is not thread-safe.
|
||||
//! This will be called by GetNewExternalRequest or GetNewExternalRequestBatch which are responsible
|
||||
//! for managing the lock to the recycle bin.
|
||||
FileRequestPtr GetNewExternalRequestUnguarded();
|
||||
|
||||
inline static constexpr size_t s_initialRecycleBinSize = 64;
|
||||
inline static constexpr size_t s_initialRecycleBinSize = 64;
|
||||
|
||||
AZStd::mutex m_externalRecycleBinGuard;
|
||||
AZStd::vector<ExternalFileRequest*> m_externalRecycleBin;
|
||||
AZStd::vector<FileRequest*> m_internalRecycleBin;
|
||||
|
||||
// The completion is guarded so other threads can perform async IO and safely mark requests as completed.
|
||||
AZStd::recursive_mutex m_completedGuard;
|
||||
AZStd::queue<FileRequest*> m_completed;
|
||||
AZStd::mutex m_externalRecycleBinGuard;
|
||||
AZStd::vector<ExternalFileRequest*> m_externalRecycleBin;
|
||||
AZStd::vector<FileRequest*> m_internalRecycleBin;
|
||||
|
||||
// The prepared request queue is not guarded and should only be called from the main Streamer thread.
|
||||
PreparedQueue m_preparedRequests;
|
||||
// The completion is guarded so other threads can perform async IO and safely mark requests as completed.
|
||||
AZStd::recursive_mutex m_completedGuard;
|
||||
AZStd::queue<FileRequest*> m_completed;
|
||||
|
||||
// The prepared request queue is not guarded and should only be called from the main Streamer thread.
|
||||
PreparedQueue m_preparedRequests;
|
||||
|
||||
#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
|
||||
//! By how much time the prediction was off. This mostly covers the latter part of scheduling, which
|
||||
//! gets more precise the closer the request gets to completion.
|
||||
AZ::Statistics::RunningStatistic m_predictionAccuracyUsStat;
|
||||
//! By how much time the prediction was off. This mostly covers the latter part of scheduling, which
|
||||
//! gets more precise the closer the request gets to completion.
|
||||
AZ::Statistics::RunningStatistic m_predictionAccuracyUsStat;
|
||||
|
||||
//! Tracks the percentage of requests with late predictions where the request completed earlier than expected,
|
||||
//! versus the requests that completed later than predicted.
|
||||
AZ::Statistics::RunningStatistic m_latePredictionsPercentageStat;
|
||||
//! Tracks the percentage of requests with late predictions where the request completed earlier than expected,
|
||||
//! versus the requests that completed later than predicted.
|
||||
AZ::Statistics::RunningStatistic m_latePredictionsPercentageStat;
|
||||
|
||||
//! Percentage of requests that missed their deadline. If percentage is too high it can indicate that
|
||||
//! there are too many file requests or the deadlines for requests are too tight.
|
||||
AZ::Statistics::RunningStatistic m_missedDeadlinePercentageStat;
|
||||
//! Percentage of requests that missed their deadline. If percentage is too high it can indicate that
|
||||
//! there are too many file requests or the deadlines for requests are too tight.
|
||||
AZ::Statistics::RunningStatistic m_missedDeadlinePercentageStat;
|
||||
#endif // AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
|
||||
|
||||
//! Platform-specific synchronization object used to suspend the Streamer thread and wake it up to resume procesing.
|
||||
AZ::Platform::StreamerContextThreadSync m_threadSync;
|
||||
//! Platform-specific synchronization object used to suspend the Streamer thread and wake it up to resume procesing.
|
||||
AZ::Platform::StreamerContextThreadSync m_threadSync;
|
||||
|
||||
size_t m_pendingIdCounter{ 0 };
|
||||
};
|
||||
} // namespace IO
|
||||
} // namespace AZ
|
||||
size_t m_pendingIdCounter{ 0 };
|
||||
};
|
||||
} // namespace AZ::IO
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/FileIOEventBus.h>
|
||||
|
||||
#include <AzCore/PlatformIncl.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
@@ -101,7 +100,6 @@ bool SystemFile::Open(const char* fileName, int mode, int platformFlags)
|
||||
{
|
||||
if (strlen(fileName) > m_fileName.max_size())
|
||||
{
|
||||
EBUS_EVENT(FileIOEventBus, OnError, this, nullptr, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -109,17 +107,6 @@ bool SystemFile::Open(const char* fileName, int mode, int platformFlags)
|
||||
m_fileName = fileName;
|
||||
}
|
||||
|
||||
if (FileIOBus::HasHandlers())
|
||||
{
|
||||
bool isOpen = false;
|
||||
bool isHandled = false;
|
||||
EBUS_EVENT_RESULT(isHandled, FileIOBus, OnOpen, *this, m_fileName.c_str(), mode, platformFlags, isOpen);
|
||||
if (isHandled)
|
||||
{
|
||||
return isOpen;
|
||||
}
|
||||
}
|
||||
|
||||
AZ_Assert(!IsOpen(), "This file (%s) is already open!", m_fileName.c_str());
|
||||
|
||||
return PlatformOpen(mode, platformFlags);
|
||||
@@ -133,31 +120,11 @@ bool SystemFile::ReOpen(int mode, int platformFlags)
|
||||
|
||||
void SystemFile::Close()
|
||||
{
|
||||
if (FileIOBus::HasHandlers())
|
||||
{
|
||||
bool isHandled = false;
|
||||
EBUS_EVENT_RESULT(isHandled, FileIOBus, OnClose, *this);
|
||||
if (isHandled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PlatformClose();
|
||||
}
|
||||
|
||||
void SystemFile::Seek(SeekSizeType offset, SeekMode mode)
|
||||
{
|
||||
if (FileIOBus::HasHandlers())
|
||||
{
|
||||
bool isHandled = false;
|
||||
EBUS_EVENT_RESULT(isHandled, FileIOBus, OnSeek, *this, offset, mode);
|
||||
if (isHandled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Platform::Seek(m_handle, this, offset, mode);
|
||||
}
|
||||
|
||||
@@ -178,33 +145,11 @@ AZ::u64 SystemFile::ModificationTime()
|
||||
|
||||
SystemFile::SizeType SystemFile::Read(SizeType byteSize, void* buffer)
|
||||
{
|
||||
if (FileIOBus::HasHandlers())
|
||||
{
|
||||
SizeType numRead = 0;
|
||||
bool isHandled = false;
|
||||
EBUS_EVENT_RESULT(isHandled, FileIOBus, OnRead, *this, byteSize, buffer, numRead);
|
||||
if (isHandled)
|
||||
{
|
||||
return numRead;
|
||||
}
|
||||
}
|
||||
|
||||
return Platform::Read(m_handle, this, byteSize, buffer);
|
||||
}
|
||||
|
||||
SystemFile::SizeType SystemFile::Write(const void* buffer, SizeType byteSize)
|
||||
{
|
||||
if (FileIOBus::HasHandlers())
|
||||
{
|
||||
SizeType numWritten = 0;
|
||||
bool isHandled = false;
|
||||
EBUS_EVENT_RESULT(isHandled, FileIOBus, OnWrite, *this, buffer, byteSize, numWritten);
|
||||
if (isHandled)
|
||||
{
|
||||
return numWritten;
|
||||
}
|
||||
}
|
||||
|
||||
return Platform::Write(m_handle, this, buffer, byteSize);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
AZ_CVAR(float, cl_jobThreadsConcurrencyRatio, 0.6f, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system multiplier on the number of hw threads the machine creates at initialization");
|
||||
AZ_CVAR(uint32_t, cl_jobThreadsNumReserved, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system number of hardware threads that are reserved for O3DE system threads");
|
||||
AZ_CVAR(uint32_t, cl_jobThreadsMinNumber, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system minimum number of worker threads to create after scaling the number of hw threads");
|
||||
AZ_CVAR(uint32_t, cl_jobThreadsMinNumber, 3, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system minimum number of worker threads to create after scaling the number of hw threads");
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace AZ
|
||||
behaviorContext->Class<Aabb>()
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "math")
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
->Attribute(AZ::Script::Attributes::GenericConstructorOverride, &AabbDefaultConstructor)
|
||||
->Property("min", &Aabb::GetMin, &Aabb::SetMin)
|
||||
@@ -112,46 +112,46 @@ namespace AZ
|
||||
->Method("GetCenter", &Aabb::GetCenter)
|
||||
->Method("Set", &Aabb::Set)
|
||||
->Attribute(AZ::Script::Attributes::MethodOverride, &AabbSetGeneric)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method("CreateFromObb", &Aabb::CreateFromObb)
|
||||
->Method("GetXExtent", &Aabb::GetXExtent)
|
||||
->Method("GetYExtent", &Aabb::GetYExtent)
|
||||
->Method("GetZExtent", &Aabb::GetZExtent)
|
||||
->Method("GetAsSphere", &Aabb::GetAsSphere, nullptr, "() -> Vector3(center) and float(radius)")
|
||||
->Attribute(AZ::Script::Attributes::MethodOverride, &AabbGetAsSphereMultipleReturn)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method<bool (Aabb::*)(const Aabb&) const>("Contains", &Aabb::Contains, nullptr, "const Vector3& or const Aabb&")
|
||||
->Attribute(AZ::Script::Attributes::MethodOverride, &AabbContainsGeneric)
|
||||
->Method<bool (Aabb::*)(const Vector3&) const>("ContainsVector3", &Aabb::Contains, nullptr, "const Vector3&")
|
||||
->Attribute(AZ::Script::Attributes::Ignore, 0) // ignore for script since we already got the generic contains above
|
||||
->Method("Overlaps", &Aabb::Overlaps)
|
||||
->Method("Expand", &Aabb::Expand)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method("GetExpanded", &Aabb::GetExpanded)
|
||||
->Method("AddPoint", &Aabb::AddPoint)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method("AddAabb", &Aabb::AddAabb)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method("GetDistance", &Aabb::GetDistance)
|
||||
->Method("GetClamped", &Aabb::GetClamped)
|
||||
->Method("Clamp", &Aabb::Clamp)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method("SetNull", &Aabb::SetNull)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method("Translate", &Aabb::Translate)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method("GetTranslated", &Aabb::GetTranslated)
|
||||
->Method("GetSurfaceArea", &Aabb::GetSurfaceArea)
|
||||
->Method("GetTransformedObb", static_cast<Obb(Aabb::*)(const Transform&) const>(&Aabb::GetTransformedObb))
|
||||
->Method("GetTransformedAabb", static_cast<Aabb(Aabb::*)(const Transform&) const>(&Aabb::GetTransformedAabb))
|
||||
->Method("ApplyTransform", &Aabb::ApplyTransform)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method("Clone", [](const Aabb& rhs) -> Aabb { return rhs; })
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
|
||||
->Method("IsFinite", &Aabb::IsFinite)
|
||||
->Method("Equal", &Aabb::operator==)
|
||||
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All);
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ namespace AZ
|
||||
behaviorContext->Class<Color>()->
|
||||
Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)->
|
||||
Attribute(AZ::Script::Attributes::Module, "math")->
|
||||
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)->
|
||||
Constructor<float>()->
|
||||
Constructor<float, float, float, float>()->
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
//
|
||||
// When AZ_CRC("My string") is used by default it will map to AZ::Crc32("My string").
|
||||
// We do have a pro-processor program which will precompute the crc for you and
|
||||
// transform that macro to AZ_CRC("My string",0xabcdef00) this will expand to just 0xabcdef00.
|
||||
// transform that macro to AZ_CRC("My string", 0x18fbd270) this will expand to just 0x18fbd270.
|
||||
// This will remove completely the "My string" from your executable, it will add it to a database and so on.
|
||||
// WHen you want to update the string, just change the string.
|
||||
// If you don't run the precompile step the code should still run fine, except it will be slower,
|
||||
@@ -24,7 +24,7 @@
|
||||
// a constant expression.
|
||||
// For example
|
||||
// switch(id) {
|
||||
// case AZ_CRC("My string",0xabcdef00): {} break; // this will compile fine
|
||||
// case AZ_CRC("My string",0x18fbd270): {} break; // this will compile fine
|
||||
// case AZ_CRC("My string"): {} break; // this will cause "error C2051: case expression not constant"
|
||||
// }
|
||||
// So it's you choice what you do, depending on your needs.
|
||||
|
||||
@@ -260,7 +260,7 @@ namespace AZ
|
||||
behaviorContext->Class<Matrix3x3>()->
|
||||
Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)->
|
||||
Attribute(Script::Attributes::Module, "math")->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
|
||||
Attribute(AZ::Script::Attributes::ConstructorOverride, &Internal::Matrix3x3ScriptConstructor)->
|
||||
Attribute(Script::Attributes::GenericConstructorOverride, &Internal::Matrix3x3DefaultConstructor)->
|
||||
|
||||
@@ -280,7 +280,7 @@ namespace AZ
|
||||
behaviorContext->Class<Matrix4x4>()->
|
||||
Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)->
|
||||
Attribute(Script::Attributes::Module, "math")->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
|
||||
Attribute(Script::Attributes::GenericConstructorOverride, &Internal::Matrix4x4DefaultConstructor)->
|
||||
Property<Vector4(Matrix4x4::*)() const, void (Matrix4x4::*)(const Vector4&)>("basisX", &Matrix4x4::GetBasisX, &Matrix4x4::SetBasisX)->
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace AZ
|
||||
if (behaviorContext)
|
||||
{
|
||||
behaviorContext->Class<Obb>()->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
|
||||
Attribute(Script::Attributes::GenericConstructorOverride, &Internal::ObbDefaultConstructor)->
|
||||
Property("position", &Obb::GetPosition, &Obb::SetPosition)->
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace AZ
|
||||
if (behaviorContext)
|
||||
{
|
||||
behaviorContext->Class<Plane>()->
|
||||
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)->
|
||||
Attribute(AZ::Script::Attributes::GenericConstructorOverride, &Internal::PlaneDefaultConstructor)->
|
||||
Method("ToString", &Internal::PlaneToString)->
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace AZ
|
||||
behaviorContext->Class<Quaternion>()->
|
||||
Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)->
|
||||
Attribute(AZ::Script::Attributes::Module, "math")->
|
||||
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Constructor<float>()->
|
||||
Constructor<float, float, float, float>()->
|
||||
Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)->
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace AZ
|
||||
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->Class<SplineAddress>()->
|
||||
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List)->
|
||||
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Constructor<u64, float>()->
|
||||
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
|
||||
Attribute(Script::Attributes::ConstructorOverride, &Internal::SplineAddressScriptConstructor)->
|
||||
@@ -118,7 +118,7 @@ namespace AZ
|
||||
Property("rayDistance", [](RaySplineQueryResult* thisPtr) { return thisPtr->m_rayDistance; }, nullptr);
|
||||
|
||||
behaviorContext->Class<Spline>()->
|
||||
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List)->
|
||||
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::RuntimeOwn)->
|
||||
Method("GetNearestAddressRay", &Spline::GetNearestAddressRay)->
|
||||
Method("GetNearestAddressPosition", &Spline::GetNearestAddressPosition)->
|
||||
|
||||
@@ -293,7 +293,7 @@ namespace AZ
|
||||
behaviorContext->Class<Transform>()->
|
||||
Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)->
|
||||
Attribute(Script::Attributes::Module, "math")->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
|
||||
Attribute(Script::Attributes::GenericConstructorOverride, &Internal::TransformDefaultConstructor)->
|
||||
Constructor<const Vector3&, const Quaternion&, float>()->
|
||||
@@ -312,35 +312,35 @@ namespace AZ
|
||||
Attribute(Script::Attributes::Ignore, 0)-> // ignore for script since we already got the generic multiply above
|
||||
Method<Transform(Transform::*)(const Transform&) const>("MultiplyTransform", &Transform::operator*)->
|
||||
Attribute(Script::Attributes::Ignore, 0)-> // ignore for script since we already got the generic multiply above
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Method("Equal", &Transform::operator==)->
|
||||
Attribute(Script::Attributes::Operator, Script::Attributes::OperatorType::Equal)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Method("Clone", [](const Transform& rhs) -> Transform { return rhs; })->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Method("GetTranslation", &Transform::GetTranslation)->
|
||||
Method("GetBasisAndTranslation", &Transform::GetBasisAndTranslation)->
|
||||
Attribute(Script::Attributes::MethodOverride, &Internal::TransformGetBasisAndTranslationMultipleReturn)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Method("TransformVector", &Transform::TransformVector)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Method<void (Transform::*)(const Vector3&)>("SetTranslation", &Transform::SetTranslation)->
|
||||
Attribute(Script::Attributes::MethodOverride, &Internal::TransformSetTranslationGeneric)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Method("GetRotation", &Transform::GetRotation)->
|
||||
Method<void (Transform::*)(const Quaternion&)>("SetRotation", &Transform::SetRotation)->
|
||||
Method("GetUniformScale", &Transform::GetUniformScale)->
|
||||
Method("SetUniformScale", &Transform::SetUniformScale)->
|
||||
Method("ExtractUniformScale", &Transform::ExtractUniformScale)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Method("MultiplyByUniformScale", &Transform::MultiplyByUniformScale)->
|
||||
Method("GetInverse", &Transform::GetInverse)->
|
||||
Method("Invert", &Transform::Invert)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Method("IsOrthogonal", &Transform::IsOrthogonal, behaviorContext->MakeDefaultValues(Constants::Tolerance))->
|
||||
Method("GetOrthogonalized", &Transform::GetOrthogonalized)->
|
||||
Method("Orthogonalize", &Transform::Orthogonalize)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Method("IsClose", &Transform::IsClose, behaviorContext->MakeDefaultValues(Constants::Tolerance))->
|
||||
Method("IsFinite", &Transform::IsFinite)->
|
||||
Method("CreateIdentity", &Transform::CreateIdentity)->
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace AZ
|
||||
behaviorContext->Class<Vector2>()->
|
||||
Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)->
|
||||
Attribute(Script::Attributes::Module, "math")->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Constructor<float>()->
|
||||
Constructor<float, float>()->
|
||||
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace AZ
|
||||
behaviorContext->Class<Vector3>()->
|
||||
Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)->
|
||||
Attribute(Script::Attributes::Module, "math")->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Constructor<float>()->
|
||||
Constructor<float, float, float>()->
|
||||
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
|
||||
|
||||
@@ -215,7 +215,7 @@ namespace AZ
|
||||
behaviorContext->Class<Vector4>()->
|
||||
Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)->
|
||||
Attribute(Script::Attributes::Module, "math")->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
|
||||
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
|
||||
Constructor<float>()->
|
||||
Constructor<float, float, float, float>()->
|
||||
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#include <AzCore/PlatformIncl.h>
|
||||
#include <AzCore/Memory/AllocationRecords.h>
|
||||
#include <AzCore/Memory/AllocatorManager.h>
|
||||
#include <AzCore/Driller/DrillerBus.h>
|
||||
|
||||
#include <AzCore/std/time.h>
|
||||
#include <AzCore/std/parallel/mutex.h>
|
||||
#include <AzCore/std/parallel/scoped_lock.h>
|
||||
|
||||
#include <AzCore/Debug/StackTracer.h>
|
||||
|
||||
@@ -26,30 +26,25 @@ using namespace AZ::Debug;
|
||||
// AllocationRecords
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
AllocationRecords::AllocationRecords(unsigned char stackRecordLevels, bool isMemoryGuard, bool isMarkUnallocatedMemory, const char* allocatorName)
|
||||
AllocationRecords::AllocationRecords(unsigned char stackRecordLevels, [[maybe_unused]] bool isMemoryGuard, bool isMarkUnallocatedMemory, const char* allocatorName)
|
||||
: m_mode(AllocatorManager::Instance().m_defaultTrackingRecordMode)
|
||||
, m_isAutoIntegrityCheck(false)
|
||||
, m_isMarkUnallocatedMemory(isMarkUnallocatedMemory)
|
||||
, m_saveNames(false)
|
||||
, m_decodeImmediately(false)
|
||||
, m_numStackLevels(stackRecordLevels)
|
||||
#if defined(ENABLE_MEMORY_GUARD)
|
||||
, m_memoryGuardSize(isMemoryGuard ? sizeof(Debug::GuardValue) : 0)
|
||||
#else
|
||||
, m_memoryGuardSize(0)
|
||||
#endif
|
||||
, m_requestedAllocs(0)
|
||||
, m_requestedBytes(0)
|
||||
, m_requestedBytesPeak(0)
|
||||
, m_allocatorName(allocatorName)
|
||||
{
|
||||
#if defined(ENABLE_MEMORY_GUARD)
|
||||
m_memoryGuardSize = isMemoryGuard ? sizeof(Debug::GuardValue) : 0;
|
||||
#else
|
||||
(void)isMemoryGuard;
|
||||
m_memoryGuardSize = 0;
|
||||
#endif
|
||||
#if AZ_TRAIT_OS_HAS_CRITICAL_SECTION_SPIN_COUNT
|
||||
SetCriticalSectionSpinCount(DrillerEBusMutex::GetMutex().native_handle(), 4000);
|
||||
#endif
|
||||
// preallocate some buckets
|
||||
//m_records.rehash(20000);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ~AllocationRecords
|
||||
@@ -73,7 +68,7 @@ AllocationRecords::~AllocationRecords()
|
||||
void
|
||||
AllocationRecords::lock()
|
||||
{
|
||||
DrillerEBusMutex::GetMutex().lock();
|
||||
m_recordsMutex.lock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -82,7 +77,7 @@ AllocationRecords::lock()
|
||||
//=========================================================================
|
||||
bool AllocationRecords::try_lock()
|
||||
{
|
||||
return DrillerEBusMutex::GetMutex().try_lock();
|
||||
return m_recordsMutex.try_lock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -92,7 +87,7 @@ bool AllocationRecords::try_lock()
|
||||
void
|
||||
AllocationRecords::unlock()
|
||||
{
|
||||
DrillerEBusMutex::GetMutex().unlock();
|
||||
m_recordsMutex.unlock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -117,7 +112,7 @@ AllocationRecords::RegisterAllocation(void* address, size_t byteSize, size_t ali
|
||||
{
|
||||
if (m_isAutoIntegrityCheck)
|
||||
{
|
||||
IntegrityCheckNoLock();
|
||||
IntegrityCheck();
|
||||
}
|
||||
|
||||
AZ_Assert(byteSize>sizeof(Debug::GuardValue), "Did you forget to add the extra MemoryGuardSize() bytes?");
|
||||
@@ -125,7 +120,11 @@ AllocationRecords::RegisterAllocation(void* address, size_t byteSize, size_t ali
|
||||
new(reinterpret_cast<char*>(address)+byteSize) Debug::GuardValue();
|
||||
}
|
||||
|
||||
Debug::AllocationRecordsType::pair_iter_bool iterBool = m_records.insert_key(address);
|
||||
Debug::AllocationRecordsType::pair_iter_bool iterBool;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_recordsMutex);
|
||||
iterBool = m_records.insert_key(address);
|
||||
}
|
||||
|
||||
if (!iterBool.second)
|
||||
{
|
||||
@@ -210,7 +209,15 @@ AllocationRecords::RegisterAllocation(void* address, size_t byteSize, size_t ali
|
||||
|
||||
// statistics
|
||||
m_requestedBytes += byteSize;
|
||||
m_requestedBytesPeak = AZStd::GetMax(m_requestedBytesPeak, m_requestedBytes);
|
||||
|
||||
size_t currentRequestedBytePeak;
|
||||
size_t newRequestedBytePeak;
|
||||
do
|
||||
{
|
||||
currentRequestedBytePeak = m_requestedBytesPeak.load(std::memory_order::memory_order_relaxed);
|
||||
newRequestedBytePeak = AZStd::GetMax(currentRequestedBytePeak, m_requestedBytes.load(std::memory_order::memory_order_relaxed));
|
||||
} while (!m_requestedBytesPeak.compare_exchange_weak(currentRequestedBytePeak, newRequestedBytePeak));
|
||||
|
||||
++m_requestedAllocs;
|
||||
|
||||
return &ai;
|
||||
@@ -220,8 +227,7 @@ AllocationRecords::RegisterAllocation(void* address, size_t byteSize, size_t ali
|
||||
// UnregisterAllocation
|
||||
// [9/11/2009]
|
||||
//=========================================================================
|
||||
void
|
||||
AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t alignment, AllocationInfo* info)
|
||||
void AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t alignment, AllocationInfo* info)
|
||||
{
|
||||
if (m_mode == RECORD_NO_RECORDS)
|
||||
{
|
||||
@@ -232,24 +238,38 @@ AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t a
|
||||
return;
|
||||
}
|
||||
|
||||
Debug::AllocationRecordsType::iterator iter = m_records.find(address);
|
||||
|
||||
// We cannot assert if an allocation does not exist because our allocators start up way before the driller is started and the Allocator Records would be created.
|
||||
// It is currently impossible to actually track all allocations that happen before a certain point
|
||||
//AZ_Assert(iter!=m_records.end(), "Could not find address 0x%p in the allocator!", address);
|
||||
if (iter == m_records.end())
|
||||
AllocationInfo allocationInfo;
|
||||
{
|
||||
return;
|
||||
AZStd::scoped_lock lock(m_recordsMutex);
|
||||
Debug::AllocationRecordsType::iterator iter = m_records.find(address);
|
||||
// We cannot assert if an allocation does not exist because allocations may have been made before tracking was enabled.
|
||||
// It is currently impossible to actually track all allocations that happen before a certain point
|
||||
// AZ_Assert(iter!=m_records.end(), "Could not find address 0x%p in the allocator!", address);
|
||||
if (iter == m_records.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
allocationInfo = iter->second;
|
||||
m_records.erase(iter);
|
||||
|
||||
// try to be more aggressive and keep the memory footprint low.
|
||||
// \todo store the load factor at the last rehash to avoid unnecessary rehash
|
||||
if (m_records.load_factor() < 0.9f)
|
||||
{
|
||||
m_records.rehash(0);
|
||||
}
|
||||
}
|
||||
AllocatorManager::Instance().DebugBreak(address, iter->second);
|
||||
|
||||
|
||||
AllocatorManager::Instance().DebugBreak(address, allocationInfo);
|
||||
|
||||
(void)byteSize;
|
||||
(void)alignment;
|
||||
AZ_Assert(byteSize==0||byteSize==iter->second.m_byteSize, "Mismatched byteSize at deallocation! You supplied an invalid value!");
|
||||
AZ_Assert(alignment==0||alignment==iter->second.m_alignment, "Mismatched alignment at deallocation! You supplied an invalid value!");
|
||||
AZ_Assert(byteSize==0||byteSize==allocationInfo.m_byteSize, "Mismatched byteSize at deallocation! You supplied an invalid value!");
|
||||
AZ_Assert(alignment==0||alignment==allocationInfo.m_alignment, "Mismatched alignment at deallocation! You supplied an invalid value!");
|
||||
|
||||
// statistics
|
||||
m_requestedBytes -= iter->second.m_byteSize;
|
||||
m_requestedBytes -= allocationInfo.m_byteSize;
|
||||
|
||||
#if defined(ENABLE_MEMORY_GUARD)
|
||||
// memory guard
|
||||
@@ -258,18 +278,18 @@ AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t a
|
||||
if (m_isAutoIntegrityCheck)
|
||||
{
|
||||
// full integrity check
|
||||
IntegrityCheckNoLock();
|
||||
IntegrityCheck();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check current allocation
|
||||
char* guardAddress = reinterpret_cast<char*>(address)+iter->second.m_byteSize;
|
||||
char* guardAddress = reinterpret_cast<char*>(address)+allocationInfo.m_byteSize;
|
||||
Debug::GuardValue* guard = reinterpret_cast<Debug::GuardValue*>(guardAddress);
|
||||
if (!guard->Validate())
|
||||
{
|
||||
AZ_Printf("Memory", "Memory stomp located at address %p, part of allocation:", guardAddress);
|
||||
PrintAllocationsCB printAlloc(true);
|
||||
printAlloc(address, iter->second, m_numStackLevels);
|
||||
printAlloc(address, allocationInfo, m_numStackLevels);
|
||||
AZ_Assert(false, "MEMORY STOMP DETECTED!!!");
|
||||
}
|
||||
guard->~GuardValue();
|
||||
@@ -278,33 +298,26 @@ AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t a
|
||||
#endif
|
||||
|
||||
// delete allocation record
|
||||
if (iter->second.m_namesBlock)
|
||||
if (allocationInfo.m_namesBlock)
|
||||
{
|
||||
m_records.get_allocator().deallocate(iter->second.m_namesBlock, iter->second.m_namesBlockSize, 1);
|
||||
iter->second.m_namesBlock = nullptr;
|
||||
iter->second.m_namesBlockSize = 0;
|
||||
iter->second.m_name = nullptr;
|
||||
iter->second.m_fileName = nullptr;
|
||||
m_records.get_allocator().deallocate(allocationInfo.m_namesBlock, allocationInfo.m_namesBlockSize, 1);
|
||||
allocationInfo.m_namesBlock = nullptr;
|
||||
allocationInfo.m_namesBlockSize = 0;
|
||||
allocationInfo.m_name = nullptr;
|
||||
allocationInfo.m_fileName = nullptr;
|
||||
}
|
||||
if (iter->second.m_stackFrames)
|
||||
if (allocationInfo.m_stackFrames)
|
||||
{
|
||||
m_records.get_allocator().deallocate(iter->second.m_stackFrames, sizeof(AZ::Debug::StackFrame)*m_numStackLevels, 1);
|
||||
iter->second.m_stackFrames = nullptr;
|
||||
m_records.get_allocator().deallocate(allocationInfo.m_stackFrames, sizeof(AZ::Debug::StackFrame)*m_numStackLevels, 1);
|
||||
allocationInfo.m_stackFrames = nullptr;
|
||||
}
|
||||
|
||||
if (info)
|
||||
{
|
||||
*info = iter->second;
|
||||
*info = allocationInfo;
|
||||
}
|
||||
|
||||
m_records.erase(iter);
|
||||
|
||||
// try to be more aggressive and keep the memory footprint low.
|
||||
// \todo store the load factor at the last rehash to avoid unnecessary rehash
|
||||
if (m_records.load_factor()<0.9f)
|
||||
{
|
||||
m_records.rehash(0);
|
||||
}
|
||||
|
||||
// if requested set memory to a specific value.
|
||||
if (m_isMarkUnallocatedMemory)
|
||||
@@ -325,9 +338,14 @@ AllocationRecords::ResizeAllocation(void* address, size_t newSize)
|
||||
return;
|
||||
}
|
||||
|
||||
Debug::AllocationRecordsType::iterator iter = m_records.find(address);
|
||||
AZ_Assert(iter!=m_records.end(), "Could not find address 0x%p in the allocator!", address);
|
||||
AllocatorManager::Instance().DebugBreak(address, iter->second);
|
||||
AllocationInfo* allocationInfo;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_recordsMutex);
|
||||
Debug::AllocationRecordsType::iterator iter = m_records.find(address);
|
||||
AZ_Assert(iter != m_records.end(), "Could not find address 0x%p in the allocator!", address);
|
||||
allocationInfo = &iter->second;
|
||||
}
|
||||
AllocatorManager::Instance().DebugBreak(address, *allocationInfo);
|
||||
|
||||
#if defined(ENABLE_MEMORY_GUARD)
|
||||
if (m_memoryGuardSize == sizeof(Debug::GuardValue))
|
||||
@@ -335,12 +353,12 @@ AllocationRecords::ResizeAllocation(void* address, size_t newSize)
|
||||
if (m_isAutoIntegrityCheck)
|
||||
{
|
||||
// full integrity check
|
||||
IntegrityCheckNoLock();
|
||||
IntegrityCheck();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check memory guard
|
||||
char* guardAddress = reinterpret_cast<char*>(address)+iter->second.m_byteSize;
|
||||
char* guardAddress = reinterpret_cast<char*>(address) + allocationInfo->m_byteSize;
|
||||
Debug::GuardValue* guard = reinterpret_cast<Debug::GuardValue*>(guardAddress);
|
||||
if (!guard->Validate())
|
||||
{
|
||||
@@ -358,13 +376,19 @@ AllocationRecords::ResizeAllocation(void* address, size_t newSize)
|
||||
#endif
|
||||
|
||||
// statistics
|
||||
m_requestedBytes -= iter->second.m_byteSize;
|
||||
m_requestedBytes -= allocationInfo->m_byteSize;
|
||||
m_requestedBytes += newSize;
|
||||
m_requestedBytesPeak = AZStd::GetMax(m_requestedBytesPeak, m_requestedBytes);
|
||||
size_t currentRequestedBytePeak;
|
||||
size_t newRequestedBytePeak;
|
||||
do
|
||||
{
|
||||
currentRequestedBytePeak = m_requestedBytesPeak.load(std::memory_order::memory_order_relaxed);
|
||||
newRequestedBytePeak = AZStd::GetMax(currentRequestedBytePeak, m_requestedBytes.load(std::memory_order::memory_order_relaxed));
|
||||
} while (!m_requestedBytesPeak.compare_exchange_weak(currentRequestedBytePeak, newRequestedBytePeak));
|
||||
++m_requestedAllocs;
|
||||
|
||||
// update allocation size
|
||||
iter->second.m_byteSize = newSize;
|
||||
allocationInfo->m_byteSize = newSize;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -374,21 +398,20 @@ AllocationRecords::ResizeAllocation(void* address, size_t newSize)
|
||||
void
|
||||
AllocationRecords::SetMode(Mode mode)
|
||||
{
|
||||
DrillerEBusMutex::GetMutex().lock();
|
||||
|
||||
if (mode==RECORD_NO_RECORDS)
|
||||
if (mode == RECORD_NO_RECORDS)
|
||||
{
|
||||
m_records.clear();
|
||||
{
|
||||
AZStd::scoped_lock lock(m_recordsMutex);
|
||||
m_records.clear();
|
||||
}
|
||||
m_requestedBytes = 0;
|
||||
m_requestedBytesPeak = 0;
|
||||
m_requestedAllocs = 0;
|
||||
}
|
||||
|
||||
AZ_Warning("Memory", m_mode!=RECORD_NO_RECORDS||mode==RECORD_NO_RECORDS, "Records recording was disabled and now it's enabled! You might get assert when you free memory, if a you have allocations which were not recorded!");
|
||||
AZ_Warning("Memory", m_mode != RECORD_NO_RECORDS || mode == RECORD_NO_RECORDS, "Records recording was disabled and now it's enabled! You might get assert when you free memory, if a you have allocations which were not recorded!");
|
||||
|
||||
m_mode = mode;
|
||||
|
||||
DrillerEBusMutex::GetMutex().unlock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -398,11 +421,14 @@ AllocationRecords::SetMode(Mode mode)
|
||||
void
|
||||
AllocationRecords::EnumerateAllocations(AllocationInfoCBType cb)
|
||||
{
|
||||
DrillerEBusMutex::GetMutex().lock();
|
||||
// enumerate all allocations and stop if requested.
|
||||
// Since allocations can change during the iteration (code that prints out the records could allocate, which will
|
||||
// mutate m_records), we are going to make a copy and iterate the copy.
|
||||
const Debug::AllocationRecordsType recordsCopy = m_records;
|
||||
Debug::AllocationRecordsType recordsCopy;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_recordsMutex);
|
||||
recordsCopy = m_records;
|
||||
}
|
||||
for (Debug::AllocationRecordsType::const_iterator iter = recordsCopy.begin(); iter != recordsCopy.end(); ++iter)
|
||||
{
|
||||
if (!cb(iter->first, iter->second, m_numStackLevels))
|
||||
@@ -410,7 +436,6 @@ AllocationRecords::EnumerateAllocations(AllocationInfoCBType cb)
|
||||
break;
|
||||
}
|
||||
}
|
||||
DrillerEBusMutex::GetMutex().unlock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -420,38 +445,29 @@ AllocationRecords::EnumerateAllocations(AllocationInfoCBType cb)
|
||||
void
|
||||
AllocationRecords::IntegrityCheck() const
|
||||
{
|
||||
#if defined(ENABLE_MEMORY_GUARD)
|
||||
if (m_memoryGuardSize == sizeof(Debug::GuardValue))
|
||||
{
|
||||
DrillerEBusMutex::GetMutex().lock();
|
||||
|
||||
IntegrityCheckNoLock();
|
||||
|
||||
DrillerEBusMutex::GetMutex().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// IntegrityCheckNoLock
|
||||
// [9/13/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
AllocationRecords::IntegrityCheckNoLock() const
|
||||
{
|
||||
#if defined(ENABLE_MEMORY_GUARD)
|
||||
for (Debug::AllocationRecordsType::const_iterator iter = m_records.begin(); iter != m_records.end(); ++iter)
|
||||
{
|
||||
// check memory guard
|
||||
const char* guardAddress = reinterpret_cast<const char*>(iter->first)+ iter->second.m_byteSize;
|
||||
if (!reinterpret_cast<const Debug::GuardValue*>(guardAddress)->Validate())
|
||||
Debug::AllocationRecordsType recordsCopy;
|
||||
{
|
||||
// We have to turn off the integrity check at this point if we want to succesfully report the memory
|
||||
// stomp we just found. If we don't turn this off, the printf just winds off the stack as each memory
|
||||
// allocation done therein recurses this same code.
|
||||
*const_cast<bool*>(&m_isAutoIntegrityCheck) = false;
|
||||
AZ_Printf("Memory", "Memory stomp located at address %p, part of allocation:", guardAddress);
|
||||
PrintAllocationsCB printAlloc(true);
|
||||
printAlloc(iter->first, iter->second, m_numStackLevels);
|
||||
AZ_Error("Memory", false, "MEMORY STOMP DETECTED!!!");
|
||||
AZStd::scoped_lock lock(m_recordsMutex);
|
||||
recordsCopy = m_records;
|
||||
}
|
||||
for (Debug::AllocationRecordsType::const_iterator iter = recordsCopy.begin(); iter != recordsCopy.end(); ++iter)
|
||||
{
|
||||
// check memory guard
|
||||
const char* guardAddress = reinterpret_cast<const char*>(iter->first)+ iter->second.m_byteSize;
|
||||
if (!reinterpret_cast<const Debug::GuardValue*>(guardAddress)->Validate())
|
||||
{
|
||||
// We have to turn off the integrity check at this point if we want to succesfully report the memory
|
||||
// stomp we just found. If we don't turn this off, the printf just winds off the stack as each memory
|
||||
// allocation done therein recurses this same code.
|
||||
*const_cast<bool*>(&m_isAutoIntegrityCheck) = false;
|
||||
AZ_Printf("Memory", "Memory stomp located at address %p, part of allocation:", guardAddress);
|
||||
PrintAllocationsCB printAlloc(true);
|
||||
printAlloc(iter->first, iter->second, m_numStackLevels);
|
||||
AZ_Error("Memory", false, "MEMORY STOMP DETECTED!!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -120,10 +120,9 @@ namespace AZ
|
||||
*/
|
||||
class AllocationRecords
|
||||
{
|
||||
friend class MemoryDriller;
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(AllocationRecords, OSAllocator, 0);
|
||||
|
||||
public:
|
||||
enum Mode : int
|
||||
{
|
||||
RECORD_NO_RECORDS, ///< Never record any information.
|
||||
@@ -178,7 +177,7 @@ namespace AZ
|
||||
/// Returns peak of requested memory. IMPORTANT: This is user requested memory! Any allocator overhead is NOT included.
|
||||
size_t RequestedBytesPeak() const { return m_requestedBytesPeak; }
|
||||
/// Reset the peak allocation to the current requested memory.
|
||||
void ResetPeakBytes() { m_requestedBytesPeak = m_requestedBytes; }
|
||||
void ResetPeakBytes() { m_requestedBytesPeak.store(m_requestedBytes); }
|
||||
/// Return requested user bytes. IMPORTANT: This is user requested memory! Any allocator overhead is NOT included.
|
||||
size_t RequestedBytes() const { return m_requestedBytes; }
|
||||
/// Returns total number of requested allocations.
|
||||
@@ -186,8 +185,6 @@ namespace AZ
|
||||
|
||||
const char* GetAllocatorName() const { return m_allocatorName; }
|
||||
|
||||
protected:
|
||||
|
||||
// @{ Allocation tracking management - we assume this functions are called with the lock locked.
|
||||
const AllocationInfo* RegisterAllocation(void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount);
|
||||
void UnregisterAllocation(void* address, size_t byteSize, size_t alignment, AllocationInfo* info);
|
||||
@@ -195,9 +192,9 @@ namespace AZ
|
||||
void ResizeAllocation(void* address, size_t newSize);
|
||||
// @}
|
||||
|
||||
void IntegrityCheckNoLock() const;
|
||||
|
||||
protected:
|
||||
Debug::AllocationRecordsType m_records;
|
||||
AZStd::spin_mutex m_recordsMutex;
|
||||
Mode m_mode;
|
||||
bool m_isAutoIntegrityCheck;
|
||||
bool m_isMarkUnallocatedMemory; ///< True if we want to set value 0xcd in unallocated memory.
|
||||
@@ -205,9 +202,9 @@ namespace AZ
|
||||
bool m_decodeImmediately;
|
||||
unsigned char m_numStackLevels;
|
||||
unsigned int m_memoryGuardSize;
|
||||
size_t m_requestedAllocs;
|
||||
size_t m_requestedBytes;
|
||||
size_t m_requestedBytesPeak;
|
||||
AZStd::atomic<size_t> m_requestedAllocs;
|
||||
AZStd::atomic<size_t> m_requestedBytes;
|
||||
AZStd::atomic<size_t> m_requestedBytesPeak;
|
||||
|
||||
const char* m_allocatorName;
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
#include <AzCore/Memory/AllocatorManager.h>
|
||||
#include <AzCore/Memory/MemoryDrillerBus.h>
|
||||
|
||||
using namespace AZ;
|
||||
|
||||
@@ -74,16 +73,24 @@ void AllocatorBase::PostCreate()
|
||||
}
|
||||
}
|
||||
|
||||
#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED
|
||||
m_platformMemoryInstrumentationGroupId = AZ::PlatformMemoryInstrumentation::GetNextGroupId();
|
||||
AZ::PlatformMemoryInstrumentation::RegisterGroup(m_platformMemoryInstrumentationGroupId, GetDescription(), AZ::PlatformMemoryInstrumentation::m_groupRoot);
|
||||
#endif
|
||||
const auto debugConfig = GetDebugConfig();
|
||||
if (!debugConfig.m_excludeFromDebugging)
|
||||
{
|
||||
SetRecords(aznew Debug::AllocationRecords((unsigned char)debugConfig.m_stackRecordLevels, debugConfig.m_usesMemoryGuards, debugConfig.m_marksUnallocatedMemory, GetName()));
|
||||
}
|
||||
|
||||
m_isReady = true;
|
||||
}
|
||||
|
||||
void AllocatorBase::PreDestroy()
|
||||
{
|
||||
Debug::AllocationRecords* allocatorRecords = GetRecords();
|
||||
if(allocatorRecords)
|
||||
{
|
||||
delete allocatorRecords;
|
||||
SetRecords(nullptr);
|
||||
}
|
||||
|
||||
if (m_registrationEnabled && AZ::AllocatorManager::IsReady())
|
||||
{
|
||||
AllocatorManager::Instance().UnRegisterAllocator(this);
|
||||
@@ -130,11 +137,11 @@ void AllocatorBase::ProfileAllocation(void* ptr, size_t byteSize, size_t alignme
|
||||
|
||||
if (m_isProfilingActive)
|
||||
{
|
||||
#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED
|
||||
AZ::PlatformMemoryInstrumentation::Alloc(ptr, byteSize, 0, m_platformMemoryInstrumentationGroupId);
|
||||
#else
|
||||
EBUS_EVENT(AZ::Debug::MemoryDrillerBus, RegisterAllocation, this, ptr, byteSize, alignment, name, fileName, lineNum, suppressStackRecord);
|
||||
#endif
|
||||
auto records = GetRecords();
|
||||
if (records)
|
||||
{
|
||||
records->RegisterAllocation(ptr, byteSize, alignment, name, fileName, lineNum, suppressStackRecord + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,37 +149,25 @@ void AllocatorBase::ProfileDeallocation(void* ptr, size_t byteSize, size_t align
|
||||
{
|
||||
if (m_isProfilingActive)
|
||||
{
|
||||
#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED
|
||||
AZ::PlatformMemoryInstrumentation::Free(ptr);
|
||||
#else
|
||||
EBUS_EVENT(AZ::Debug::MemoryDrillerBus, UnregisterAllocation, this, ptr, byteSize, alignment, info);
|
||||
#endif
|
||||
auto records = GetRecords();
|
||||
if (records)
|
||||
{
|
||||
records->UnregisterAllocation(ptr, byteSize, alignment, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AllocatorBase::ProfileReallocationBegin(void* ptr, size_t newSize)
|
||||
void AllocatorBase::ProfileReallocationBegin([[maybe_unused]] void* ptr, [[maybe_unused]] size_t newSize)
|
||||
{
|
||||
if (m_isProfilingActive)
|
||||
{
|
||||
#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED
|
||||
AZ::PlatformMemoryInstrumentation::ReallocBegin(ptr, newSize, m_platformMemoryInstrumentationGroupId);
|
||||
#else
|
||||
// Driller API intensionally not called, only End is required.
|
||||
AZ_UNUSED(ptr);
|
||||
AZ_UNUSED(newSize);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void AllocatorBase::ProfileReallocationEnd(void* ptr, void* newPtr, size_t newSize, size_t newAlignment)
|
||||
{
|
||||
if (m_isProfilingActive)
|
||||
{
|
||||
#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED
|
||||
AZ::PlatformMemoryInstrumentation::ReallocEnd(newPtr, newSize, 0);
|
||||
#else
|
||||
EBUS_EVENT(AZ::Debug::MemoryDrillerBus, ReallocateAllocation, this, ptr, newPtr, newSize, newAlignment);
|
||||
#endif
|
||||
Debug::AllocationInfo info;
|
||||
ProfileDeallocation(ptr, 0, 0, &info);
|
||||
ProfileAllocation(newPtr, newSize, newAlignment, info.m_name, info.m_fileName, info.m_lineNum, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +180,11 @@ void AllocatorBase::ProfileResize(void* ptr, size_t newSize)
|
||||
{
|
||||
if (newSize && m_isProfilingActive)
|
||||
{
|
||||
EBUS_EVENT(AZ::Debug::MemoryDrillerBus, ResizeAllocation, this, ptr, newSize);
|
||||
auto records = GetRecords();
|
||||
if (records)
|
||||
{
|
||||
records->ResizeAllocation(ptr, newSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Memory/IAllocator.h>
|
||||
#include <AzCore/Memory/PlatformMemoryInstrumentation.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -103,16 +102,13 @@ namespace AZ
|
||||
|
||||
const char* m_name = nullptr;
|
||||
const char* m_desc = nullptr;
|
||||
Debug::AllocationRecords* m_records = nullptr; // Cached pointer to allocation records. Works together with the MemoryDriller.
|
||||
Debug::AllocationRecords* m_records = nullptr; // Cached pointer to allocation records
|
||||
size_t m_memoryGuardSize = 0;
|
||||
bool m_isLazilyCreated = false;
|
||||
bool m_isProfilingActive = false;
|
||||
bool m_isReady = false;
|
||||
bool m_canBeOverridden = true;
|
||||
bool m_registrationEnabled = true;
|
||||
#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED
|
||||
uint16_t m_platformMemoryInstrumentationGroupId = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
namespace Internal {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <AzCore/Memory/AllocationRecords.h>
|
||||
#include <AzCore/Memory/AllocatorOverrideShim.h>
|
||||
#include <AzCore/Memory/MallocSchema.h>
|
||||
#include <AzCore/Memory/MemoryDrillerBus.h>
|
||||
|
||||
#include <AzCore/std/parallel/lock.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
@@ -215,8 +214,6 @@ AllocatorManager::RegisterAllocator(class IAllocator* alloc)
|
||||
#ifdef AZCORE_MEMORY_ENABLE_OVERRIDES
|
||||
ConfigureAllocatorOverrides(alloc);
|
||||
#endif
|
||||
|
||||
EBUS_EVENT(Debug::MemoryDrillerBus, RegisterAllocator, alloc);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -319,11 +316,6 @@ AllocatorManager::UnRegisterAllocator(class IAllocator* alloc)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorListMutex);
|
||||
|
||||
if (alloc->GetRecords())
|
||||
{
|
||||
EBUS_EVENT(Debug::MemoryDrillerBus, UnregisterAllocator, alloc);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_numAllocators; ++i)
|
||||
{
|
||||
if (m_allocators[i] == alloc)
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <AzCore/Memory/BestFitExternalMapSchema.h>
|
||||
|
||||
#include <AzCore/Memory/AllocationRecords.h>
|
||||
#include <AzCore/Memory/MemoryDrillerBus.h>
|
||||
|
||||
#include <AzCore/std/functional.h>
|
||||
|
||||
@@ -79,8 +78,14 @@ AllocatorDebugConfig BestFitExternalMapAllocator::GetDebugConfig()
|
||||
// Allocate
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::pointer_type
|
||||
BestFitExternalMapAllocator::Allocate(size_type byteSize, size_type alignment, int flags, const char* name, const char* fileName, int lineNum, unsigned int suppressStackRecord)
|
||||
BestFitExternalMapAllocator::pointer_type BestFitExternalMapAllocator::Allocate(
|
||||
size_type byteSize,
|
||||
size_type alignment,
|
||||
int flags,
|
||||
[[maybe_unused]] const char* name,
|
||||
[[maybe_unused]] const char* fileName,
|
||||
[[maybe_unused]] int lineNum,
|
||||
unsigned int suppressStackRecord)
|
||||
{
|
||||
(void)suppressStackRecord;
|
||||
|
||||
@@ -89,17 +94,6 @@ BestFitExternalMapAllocator::Allocate(size_type byteSize, size_type alignment, i
|
||||
byteSize = MemorySizeAdjustedUp(byteSize);
|
||||
|
||||
BestFitExternalMapAllocator::pointer_type address = m_schema->Allocate(byteSize, alignment, flags);
|
||||
if (address == nullptr)
|
||||
{
|
||||
if (!OnOutOfMemory(byteSize, alignment, flags, name, fileName, lineNum))
|
||||
{
|
||||
if (GetRecords())
|
||||
{
|
||||
EBUS_EVENT(Debug::MemoryDrillerBus, DumpAllAllocations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AZ_Assert(address != nullptr, "BestFitExternalMapAllocator: Failed to allocate %d bytes aligned on %d (flags: 0x%08x) %s : %s (%d)!", byteSize, alignment, flags, name ? name : "(no name)", fileName ? fileName : "(no file name)", lineNum);
|
||||
AZ_MEMORY_PROFILE(ProfileAllocation(address, byteSize, alignment, name, fileName, lineNum, suppressStackRecord + 1));
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace AZ
|
||||
namespace Debug
|
||||
{
|
||||
class AllocationRecords;
|
||||
class MemoryDriller;
|
||||
}
|
||||
|
||||
namespace AllocatorStorage
|
||||
@@ -83,7 +82,7 @@ namespace AZ
|
||||
/// Sets the number of entries to omit from the top of the callstack when recording stack traces.
|
||||
AllocatorDebugConfig& StackRecordLevels(int levels) { m_stackRecordLevels = levels; return *this; }
|
||||
|
||||
/// Set to true if this allocator should not have its records recorded and analyzed by systems like the MemoryDriller.
|
||||
/// Set to true if this allocator should not have its records recorded and analyzed.
|
||||
AllocatorDebugConfig& ExcludeFromDebugging(bool exclude = true) { m_excludeFromDebugging = exclude; return *this; }
|
||||
|
||||
/// Set to true if this allocator expands allocations with guard sections to detect overruns.
|
||||
@@ -207,8 +206,6 @@ namespace AZ
|
||||
|
||||
template<class Allocator>
|
||||
friend class AllocatorWrapper;
|
||||
|
||||
friend class Debug::MemoryDriller;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,291 +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/Memory/MemoryDriller.h>
|
||||
#include <AzCore/Math/Crc.h>
|
||||
|
||||
#include <AzCore/Memory/AllocatorManager.h>
|
||||
#include <AzCore/Memory/AllocationRecords.h>
|
||||
|
||||
#include <AzCore/IO/GenericStreams.h>
|
||||
#include <AzCore/Debug/StackTracer.h>
|
||||
|
||||
namespace AZ::Debug
|
||||
{
|
||||
//=========================================================================
|
||||
// MemoryDriller
|
||||
// [2/6/2013]
|
||||
//=========================================================================
|
||||
MemoryDriller::MemoryDriller(const Descriptor& desc)
|
||||
{
|
||||
(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
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// 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->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));
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// 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 AZ::Debug
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_MEMORY_DRILLER_H
|
||||
#define AZCORE_MEMORY_DRILLER_H 1
|
||||
|
||||
#include <AzCore/Driller/Driller.h>
|
||||
#include <AzCore/Memory/MemoryDrillerBus.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
struct StackFrame;
|
||||
|
||||
/**
|
||||
* Trace messages driller class
|
||||
*/
|
||||
class MemoryDriller
|
||||
: public Driller
|
||||
, public MemoryDrillerBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(MemoryDriller, OSAllocator, 0)
|
||||
|
||||
// TODO: Centralized settings for memory tracking.
|
||||
struct Descriptor
|
||||
{
|
||||
};
|
||||
|
||||
MemoryDriller(const Descriptor& desc = Descriptor());
|
||||
~MemoryDriller();
|
||||
|
||||
protected:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Driller
|
||||
const char* GroupName() const override { return "SystemDrillers"; }
|
||||
const char* GetName() const override { return "MemoryDriller"; }
|
||||
const char* GetDescription() const override { return "Reports all allocators and memory allocations."; }
|
||||
void Start(const Param* params = NULL, int numParams = 0) override;
|
||||
void Stop() override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// MemoryDrillerBus
|
||||
void RegisterAllocator(IAllocator* allocator) override;
|
||||
void UnregisterAllocator(IAllocator* allocator) override;
|
||||
|
||||
void RegisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount) override;
|
||||
void UnregisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, AllocationInfo* info) override;
|
||||
void ReallocateAllocation(IAllocator* allocator, void* prevAddress, void* newAddress, size_t newByteSize, size_t newAlignment) override;
|
||||
void ResizeAllocation(IAllocator* allocator, void* address, size_t newSize) override;
|
||||
|
||||
void DumpAllAllocations() override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterAllocatorOutput(IAllocator* allocator);
|
||||
void RegisterAllocationOutput(IAllocator* allocator, void* address, const AllocationInfo* info);
|
||||
private:
|
||||
// Store a list of all of our allocator records so we can dump them all without having to know about the allocators
|
||||
AZStd::list<Debug::AllocationRecords*, OSStdAllocator> m_allAllocatorRecords;
|
||||
};
|
||||
} // namespace Debug
|
||||
} // namespace AZ
|
||||
|
||||
#endif // AZCORE_MEMORY_DRILLER_H
|
||||
#pragma once
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_MEMORY_DRILLER_BUS_H
|
||||
#define AZCORE_MEMORY_DRILLER_BUS_H 1
|
||||
|
||||
#include <AzCore/Driller/DrillerBus.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class IAllocator;
|
||||
namespace Debug
|
||||
{
|
||||
//class AllocationRecords;
|
||||
struct AllocationInfo;
|
||||
|
||||
/**
|
||||
* Memory allocations driller message.
|
||||
*
|
||||
* We use a driller bus so all messages are sending in exclusive matter no other driller messages
|
||||
* can be triggered at that moment, so we already preserve the calling order. You can assume
|
||||
* all access code in the driller framework in guarded. You can manually lock the driller mutex are you
|
||||
* use by using \ref AZ::Debug::DrillerEBusMutex.
|
||||
*/
|
||||
class MemoryDrillerMessages
|
||||
: public AZ::Debug::DrillerEBusTraits
|
||||
{
|
||||
public:
|
||||
virtual ~MemoryDrillerMessages() {}
|
||||
|
||||
/// Register allocation (with customizable tracking settings - TODO: we should centralize this settings and remove them from here)
|
||||
virtual void RegisterAllocator(IAllocator* allocator) = 0;
|
||||
virtual void UnregisterAllocator(IAllocator* allocator) = 0;
|
||||
|
||||
virtual void RegisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount) = 0;
|
||||
virtual void UnregisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, AllocationInfo* info) = 0;
|
||||
virtual void ReallocateAllocation(IAllocator* allocator, void* prevAddress, void* newAddress, size_t newByteSize, size_t newAlignment) = 0;
|
||||
virtual void ResizeAllocation(IAllocator* allocator, void* address, size_t newSize) = 0;
|
||||
|
||||
virtual void DumpAllAllocations() = 0;
|
||||
};
|
||||
|
||||
typedef AZ::EBus<MemoryDrillerMessages> MemoryDrillerBus;
|
||||
} // namespace Debug
|
||||
} // namespace AZ
|
||||
|
||||
#endif // AZCORE_MEMORY_DRILLER_BUS_H
|
||||
#pragma once
|
||||
@@ -20,7 +20,7 @@ namespace AZ
|
||||
* OS allocator should be used for direct OS allocations (C heap)
|
||||
* It's memory usage is NOT tracked. If you don't create this allocator, it will be implicitly
|
||||
* created by the SystemAllocator when it is needed. In addition this allocator is used for
|
||||
* debug data (like drillers, memory trackng, etc.)
|
||||
* debug data (like memory tracking, etc.)
|
||||
*/
|
||||
class OSAllocator
|
||||
: public AllocatorBase
|
||||
|
||||
@@ -1,42 +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/AzCore_Traits_Platform.h>
|
||||
|
||||
#if AZ_TRAIT_OS_MEMORY_INSTRUMENTATION && !defined(_RELEASE)
|
||||
#define PLATFORM_MEMORY_INSTRUMENTATION_ENABLED 1
|
||||
#else
|
||||
#define PLATFORM_MEMORY_INSTRUMENTATION_ENABLED 0
|
||||
#endif
|
||||
|
||||
#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/std/parallel/mutex.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
/**
|
||||
* PlatformMemoryInstrumentation - Abstraction layer for platform specific memory instrumentation.
|
||||
*/
|
||||
class PlatformMemoryInstrumentation
|
||||
{
|
||||
public:
|
||||
static uint16_t GetNextGroupId() { return m_nextGroupId++; };
|
||||
static void RegisterGroup(uint16_t id, const char* name, uint16_t parentGroup);
|
||||
static void Alloc(const void* ptr, uint64_t size, uint32_t padding, uint16_t group);
|
||||
static void Free(const void* ptr);
|
||||
static void ReallocBegin(const void* origPtr, uint64_t size, uint16_t group);
|
||||
static void ReallocEnd(const void* newPtr, uint64_t size, uint32_t padding);
|
||||
static const uint16_t m_groupRoot;
|
||||
static uint16_t m_nextGroupId;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PLATFORM_MEMORY_INSTRUMENTATION_ENABLED
|
||||
@@ -12,9 +12,6 @@
|
||||
#include <AzCore/Memory/PoolSchema.h>
|
||||
#include <AzCore/Memory/AllocationRecords.h>
|
||||
|
||||
#include <AzCore/Memory/MemoryDrillerBus.h>
|
||||
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
template<class Allocator>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <AzCore/Memory/OSAllocator.h>
|
||||
#include <AzCore/Memory/AllocationRecords.h>
|
||||
#include <AzCore/Memory/MemoryDrillerBus.h>
|
||||
|
||||
#include <AzCore/std/functional.h>
|
||||
|
||||
@@ -248,14 +247,6 @@ SystemAllocator::Allocate(size_type byteSize, size_type alignment, int flags, co
|
||||
if (address == nullptr)
|
||||
{
|
||||
byteSize = MemorySizeAdjustedDown(byteSize); // restore original size
|
||||
|
||||
if (!OnOutOfMemory(byteSize, alignment, flags, name, fileName, lineNum))
|
||||
{
|
||||
if (GetRecords())
|
||||
{
|
||||
EBUS_EVENT(Debug::MemoryDrillerBus, DumpAllAllocations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AZ_Assert(address != nullptr, "SystemAllocator: Failed to allocate %d bytes aligned on %d (flags: 0x%08x) %s : %s (%d)!", byteSize, alignment, flags, name ? name : "(no name)", fileName ? fileName : "(no file name)", lineNum);
|
||||
|
||||
@@ -5073,13 +5073,26 @@ LUA_API const Node* lua_getDummyNode()
|
||||
// Check all constructors if they have use ScriptDataContext and if so choose this one
|
||||
if (!customConstructorMethod)
|
||||
{
|
||||
int overrideIndex = -1;
|
||||
AZ::AttributeReader(nullptr, FindAttribute
|
||||
( Script::Attributes::DefaultConstructorOverrideIndex, behaviorClass->m_attributes)).Read<int>(overrideIndex);
|
||||
|
||||
int methodIndex = 0;
|
||||
for (BehaviorMethod* method : behaviorClass->m_constructors)
|
||||
{
|
||||
if (methodIndex == overrideIndex)
|
||||
{
|
||||
customConstructorMethod = method;
|
||||
break;
|
||||
}
|
||||
|
||||
if (method->GetNumArguments() && method->GetArgument(method->GetNumArguments() - 1)->m_typeId == AZ::AzTypeInfo<ScriptDataContext>::Uuid())
|
||||
{
|
||||
customConstructorMethod = method;
|
||||
break;
|
||||
}
|
||||
|
||||
++methodIndex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace AZ
|
||||
static constexpr AZ::Crc32 ClassNameOverride = AZ_CRC_CE("ScriptClassNameOverride"); ///< Provide a custom name for script reflection, that doesn't match the behavior Context name
|
||||
static constexpr AZ::Crc32 MethodOverride = AZ_CRC_CE("ScriptFunctionOverride"); ///< Use a custom function in the attribute instead of the function
|
||||
static constexpr AZ::Crc32 ConstructorOverride = AZ_CRC_CE("ConstructorOverride"); ///< You can provide a custom constructor to be called when created from Lua script
|
||||
static constexpr AZ::Crc32 DefaultConstructorOverrideIndex = AZ_CRC_CE("DefaultConstructorOverrideIndex"); ///< Use a different class constructor as the default constructor in Lua
|
||||
static constexpr AZ::Crc32 EventHandlerCreationFunction = AZ_CRC_CE("EventHandlerCreationFunction"); ///< helps create a handler for any script target so that script functions can be used for AZ::Event signals
|
||||
static constexpr AZ::Crc32 GenericConstructorOverride = AZ_CRC_CE("GenericConstructorOverride"); ///< You can provide a custom constructor to be called when creating a script
|
||||
static constexpr AZ::Crc32 ReaderWriterOverride = AZ_CRC_CE("ReaderWriterOverride"); ///< paired with \ref ScriptContext::CustomReaderWriter allows you to customize read/write to Lua VM
|
||||
|
||||
@@ -105,6 +105,8 @@ void ScriptContextDebug::ConnectHook()
|
||||
void ScriptContextDebug::DisconnectHook()
|
||||
{
|
||||
lua_sethook(m_context.NativeContext(), nullptr, 0, 0);
|
||||
m_currentStackLevel = -1;
|
||||
m_stepStackLevel = -1;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -651,6 +653,11 @@ void AZ::LuaHook(lua_State* l, lua_Debug* ar)
|
||||
context->PopCallstack();
|
||||
}
|
||||
context->m_currentStackLevel--;
|
||||
|
||||
if (context->m_currentStackLevel == -1)
|
||||
{
|
||||
context->m_stepStackLevel = -1;
|
||||
}
|
||||
}
|
||||
else if (ar->event == LUA_HOOKLINE)
|
||||
{
|
||||
@@ -731,7 +738,7 @@ void AZ::LuaHook(lua_State* l, lua_Debug* ar)
|
||||
//}
|
||||
}
|
||||
|
||||
if (doBreak)
|
||||
if (doBreak && bp->m_lineNumber > 0)
|
||||
{
|
||||
context->m_luaDebug = ar;
|
||||
context->m_breakCallback(context, bp);
|
||||
|
||||
@@ -133,6 +133,8 @@ namespace AZ
|
||||
const static AZ::Crc32 AllowClearAsset = AZ_CRC("AllowClearAsset", 0x24827182);
|
||||
// Show the name of the asset that was produced from the source asset
|
||||
const static AZ::Crc32 ShowProductAssetFileName = AZ_CRC("ShowProductAssetFileName");
|
||||
//! Regular expression pattern filter for source files
|
||||
const static AZ::Crc32 SourceAssetFilterPattern = AZ_CRC_CE("SourceAssetFilterPattern");
|
||||
|
||||
//! Component icon attributes
|
||||
const static AZ::Crc32 Icon = AZ_CRC("Icon", 0x659429db);
|
||||
|
||||
@@ -204,6 +204,22 @@ namespace AZ
|
||||
// BaseJsonSerializer
|
||||
//
|
||||
|
||||
JsonSerializationResult::Result BaseJsonSerializer::Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
|
||||
JsonDeserializerContext& context)
|
||||
{
|
||||
JsonSerializationResult::ResultCode result(JsonSerializationResult::Tasks::ReadField);
|
||||
result.Combine(ContinueLoading(outputValue, outputValueTypeId, inputValue, context, ContinuationFlags::IgnoreTypeSerializer));
|
||||
return context.Report(result, "Ignoring custom serialization during load");
|
||||
}
|
||||
|
||||
JsonSerializationResult::Result BaseJsonSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
|
||||
const Uuid& valueTypeId, JsonSerializerContext& context)
|
||||
{
|
||||
JsonSerializationResult::ResultCode result(JsonSerializationResult::Tasks::WriteValue);
|
||||
result.Combine(ContinueStoring(outputValue, inputValue, defaultValue, valueTypeId, context, ContinuationFlags::IgnoreTypeSerializer));
|
||||
return context.Report(result, "Ignoring custom serialization during store");
|
||||
}
|
||||
|
||||
BaseJsonSerializer::OperationFlags BaseJsonSerializer::GetOperationsFlags() const
|
||||
{
|
||||
return OperationFlags::None;
|
||||
|
||||
@@ -180,13 +180,16 @@ namespace AZ
|
||||
|
||||
//! Transforms the data from the rapidjson Value to outputValue, if the conversion is possible and supported.
|
||||
//! The serializer is responsible for casting to the proper type and safely writing to the outputValue memory.
|
||||
//! \note The default implementation is to load the object ignoring a custom serializers for the type, which allows for custom serializers
|
||||
//! to modify the object after all default loading has occurred.
|
||||
virtual JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
|
||||
JsonDeserializerContext& context) = 0;
|
||||
JsonDeserializerContext& context);
|
||||
|
||||
//! Write the input value to a rapidjson value if the default value is not null and doesn't match the input value, otherwise
|
||||
//! an error is returned and sets the rapidjson value to a null value.
|
||||
//! \note The default implementation is to store the object ignoring custom serializers.
|
||||
virtual JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
|
||||
const Uuid& valueTypeId, JsonSerializerContext& context) = 0;
|
||||
const Uuid& valueTypeId, JsonSerializerContext& context);
|
||||
|
||||
//! Returns the operation flags which tells the Json Serialization how this custom json serializer can be used.
|
||||
virtual OperationFlags GetOperationsFlags() const;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/Console/ConsoleFunctor.h>
|
||||
#include <AzCore/IO/ByteContainerStream.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Settings/SettingsRegistryConsoleUtils.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
@@ -36,7 +37,7 @@ namespace AZ::SettingsRegistryConsoleUtils
|
||||
combinedKeyValueCommand.c_str());
|
||||
AZ::Debug::Trace::Output("SettingsRegistry", setOutput.c_str());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static void ConsoleRemoveSettingsRegistryValue(SettingsRegistryInterface& settingsRegistry, const ConsoleCommandContainer& commandArgs)
|
||||
{
|
||||
@@ -57,7 +58,7 @@ namespace AZ::SettingsRegistryConsoleUtils
|
||||
AZ::Debug::Trace::Output("SettingsRegistry", removeOutput.c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static void ConsoleDumpSettingsRegistryValue(SettingsRegistryInterface& settingsRegistry, const ConsoleCommandContainer& commandArgs)
|
||||
{
|
||||
@@ -88,13 +89,39 @@ namespace AZ::SettingsRegistryConsoleUtils
|
||||
}
|
||||
|
||||
AZ::Debug::Trace::Output("SettingsRegistry", outputString.c_str());
|
||||
};
|
||||
}
|
||||
|
||||
static void ConsoleDumpAllSettingsRegistryValues(SettingsRegistryInterface& settingsRegistry,
|
||||
[[maybe_unused]] const ConsoleCommandContainer& commandArgs)
|
||||
{
|
||||
ConsoleDumpSettingsRegistryValue(settingsRegistry, { "" });
|
||||
};
|
||||
}
|
||||
|
||||
static void ConsoleMergeFileToSettingsRegistry(SettingsRegistryInterface& settingsRegistry, const ConsoleCommandContainer& commandArgs)
|
||||
{
|
||||
if (commandArgs.empty())
|
||||
{
|
||||
AZ_Error("SettingsRegistryConsoleUtils", false, "Command %s requires a <file path> argument to locate json file to merge",
|
||||
SettingsRegistryMergeFile);
|
||||
return;
|
||||
}
|
||||
|
||||
auto commandArgumentsIter = commandArgs.begin();
|
||||
// Extract the JSON pointer path from the argument list
|
||||
AZStd::string_view filePath{ *commandArgumentsIter++ };
|
||||
AZ::SettingsRegistryInterface::FixedValueString jsonAnchorPath;
|
||||
AZ::StringFunc::Join(jsonAnchorPath, commandArgumentsIter, commandArgs.end(), ' ');
|
||||
|
||||
const auto mergeFormat = AZ::IO::PathView(filePath).Extension() != ".setregpatch" ? AZ::SettingsRegistryInterface::Format::JsonMergePatch : AZ::SettingsRegistryInterface::Format::JsonPatch;
|
||||
if (settingsRegistry.MergeSettingsFile(filePath, mergeFormat, jsonAnchorPath))
|
||||
{
|
||||
const auto mergeFileOutput = AZ::SettingsRegistryInterface::FixedValueString::format(
|
||||
R"(Merged json file "%*.s" anchored to json path "%s" into the global settings registry)" "\n",
|
||||
AZ_STRING_ARG(filePath), jsonAnchorPath.c_str());
|
||||
AZ::Debug::Trace::Output("SettingsRegistry", mergeFileOutput.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] ConsoleFunctorHandle RegisterAzConsoleCommands(SettingsRegistryInterface& registry, AZ::IConsole& azConsole)
|
||||
{
|
||||
@@ -115,6 +142,11 @@ namespace AZ::SettingsRegistryConsoleUtils
|
||||
resultHandle.m_consoleFunctors.emplace_back(azConsole, SettingsRegistryDumpAll,
|
||||
R"(Dumps all values from the global settings registry)" "\n",
|
||||
ConsoleFunctorFlags::Null, AZ::TypeId::CreateNull(), registry, &ConsoleDumpAllSettingsRegistryValues);
|
||||
resultHandle.m_consoleFunctors.emplace_back(azConsole, SettingsRegistryMergeFile,
|
||||
R"(Merges File into the global settings registry)" "\n"
|
||||
R"(@param file-path - path to JSON formatted file to merge)" "\n"
|
||||
R"(@param anchor-path - JSON path to anchor merge operation. Defaults to "")" "\n",
|
||||
ConsoleFunctorFlags::Null, AZ::TypeId::CreateNull(), registry, &ConsoleMergeFileToSettingsRegistry);
|
||||
|
||||
return resultHandle;
|
||||
}
|
||||
|
||||
@@ -14,15 +14,16 @@
|
||||
|
||||
namespace AZ::SettingsRegistryConsoleUtils
|
||||
{
|
||||
//! Only 4 console command are registered for the settings registry
|
||||
//! "regset", "regremove", "regdump", "regdumpall"
|
||||
//! The following console command are registered for the settings registry
|
||||
//! "regset", "regremove", "regdump", "regdumpall", "regset-file"
|
||||
//! The value should be increased if more commands are needed
|
||||
inline constexpr size_t MaxSettingsRegistryConsoleFunctors = 4;
|
||||
inline constexpr size_t MaxSettingsRegistryConsoleFunctors = 5;
|
||||
|
||||
inline constexpr const char* SettingsRegistrySet = "sr_regset";
|
||||
inline constexpr const char* SettingsRegistryRemove = "sr_regremove";
|
||||
inline constexpr const char* SettingsRegistryDump = "sr_regdump";
|
||||
inline constexpr const char* SettingsRegistryDumpAll = "sr_regdumpall";
|
||||
inline constexpr const char* SettingsRegistryMergeFile = "sr_regset_file";
|
||||
|
||||
// RAII structure which owns the instances of the Settings Registry Console commands
|
||||
// registered with an AZ Console
|
||||
@@ -51,6 +52,10 @@ namespace AZ::SettingsRegistryConsoleUtils
|
||||
//!
|
||||
//! "sr_regdumpall" accepts 0 arguments and dumps the entire settings registry
|
||||
//! NOTE: this might result in a large amount of output to the console
|
||||
//!
|
||||
//! "sr_regset_file" accepts 1 or 2 arguments - <file-path> [<anchor json path>]
|
||||
//! Merges the json formatted file <file path> into the settings registry underneath the root anchor ""
|
||||
//! or <anchor json path> if supplied
|
||||
[[nodiscard]] ConsoleFunctorHandle RegisterAzConsoleCommands(SettingsRegistryInterface& registry, AZ::IConsole& azConsole);
|
||||
|
||||
}
|
||||
|
||||
@@ -19,9 +19,6 @@
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/Settings/CommandLine.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
#include <AzCore/std/string/wildcard.h>
|
||||
#include <AzCore/std/tuple.h>
|
||||
#include <AzCore/std/containers/variant.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
#include <cinttypes>
|
||||
@@ -983,7 +980,7 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
// code in the loop makes calls that mutates the `commandLine` instance, invalidating the iterators. Making a copy
|
||||
// ensures that the iterators remain valid.
|
||||
// NOLINTNEXTLINE(performance-unnecessary-value-param)
|
||||
void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, AZ::CommandLine commandLine, bool executeCommands)
|
||||
void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, AZ::CommandLine commandLine, bool executeRegdumpCommands)
|
||||
{
|
||||
// Iterate over all the command line options in order to parse the --regset and --regremove
|
||||
// arguments in the order they were supplied
|
||||
@@ -998,18 +995,44 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (commandArgument.m_option == "regset-file")
|
||||
{
|
||||
AZStd::string_view fileArg(commandArgument.m_value);
|
||||
AZStd::string_view jsonAnchorPath;
|
||||
// double colons is treated as the separator for an anchor path
|
||||
// single colon cannot be used as it is used in Windows paths
|
||||
if (auto anchorPathIndex = AZ::StringFunc::Find(fileArg, "::");
|
||||
anchorPathIndex != AZStd::string_view::npos)
|
||||
{
|
||||
jsonAnchorPath = fileArg.substr(anchorPathIndex + 2);
|
||||
fileArg = fileArg.substr(0, anchorPathIndex);
|
||||
}
|
||||
if (!fileArg.empty())
|
||||
{
|
||||
AZ::IO::PathView filePath(fileArg);
|
||||
const auto mergeFormat = filePath.Extension() != ".setregpatch"
|
||||
? AZ::SettingsRegistryInterface::Format::JsonMergePatch
|
||||
: AZ::SettingsRegistryInterface::Format::JsonPatch;
|
||||
if (!registry.MergeSettingsFile(filePath.Native(), mergeFormat, jsonAnchorPath))
|
||||
{
|
||||
AZ_Warning("SettingsRegistryMergeUtils", false, R"(Merging of file "%.*s" to the Settings Registry has failed at anchor "%.*s".)",
|
||||
AZ_STRING_ARG(filePath.Native()), AZ_STRING_ARG(jsonAnchorPath));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (commandArgument.m_option == "regremove")
|
||||
{
|
||||
if (!registry.Remove(commandArgument.m_value))
|
||||
{
|
||||
AZ_Warning("SettingsRegistryMergeUtils", false, "Unable to remove value at JSON Pointer %s for --regremove.",
|
||||
commandArgument.m_value.data());
|
||||
commandArgument.m_value.c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (executeCommands)
|
||||
if (executeRegdumpCommands)
|
||||
{
|
||||
constexpr bool prettifyOutput = true;
|
||||
const size_t regdumpSwitchValues = commandLine.GetNumSwitchValues("regdump");
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Debug/AssetTracking.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
|
||||
@@ -157,6 +157,22 @@ namespace AZ::Statistics
|
||||
}
|
||||
}
|
||||
|
||||
void GetAllStatistics(AZStd::vector<NamedRunningStatistic*>& stats)
|
||||
{
|
||||
for (auto& iter : m_profilers)
|
||||
{
|
||||
iter.second.m_profiler.GetStatsManager().GetAllStatistics(stats);
|
||||
}
|
||||
}
|
||||
|
||||
void GetAllStatisticsOfUnits(AZStd::vector<NamedRunningStatistic*>& stats, const char* units)
|
||||
{
|
||||
for (auto& iter : m_profilers)
|
||||
{
|
||||
iter.second.m_profiler.GetStatsManager().GetAllStatisticsOfUnits(stats, units);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct ProfilerInfo
|
||||
{
|
||||
|
||||
@@ -56,13 +56,25 @@ namespace AZ
|
||||
|
||||
void GetAllStatistics(AZStd::vector<NamedRunningStatistic*>& vector)
|
||||
{
|
||||
for (auto const& it : m_statistics)
|
||||
for (const auto& it : m_statistics)
|
||||
{
|
||||
NamedRunningStatistic* stat = it.second;
|
||||
vector.push_back(stat);
|
||||
}
|
||||
}
|
||||
|
||||
void GetAllStatisticsOfUnits(AZStd::vector<NamedRunningStatistic*>& vector, const char* units)
|
||||
{
|
||||
for (const auto& it : m_statistics)
|
||||
{
|
||||
NamedRunningStatistic* stat = it.second;
|
||||
if (stat->GetUnits() == units)
|
||||
{
|
||||
vector.push_back(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! Helper method to apply units to statistics with empty units string.
|
||||
AZ::u32 ApplyUnits(const AZStd::string& units)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <AzCore/std/parallel/binary_semaphore.h>
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace AZ
|
||||
TimeSystem::TimeSystem()
|
||||
{
|
||||
m_lastInvokedTimeUs = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
|
||||
m_realLastInvokedTimeUs = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
|
||||
AZ::Interface<ITime>::Register(this);
|
||||
ITimeRequestBus::Handler::BusConnect();
|
||||
}
|
||||
@@ -101,7 +102,11 @@ namespace AZ
|
||||
|
||||
TimeUs TimeSystem::GetRealElapsedTimeUs() const
|
||||
{
|
||||
return static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
|
||||
const TimeUs currentTime = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
|
||||
m_realAccumulatedTimeUs += currentTime - m_realLastInvokedTimeUs;
|
||||
m_realLastInvokedTimeUs = currentTime;
|
||||
|
||||
return m_realAccumulatedTimeUs;
|
||||
}
|
||||
|
||||
TimeUs TimeSystem::GetSimulationTickDeltaTimeUs() const
|
||||
|
||||
@@ -64,6 +64,14 @@ namespace AZ
|
||||
//! Mutable to allow GetElapsedTimeMs/TimeUs() to be a const functions.
|
||||
mutable TimeUs m_accumulatedTimeUs = AZ::Time::ZeroTimeUs;
|
||||
|
||||
//! Used to calculate the delta time between calls to GetRealElapsedTimeMs/TimeUs().
|
||||
//! Mutable to allow GetRealElapsedTimeMs/TimeUs() to be a const functions.
|
||||
mutable TimeUs m_realLastInvokedTimeUs = AZ::Time::ZeroTimeUs;
|
||||
|
||||
//! Accumulates the delta time of GetRealElapsedTimeMs/TimeUs() calls.
|
||||
//! Mutable to allow GetRealElapsedTimeMs/TimeUs() to be a const functions.
|
||||
mutable TimeUs m_realAccumulatedTimeUs = AZ::Time::ZeroTimeUs;
|
||||
|
||||
//! The current game tick delta time.
|
||||
//! Can be affected by time system cvars.
|
||||
//! Updated in AdvanceTickDeltaTimes().
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
|
||||
#include <AzCore/Debug/BudgetTracker.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Driller/Driller.h>
|
||||
#include <AzCore/Memory/MemoryDriller.h>
|
||||
#include <AzCore/Memory/AllocationRecords.h>
|
||||
|
||||
#if defined(HAVE_BENCHMARK)
|
||||
@@ -39,7 +37,6 @@ namespace UnitTest
|
||||
*/
|
||||
class AllocatorsBase
|
||||
{
|
||||
AZ::Debug::DrillerManager* m_drillerManager;
|
||||
bool m_ownsAllocator{};
|
||||
public:
|
||||
|
||||
@@ -47,8 +44,7 @@ namespace UnitTest
|
||||
|
||||
void SetupAllocator(const AZ::SystemAllocator::Descriptor& allocatorDesc = {})
|
||||
{
|
||||
m_drillerManager = AZ::Debug::DrillerManager::Create();
|
||||
m_drillerManager->Register(aznew AZ::Debug::MemoryDriller);
|
||||
AZ::AllocatorManager::Instance().EnterProfilingMode();
|
||||
AZ::AllocatorManager::Instance().SetDefaultTrackingMode(AZ::Debug::AllocationRecords::RECORD_FULL);
|
||||
|
||||
// Only create the SystemAllocator if it s not ready
|
||||
@@ -68,9 +64,9 @@ namespace UnitTest
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
|
||||
}
|
||||
m_ownsAllocator = false;
|
||||
AZ::Debug::DrillerManager::Destroy(m_drillerManager);
|
||||
|
||||
AZ::AllocatorManager::Instance().SetDefaultTrackingMode(AZ::Debug::AllocationRecords::RECORD_NO_RECORDS);
|
||||
AZ::AllocatorManager::Instance().ExitProfilingMode();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -93,8 +89,7 @@ namespace UnitTest
|
||||
* Helper class to handle the boiler plate of setting up a test fixture that uses the system allocators
|
||||
* If you wish to do additional setup and tear down be sure to call the base class SetUp first and TearDown
|
||||
* last.
|
||||
* By default memory tracking through driller is enabled.
|
||||
* Defaults to a heap size of 15 MB
|
||||
* By default memory tracking is enabled.
|
||||
*/
|
||||
|
||||
class AllocatorsTestFixture
|
||||
@@ -123,8 +118,7 @@ namespace UnitTest
|
||||
* Helper class to handle the boiler plate of setting up a benchmark fixture that uses the system allocators
|
||||
* If you wish to do additional setup and tear down be sure to call the base class SetUp first and TearDown
|
||||
* last.
|
||||
* By default memory tracking through driller is disabled.
|
||||
* Defaults to a heap size of 15 MB
|
||||
* By default memory tracking is enabled.
|
||||
*/
|
||||
class AllocatorsBenchmarkFixture
|
||||
: public ::benchmark::Fixture
|
||||
|
||||
@@ -69,6 +69,15 @@ namespace UnitTest
|
||||
return numAssertsFailed;
|
||||
}
|
||||
|
||||
void ResetSuppressionSettingsToDefault()
|
||||
{
|
||||
m_suppressErrors = true;
|
||||
m_suppressWarnings = true;
|
||||
m_suppressAsserts = true;
|
||||
m_suppressOutput = true;
|
||||
m_suppressPrintf = true;
|
||||
}
|
||||
|
||||
bool m_isAssertTest;
|
||||
bool m_suppressErrors = true;
|
||||
bool m_suppressWarnings = true;
|
||||
|
||||
@@ -165,13 +165,12 @@ namespace AZ::Utils
|
||||
}
|
||||
|
||||
Container fileContent;
|
||||
fileContent.resize(length);
|
||||
fileContent.resize_no_construct(length);
|
||||
AZ::IO::SizeType bytesRead = file.Read(length, fileContent.data());
|
||||
file.Close();
|
||||
|
||||
// Resize again just in case bytesRead is less than length for some reason
|
||||
fileContent.resize(bytesRead);
|
||||
|
||||
fileContent.resize_no_construct(bytesRead);
|
||||
return AZ::Success(AZStd::move(fileContent));
|
||||
}
|
||||
|
||||
|
||||
@@ -92,10 +92,6 @@ set(FILES
|
||||
Compression/Compression.h
|
||||
Compression/zstd_compression.cpp
|
||||
Compression/zstd_compression.h
|
||||
Debug/AssetTracking.cpp
|
||||
Debug/AssetTracking.h
|
||||
Debug/AssetTrackingTypesImpl.h
|
||||
Debug/AssetTrackingTypes.h
|
||||
Debug/Budget.h
|
||||
Debug/Budget.cpp
|
||||
Debug/BudgetTracker.h
|
||||
@@ -111,18 +107,10 @@ set(FILES
|
||||
Debug/ProfilerReflection.cpp
|
||||
Debug/ProfilerReflection.h
|
||||
Debug/StackTracer.h
|
||||
Debug/EventTrace.h
|
||||
Debug/EventTrace.cpp
|
||||
Debug/EventTraceDriller.h
|
||||
Debug/EventTraceDriller.cpp
|
||||
Debug/EventTraceDrillerBus.h
|
||||
Debug/Timer.h
|
||||
Debug/Trace.cpp
|
||||
Debug/Trace.h
|
||||
Debug/TraceMessageBus.h
|
||||
Debug/TraceMessagesDriller.cpp
|
||||
Debug/TraceMessagesDriller.h
|
||||
Debug/TraceMessagesDrillerBus.h
|
||||
Debug/TraceReflection.cpp
|
||||
Debug/TraceReflection.h
|
||||
DOM/DomBackend.cpp
|
||||
@@ -138,14 +126,6 @@ set(FILES
|
||||
DOM/Backends/JSON/JsonBackend.h
|
||||
DOM/Backends/JSON/JsonSerializationUtils.cpp
|
||||
DOM/Backends/JSON/JsonSerializationUtils.h
|
||||
Driller/DefaultStringPool.h
|
||||
Driller/Driller.cpp
|
||||
Driller/Driller.h
|
||||
Driller/DrillerBus.cpp
|
||||
Driller/DrillerBus.h
|
||||
Driller/DrillerRootHandler.h
|
||||
Driller/Stream.cpp
|
||||
Driller/Stream.h
|
||||
EBus/BusImpl.h
|
||||
EBus/EBus.h
|
||||
EBus/EBusEnvironment.cpp
|
||||
@@ -182,7 +162,6 @@ set(FILES
|
||||
IO/CompressorZStd.h
|
||||
IO/FileIO.cpp
|
||||
IO/FileIO.h
|
||||
IO/FileIOEventBus.h
|
||||
IO/FileReader.cpp
|
||||
IO/FileReader.h
|
||||
IO/IOUtils.h
|
||||
@@ -198,6 +177,8 @@ set(FILES
|
||||
IO/Path/Path.inl
|
||||
IO/Path/PathIterable.inl
|
||||
IO/Path/PathParser.inl
|
||||
IO/Path/PathReflect.cpp
|
||||
IO/Path/PathReflect.h
|
||||
IO/Path/Path_fwd.h
|
||||
IO/SystemFile.cpp
|
||||
IO/SystemFile.h
|
||||
@@ -406,16 +387,12 @@ set(FILES
|
||||
Memory/Memory.h
|
||||
Memory/MemoryComponent.cpp
|
||||
Memory/MemoryComponent.h
|
||||
Memory/MemoryDriller.cpp
|
||||
Memory/MemoryDriller.h
|
||||
Memory/MemoryDrillerBus.h
|
||||
Memory/nedmalloc.inl
|
||||
Memory/NewAndDelete.inl
|
||||
Memory/OSAllocator.cpp
|
||||
Memory/OSAllocator.h
|
||||
Memory/OverrunDetectionAllocator.cpp
|
||||
Memory/OverrunDetectionAllocator.h
|
||||
Memory/PlatformMemoryInstrumentation.h
|
||||
Memory/PoolAllocator.h
|
||||
Memory/PoolSchema.cpp
|
||||
Memory/PoolSchema.h
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user