Merge branch 'development' into memory/overrideshim_removal
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> # Conflicts: # Code/Framework/AzCore/AzCore/Memory/AllocatorBase.cpp # Code/Framework/AzCore/AzCore/Memory/BestFitExternalMapAllocator.cpp # Code/Framework/AzCore/AzCore/Memory/BestFitExternalMapSchema.cpp # Code/Framework/AzCore/AzCore/Memory/PoolSchema.cpp # Code/Framework/AzCore/AzCore/Memory/SystemAllocator.cpp # Code/Framework/AzCore/Tests/Memory/AllocatorBenchmarks.cpp
This commit is contained in:
@@ -168,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.
|
||||
@@ -200,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;
|
||||
@@ -430,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
|
||||
@@ -586,26 +586,26 @@ namespace AZ
|
||||
|
||||
/// 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
|
||||
@@ -1094,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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -1218,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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +484,7 @@ namespace AZ::IO
|
||||
// as_posix
|
||||
//! Replicates the behavior of the Python pathlib as_posix method
|
||||
//! by replacing the Windows Path Separator with the Posix Path Seperator
|
||||
constexpr string_type AsPosix() const;
|
||||
AZStd::string StringAsPosix() const;
|
||||
constexpr AZStd::fixed_string<MaxPathLength> FixedMaxPathStringAsPosix() const noexcept;
|
||||
|
||||
|
||||
@@ -1043,6 +1043,13 @@ namespace AZ::IO
|
||||
// as_posix
|
||||
// Returns a copy of the path with the path separators converted to PosixPathSeparator
|
||||
template <typename StringType>
|
||||
constexpr auto BasicPath<StringType>::AsPosix() const -> string_type
|
||||
{
|
||||
string_type resultPath(m_path.begin(), m_path.end());
|
||||
AZStd::replace(resultPath.begin(), resultPath.end(), WindowsPathSeparator, PosixPathSeparator);
|
||||
return resultPath;
|
||||
}
|
||||
template <typename StringType>
|
||||
AZStd::string BasicPath<StringType>::StringAsPosix() const
|
||||
{
|
||||
AZStd::string resultPath(m_path.begin(), m_path.end());
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
*/
|
||||
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/Serialization/Json/PathSerializer.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
|
||||
@@ -35,10 +37,8 @@ namespace AZ::IO
|
||||
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()));
|
||||
auto posixPathString{ reinterpret_cast<const PathType*>(classPtr)->AsPosix() };
|
||||
return static_cast<size_t>(stream.Write(posixPathString.size(), posixPathString.c_str()));
|
||||
}
|
||||
|
||||
bool Load(void* classPtr, IO::GenericStream& stream, unsigned int, bool) override
|
||||
@@ -73,5 +73,11 @@ namespace AZ::IO
|
||||
AZ::SerializeContext::IDataSerializer::CreateDefaultDeleteDeleter() })
|
||||
;
|
||||
}
|
||||
else if (auto jsonContext = azrtti_cast<JsonRegistrationContext*>(context))
|
||||
{
|
||||
jsonContext->Serializer<JsonPathSerializer>()
|
||||
->HandlesType<Path>()
|
||||
->HandlesType<FixedMaxPath>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -109,6 +109,7 @@ namespace AZ
|
||||
*/
|
||||
static EnvironmentVariable<T*> s_instance;
|
||||
static AZStd::shared_mutex s_mutex;
|
||||
static bool s_instanceAssigned;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -117,6 +118,9 @@ namespace AZ
|
||||
template <typename T>
|
||||
AZStd::shared_mutex Interface<T>::s_mutex;
|
||||
|
||||
template <typename T>
|
||||
bool Interface<T>::s_instanceAssigned;
|
||||
|
||||
template <typename T>
|
||||
void Interface<T>::Register(T* type)
|
||||
{
|
||||
@@ -135,18 +139,19 @@ namespace AZ
|
||||
AZStd::unique_lock<AZStd::shared_mutex> lock(s_mutex);
|
||||
s_instance = Environment::CreateVariable<T*>(GetVariableName());
|
||||
s_instance.Get() = type;
|
||||
s_instanceAssigned = true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Interface<T>::Unregister(T* type)
|
||||
{
|
||||
if (!s_instance || !s_instance.Get())
|
||||
if (!s_instanceAssigned)
|
||||
{
|
||||
AZ_Assert(false, "Interface '%s' not registered on this module!", AzTypeInfo<T>::Name());
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_instance.Get() != type)
|
||||
if (s_instance && s_instance.Get() != type)
|
||||
{
|
||||
AZ_Assert(false, "Interface '%s' is not the same instance that was registered! [Expected '%p', Found '%p']", AzTypeInfo<T>::Name(), type, s_instance.Get());
|
||||
return;
|
||||
@@ -156,6 +161,7 @@ namespace AZ
|
||||
AZStd::unique_lock<AZStd::shared_mutex> lock(s_mutex);
|
||||
*s_instance = nullptr;
|
||||
s_instance.Reset();
|
||||
s_instanceAssigned = false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -165,9 +171,9 @@ namespace AZ
|
||||
// This is the fast path which won't block.
|
||||
{
|
||||
AZStd::shared_lock<AZStd::shared_mutex> lock(s_mutex);
|
||||
if (s_instance)
|
||||
if (s_instanceAssigned)
|
||||
{
|
||||
return s_instance.Get();
|
||||
return s_instance ? s_instance.Get() : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +181,7 @@ namespace AZ
|
||||
// take the full lock and request it.
|
||||
AZStd::unique_lock<AZStd::shared_mutex> lock(s_mutex);
|
||||
s_instance = Environment::FindVariable<T*>(GetVariableName());
|
||||
s_instanceAssigned = true;
|
||||
return s_instance ? s_instance.Get() : nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>()->
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)->
|
||||
|
||||
@@ -19,12 +19,14 @@ namespace AZ
|
||||
|
||||
AZ_MATH_INLINE Plane Plane::CreateFromNormalAndPoint(const Vector3& normal, const Vector3& point)
|
||||
{
|
||||
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is not normalized");
|
||||
return Plane(Simd::Vec4::ConstructPlane(normal.GetSimdValue(), point.GetSimdValue()));
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE Plane Plane::CreateFromNormalAndDistance(const Vector3& normal, float dist)
|
||||
{
|
||||
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is not normalized");
|
||||
Plane result;
|
||||
result.Set(normal, dist);
|
||||
return result;
|
||||
@@ -33,6 +35,7 @@ namespace AZ
|
||||
|
||||
AZ_MATH_INLINE Plane Plane::CreateFromCoefficients(const float a, const float b, const float c, const float d)
|
||||
{
|
||||
AZ_MATH_ASSERT(Vector3(a, b, c).IsNormalized(), "This normal is notormalized");
|
||||
Plane result;
|
||||
result.Set(a, b, c, d);
|
||||
return result;
|
||||
@@ -65,18 +68,21 @@ namespace AZ
|
||||
|
||||
AZ_MATH_INLINE void Plane::Set(const Vector3& normal, float d)
|
||||
{
|
||||
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is notormalized");
|
||||
m_plane.Set(normal, d);
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE void Plane::Set(float a, float b, float c, float d)
|
||||
{
|
||||
AZ_MATH_ASSERT(Vector3(a, b, c).IsNormalized(), "This normal is notormalized");
|
||||
m_plane.Set(a, b, c, d);
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE void Plane::SetNormal(const Vector3& normal)
|
||||
{
|
||||
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is notormalized");
|
||||
m_plane.SetX(normal.GetX());
|
||||
m_plane.SetY(normal.GetY());
|
||||
m_plane.SetZ(normal.GetZ());
|
||||
|
||||
@@ -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)->
|
||||
@@ -254,13 +254,13 @@ namespace AZ
|
||||
Method("CreateFromMatrix3x3", &Quaternion::CreateFromMatrix3x3)->
|
||||
Method("CreateFromMatrix4x4", &Quaternion::CreateFromMatrix4x4)->
|
||||
Method("CreateFromAxisAngle", &Quaternion::CreateFromAxisAngle)->
|
||||
Method("CreateFromScaledAxisAngle", &Quaternion::CreateFromScaledAxisAngle)->
|
||||
Method("CreateShortestArc", &Quaternion::CreateShortestArc)->
|
||||
Method("CreateFromEulerAnglesDegrees", &Quaternion::CreateFromEulerAnglesDegrees)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Quaternion Quaternion::CreateFromMatrix3x3(const Matrix3x3& m)
|
||||
{
|
||||
return CreateFromBasis(m.GetBasisX(), m.GetBasisY(), m.GetBasisZ());
|
||||
@@ -430,4 +430,24 @@ namespace AZ
|
||||
outAngle = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vector3 Quaternion::ConvertToScaledAxisAngle() const
|
||||
{
|
||||
// Take the log of the quaternion to convert it to the exponential map
|
||||
// and multiply it by 2.0 to bring it into the scaled axis-angle representation.
|
||||
const AZ::Vector3 imaginary = GetImaginary();
|
||||
const float length = imaginary.GetLength();
|
||||
if (length < AZ::Constants::FloatEpsilon)
|
||||
{
|
||||
return imaginary * 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float halfAngle = acosf(AZ::GetClamp(GetW(), -1.0f, 1.0f));
|
||||
|
||||
// Multiply by 2.0 to convert the half angle into the full one.
|
||||
return halfAngle * 2.0f * (imaginary / length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,11 @@ namespace AZ
|
||||
//! Sets components using a Vector3 for the imaginary part and a float for the real part.
|
||||
static Quaternion CreateFromVector3AndValue(const Vector3& v, float w);
|
||||
|
||||
//! Sets the quaternion to be a rotation around a specified axis.
|
||||
//! Sets the quaternion to be a rotation around a specified axis in radians.
|
||||
//! @{
|
||||
static Quaternion CreateRotationX(float angle);
|
||||
static Quaternion CreateRotationY(float angle);
|
||||
static Quaternion CreateRotationZ(float angle);
|
||||
static Quaternion CreateRotationX(float angleInRadians);
|
||||
static Quaternion CreateRotationY(float angleInRadians);
|
||||
static Quaternion CreateRotationZ(float angleInRadians);
|
||||
//! @}
|
||||
|
||||
//! Creates a quaternion from a Matrix3x3
|
||||
@@ -77,6 +77,9 @@ namespace AZ
|
||||
|
||||
static Quaternion CreateFromAxisAngle(const Vector3& axis, float angle);
|
||||
|
||||
//! Create a quaternion from a scaled axis-angle representation.
|
||||
static Quaternion CreateFromScaledAxisAngle(const Vector3& scaledAxisAngle);
|
||||
|
||||
static Quaternion CreateShortestArc(const Vector3& v1, const Vector3& v2);
|
||||
|
||||
//! Creates a quaternion using rotation in degrees about the axes. First rotated about the X axis, followed by the Y axis, then the Z axis.
|
||||
@@ -165,6 +168,14 @@ namespace AZ
|
||||
float NormalizeWithLengthEstimate();
|
||||
//! @}
|
||||
|
||||
//! Get the shortest equivalent of the rotation.
|
||||
//! In case the w component of the quaternion is negative the rotation is > 180° and taking the longer path.
|
||||
//! The quaternion will be inverted in that case to take the shortest path of rotation.
|
||||
//! @{
|
||||
Quaternion GetShortestEquivalent() const;
|
||||
void ShortestEquivalent();
|
||||
//! @}
|
||||
|
||||
//! Linearly interpolate towards a destination quaternion.
|
||||
//! @param[in] dest The quaternion to interpolate towards.
|
||||
//! @param[in] t Normalized interpolation value where 0.0 represents the current and 1.0 the destination value.
|
||||
@@ -231,6 +242,9 @@ namespace AZ
|
||||
//! @param[out] outAngle A float rotation angle around the axis in radians.
|
||||
void ConvertToAxisAngle(Vector3& outAxis, float& outAngle) const;
|
||||
|
||||
//! Convert the quaternion into scaled axis-angle representation.
|
||||
Vector3 ConvertToScaledAxisAngle() const;
|
||||
|
||||
//! Returns the imaginary (X/Y/Z) portion of the quaternion.
|
||||
Vector3 GetImaginary() const;
|
||||
|
||||
|
||||
@@ -73,27 +73,27 @@ namespace AZ
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationX(float angle)
|
||||
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationX(float angleInRadians)
|
||||
{
|
||||
const float halfAngle = 0.5f * angle;
|
||||
const float halfAngle = 0.5f * angleInRadians;
|
||||
float sin, cos;
|
||||
SinCos(halfAngle, sin, cos);
|
||||
return Quaternion(sin, 0.0f, 0.0f, cos);
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationY(float angle)
|
||||
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationY(float angleInRadians)
|
||||
{
|
||||
const float halfAngle = 0.5f * angle;
|
||||
const float halfAngle = 0.5f * angleInRadians;
|
||||
float sin, cos;
|
||||
SinCos(halfAngle, sin, cos);
|
||||
return Quaternion(0.0f, sin, 0.0f, cos);
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationZ(float angle)
|
||||
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationZ(float angleInRadians)
|
||||
{
|
||||
const float halfAngle = 0.5f * angle;
|
||||
const float halfAngle = 0.5f * angleInRadians;
|
||||
float sin, cos;
|
||||
SinCos(halfAngle, sin, cos);
|
||||
return Quaternion(0.0f, 0.0f, sin, cos);
|
||||
@@ -109,6 +109,24 @@ namespace AZ
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE Quaternion Quaternion::CreateFromScaledAxisAngle(const Vector3& scaledAxisAngle)
|
||||
{
|
||||
const AZ::Vector3 exponentialMap = scaledAxisAngle / 2.0f;
|
||||
const float halfAngle = exponentialMap.GetLength();
|
||||
|
||||
if (halfAngle < AZ::Constants::FloatEpsilon)
|
||||
{
|
||||
return AZ::Quaternion::CreateFromVector3AndValue(exponentialMap, 1.0f).GetNormalized();
|
||||
}
|
||||
else
|
||||
{
|
||||
float sin, cos;
|
||||
SinCos(halfAngle, sin, cos);
|
||||
return AZ::Quaternion::CreateFromVector3AndValue((sin / halfAngle) * exponentialMap, cos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE void Quaternion::StoreToFloat4(float* values) const
|
||||
{
|
||||
Simd::Vec4::StoreUnaligned(values, m_value);
|
||||
@@ -327,6 +345,23 @@ namespace AZ
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE Quaternion Quaternion::GetShortestEquivalent() const
|
||||
{
|
||||
if (GetW() < 0.0f)
|
||||
{
|
||||
return -(*this);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE void Quaternion::ShortestEquivalent()
|
||||
{
|
||||
*this = GetShortestEquivalent();
|
||||
}
|
||||
|
||||
|
||||
AZ_MATH_INLINE Quaternion Quaternion::Lerp(const Quaternion& dest, float t) const
|
||||
{
|
||||
if (Dot(dest) >= 0.0f)
|
||||
|
||||
@@ -9,39 +9,38 @@
|
||||
#include <AzCore/Math/Sfmt.h>
|
||||
|
||||
#include <AzCore/Math/Random.h>
|
||||
#include <AzCore/std/parallel/lock.h>
|
||||
#include <AzCore/Module/Environment.h>
|
||||
#include <AzCore/std/parallel/lock.h>
|
||||
|
||||
#include <string.h> // for memset
|
||||
|
||||
namespace AZ::SfmtInternal
|
||||
{
|
||||
static const int N32 = N * 4;
|
||||
static const int N64 = N * 2;
|
||||
static const int POS1 = 122;
|
||||
static const int SL1 = 18;
|
||||
static const int SR1 = 11;
|
||||
static const int SL2 = 1;
|
||||
static const int SR2 = 1;
|
||||
static const unsigned int MSK1 = 0xdfffffefU;
|
||||
static const unsigned int MSK2 = 0xddfecb7fU;
|
||||
static const unsigned int MSK3 = 0xbffaffffU;
|
||||
static const unsigned int MSK4 = 0xbffffff6U;
|
||||
static const unsigned int PARITY1 = 0x00000001U;
|
||||
static const unsigned int PARITY2 = 0x00000000U;
|
||||
static const unsigned int PARITY3 = 0x00000000U;
|
||||
static const unsigned int PARITY4 = 0x13c9e684U;
|
||||
static const int N32 = N * 4;
|
||||
static const int N64 = N * 2;
|
||||
static const int POS1 = 122;
|
||||
static const int SL1 = 18;
|
||||
static const int SR1 = 11;
|
||||
static const int SL2 = 1;
|
||||
static const int SR2 = 1;
|
||||
static const unsigned int MSK1 = 0xdfffffefU;
|
||||
static const unsigned int MSK2 = 0xddfecb7fU;
|
||||
static const unsigned int MSK3 = 0xbffaffffU;
|
||||
static const unsigned int MSK4 = 0xbffffff6U;
|
||||
static const unsigned int PARITY1 = 0x00000001U;
|
||||
static const unsigned int PARITY2 = 0x00000000U;
|
||||
static const unsigned int PARITY3 = 0x00000000U;
|
||||
static const unsigned int PARITY4 = 0x13c9e684U;
|
||||
|
||||
/** a parity check vector which certificate the period of 2^{MEXP} */
|
||||
static unsigned int parity[4] = {PARITY1, PARITY2, PARITY3, PARITY4};
|
||||
static unsigned int parity[4] = { PARITY1, PARITY2, PARITY3, PARITY4 };
|
||||
|
||||
#ifdef ONLY64
|
||||
# define idxof(_i) (_i ^ 1)
|
||||
#define idxof(_i) (_i ^ 1)
|
||||
#else
|
||||
# define idxof(_i) _i
|
||||
#define idxof(_i) _i
|
||||
#endif // ONLY64
|
||||
|
||||
|
||||
#if AZ_TRAIT_USE_PLATFORM_SIMD_SSE
|
||||
/**
|
||||
* This function represents the recursion formula.
|
||||
@@ -52,7 +51,8 @@ namespace AZ::SfmtInternal
|
||||
* @param mask 128-bit mask
|
||||
* @return output
|
||||
*/
|
||||
AZ_FORCE_INLINE static Simd::Vec4::Int32Type simd_recursion(Simd::Vec4::Int32Type* a, Simd::Vec4::Int32Type* b, Simd::Vec4::Int32Type c, Simd::Vec4::Int32Type d, Simd::Vec4::Int32Type mask)
|
||||
AZ_FORCE_INLINE static Simd::Vec4::Int32Type simd_recursion(
|
||||
Simd::Vec4::Int32Type* a, Simd::Vec4::Int32Type* b, Simd::Vec4::Int32Type c, Simd::Vec4::Int32Type d, Simd::Vec4::Int32Type mask)
|
||||
{
|
||||
Simd::Vec4::Int32Type v, x, y, z;
|
||||
x = *a;
|
||||
@@ -151,7 +151,7 @@ namespace AZ::SfmtInternal
|
||||
inline void rshift128(w128_t* out, w128_t const* in, int shift)
|
||||
{
|
||||
AZ::u64 th, tl, oh, ol;
|
||||
#ifdef ONLY64
|
||||
#ifdef ONLY64
|
||||
th = ((AZ::u64)in->u[2] << 32) | ((AZ::u64)in->u[3]);
|
||||
tl = ((AZ::u64)in->u[0] << 32) | ((AZ::u64)in->u[1]);
|
||||
|
||||
@@ -204,7 +204,7 @@ namespace AZ::SfmtInternal
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void do_recursion(w128_t* r, w128_t* a, w128_t* b, w128_t* c, w128_t* d)
|
||||
inline void do_recursion(w128_t* r, w128_t* a, w128_t* b, w128_t* c, w128_t* d)
|
||||
{
|
||||
w128_t x;
|
||||
w128_t y;
|
||||
@@ -229,7 +229,7 @@ namespace AZ::SfmtInternal
|
||||
inline void gen_rand_all(Sfmt& g)
|
||||
{
|
||||
int i;
|
||||
w128_t* r1, * r2;
|
||||
w128_t *r1, *r2;
|
||||
|
||||
r1 = &g.m_sfmt[N - 2];
|
||||
r2 = &g.m_sfmt[N - 1];
|
||||
@@ -257,7 +257,7 @@ namespace AZ::SfmtInternal
|
||||
inline void gen_rand_array(Sfmt& g, w128_t* array, int size)
|
||||
{
|
||||
int i, j;
|
||||
w128_t* r1, * r2;
|
||||
w128_t *r1, *r2;
|
||||
|
||||
r1 = &g.m_sfmt[N - 2];
|
||||
r2 = &g.m_sfmt[N - 1];
|
||||
@@ -295,82 +295,80 @@ namespace AZ::SfmtInternal
|
||||
#endif
|
||||
} // namespace AZ::SfmtInternal
|
||||
|
||||
using namespace AZ;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Statics
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
static EnvironmentVariable<AZ::Sfmt> s_sfmt;
|
||||
static const char* s_globalSfmtName = "GlobalSfmt";
|
||||
|
||||
Sfmt& Sfmt::GetInstance()
|
||||
namespace AZ
|
||||
{
|
||||
if (!s_sfmt)
|
||||
static EnvironmentVariable<AZ::Sfmt> s_sfmt;
|
||||
static const char* s_globalSfmtName = "GlobalSfmt";
|
||||
|
||||
Sfmt& Sfmt::GetInstance()
|
||||
{
|
||||
s_sfmt = AZ::Environment::FindVariable<Sfmt>(s_globalSfmtName);
|
||||
if (!s_sfmt)
|
||||
{
|
||||
Sfmt::Create();
|
||||
s_sfmt = AZ::Environment::FindVariable<Sfmt>(s_globalSfmtName);
|
||||
if (!s_sfmt)
|
||||
{
|
||||
Sfmt::Create();
|
||||
}
|
||||
}
|
||||
|
||||
return s_sfmt.Get();
|
||||
}
|
||||
|
||||
void Sfmt::Create()
|
||||
{
|
||||
if (!s_sfmt)
|
||||
{
|
||||
s_sfmt = AZ::Environment::CreateVariable<AZ::Sfmt>(s_globalSfmtName);
|
||||
}
|
||||
}
|
||||
|
||||
return s_sfmt.Get();
|
||||
}
|
||||
|
||||
void Sfmt::Create()
|
||||
{
|
||||
if (!s_sfmt)
|
||||
void Sfmt::Destroy()
|
||||
{
|
||||
s_sfmt = AZ::Environment::CreateVariable<AZ::Sfmt>(s_globalSfmtName);
|
||||
s_sfmt.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Sfmt::Destroy()
|
||||
{
|
||||
s_sfmt.Reset();
|
||||
}
|
||||
//=========================================================================
|
||||
// Sfmt
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
Sfmt::Sfmt()
|
||||
{
|
||||
m_psfmt32 = &m_sfmt[0].u[0];
|
||||
m_psfmt64 = reinterpret_cast<AZ::u64*>(m_psfmt32);
|
||||
|
||||
//=========================================================================
|
||||
// Sfmt
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
Sfmt::Sfmt()
|
||||
{
|
||||
m_psfmt32 = &m_sfmt[0].u[0];
|
||||
m_psfmt64 = reinterpret_cast<AZ::u64*>(m_psfmt32);
|
||||
Seed();
|
||||
}
|
||||
|
||||
Seed();
|
||||
}
|
||||
//=========================================================================
|
||||
// Seed
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
Sfmt::Sfmt(AZ::u32* keys, int numKeys)
|
||||
{
|
||||
m_psfmt32 = &m_sfmt[0].u[0];
|
||||
m_psfmt64 = reinterpret_cast<AZ::u64*>(m_psfmt32);
|
||||
|
||||
//=========================================================================
|
||||
// Seed
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
Sfmt::Sfmt(AZ::u32* keys, int numKeys)
|
||||
{
|
||||
m_psfmt32 = &m_sfmt[0].u[0];
|
||||
m_psfmt64 = reinterpret_cast<AZ::u64*>(m_psfmt32);
|
||||
Seed(keys, numKeys);
|
||||
}
|
||||
|
||||
Seed(keys, numKeys);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Seed
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
void
|
||||
Sfmt::Seed()
|
||||
{
|
||||
// buffer with random values
|
||||
AZ::u32 buffer[32];
|
||||
BetterPseudoRandom rnd;
|
||||
bool result = rnd.GetRandom(buffer, sizeof(buffer));
|
||||
(void)result;
|
||||
AZ_Warning("System", result, "Failed to seed properly the Smft generator!");
|
||||
Seed(buffer, AZ_ARRAY_SIZE(buffer));
|
||||
}
|
||||
//=========================================================================
|
||||
// Seed
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
void Sfmt::Seed()
|
||||
{
|
||||
// buffer with random values
|
||||
AZ::u32 buffer[32];
|
||||
BetterPseudoRandom rnd;
|
||||
bool result = rnd.GetRandom(buffer, sizeof(buffer));
|
||||
(void)result;
|
||||
AZ_Warning("System", result, "Failed to seed properly the Smft generator!");
|
||||
Seed(buffer, AZ_ARRAY_SIZE(buffer));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function represents a function used in the initialization
|
||||
@@ -388,226 +386,222 @@ Sfmt::Seed()
|
||||
*/
|
||||
#define azsfmt_func2(x) ((x ^ (x >> 27)) * (AZ::u32)1566083941UL)
|
||||
|
||||
//=========================================================================
|
||||
// Seed
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
void
|
||||
Sfmt::Seed(AZ::u32* keys, int numKeys)
|
||||
{
|
||||
using SfmtInternal::N;
|
||||
using SfmtInternal::N32;
|
||||
int i, j, count;
|
||||
AZ::u32 r;
|
||||
int lag;
|
||||
int mid;
|
||||
int size = N * 4;
|
||||
//=========================================================================
|
||||
// Seed
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
void Sfmt::Seed(AZ::u32* keys, int numKeys)
|
||||
{
|
||||
using SfmtInternal::N;
|
||||
using SfmtInternal::N32;
|
||||
int i, j, count;
|
||||
AZ::u32 r;
|
||||
int lag;
|
||||
int mid;
|
||||
int size = N * 4;
|
||||
|
||||
if (size >= 623)
|
||||
{
|
||||
lag = 11;
|
||||
}
|
||||
else if (size >= 68)
|
||||
{
|
||||
lag = 7;
|
||||
}
|
||||
else if (size >= 39)
|
||||
{
|
||||
lag = 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
lag = 3;
|
||||
}
|
||||
mid = (size - lag) / 2;
|
||||
if (size >= 623)
|
||||
{
|
||||
lag = 11;
|
||||
}
|
||||
else if (size >= 68)
|
||||
{
|
||||
lag = 7;
|
||||
}
|
||||
else if (size >= 39)
|
||||
{
|
||||
lag = 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
lag = 3;
|
||||
}
|
||||
mid = (size - lag) / 2;
|
||||
|
||||
memset(m_sfmt, 0x8b, sizeof(m_sfmt));
|
||||
if (numKeys + 1 > SfmtInternal::N32)
|
||||
{
|
||||
count = numKeys + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = N32;
|
||||
}
|
||||
r = azsfmt_func1((m_psfmt32[idxof(0)] ^ m_psfmt32[idxof(mid)] ^ m_psfmt32[idxof(N32 - 1)]));
|
||||
m_psfmt32[idxof(mid)] += r;
|
||||
r += numKeys;
|
||||
m_psfmt32[idxof(mid + lag)] += r;
|
||||
m_psfmt32[idxof(0)] = r;
|
||||
memset(m_sfmt, 0x8b, sizeof(m_sfmt));
|
||||
if (numKeys + 1 > SfmtInternal::N32)
|
||||
{
|
||||
count = numKeys + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = N32;
|
||||
}
|
||||
r = azsfmt_func1((m_psfmt32[idxof(0)] ^ m_psfmt32[idxof(mid)] ^ m_psfmt32[idxof(N32 - 1)]));
|
||||
m_psfmt32[idxof(mid)] += r;
|
||||
r += numKeys;
|
||||
m_psfmt32[idxof(mid + lag)] += r;
|
||||
m_psfmt32[idxof(0)] = r;
|
||||
|
||||
count--;
|
||||
for (i = 1, j = 0; (j < count) && (j < numKeys); j++)
|
||||
{
|
||||
r = azsfmt_func1((m_psfmt32[idxof(i)] ^ m_psfmt32[idxof((i + mid) % N32)] ^ m_psfmt32[idxof((i + N32 - 1) % N32)]));
|
||||
m_psfmt32[idxof((i + mid) % N32)] += r;
|
||||
r += keys[j] + i;
|
||||
m_psfmt32[idxof((i + mid + lag) % N32)] += r;
|
||||
m_psfmt32[idxof(i)] = r;
|
||||
i = (i + 1) % N32;
|
||||
}
|
||||
for (; j < count; j++)
|
||||
{
|
||||
r = azsfmt_func1((m_psfmt32[idxof(i)] ^ m_psfmt32[idxof((i + mid) % N32)] ^ m_psfmt32[idxof((i + N32 - 1) % N32)]));
|
||||
m_psfmt32[idxof((i + mid) % N32)] += r;
|
||||
r += i;
|
||||
m_psfmt32[idxof((i + mid + lag) % N32)] += r;
|
||||
m_psfmt32[idxof(i)] = r;
|
||||
i = (i + 1) % N32;
|
||||
}
|
||||
for (j = 0; j < N32; j++)
|
||||
{
|
||||
r = azsfmt_func2((m_psfmt32[idxof(i)] + m_psfmt32[idxof((i + mid) % N32)] + m_psfmt32[idxof((i + N32 - 1) % N32)]));
|
||||
m_psfmt32[idxof((i + mid) % N32)] ^= r;
|
||||
r -= i;
|
||||
m_psfmt32[idxof((i + mid + lag) % N32)] ^= r;
|
||||
m_psfmt32[idxof(i)] = r;
|
||||
i = (i + 1) % N32;
|
||||
}
|
||||
count--;
|
||||
for (i = 1, j = 0; (j < count) && (j < numKeys); j++)
|
||||
{
|
||||
r = azsfmt_func1((m_psfmt32[idxof(i)] ^ m_psfmt32[idxof((i + mid) % N32)] ^ m_psfmt32[idxof((i + N32 - 1) % N32)]));
|
||||
m_psfmt32[idxof((i + mid) % N32)] += r;
|
||||
r += keys[j] + i;
|
||||
m_psfmt32[idxof((i + mid + lag) % N32)] += r;
|
||||
m_psfmt32[idxof(i)] = r;
|
||||
i = (i + 1) % N32;
|
||||
}
|
||||
for (; j < count; j++)
|
||||
{
|
||||
r = azsfmt_func1((m_psfmt32[idxof(i)] ^ m_psfmt32[idxof((i + mid) % N32)] ^ m_psfmt32[idxof((i + N32 - 1) % N32)]));
|
||||
m_psfmt32[idxof((i + mid) % N32)] += r;
|
||||
r += i;
|
||||
m_psfmt32[idxof((i + mid + lag) % N32)] += r;
|
||||
m_psfmt32[idxof(i)] = r;
|
||||
i = (i + 1) % N32;
|
||||
}
|
||||
for (j = 0; j < N32; j++)
|
||||
{
|
||||
r = azsfmt_func2((m_psfmt32[idxof(i)] + m_psfmt32[idxof((i + mid) % N32)] + m_psfmt32[idxof((i + N32 - 1) % N32)]));
|
||||
m_psfmt32[idxof((i + mid) % N32)] ^= r;
|
||||
r -= i;
|
||||
m_psfmt32[idxof((i + mid + lag) % N32)] ^= r;
|
||||
m_psfmt32[idxof(i)] = r;
|
||||
i = (i + 1) % N32;
|
||||
}
|
||||
|
||||
m_index = N32;
|
||||
PeriodCertification();
|
||||
}
|
||||
m_index = N32;
|
||||
PeriodCertification();
|
||||
}
|
||||
|
||||
#undef azsfmt_func1
|
||||
#undef azsfmt_func2
|
||||
|
||||
//=========================================================================
|
||||
// PeriodCertification
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
void
|
||||
Sfmt::PeriodCertification()
|
||||
{
|
||||
int inner = 0;
|
||||
int i, j;
|
||||
AZ::u32 work;
|
||||
//=========================================================================
|
||||
// PeriodCertification
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
void Sfmt::PeriodCertification()
|
||||
{
|
||||
int inner = 0;
|
||||
int i, j;
|
||||
AZ::u32 work;
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
inner ^= m_psfmt32[idxof(i)] & SfmtInternal::parity[i];
|
||||
}
|
||||
for (i = 16; i > 0; i >>= 1)
|
||||
{
|
||||
inner ^= inner >> i;
|
||||
}
|
||||
inner &= 1;
|
||||
/* check OK */
|
||||
if (inner == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
/* check NG, and modification */
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
work = 1;
|
||||
for (j = 0; j < 32; j++)
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
if ((work & SfmtInternal::parity[i]) != 0)
|
||||
inner ^= m_psfmt32[idxof(i)] & SfmtInternal::parity[i];
|
||||
}
|
||||
for (i = 16; i > 0; i >>= 1)
|
||||
{
|
||||
inner ^= inner >> i;
|
||||
}
|
||||
inner &= 1;
|
||||
/* check OK */
|
||||
if (inner == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
/* check NG, and modification */
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
work = 1;
|
||||
for (j = 0; j < 32; j++)
|
||||
{
|
||||
m_psfmt32[idxof(i)] ^= work;
|
||||
return;
|
||||
if ((work & SfmtInternal::parity[i]) != 0)
|
||||
{
|
||||
m_psfmt32[idxof(i)] ^= work;
|
||||
return;
|
||||
}
|
||||
work = work << 1;
|
||||
}
|
||||
work = work << 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Rand32
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
AZ::u32 Sfmt::Rand32()
|
||||
{
|
||||
int index = m_index.fetch_add(1);
|
||||
if (index >= SfmtInternal::N32)
|
||||
//=========================================================================
|
||||
// Rand32
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
AZ::u32 Sfmt::Rand32()
|
||||
{
|
||||
AZStd::lock_guard<decltype(m_generationMutex)> lock(m_generationMutex);
|
||||
// if this thread is the one that sets m_index to 0, then this thread
|
||||
// does the generation
|
||||
index += 1; // compare against the result of fetch_add(1) above
|
||||
if (m_index.compare_exchange_strong(index, 0))
|
||||
int index = m_index.fetch_add(1);
|
||||
if (index >= SfmtInternal::N32)
|
||||
{
|
||||
SfmtInternal::gen_rand_all(*this);
|
||||
AZStd::lock_guard<decltype(m_generationMutex)> lock(m_generationMutex);
|
||||
// if this thread is the one that sets m_index to 0, then this thread
|
||||
// does the generation
|
||||
index += 1; // compare against the result of fetch_add(1) above
|
||||
if (m_index.compare_exchange_strong(index, 0))
|
||||
{
|
||||
SfmtInternal::gen_rand_all(*this);
|
||||
}
|
||||
// try again, with the new table
|
||||
return Rand32();
|
||||
}
|
||||
// try again, with the new table
|
||||
return Rand32();
|
||||
return m_psfmt32[index];
|
||||
}
|
||||
return m_psfmt32[index];
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Rand64
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
AZ::u64 Sfmt::Rand64()
|
||||
{
|
||||
int index = m_index.fetch_add(2);
|
||||
if (index >= (SfmtInternal::N32 - 1))
|
||||
//=========================================================================
|
||||
// Rand64
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
AZ::u64 Sfmt::Rand64()
|
||||
{
|
||||
AZStd::lock_guard<decltype(m_generationMutex)> lock(m_generationMutex);
|
||||
// if this thread is the one that sets m_index to 0, then this thread
|
||||
// does the generation
|
||||
index += 2; // compare against the result of fetch_add(2) above
|
||||
if (m_index.compare_exchange_strong(index, 0))
|
||||
int index = m_index.fetch_add(2);
|
||||
if (index >= (SfmtInternal::N32 - 1))
|
||||
{
|
||||
SfmtInternal::gen_rand_all(*this);
|
||||
AZStd::lock_guard<decltype(m_generationMutex)> lock(m_generationMutex);
|
||||
// if this thread is the one that sets m_index to 0, then this thread
|
||||
// does the generation
|
||||
index += 2; // compare against the result of fetch_add(2) above
|
||||
if (m_index.compare_exchange_strong(index, 0))
|
||||
{
|
||||
SfmtInternal::gen_rand_all(*this);
|
||||
}
|
||||
// try again, with the new table
|
||||
return Rand64();
|
||||
}
|
||||
// try again, with the new table
|
||||
return Rand64();
|
||||
|
||||
AZ::u64 r;
|
||||
r = m_psfmt64[index / 2];
|
||||
return r;
|
||||
}
|
||||
|
||||
AZ::u64 r;
|
||||
r = m_psfmt64[index / 2];
|
||||
return r;
|
||||
}
|
||||
//=========================================================================
|
||||
// FillArray32
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
void Sfmt::FillArray32(AZ::u32* array, int size)
|
||||
{
|
||||
AZ_MATH_ASSERT(m_index == SfmtInternal::N32, "Invalid m_index! Reinitialize!");
|
||||
AZ_MATH_ASSERT(size % 4 == 0, "Size must be multiple of 4!");
|
||||
AZ_MATH_ASSERT(size >= SfmtInternal::N32, "Size must be bigger than %d GetMinArray32Size()!", SfmtInternal::N32);
|
||||
|
||||
//=========================================================================
|
||||
// FillArray32
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
void
|
||||
Sfmt::FillArray32(AZ::u32* array, int size)
|
||||
{
|
||||
AZ_MATH_ASSERT(m_index == SfmtInternal::N32, "Invalid m_index! Reinitialize!");
|
||||
AZ_MATH_ASSERT(size % 4 == 0, "Size must be multiple of 4!");
|
||||
AZ_MATH_ASSERT(size >= SfmtInternal::N32, "Size must be bigger than %d GetMinArray32Size()!", SfmtInternal::N32);
|
||||
SfmtInternal::gen_rand_array(*this, (SfmtInternal::w128_t*)array, size / 4);
|
||||
m_index = SfmtInternal::N32;
|
||||
}
|
||||
|
||||
SfmtInternal::gen_rand_array(*this, (SfmtInternal::w128_t*)array, size / 4);
|
||||
m_index = SfmtInternal::N32;
|
||||
}
|
||||
//=========================================================================
|
||||
// FillArray64
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
void Sfmt::FillArray64(AZ::u64* array, int size)
|
||||
{
|
||||
AZ_MATH_ASSERT(m_index == SfmtInternal::N32, "Invalid m_index! Reinitialize!");
|
||||
AZ_MATH_ASSERT(size % 4 == 0, "Size must be multiple of 4!");
|
||||
AZ_MATH_ASSERT(size >= SfmtInternal::N64, "Size must be bigger than %d GetMinArray64Size()!", SfmtInternal::N64);
|
||||
|
||||
//=========================================================================
|
||||
// FillArray64
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
void
|
||||
Sfmt::FillArray64(AZ::u64* array, int size)
|
||||
{
|
||||
AZ_MATH_ASSERT(m_index == SfmtInternal::N32, "Invalid m_index! Reinitialize!");
|
||||
AZ_MATH_ASSERT(size % 4 == 0, "Size must be multiple of 4!");
|
||||
AZ_MATH_ASSERT(size >= SfmtInternal::N64, "Size must be bigger than %d GetMinArray64Size()!", SfmtInternal::N64);
|
||||
SfmtInternal::gen_rand_array(*this, (SfmtInternal::w128_t*)array, size / 2);
|
||||
m_index = SfmtInternal::N32;
|
||||
}
|
||||
|
||||
SfmtInternal::gen_rand_array(*this, (SfmtInternal::w128_t*)array, size / 2);
|
||||
m_index = SfmtInternal::N32;
|
||||
}
|
||||
//=========================================================================
|
||||
// GetMinArray32Size
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
int Sfmt::GetMinArray32Size() const
|
||||
{
|
||||
return SfmtInternal::N32;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// GetMinArray32Size
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
int
|
||||
Sfmt::GetMinArray32Size() const
|
||||
{
|
||||
return SfmtInternal::N32;
|
||||
}
|
||||
//=========================================================================
|
||||
// GetMinArray64Size
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
int Sfmt::GetMinArray64Size() const
|
||||
{
|
||||
return SfmtInternal::N64;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// GetMinArray64Size
|
||||
// [4/10/2012]
|
||||
//=========================================================================
|
||||
int
|
||||
Sfmt::GetMinArray64Size() const
|
||||
{
|
||||
return SfmtInternal::N64;
|
||||
}
|
||||
} // namespace AZ
|
||||
|
||||
@@ -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)->
|
||||
|
||||
@@ -16,438 +16,421 @@
|
||||
|
||||
#include <AzCore/Debug/StackTracer.h>
|
||||
|
||||
using namespace AZ;
|
||||
using namespace AZ::Debug;
|
||||
namespace AZ::Debug
|
||||
{
|
||||
// Many PC tools break with alloc/free size mismatches when the memory guard is enabled. Disable for now
|
||||
//#define ENABLE_MEMORY_GUARD
|
||||
|
||||
// Many PC tools break with alloc/free size mismatches when the memory guard is enabled. Disable for now
|
||||
//#define ENABLE_MEMORY_GUARD
|
||||
|
||||
//=========================================================================
|
||||
// AllocationRecords
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
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)
|
||||
//=========================================================================
|
||||
// AllocationRecords
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
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)
|
||||
, m_memoryGuardSize(isMemoryGuard ? sizeof(Debug::GuardValue) : 0)
|
||||
#else
|
||||
, m_memoryGuardSize(0)
|
||||
, m_memoryGuardSize(0)
|
||||
#endif
|
||||
, m_requestedAllocs(0)
|
||||
, m_requestedBytes(0)
|
||||
, m_requestedBytesPeak(0)
|
||||
, m_allocatorName(allocatorName)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ~AllocationRecords
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
AllocationRecords::~AllocationRecords()
|
||||
{
|
||||
if (!AllocatorManager::Instance().m_isAllocatorLeaking)
|
||||
, m_requestedAllocs(0)
|
||||
, m_requestedBytes(0)
|
||||
, m_requestedBytesPeak(0)
|
||||
, m_allocatorName(allocatorName)
|
||||
{
|
||||
// dump all allocation (we should not have any at this point).
|
||||
bool includeNameAndFilename = (m_saveNames || m_mode == RECORD_FULL);
|
||||
EnumerateAllocations(PrintAllocationsCB(true, includeNameAndFilename));
|
||||
AZ_Error("Memory", m_records.empty(), "We still have %d allocations on record! They must be freed prior to destroy!", m_records.size());
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// lock
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
void
|
||||
AllocationRecords::lock()
|
||||
{
|
||||
m_recordsMutex.lock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// try_lock
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
bool AllocationRecords::try_lock()
|
||||
{
|
||||
return m_recordsMutex.try_lock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// unlock
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
void
|
||||
AllocationRecords::unlock()
|
||||
{
|
||||
m_recordsMutex.unlock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// RegisterAllocation
|
||||
// [9/11/2009]
|
||||
//=========================================================================
|
||||
const AllocationInfo*
|
||||
AllocationRecords::RegisterAllocation(void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount)
|
||||
{
|
||||
(void)stackSuppressCount;
|
||||
if (m_mode == RECORD_NO_RECORDS)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (address == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// memory guard
|
||||
if (m_memoryGuardSize == sizeof(Debug::GuardValue))
|
||||
//=========================================================================
|
||||
// ~AllocationRecords
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
AllocationRecords::~AllocationRecords()
|
||||
{
|
||||
if (m_isAutoIntegrityCheck)
|
||||
if (!AllocatorManager::Instance().m_isAllocatorLeaking)
|
||||
{
|
||||
IntegrityCheck();
|
||||
// dump all allocation (we should not have any at this point).
|
||||
bool includeNameAndFilename = (m_saveNames || m_mode == RECORD_FULL);
|
||||
EnumerateAllocations(PrintAllocationsCB(true, includeNameAndFilename));
|
||||
AZ_Error(
|
||||
"Memory", m_records.empty(), "We still have %d allocations on record! They must be freed prior to destroy!",
|
||||
m_records.size());
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// lock
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
void AllocationRecords::lock()
|
||||
{
|
||||
m_recordsMutex.lock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// try_lock
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
bool AllocationRecords::try_lock()
|
||||
{
|
||||
return m_recordsMutex.try_lock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// unlock
|
||||
// [9/16/2009]
|
||||
//=========================================================================
|
||||
void AllocationRecords::unlock()
|
||||
{
|
||||
m_recordsMutex.unlock();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// RegisterAllocation
|
||||
// [9/11/2009]
|
||||
//=========================================================================
|
||||
const AllocationInfo* AllocationRecords::RegisterAllocation(
|
||||
void* address,
|
||||
size_t byteSize,
|
||||
size_t alignment,
|
||||
const char* name,
|
||||
const char* fileName,
|
||||
int lineNum,
|
||||
unsigned int stackSuppressCount)
|
||||
{
|
||||
(void)stackSuppressCount;
|
||||
if (m_mode == RECORD_NO_RECORDS)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (address == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AZ_Assert(byteSize>sizeof(Debug::GuardValue), "Did you forget to add the extra MemoryGuardSize() bytes?");
|
||||
byteSize -= sizeof(Debug::GuardValue);
|
||||
new(reinterpret_cast<char*>(address)+byteSize) Debug::GuardValue();
|
||||
}
|
||||
|
||||
Debug::AllocationRecordsType::pair_iter_bool iterBool;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_recordsMutex);
|
||||
iterBool = m_records.insert_key(address);
|
||||
}
|
||||
|
||||
if (!iterBool.second)
|
||||
{
|
||||
// If that memory address was already registered, print the stack trace of the previous registration
|
||||
PrintAllocationsCB(true, (m_saveNames || m_mode == RECORD_FULL))(address, iterBool.first->second, m_numStackLevels);
|
||||
AZ_Assert(iterBool.second, "Memory address 0x%p is already allocated and in the records!", address);
|
||||
}
|
||||
|
||||
Debug::AllocationInfo& ai = iterBool.first->second;
|
||||
ai.m_byteSize = byteSize;
|
||||
ai.m_alignment = static_cast<unsigned int>(alignment);
|
||||
if ((m_saveNames || m_mode == RECORD_FULL) && name && fileName)
|
||||
{
|
||||
// In RECORD_FULL mode or when specifically enabled in app descriptor with
|
||||
// m_allocationRecordsSaveNames, we allocate our own memory to save off name and fileName.
|
||||
// When testing for memory leaks, on process shutdown AllocationRecords::~AllocationRecords
|
||||
// gets called to enumerate the remaining (leaked) allocations. Unfortunately, any names
|
||||
// referenced in dynamic module memory whose modules are unloaded won't be valid
|
||||
// references anymore and we won't get useful information from the enumeration print.
|
||||
// This code block ensures we keep our name/fileName valid for when we need it.
|
||||
const size_t nameLength = strlen(name);
|
||||
const size_t fileNameLength = strlen(fileName);
|
||||
const size_t totalLength = nameLength + fileNameLength + 2; // + 2 for terminating null characters
|
||||
ai.m_namesBlock = m_records.get_allocator().allocate(totalLength, 1);
|
||||
ai.m_namesBlockSize = totalLength;
|
||||
char* savedName = reinterpret_cast<char*>(ai.m_namesBlock);
|
||||
char* savedFileName = savedName + nameLength + 1;
|
||||
memcpy(reinterpret_cast<void*>(savedName), reinterpret_cast<const void*>(name), nameLength + 1);
|
||||
memcpy(reinterpret_cast<void*>(savedFileName), reinterpret_cast<const void*>(fileName), fileNameLength + 1);
|
||||
ai.m_name = savedName;
|
||||
ai.m_fileName = savedFileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
ai.m_name = name;
|
||||
ai.m_fileName = fileName;
|
||||
ai.m_namesBlock = nullptr;
|
||||
ai.m_namesBlockSize = 0;
|
||||
}
|
||||
ai.m_lineNum = lineNum;
|
||||
ai.m_timeStamp = AZStd::GetTimeNowMicroSecond();
|
||||
|
||||
// if we don't have a fileName,lineNum record the stack or if the user requested it.
|
||||
if ((fileName == nullptr && m_mode == RECORD_STACK_IF_NO_FILE_LINE) || m_mode == RECORD_FULL)
|
||||
{
|
||||
ai.m_stackFrames = m_numStackLevels ? reinterpret_cast<AZ::Debug::StackFrame*>(m_records.get_allocator().allocate(sizeof(AZ::Debug::StackFrame)*m_numStackLevels, 1)) : nullptr;
|
||||
if (ai.m_stackFrames)
|
||||
// memory guard
|
||||
if (m_memoryGuardSize == sizeof(Debug::GuardValue))
|
||||
{
|
||||
Debug::StackRecorder::Record(ai.m_stackFrames, m_numStackLevels, stackSuppressCount + 1);
|
||||
|
||||
if (m_decodeImmediately)
|
||||
if (m_isAutoIntegrityCheck)
|
||||
{
|
||||
// OPTIONAL DEBUGGING CODE - enable in app descriptor m_allocationRecordsAttemptDecodeImmediately
|
||||
// This is optionally-enabled code for tracking down memory allocations
|
||||
// that fail to be decoded. DecodeFrames() typically runs at the end of
|
||||
// your application when leaks were found. Sometimes you have stack prints
|
||||
// full of "(module-name not available)" and "(function-name not available)"
|
||||
// that are not actionable. If you have those, enable this code. It'll slow
|
||||
// down your process significantly because for every allocation recorded
|
||||
// we get the stack trace on the spot. Put a breakpoint in DecodeFrames()
|
||||
// at the "(module-name not available)" and "(function-name not available)"
|
||||
// locations and now at the moment those allocations happen you'll have the
|
||||
// full stack trace available and the ability to debug what could be causing it
|
||||
IntegrityCheck();
|
||||
}
|
||||
|
||||
AZ_Assert(byteSize > sizeof(Debug::GuardValue), "Did you forget to add the extra MemoryGuardSize() bytes?");
|
||||
byteSize -= sizeof(Debug::GuardValue);
|
||||
new (reinterpret_cast<char*>(address) + byteSize) Debug::GuardValue();
|
||||
}
|
||||
|
||||
Debug::AllocationRecordsType::pair_iter_bool iterBool;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_recordsMutex);
|
||||
iterBool = m_records.insert_key(address);
|
||||
}
|
||||
|
||||
if (!iterBool.second)
|
||||
{
|
||||
// If that memory address was already registered, print the stack trace of the previous registration
|
||||
PrintAllocationsCB(true, (m_saveNames || m_mode == RECORD_FULL))(address, iterBool.first->second, m_numStackLevels);
|
||||
AZ_Assert(iterBool.second, "Memory address 0x%p is already allocated and in the records!", address);
|
||||
}
|
||||
|
||||
Debug::AllocationInfo& ai = iterBool.first->second;
|
||||
ai.m_byteSize = byteSize;
|
||||
ai.m_alignment = static_cast<unsigned int>(alignment);
|
||||
if ((m_saveNames || m_mode == RECORD_FULL) && name && fileName)
|
||||
{
|
||||
// In RECORD_FULL mode or when specifically enabled in app descriptor with
|
||||
// m_allocationRecordsSaveNames, we allocate our own memory to save off name and fileName.
|
||||
// When testing for memory leaks, on process shutdown AllocationRecords::~AllocationRecords
|
||||
// gets called to enumerate the remaining (leaked) allocations. Unfortunately, any names
|
||||
// referenced in dynamic module memory whose modules are unloaded won't be valid
|
||||
// references anymore and we won't get useful information from the enumeration print.
|
||||
// This code block ensures we keep our name/fileName valid for when we need it.
|
||||
const size_t nameLength = strlen(name);
|
||||
const size_t fileNameLength = strlen(fileName);
|
||||
const size_t totalLength = nameLength + fileNameLength + 2; // + 2 for terminating null characters
|
||||
ai.m_namesBlock = m_records.get_allocator().allocate(totalLength, 1);
|
||||
ai.m_namesBlockSize = totalLength;
|
||||
char* savedName = reinterpret_cast<char*>(ai.m_namesBlock);
|
||||
char* savedFileName = savedName + nameLength + 1;
|
||||
memcpy(reinterpret_cast<void*>(savedName), reinterpret_cast<const void*>(name), nameLength + 1);
|
||||
memcpy(reinterpret_cast<void*>(savedFileName), reinterpret_cast<const void*>(fileName), fileNameLength + 1);
|
||||
ai.m_name = savedName;
|
||||
ai.m_fileName = savedFileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
ai.m_name = name;
|
||||
ai.m_fileName = fileName;
|
||||
ai.m_namesBlock = nullptr;
|
||||
ai.m_namesBlockSize = 0;
|
||||
}
|
||||
ai.m_lineNum = lineNum;
|
||||
ai.m_timeStamp = AZStd::GetTimeNowMicroSecond();
|
||||
|
||||
// if we don't have a fileName,lineNum record the stack or if the user requested it.
|
||||
if ((fileName == nullptr && m_mode == RECORD_STACK_IF_NO_FILE_LINE) || m_mode == RECORD_FULL)
|
||||
{
|
||||
ai.m_stackFrames = m_numStackLevels ? reinterpret_cast<AZ::Debug::StackFrame*>(m_records.get_allocator().allocate(
|
||||
sizeof(AZ::Debug::StackFrame) * m_numStackLevels, 1))
|
||||
: nullptr;
|
||||
if (ai.m_stackFrames)
|
||||
{
|
||||
Debug::StackRecorder::Record(ai.m_stackFrames, m_numStackLevels, stackSuppressCount + 1);
|
||||
|
||||
if (m_decodeImmediately)
|
||||
{
|
||||
const unsigned char decodeStep = 40;
|
||||
Debug::SymbolStorage::StackLine lines[decodeStep];
|
||||
unsigned char iFrame = 0;
|
||||
unsigned char numStackLevels = m_numStackLevels;
|
||||
while (numStackLevels > 0)
|
||||
// OPTIONAL DEBUGGING CODE - enable in app descriptor m_allocationRecordsAttemptDecodeImmediately
|
||||
// This is optionally-enabled code for tracking down memory allocations
|
||||
// that fail to be decoded. DecodeFrames() typically runs at the end of
|
||||
// your application when leaks were found. Sometimes you have stack prints
|
||||
// full of "(module-name not available)" and "(function-name not available)"
|
||||
// that are not actionable. If you have those, enable this code. It'll slow
|
||||
// down your process significantly because for every allocation recorded
|
||||
// we get the stack trace on the spot. Put a breakpoint in DecodeFrames()
|
||||
// at the "(module-name not available)" and "(function-name not available)"
|
||||
// locations and now at the moment those allocations happen you'll have the
|
||||
// full stack trace available and the ability to debug what could be causing it
|
||||
{
|
||||
unsigned char numToDecode = AZStd::GetMin(decodeStep, numStackLevels);
|
||||
Debug::SymbolStorage::DecodeFrames(&ai.m_stackFrames[iFrame], numToDecode, lines);
|
||||
numStackLevels -= numToDecode;
|
||||
iFrame += numToDecode;
|
||||
const unsigned char decodeStep = 40;
|
||||
Debug::SymbolStorage::StackLine lines[decodeStep];
|
||||
unsigned char iFrame = 0;
|
||||
unsigned char numStackLevels = m_numStackLevels;
|
||||
while (numStackLevels > 0)
|
||||
{
|
||||
unsigned char numToDecode = AZStd::GetMin(decodeStep, numStackLevels);
|
||||
Debug::SymbolStorage::DecodeFrames(&ai.m_stackFrames[iFrame], numToDecode, lines);
|
||||
numStackLevels -= numToDecode;
|
||||
iFrame += numToDecode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AllocatorManager::Instance().DebugBreak(address, ai);
|
||||
|
||||
// statistics
|
||||
m_requestedBytes += byteSize;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
AllocatorManager::Instance().DebugBreak(address, ai);
|
||||
|
||||
// statistics
|
||||
m_requestedBytes += byteSize;
|
||||
|
||||
size_t currentRequestedBytePeak;
|
||||
size_t newRequestedBytePeak;
|
||||
do
|
||||
//=========================================================================
|
||||
// UnregisterAllocation
|
||||
// [9/11/2009]
|
||||
//=========================================================================
|
||||
void AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t alignment, AllocationInfo* info)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// UnregisterAllocation
|
||||
// [9/11/2009]
|
||||
//=========================================================================
|
||||
void AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t alignment, AllocationInfo* info)
|
||||
{
|
||||
if (m_mode == RECORD_NO_RECORDS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (address == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AllocationInfo allocationInfo;
|
||||
{
|
||||
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())
|
||||
if (m_mode == RECORD_NO_RECORDS)
|
||||
{
|
||||
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)
|
||||
if (address == nullptr)
|
||||
{
|
||||
m_records.rehash(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AllocatorManager::Instance().DebugBreak(address, allocationInfo);
|
||||
|
||||
(void)byteSize;
|
||||
(void)alignment;
|
||||
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 -= allocationInfo.m_byteSize;
|
||||
|
||||
#if defined(ENABLE_MEMORY_GUARD)
|
||||
// memory guard
|
||||
if (m_memoryGuardSize == sizeof(Debug::GuardValue))
|
||||
{
|
||||
if (m_isAutoIntegrityCheck)
|
||||
{
|
||||
// full integrity check
|
||||
IntegrityCheck();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check current allocation
|
||||
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, allocationInfo, m_numStackLevels);
|
||||
AZ_Assert(false, "MEMORY STOMP DETECTED!!!");
|
||||
}
|
||||
guard->~GuardValue();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// delete allocation record
|
||||
if (allocationInfo.m_namesBlock)
|
||||
{
|
||||
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 (allocationInfo.m_stackFrames)
|
||||
{
|
||||
m_records.get_allocator().deallocate(allocationInfo.m_stackFrames, sizeof(AZ::Debug::StackFrame)*m_numStackLevels, 1);
|
||||
allocationInfo.m_stackFrames = nullptr;
|
||||
}
|
||||
|
||||
if (info)
|
||||
{
|
||||
*info = allocationInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// if requested set memory to a specific value.
|
||||
if (m_isMarkUnallocatedMemory)
|
||||
{
|
||||
memset(address, GetUnallocatedMarkValue(), byteSize);
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ResizeAllocation
|
||||
// [9/20/2009]
|
||||
//=========================================================================
|
||||
void
|
||||
AllocationRecords::ResizeAllocation(void* address, size_t newSize)
|
||||
{
|
||||
if (m_mode == RECORD_NO_RECORDS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
if (m_isAutoIntegrityCheck)
|
||||
{
|
||||
// full integrity check
|
||||
IntegrityCheck();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check memory guard
|
||||
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);
|
||||
AZ_Assert(false, "MEMORY STOMP DETECTED!!!");
|
||||
}
|
||||
guard->~GuardValue();
|
||||
}
|
||||
// init the new memory guard
|
||||
newSize -= sizeof(Debug::GuardValue);
|
||||
new(reinterpret_cast<char*>(address)+newSize) Debug::GuardValue();
|
||||
}
|
||||
#endif
|
||||
|
||||
// statistics
|
||||
m_requestedBytes -= allocationInfo->m_byteSize;
|
||||
m_requestedBytes += newSize;
|
||||
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
|
||||
allocationInfo->m_byteSize = newSize;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// EnumerateAllocations
|
||||
// [9/29/2009]
|
||||
//=========================================================================
|
||||
void
|
||||
AllocationRecords::SetMode(Mode mode)
|
||||
{
|
||||
if (mode == RECORD_NO_RECORDS)
|
||||
{
|
||||
AllocationInfo allocationInfo;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_recordsMutex);
|
||||
m_records.clear();
|
||||
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);
|
||||
}
|
||||
}
|
||||
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!");
|
||||
AllocatorManager::Instance().DebugBreak(address, allocationInfo);
|
||||
|
||||
m_mode = mode;
|
||||
}
|
||||
(void)byteSize;
|
||||
(void)alignment;
|
||||
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!");
|
||||
|
||||
//=========================================================================
|
||||
// EnumerateAllocations
|
||||
// [9/29/2009]
|
||||
//=========================================================================
|
||||
void
|
||||
AllocationRecords::EnumerateAllocations(AllocationInfoCBType cb)
|
||||
{
|
||||
// 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.
|
||||
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))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// statistics
|
||||
m_requestedBytes -= allocationInfo.m_byteSize;
|
||||
|
||||
//=========================================================================
|
||||
// IntegrityCheck
|
||||
// [9/9/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
AllocationRecords::IntegrityCheck() const
|
||||
{
|
||||
#if defined(ENABLE_MEMORY_GUARD)
|
||||
if (m_memoryGuardSize == sizeof(Debug::GuardValue))
|
||||
// memory guard
|
||||
if (m_memoryGuardSize == sizeof(Debug::GuardValue))
|
||||
{
|
||||
if (m_isAutoIntegrityCheck)
|
||||
{
|
||||
// full integrity check
|
||||
IntegrityCheck();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check current allocation
|
||||
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, allocationInfo, m_numStackLevels);
|
||||
AZ_Assert(false, "MEMORY STOMP DETECTED!!!");
|
||||
}
|
||||
guard->~GuardValue();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// delete allocation record
|
||||
if (allocationInfo.m_namesBlock)
|
||||
{
|
||||
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 (allocationInfo.m_stackFrames)
|
||||
{
|
||||
m_records.get_allocator().deallocate(allocationInfo.m_stackFrames, sizeof(AZ::Debug::StackFrame) * m_numStackLevels, 1);
|
||||
allocationInfo.m_stackFrames = nullptr;
|
||||
}
|
||||
|
||||
if (info)
|
||||
{
|
||||
*info = allocationInfo;
|
||||
}
|
||||
|
||||
// if requested set memory to a specific value.
|
||||
if (m_isMarkUnallocatedMemory)
|
||||
{
|
||||
memset(address, GetUnallocatedMarkValue(), byteSize);
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ResizeAllocation
|
||||
// [9/20/2009]
|
||||
//=========================================================================
|
||||
void AllocationRecords::ResizeAllocation(void* address, size_t newSize)
|
||||
{
|
||||
if (m_mode == RECORD_NO_RECORDS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
if (m_isAutoIntegrityCheck)
|
||||
{
|
||||
// full integrity check
|
||||
IntegrityCheck();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check memory guard
|
||||
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);
|
||||
AZ_Assert(false, "MEMORY STOMP DETECTED!!!");
|
||||
}
|
||||
guard->~GuardValue();
|
||||
}
|
||||
// init the new memory guard
|
||||
newSize -= sizeof(Debug::GuardValue);
|
||||
new (reinterpret_cast<char*>(address) + newSize) Debug::GuardValue();
|
||||
}
|
||||
#endif
|
||||
|
||||
// statistics
|
||||
m_requestedBytes -= allocationInfo->m_byteSize;
|
||||
m_requestedBytes += newSize;
|
||||
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
|
||||
allocationInfo->m_byteSize = newSize;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// EnumerateAllocations
|
||||
// [9/29/2009]
|
||||
//=========================================================================
|
||||
void AllocationRecords::SetMode(Mode mode)
|
||||
{
|
||||
if (mode == RECORD_NO_RECORDS)
|
||||
{
|
||||
{
|
||||
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!");
|
||||
|
||||
m_mode = mode;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// EnumerateAllocations
|
||||
// [9/29/2009]
|
||||
//=========================================================================
|
||||
void AllocationRecords::EnumerateAllocations(AllocationInfoCBType cb)
|
||||
{
|
||||
// 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.
|
||||
Debug::AllocationRecordsType recordsCopy;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_recordsMutex);
|
||||
@@ -455,67 +438,93 @@ AllocationRecords::IntegrityCheck() const
|
||||
}
|
||||
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())
|
||||
if (!cb(iter->first, iter->second, m_numStackLevels))
|
||||
{
|
||||
// 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!!!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// operator()
|
||||
// [9/29/2009]
|
||||
//=========================================================================
|
||||
bool
|
||||
PrintAllocationsCB::operator()(void* address, const AllocationInfo& info, unsigned char numStackLevels)
|
||||
{
|
||||
if (m_includeNameAndFilename && info.m_name)
|
||||
//=========================================================================
|
||||
// IntegrityCheck
|
||||
// [9/9/2011]
|
||||
//=========================================================================
|
||||
void AllocationRecords::IntegrityCheck() const
|
||||
{
|
||||
AZ_Printf("Memory", "Allocation Name: \"%s\" Addr: 0%p Size: %d Alignment: %d\n", info.m_name, address, info.m_byteSize, info.m_alignment);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Printf("Memory", "Allocation Addr: 0%p Size: %d Alignment: %d\n", address, info.m_byteSize, info.m_alignment);
|
||||
}
|
||||
|
||||
if (m_isDetailed)
|
||||
{
|
||||
if (!info.m_stackFrames)
|
||||
#if defined(ENABLE_MEMORY_GUARD)
|
||||
if (m_memoryGuardSize == sizeof(Debug::GuardValue))
|
||||
{
|
||||
AZ_Printf("Memory", " %s (%d)\n", info.m_fileName, info.m_lineNum);
|
||||
Debug::AllocationRecordsType recordsCopy;
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// operator()
|
||||
// [9/29/2009]
|
||||
//=========================================================================
|
||||
bool PrintAllocationsCB::operator()(void* address, const AllocationInfo& info, unsigned char numStackLevels)
|
||||
{
|
||||
if (m_includeNameAndFilename && info.m_name)
|
||||
{
|
||||
AZ_Printf(
|
||||
"Memory", "Allocation Name: \"%s\" Addr: 0%p Size: %d Alignment: %d\n", info.m_name, address, info.m_byteSize,
|
||||
info.m_alignment);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allocation callstack
|
||||
const unsigned char decodeStep = 40;
|
||||
Debug::SymbolStorage::StackLine lines[decodeStep];
|
||||
unsigned char iFrame = 0;
|
||||
while (numStackLevels>0)
|
||||
AZ_Printf("Memory", "Allocation Addr: 0%p Size: %d Alignment: %d\n", address, info.m_byteSize, info.m_alignment);
|
||||
}
|
||||
|
||||
if (m_isDetailed)
|
||||
{
|
||||
if (!info.m_stackFrames)
|
||||
{
|
||||
unsigned char numToDecode = AZStd::GetMin(decodeStep, numStackLevels);
|
||||
Debug::SymbolStorage::DecodeFrames(&info.m_stackFrames[iFrame], numToDecode, lines);
|
||||
for (unsigned char i = 0; i < numToDecode; ++i)
|
||||
AZ_Printf("Memory", " %s (%d)\n", info.m_fileName, info.m_lineNum);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allocation callstack
|
||||
const unsigned char decodeStep = 40;
|
||||
Debug::SymbolStorage::StackLine lines[decodeStep];
|
||||
unsigned char iFrame = 0;
|
||||
while (numStackLevels > 0)
|
||||
{
|
||||
if (info.m_stackFrames[iFrame+i].IsValid())
|
||||
unsigned char numToDecode = AZStd::GetMin(decodeStep, numStackLevels);
|
||||
Debug::SymbolStorage::DecodeFrames(&info.m_stackFrames[iFrame], numToDecode, lines);
|
||||
for (unsigned char i = 0; i < numToDecode; ++i)
|
||||
{
|
||||
AZ_Printf("Memory", " %s\n", lines[i]);
|
||||
if (info.m_stackFrames[iFrame + i].IsValid())
|
||||
{
|
||||
AZ_Printf("Memory", " %s\n", lines[i]);
|
||||
}
|
||||
}
|
||||
numStackLevels -= numToDecode;
|
||||
iFrame += numToDecode;
|
||||
}
|
||||
numStackLevels -= numToDecode;
|
||||
iFrame += numToDecode;
|
||||
}
|
||||
}
|
||||
return true; // continue enumerating
|
||||
}
|
||||
return true; // continue enumerating
|
||||
}
|
||||
|
||||
} // namespace AZ::Debug
|
||||
|
||||
@@ -13,175 +13,172 @@
|
||||
|
||||
#include <AzCore/std/functional.h>
|
||||
|
||||
using namespace AZ;
|
||||
|
||||
//=========================================================================
|
||||
// BestFitExternalMapAllocator
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::BestFitExternalMapAllocator()
|
||||
: AllocatorBase(nullptr, "BestFitExternalMapAllocator", "Best fit allocator with external tracking storage!")
|
||||
{}
|
||||
|
||||
//=========================================================================
|
||||
// Create
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
bool
|
||||
BestFitExternalMapAllocator::Create(const Descriptor& desc)
|
||||
namespace AZ
|
||||
{
|
||||
AZ_Assert(IsReady() == false, "BestFitExternalMapAllocator was already created!");
|
||||
if (IsReady())
|
||||
//=========================================================================
|
||||
// BestFitExternalMapAllocator
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::BestFitExternalMapAllocator()
|
||||
: AllocatorBase(nullptr, "BestFitExternalMapAllocator", "Best fit allocator with external tracking storage!")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isReady = true;
|
||||
|
||||
m_desc = desc;
|
||||
BestFitExternalMapSchema::Descriptor schemaDesc;
|
||||
schemaDesc.m_mapAllocator = desc.m_mapAllocator;
|
||||
schemaDesc.m_memoryBlock = desc.m_memoryBlock;
|
||||
schemaDesc.m_memoryBlockByteSize = desc.m_memoryBlockByteSize;
|
||||
|
||||
m_schema = azcreate(BestFitExternalMapSchema, (schemaDesc), SystemAllocator);
|
||||
if (m_schema == nullptr)
|
||||
//=========================================================================
|
||||
// Create
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
bool BestFitExternalMapAllocator::Create(const Descriptor& desc)
|
||||
{
|
||||
isReady = false;
|
||||
AZ_Assert(IsReady() == false, "BestFitExternalMapAllocator was already created!");
|
||||
if (IsReady())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isReady = true;
|
||||
|
||||
m_desc = desc;
|
||||
BestFitExternalMapSchema::Descriptor schemaDesc;
|
||||
schemaDesc.m_mapAllocator = desc.m_mapAllocator;
|
||||
schemaDesc.m_memoryBlock = desc.m_memoryBlock;
|
||||
schemaDesc.m_memoryBlockByteSize = desc.m_memoryBlockByteSize;
|
||||
|
||||
m_schema = azcreate(BestFitExternalMapSchema, (schemaDesc), SystemAllocator);
|
||||
if (m_schema == nullptr)
|
||||
{
|
||||
isReady = false;
|
||||
}
|
||||
|
||||
return isReady;
|
||||
}
|
||||
|
||||
return isReady;
|
||||
}
|
||||
//=========================================================================
|
||||
// Destroy
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
void BestFitExternalMapAllocator::Destroy()
|
||||
{
|
||||
azdestroy(m_schema, SystemAllocator);
|
||||
m_schema = nullptr;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Destroy
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
BestFitExternalMapAllocator::Destroy()
|
||||
{
|
||||
azdestroy(m_schema, SystemAllocator);
|
||||
m_schema = nullptr;
|
||||
}
|
||||
AllocatorDebugConfig BestFitExternalMapAllocator::GetDebugConfig()
|
||||
{
|
||||
return AllocatorDebugConfig()
|
||||
.ExcludeFromDebugging(!m_desc.m_allocationRecords)
|
||||
.StackRecordLevels(m_desc.m_stackRecordLevels)
|
||||
.MarksUnallocatedMemory(false)
|
||||
.UsesMemoryGuards(false);
|
||||
}
|
||||
|
||||
AllocatorDebugConfig BestFitExternalMapAllocator::GetDebugConfig()
|
||||
{
|
||||
return AllocatorDebugConfig()
|
||||
.ExcludeFromDebugging(!m_desc.m_allocationRecords)
|
||||
.StackRecordLevels(m_desc.m_stackRecordLevels)
|
||||
.MarksUnallocatedMemory(false)
|
||||
.UsesMemoryGuards(false);
|
||||
}
|
||||
//=========================================================================
|
||||
// Allocate
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
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;
|
||||
|
||||
//=========================================================================
|
||||
// Allocate
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
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;
|
||||
AZ_Assert(byteSize > 0, "You can not allocate 0 bytes!");
|
||||
AZ_Assert((alignment & (alignment - 1)) == 0, "Alignment must be power of 2!");
|
||||
byteSize = MemorySizeAdjustedUp(byteSize);
|
||||
|
||||
AZ_Assert(byteSize > 0, "You can not allocate 0 bytes!");
|
||||
AZ_Assert((alignment & (alignment - 1)) == 0, "Alignment must be power of 2!");
|
||||
byteSize = MemorySizeAdjustedUp(byteSize);
|
||||
BestFitExternalMapAllocator::pointer_type address = m_schema->Allocate(byteSize, alignment, flags);
|
||||
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));
|
||||
|
||||
BestFitExternalMapAllocator::pointer_type address = m_schema->Allocate(byteSize, alignment, flags);
|
||||
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));
|
||||
return address;
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
//=========================================================================
|
||||
// DeAllocate
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
void BestFitExternalMapAllocator::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment)
|
||||
{
|
||||
byteSize = MemorySizeAdjustedUp(byteSize);
|
||||
AZ_MEMORY_PROFILE(ProfileDeallocation(ptr, byteSize, alignment, nullptr));
|
||||
|
||||
//=========================================================================
|
||||
// DeAllocate
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
BestFitExternalMapAllocator::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment)
|
||||
{
|
||||
byteSize = MemorySizeAdjustedUp(byteSize);
|
||||
AZ_MEMORY_PROFILE(ProfileDeallocation(ptr, byteSize, alignment, nullptr));
|
||||
(void)byteSize;
|
||||
(void)alignment;
|
||||
m_schema->DeAllocate(ptr);
|
||||
}
|
||||
|
||||
(void)byteSize;
|
||||
(void)alignment;
|
||||
m_schema->DeAllocate(ptr);
|
||||
}
|
||||
//=========================================================================
|
||||
// Resize
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::size_type BestFitExternalMapAllocator::Resize(pointer_type ptr, size_type newSize)
|
||||
{
|
||||
(void)ptr;
|
||||
(void)newSize;
|
||||
/* todo */
|
||||
return 0;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Resize
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::size_type
|
||||
BestFitExternalMapAllocator::Resize(pointer_type ptr, size_type newSize)
|
||||
{
|
||||
(void)ptr;
|
||||
(void)newSize;
|
||||
/* todo */
|
||||
return 0;
|
||||
}
|
||||
//=========================================================================
|
||||
// ReAllocate
|
||||
// [9/13/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::pointer_type BestFitExternalMapAllocator::ReAllocate(
|
||||
pointer_type ptr, size_type newSize, size_type newAlignment)
|
||||
{
|
||||
(void)ptr;
|
||||
(void)newSize;
|
||||
(void)newAlignment;
|
||||
AZ_Assert(false, "Not supported!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ReAllocate
|
||||
// [9/13/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::pointer_type
|
||||
BestFitExternalMapAllocator::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment)
|
||||
{
|
||||
(void)ptr;
|
||||
(void)newSize;
|
||||
(void)newAlignment;
|
||||
AZ_Assert(false, "Not supported!");
|
||||
return nullptr;
|
||||
}
|
||||
//=========================================================================
|
||||
// AllocationSize
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::size_type BestFitExternalMapAllocator::AllocationSize(pointer_type ptr)
|
||||
{
|
||||
return MemorySizeAdjustedDown(m_schema->AllocationSize(ptr));
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// AllocationSize
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::size_type
|
||||
BestFitExternalMapAllocator::AllocationSize(pointer_type ptr)
|
||||
{
|
||||
return MemorySizeAdjustedDown(m_schema->AllocationSize(ptr));
|
||||
}
|
||||
//=========================================================================
|
||||
// NumAllocatedBytes
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::size_type BestFitExternalMapAllocator::NumAllocatedBytes() const
|
||||
{
|
||||
return m_schema->NumAllocatedBytes();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// NumAllocatedBytes
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::size_type
|
||||
BestFitExternalMapAllocator::NumAllocatedBytes() const
|
||||
{
|
||||
return m_schema->NumAllocatedBytes();
|
||||
}
|
||||
//=========================================================================
|
||||
// Capacity
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::size_type BestFitExternalMapAllocator::Capacity() const
|
||||
{
|
||||
return m_schema->Capacity();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Capacity
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::size_type
|
||||
BestFitExternalMapAllocator::Capacity() const
|
||||
{
|
||||
return m_schema->Capacity();
|
||||
}
|
||||
//=========================================================================
|
||||
// GetMaxAllocationSize
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::size_type BestFitExternalMapAllocator::GetMaxAllocationSize() const
|
||||
{
|
||||
return m_schema->GetMaxAllocationSize();
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// GetMaxAllocationSize
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapAllocator::size_type
|
||||
BestFitExternalMapAllocator::GetMaxAllocationSize() const
|
||||
{
|
||||
return m_schema->GetMaxAllocationSize();
|
||||
}
|
||||
auto BestFitExternalMapAllocator::GetMaxContiguousAllocationSize() const -> size_type
|
||||
{
|
||||
return m_schema->GetMaxContiguousAllocationSize();
|
||||
}
|
||||
|
||||
auto BestFitExternalMapAllocator::GetMaxContiguousAllocationSize() const -> size_type
|
||||
{
|
||||
return m_schema->GetMaxContiguousAllocationSize();
|
||||
}
|
||||
} // namespace AZ
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZ_BEST_FIT_EXT_MAP_ALLOCATOR_H
|
||||
#define AZ_BEST_FIT_EXT_MAP_ALLOCATOR_H
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
|
||||
@@ -73,7 +72,3 @@ namespace AZ
|
||||
};
|
||||
}
|
||||
|
||||
#endif // AZ_BEST_FIT_EXT_MAP_ALLOCATOR_H
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
@@ -9,208 +9,217 @@
|
||||
#include <AzCore/Memory/BestFitExternalMapSchema.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
|
||||
using namespace AZ;
|
||||
|
||||
//=========================================================================
|
||||
// BestFitExternalMapSchema
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapSchema::BestFitExternalMapSchema(const Descriptor& desc)
|
||||
: m_desc(desc)
|
||||
, m_used(0)
|
||||
, m_freeChunksMap(FreeMapType::key_compare(), AZStdIAllocator(desc.m_mapAllocator != nullptr ? desc.m_mapAllocator : &AllocatorInstance<SystemAllocator>::Get()))
|
||||
, m_allocChunksMap(AllocMapType::hasher(), AllocMapType::key_eq(), AZStdIAllocator(desc.m_mapAllocator != nullptr ? desc.m_mapAllocator : &AllocatorInstance<SystemAllocator>::Get()))
|
||||
namespace AZ
|
||||
{
|
||||
if (m_desc.m_mapAllocator == nullptr)
|
||||
//=========================================================================
|
||||
// BestFitExternalMapSchema
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapSchema::BestFitExternalMapSchema(const Descriptor& desc)
|
||||
: m_desc(desc)
|
||||
, m_used(0)
|
||||
, m_freeChunksMap(
|
||||
FreeMapType::key_compare(),
|
||||
AZStdIAllocator(desc.m_mapAllocator != nullptr ? desc.m_mapAllocator : &AllocatorInstance<SystemAllocator>::Get()))
|
||||
, m_allocChunksMap(
|
||||
AllocMapType::hasher(),
|
||||
AllocMapType::key_eq(),
|
||||
AZStdIAllocator(desc.m_mapAllocator != nullptr ? desc.m_mapAllocator : &AllocatorInstance<SystemAllocator>::Get()))
|
||||
{
|
||||
m_desc.m_mapAllocator = &AllocatorInstance<SystemAllocator>::Get(); // used as our sub allocator
|
||||
}
|
||||
AZ_Assert(m_desc.m_memoryBlockByteSize > 0, "You must provide memory block size!");
|
||||
AZ_Assert(m_desc.m_memoryBlock != nullptr, "You must provide memory block allocated as you with!");
|
||||
//if( m_desc.m_memoryBlock == NULL) there is no point to automate this cause we need to flag this memory special, otherwise there is no point to use this allocator at all
|
||||
// m_desc.m_memoryBlock = azmalloc(SystemAllocator,m_desc.m_memoryBlockByteSize,16);
|
||||
m_freeChunksMap.insert(AZStd::make_pair(m_desc.m_memoryBlockByteSize, reinterpret_cast<char*>(m_desc.m_memoryBlock)));
|
||||
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Allocate
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapSchema::pointer_type
|
||||
BestFitExternalMapSchema::Allocate(size_type byteSize, size_type alignment, int flags, const char*, const char*, int, unsigned int)
|
||||
{
|
||||
(void)flags;
|
||||
char* address = nullptr;
|
||||
AZ_Assert(alignment > 0 && (alignment & (alignment - 1)) == 0, "Alignment must be >0 and power of 2!");
|
||||
for (int i = 0; i < 2; ++i) // max 2 attempts to allocate
|
||||
{
|
||||
FreeMapType::iterator iter = m_freeChunksMap.find(byteSize);
|
||||
size_t blockSize = 0;
|
||||
char* blockAddress = nullptr;
|
||||
size_t preAllocBlockSize = 0;
|
||||
while (iter != m_freeChunksMap.end())
|
||||
if (m_desc.m_mapAllocator == nullptr)
|
||||
{
|
||||
blockSize = iter->first;
|
||||
blockAddress = iter->second;
|
||||
char* alignedAddr = PointerAlignUp(blockAddress, alignment);
|
||||
preAllocBlockSize = alignedAddr - blockAddress;
|
||||
if (preAllocBlockSize + byteSize <= blockSize)
|
||||
{
|
||||
m_freeChunksMap.erase(iter); // we have our allocation
|
||||
m_used += byteSize;
|
||||
address = alignedAddr;
|
||||
m_allocChunksMap.insert(AZStd::make_pair(address, byteSize));
|
||||
break;
|
||||
}
|
||||
++iter;
|
||||
m_desc.m_mapAllocator = &AllocatorInstance<SystemAllocator>::Get(); // used as our sub allocator
|
||||
}
|
||||
if (address != nullptr)
|
||||
AZ_Assert(m_desc.m_memoryBlockByteSize > 0, "You must provide memory block size!");
|
||||
AZ_Assert(m_desc.m_memoryBlock != nullptr, "You must provide memory block allocated as you with!");
|
||||
// if( m_desc.m_memoryBlock == NULL) there is no point to automate this cause we need to flag this memory special, otherwise there
|
||||
// is no point to use this allocator at all
|
||||
// m_desc.m_memoryBlock = azmalloc(SystemAllocator,m_desc.m_memoryBlockByteSize,16);
|
||||
m_freeChunksMap.insert(AZStd::make_pair(m_desc.m_memoryBlockByteSize, reinterpret_cast<char*>(m_desc.m_memoryBlock)));
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Allocate
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapSchema::pointer_type BestFitExternalMapSchema::Allocate(
|
||||
size_type byteSize,
|
||||
size_type alignment,
|
||||
[[maybe_unused]] int flags,
|
||||
[[maybe_unused]] const char* name,
|
||||
[[maybe_unused]] const char* fileName,
|
||||
[[maybe_unused]] int lineNum,
|
||||
[[maybe_unused]] unsigned int suppressStackRecord)
|
||||
{
|
||||
char* address = nullptr;
|
||||
AZ_Assert(alignment > 0 && (alignment & (alignment - 1)) == 0, "Alignment must be >0 and power of 2!");
|
||||
for (int i = 0; i < 2; ++i) // max 2 attempts to allocate
|
||||
{
|
||||
// split blocks
|
||||
if (preAllocBlockSize) // if we have a block before the alignment
|
||||
FreeMapType::iterator iter = m_freeChunksMap.find(byteSize);
|
||||
size_t blockSize = 0;
|
||||
char* blockAddress = nullptr;
|
||||
size_t preAllocBlockSize = 0;
|
||||
while (iter != m_freeChunksMap.end())
|
||||
{
|
||||
m_freeChunksMap.insert(AZStd::make_pair(preAllocBlockSize, blockAddress));
|
||||
}
|
||||
size_t postAllocBlockSize = blockSize - preAllocBlockSize - byteSize;
|
||||
if (postAllocBlockSize)
|
||||
{
|
||||
m_freeChunksMap.insert(AZStd::make_pair(postAllocBlockSize, address + byteSize));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
GarbageCollect();
|
||||
}
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// DeAllocate
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
void BestFitExternalMapSchema::DeAllocate(pointer_type ptr, size_type, size_type)
|
||||
{
|
||||
if (ptr == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
AllocMapType::iterator iter = m_allocChunksMap.find(reinterpret_cast<char*>(ptr));
|
||||
if (iter != m_allocChunksMap.end())
|
||||
{
|
||||
m_used -= iter->second;
|
||||
m_freeChunksMap.insert(AZStd::make_pair(iter->second, iter->first));
|
||||
m_allocChunksMap.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// AllocationSize
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapSchema::size_type
|
||||
BestFitExternalMapSchema::AllocationSize(pointer_type ptr)
|
||||
{
|
||||
AllocMapType::iterator iter = m_allocChunksMap.find(reinterpret_cast<char*>(ptr));
|
||||
if (iter != m_allocChunksMap.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
BestFitExternalMapSchema::size_type
|
||||
BestFitExternalMapSchema::Resize(pointer_type, size_type)
|
||||
{
|
||||
AZ_Assert(false, AZ_FUNCTION_SIGNATURE " unsupported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
BestFitExternalMapSchema::pointer_type
|
||||
BestFitExternalMapSchema::ReAllocate(pointer_type, size_type, size_type)
|
||||
{
|
||||
AZ_Assert(false, AZ_FUNCTION_SIGNATURE " unsupported");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// GetMaxAllocationSize
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapSchema::size_type
|
||||
BestFitExternalMapSchema::GetMaxAllocationSize() const
|
||||
{
|
||||
if (!m_freeChunksMap.empty())
|
||||
{
|
||||
return m_freeChunksMap.rbegin()->first;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto BestFitExternalMapSchema::GetMaxContiguousAllocationSize() const -> size_type
|
||||
{
|
||||
// Return the maximum size of any single allocation
|
||||
return AZ_CORE_MAX_ALLOCATOR_SIZE;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// GarbageCollect
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
void
|
||||
BestFitExternalMapSchema::GarbageCollect()
|
||||
{
|
||||
for (FreeMapType::iterator curBlock = m_freeChunksMap.begin(); curBlock != m_freeChunksMap.end(); )
|
||||
{
|
||||
char* curStart = curBlock->second;
|
||||
char* curEnd = curStart + curBlock->first;
|
||||
bool isMerge = false;
|
||||
for (FreeMapType::iterator nextBlock = curBlock++; nextBlock != m_freeChunksMap.end(); )
|
||||
{
|
||||
char* nextStart = nextBlock->second;
|
||||
char* nextEnd = nextStart + nextBlock->first;
|
||||
if (curStart == nextEnd)
|
||||
{
|
||||
// merge
|
||||
size_t newBlockSize = curBlock->first + nextBlock->first;
|
||||
char* newBlockAddress = nextStart;
|
||||
m_freeChunksMap.erase(nextBlock);
|
||||
FreeMapType::iterator toErase = curBlock;
|
||||
++curBlock;
|
||||
m_freeChunksMap.erase(toErase);
|
||||
FreeMapType::iterator newBlock = m_freeChunksMap.insert(AZStd::make_pair(newBlockSize, newBlockAddress)).first;
|
||||
if (curBlock != m_freeChunksMap.end() && newBlockSize < curBlock->first) // if the newBlock in before the next in the list, update next in the list to current
|
||||
blockSize = iter->first;
|
||||
blockAddress = iter->second;
|
||||
char* alignedAddr = PointerAlignUp(blockAddress, alignment);
|
||||
preAllocBlockSize = alignedAddr - blockAddress;
|
||||
if (preAllocBlockSize + byteSize <= blockSize)
|
||||
{
|
||||
curBlock = newBlock;
|
||||
m_freeChunksMap.erase(iter); // we have our allocation
|
||||
m_used += byteSize;
|
||||
address = alignedAddr;
|
||||
m_allocChunksMap.insert(AZStd::make_pair(address, byteSize));
|
||||
break;
|
||||
}
|
||||
isMerge = true;
|
||||
break;
|
||||
++iter;
|
||||
}
|
||||
else if (curEnd == nextStart)
|
||||
if (address != nullptr)
|
||||
{
|
||||
// merge
|
||||
size_t newBlockSize = curBlock->first + nextBlock->first;
|
||||
char* newBlockAddress = curStart;
|
||||
m_freeChunksMap.erase(nextBlock);
|
||||
FreeMapType::iterator toErase = curBlock;
|
||||
++curBlock;
|
||||
m_freeChunksMap.erase(toErase);
|
||||
FreeMapType::iterator newBlock = m_freeChunksMap.insert(AZStd::make_pair(newBlockSize, newBlockAddress)).first;
|
||||
if (curBlock != m_freeChunksMap.end() && newBlockSize < curBlock->first) // if the newBlock in before the next in the list, update next in the list to current
|
||||
// split blocks
|
||||
if (preAllocBlockSize) // if we have a block before the alignment
|
||||
{
|
||||
curBlock = newBlock;
|
||||
m_freeChunksMap.insert(AZStd::make_pair(preAllocBlockSize, blockAddress));
|
||||
}
|
||||
isMerge = true;
|
||||
size_t postAllocBlockSize = blockSize - preAllocBlockSize - byteSize;
|
||||
if (postAllocBlockSize)
|
||||
{
|
||||
m_freeChunksMap.insert(AZStd::make_pair(postAllocBlockSize, address + byteSize));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
++nextBlock;
|
||||
else
|
||||
{
|
||||
GarbageCollect();
|
||||
}
|
||||
}
|
||||
if (!isMerge)
|
||||
return address;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// DeAllocate
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
void BestFitExternalMapSchema::DeAllocate(pointer_type ptr, [[maybe_unused]] size_type byteSize, [[maybe_unused]] size_type alignment)
|
||||
{
|
||||
if (ptr == nullptr)
|
||||
{
|
||||
++curBlock;
|
||||
return;
|
||||
}
|
||||
AllocMapType::iterator iter = m_allocChunksMap.find(reinterpret_cast<char*>(ptr));
|
||||
if (iter != m_allocChunksMap.end())
|
||||
{
|
||||
m_used -= iter->second;
|
||||
m_freeChunksMap.insert(AZStd::make_pair(iter->second, iter->first));
|
||||
m_allocChunksMap.erase(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// AllocationSize
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapSchema::size_type BestFitExternalMapSchema::AllocationSize(pointer_type ptr)
|
||||
{
|
||||
AllocMapType::iterator iter = m_allocChunksMap.find(reinterpret_cast<char*>(ptr));
|
||||
if (iter != m_allocChunksMap.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
BestFitExternalMapSchema::size_type BestFitExternalMapSchema::Resize(pointer_type, size_type)
|
||||
{
|
||||
AZ_Assert(false, AZ_FUNCTION_SIGNATURE " unsupported");
|
||||
return 0;
|
||||
}
|
||||
|
||||
BestFitExternalMapSchema::pointer_type BestFitExternalMapSchema::ReAllocate(pointer_type, size_type, size_type)
|
||||
{
|
||||
AZ_Assert(false, AZ_FUNCTION_SIGNATURE " unsupported");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// GetMaxAllocationSize
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
BestFitExternalMapSchema::size_type BestFitExternalMapSchema::GetMaxAllocationSize() const
|
||||
{
|
||||
if (!m_freeChunksMap.empty())
|
||||
{
|
||||
return m_freeChunksMap.rbegin()->first;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto BestFitExternalMapSchema::GetMaxContiguousAllocationSize() const -> size_type
|
||||
{
|
||||
// Return the maximum size of any single allocation
|
||||
return AZ_CORE_MAX_ALLOCATOR_SIZE;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// GarbageCollect
|
||||
// [1/28/2011]
|
||||
//=========================================================================
|
||||
void BestFitExternalMapSchema::GarbageCollect()
|
||||
{
|
||||
for (FreeMapType::iterator curBlock = m_freeChunksMap.begin(); curBlock != m_freeChunksMap.end();)
|
||||
{
|
||||
char* curStart = curBlock->second;
|
||||
char* curEnd = curStart + curBlock->first;
|
||||
bool isMerge = false;
|
||||
for (FreeMapType::iterator nextBlock = curBlock++; nextBlock != m_freeChunksMap.end();)
|
||||
{
|
||||
char* nextStart = nextBlock->second;
|
||||
char* nextEnd = nextStart + nextBlock->first;
|
||||
if (curStart == nextEnd)
|
||||
{
|
||||
// merge
|
||||
size_t newBlockSize = curBlock->first + nextBlock->first;
|
||||
char* newBlockAddress = nextStart;
|
||||
m_freeChunksMap.erase(nextBlock);
|
||||
FreeMapType::iterator toErase = curBlock;
|
||||
++curBlock;
|
||||
m_freeChunksMap.erase(toErase);
|
||||
FreeMapType::iterator newBlock = m_freeChunksMap.insert(AZStd::make_pair(newBlockSize, newBlockAddress)).first;
|
||||
// if the newBlock in before the next in the list, update next in the list to current
|
||||
if (curBlock != m_freeChunksMap.end() && newBlockSize < curBlock->first)
|
||||
{
|
||||
curBlock = newBlock;
|
||||
}
|
||||
isMerge = true;
|
||||
break;
|
||||
}
|
||||
else if (curEnd == nextStart)
|
||||
{
|
||||
// merge
|
||||
size_t newBlockSize = curBlock->first + nextBlock->first;
|
||||
char* newBlockAddress = curStart;
|
||||
m_freeChunksMap.erase(nextBlock);
|
||||
FreeMapType::iterator toErase = curBlock;
|
||||
++curBlock;
|
||||
m_freeChunksMap.erase(toErase);
|
||||
FreeMapType::iterator newBlock = m_freeChunksMap.insert(AZStd::make_pair(newBlockSize, newBlockAddress)).first;
|
||||
// if the newBlock in before the next in the list, update next in the list to current
|
||||
if (curBlock != m_freeChunksMap.end() && newBlockSize < curBlock->first)
|
||||
{
|
||||
curBlock = newBlock;
|
||||
}
|
||||
isMerge = true;
|
||||
break;
|
||||
}
|
||||
++nextBlock;
|
||||
}
|
||||
if (!isMerge)
|
||||
{
|
||||
++curBlock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AZ
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZ_BEST_FIT_EXT_MAP_ALLOCATION_SCHEME_H
|
||||
#define AZ_BEST_FIT_EXT_MAP_ALLOCATION_SCHEME_H
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
@@ -78,8 +77,3 @@ namespace AZ
|
||||
AllocMapType m_allocChunksMap;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // AZ_BEST_FIT_EXT_MAP_ALLOCATION_SCHEME_H
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZ_POOL_ALLOCATION_SCHEME_H
|
||||
#define AZ_POOL_ALLOCATION_SCHEME_H
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
|
||||
@@ -162,8 +161,3 @@ namespace AZ
|
||||
template<class Allocator>
|
||||
AZ_THREAD_LOCAL ThreadPoolData* ThreadPoolSchemaHelper<Allocator>::m_threadData = 0;
|
||||
}
|
||||
|
||||
#endif // AZ_POOL_ALLOCATION_SCHEME_H
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
#define AZCORE_SYSTEM_ALLOCATOR_MALLOC 2
|
||||
|
||||
#if !defined(AZCORE_SYSTEM_ALLOCATOR)
|
||||
// define the default
|
||||
#define AZCORE_SYSTEM_ALLOCATOR AZCORE_SYSTEM_ALLOCATOR_HPHA
|
||||
// define the default
|
||||
#define AZCORE_SYSTEM_ALLOCATOR AZCORE_SYSTEM_ALLOCATOR_HPHA
|
||||
#endif
|
||||
|
||||
#if AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_HPHA
|
||||
@@ -32,263 +32,269 @@
|
||||
#error "Invalid allocator selected for SystemAllocator"
|
||||
#endif
|
||||
|
||||
|
||||
using namespace AZ;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Globals - we use global storage for the first memory schema, since we can't use dynamic memory!
|
||||
static bool g_isSystemSchemaUsed = false;
|
||||
namespace AZ
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Globals - we use global storage for the first memory schema, since we can't use dynamic memory!
|
||||
static bool g_isSystemSchemaUsed = false;
|
||||
#if AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_HPHA
|
||||
static AZStd::aligned_storage<sizeof(HphaSchema), AZStd::alignment_of<HphaSchema>::value>::type g_systemSchema;
|
||||
#elif AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_MALLOC
|
||||
static AZStd::aligned_storage<sizeof(MallocSchema), AZStd::alignment_of<MallocSchema>::value>::type g_systemSchema;
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//=========================================================================
|
||||
// SystemAllocator
|
||||
// [9/2/2009]
|
||||
//=========================================================================
|
||||
SystemAllocator::SystemAllocator()
|
||||
: AllocatorBase(nullptr, "SystemAllocator", "Fundamental generic memory allocator")
|
||||
, m_isCustom(false)
|
||||
, m_ownsOSAllocator(false)
|
||||
{
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ~SystemAllocator
|
||||
//=========================================================================
|
||||
SystemAllocator::~SystemAllocator()
|
||||
{
|
||||
if (IsReady())
|
||||
//=========================================================================
|
||||
// SystemAllocator
|
||||
// [9/2/2009]
|
||||
//=========================================================================
|
||||
SystemAllocator::SystemAllocator()
|
||||
: AllocatorBase(nullptr, "SystemAllocator", "Fundamental generic memory allocator")
|
||||
, m_isCustom(false)
|
||||
, m_ownsOSAllocator(false)
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ~Create
|
||||
// [9/2/2009]
|
||||
//=========================================================================
|
||||
bool
|
||||
SystemAllocator::Create(const Descriptor& desc)
|
||||
{
|
||||
AZ_Assert(IsReady() == false, "System allocator was already created!");
|
||||
if (IsReady())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_desc = desc;
|
||||
|
||||
if (!AllocatorInstance<OSAllocator>::IsReady())
|
||||
//=========================================================================
|
||||
// ~SystemAllocator
|
||||
//=========================================================================
|
||||
SystemAllocator::~SystemAllocator()
|
||||
{
|
||||
m_ownsOSAllocator = true;
|
||||
AllocatorInstance<OSAllocator>::Create();
|
||||
}
|
||||
bool isReady = false;
|
||||
if (desc.m_custom)
|
||||
{
|
||||
m_isCustom = true;
|
||||
m_schema = desc.m_custom;
|
||||
isReady = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_isCustom = false;
|
||||
#if AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_HPHA
|
||||
HphaSchema::Descriptor heapDesc;
|
||||
heapDesc.m_pageSize = desc.m_heap.m_pageSize;
|
||||
heapDesc.m_poolPageSize = desc.m_heap.m_poolPageSize;
|
||||
AZ_Assert(desc.m_heap.m_numFixedMemoryBlocks <= 1, "We support max1 memory block at the moment!");
|
||||
if (desc.m_heap.m_numFixedMemoryBlocks > 0)
|
||||
if (IsReady())
|
||||
{
|
||||
heapDesc.m_fixedMemoryBlock = desc.m_heap.m_fixedMemoryBlocks[0];
|
||||
heapDesc.m_fixedMemoryBlockByteSize = desc.m_heap.m_fixedMemoryBlocksByteSize[0];
|
||||
Destroy();
|
||||
}
|
||||
heapDesc.m_subAllocator = desc.m_heap.m_subAllocator;
|
||||
heapDesc.m_isPoolAllocations = desc.m_heap.m_isPoolAllocations;
|
||||
// Fix SystemAllocator from growing in small chunks
|
||||
heapDesc.m_systemChunkSize = desc.m_heap.m_systemChunkSize;
|
||||
#elif AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_MALLOC
|
||||
MallocSchema::Descriptor heapDesc;
|
||||
#endif
|
||||
if (&AllocatorInstance<SystemAllocator>::Get() == this) // if we are the system allocator
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ~Create
|
||||
// [9/2/2009]
|
||||
//=========================================================================
|
||||
bool SystemAllocator::Create(const Descriptor& desc)
|
||||
{
|
||||
AZ_Assert(IsReady() == false, "System allocator was already created!");
|
||||
if (IsReady())
|
||||
{
|
||||
AZ_Assert(!g_isSystemSchemaUsed, "AZ::SystemAllocator MUST be created first! It's the source of all allocations!");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_desc = desc;
|
||||
|
||||
if (!AllocatorInstance<OSAllocator>::IsReady())
|
||||
{
|
||||
m_ownsOSAllocator = true;
|
||||
AllocatorInstance<OSAllocator>::Create();
|
||||
}
|
||||
bool isReady = false;
|
||||
if (desc.m_custom)
|
||||
{
|
||||
m_isCustom = true;
|
||||
m_schema = desc.m_custom;
|
||||
isReady = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_isCustom = false;
|
||||
#if AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_HPHA
|
||||
HphaSchema::Descriptor heapDesc;
|
||||
heapDesc.m_pageSize = desc.m_heap.m_pageSize;
|
||||
heapDesc.m_poolPageSize = desc.m_heap.m_poolPageSize;
|
||||
AZ_Assert(desc.m_heap.m_numFixedMemoryBlocks <= 1, "We support max1 memory block at the moment!");
|
||||
if (desc.m_heap.m_numFixedMemoryBlocks > 0)
|
||||
{
|
||||
heapDesc.m_fixedMemoryBlock = desc.m_heap.m_fixedMemoryBlocks[0];
|
||||
heapDesc.m_fixedMemoryBlockByteSize = desc.m_heap.m_fixedMemoryBlocksByteSize[0];
|
||||
}
|
||||
heapDesc.m_subAllocator = desc.m_heap.m_subAllocator;
|
||||
heapDesc.m_isPoolAllocations = desc.m_heap.m_isPoolAllocations;
|
||||
// Fix SystemAllocator from growing in small chunks
|
||||
heapDesc.m_systemChunkSize = desc.m_heap.m_systemChunkSize;
|
||||
#elif AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_MALLOC
|
||||
MallocSchema::Descriptor heapDesc;
|
||||
#endif
|
||||
if (&AllocatorInstance<SystemAllocator>::Get() == this) // if we are the system allocator
|
||||
{
|
||||
AZ_Assert(!g_isSystemSchemaUsed, "AZ::SystemAllocator MUST be created first! It's the source of all allocations!");
|
||||
|
||||
#if AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_HPHA
|
||||
m_schema = new (&g_systemSchema) HphaSchema(heapDesc);
|
||||
#elif AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_MALLOC
|
||||
m_schema = new (&g_systemSchema) MallocSchema(heapDesc);
|
||||
#endif
|
||||
g_isSystemSchemaUsed = true;
|
||||
isReady = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// this class should be inheriting from SystemAllocator
|
||||
AZ_Assert(AllocatorInstance<SystemAllocator>::IsReady(), "System allocator must be created before any other allocator! They allocate from it.");
|
||||
|
||||
#if AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_HPHA
|
||||
m_schema = azcreate(HphaSchema, (heapDesc), SystemAllocator);
|
||||
#elif AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_MALLOC
|
||||
m_schema = azcreate(MallocSchema, (heapDesc), SystemAllocator);
|
||||
#endif
|
||||
if (m_schema == nullptr)
|
||||
{
|
||||
isReady = false;
|
||||
g_isSystemSchemaUsed = true;
|
||||
isReady = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isReady = true;
|
||||
// this class should be inheriting from SystemAllocator
|
||||
AZ_Assert(
|
||||
AllocatorInstance<SystemAllocator>::IsReady(),
|
||||
"System allocator must be created before any other allocator! They allocate from it.");
|
||||
|
||||
#if AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_HPHA
|
||||
m_schema = azcreate(HphaSchema, (heapDesc), SystemAllocator);
|
||||
#elif AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_MALLOC
|
||||
m_schema = azcreate(MallocSchema, (heapDesc), SystemAllocator);
|
||||
#endif
|
||||
if (m_schema == nullptr)
|
||||
{
|
||||
isReady = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
isReady = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isReady;
|
||||
}
|
||||
|
||||
return isReady;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Allocate
|
||||
// [9/2/2009]
|
||||
//=========================================================================
|
||||
void
|
||||
SystemAllocator::Destroy()
|
||||
{
|
||||
if (g_isSystemSchemaUsed)
|
||||
//=========================================================================
|
||||
// Allocate
|
||||
// [9/2/2009]
|
||||
//=========================================================================
|
||||
void SystemAllocator::Destroy()
|
||||
{
|
||||
int dummy;
|
||||
(void)dummy;
|
||||
}
|
||||
|
||||
if (!m_isCustom)
|
||||
{
|
||||
if ((void*)m_schema == (void*)&g_systemSchema)
|
||||
if (g_isSystemSchemaUsed)
|
||||
{
|
||||
int dummy;
|
||||
(void)dummy;
|
||||
}
|
||||
|
||||
if (!m_isCustom)
|
||||
{
|
||||
if ((void*)m_schema == (void*)&g_systemSchema)
|
||||
{
|
||||
#if AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_HPHA
|
||||
static_cast<HphaSchema*>(m_schema)->~HphaSchema();
|
||||
static_cast<HphaSchema*>(m_schema)->~HphaSchema();
|
||||
#elif AZCORE_SYSTEM_ALLOCATOR == AZCORE_SYSTEM_ALLOCATOR_MALLOC
|
||||
static_cast<MallocSchema*>(m_schema)->~MallocSchema();
|
||||
static_cast<MallocSchema*>(m_schema)->~MallocSchema();
|
||||
#endif
|
||||
g_isSystemSchemaUsed = false;
|
||||
g_isSystemSchemaUsed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
azdestroy(m_schema);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (m_ownsOSAllocator)
|
||||
{
|
||||
azdestroy(m_schema);
|
||||
AllocatorInstance<OSAllocator>::Destroy();
|
||||
m_ownsOSAllocator = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_ownsOSAllocator)
|
||||
AllocatorDebugConfig SystemAllocator::GetDebugConfig()
|
||||
{
|
||||
AllocatorInstance<OSAllocator>::Destroy();
|
||||
m_ownsOSAllocator = false;
|
||||
}
|
||||
}
|
||||
|
||||
AllocatorDebugConfig SystemAllocator::GetDebugConfig()
|
||||
{
|
||||
return AllocatorDebugConfig()
|
||||
.StackRecordLevels(m_desc.m_stackRecordLevels)
|
||||
.UsesMemoryGuards(!m_isCustom)
|
||||
.MarksUnallocatedMemory(!m_isCustom)
|
||||
.ExcludeFromDebugging(!m_desc.m_allocationRecords);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Allocate
|
||||
// [9/2/2009]
|
||||
//=========================================================================
|
||||
SystemAllocator::pointer_type
|
||||
SystemAllocator::Allocate(size_type byteSize, size_type alignment, int flags, const char* name, const char* fileName, int lineNum, unsigned int suppressStackRecord)
|
||||
{
|
||||
if (byteSize == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
AZ_Assert(byteSize > 0, "You can not allocate 0 bytes!");
|
||||
AZ_Assert((alignment & (alignment - 1)) == 0, "Alignment must be power of 2!");
|
||||
|
||||
byteSize = MemorySizeAdjustedUp(byteSize);
|
||||
SystemAllocator::pointer_type address = m_schema->Allocate(byteSize, alignment, flags, name, fileName, lineNum, suppressStackRecord + 1);
|
||||
|
||||
if (address == nullptr)
|
||||
{
|
||||
// Free all memory we can and try again!
|
||||
AllocatorManager::Instance().GarbageCollect();
|
||||
|
||||
address = m_schema->Allocate(byteSize, alignment, flags, name, fileName, lineNum, suppressStackRecord + 1);
|
||||
return AllocatorDebugConfig()
|
||||
.StackRecordLevels(m_desc.m_stackRecordLevels)
|
||||
.UsesMemoryGuards(!m_isCustom)
|
||||
.MarksUnallocatedMemory(!m_isCustom)
|
||||
.ExcludeFromDebugging(!m_desc.m_allocationRecords);
|
||||
}
|
||||
|
||||
if (address == nullptr)
|
||||
//=========================================================================
|
||||
// Allocate
|
||||
// [9/2/2009]
|
||||
//=========================================================================
|
||||
SystemAllocator::pointer_type SystemAllocator::Allocate(
|
||||
size_type byteSize,
|
||||
size_type alignment,
|
||||
int flags,
|
||||
const char* name,
|
||||
const char* fileName,
|
||||
int lineNum,
|
||||
unsigned int suppressStackRecord)
|
||||
{
|
||||
byteSize = MemorySizeAdjustedDown(byteSize); // restore original size
|
||||
if (byteSize == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
AZ_Assert(byteSize > 0, "You can not allocate 0 bytes!");
|
||||
AZ_Assert((alignment & (alignment - 1)) == 0, "Alignment must be power of 2!");
|
||||
|
||||
byteSize = MemorySizeAdjustedUp(byteSize);
|
||||
SystemAllocator::pointer_type address =
|
||||
m_schema->Allocate(byteSize, alignment, flags, name, fileName, lineNum, suppressStackRecord + 1);
|
||||
|
||||
if (address == nullptr)
|
||||
{
|
||||
// Free all memory we can and try again!
|
||||
AllocatorManager::Instance().GarbageCollect();
|
||||
|
||||
address = m_schema->Allocate(byteSize, alignment, flags, name, fileName, lineNum, suppressStackRecord + 1);
|
||||
}
|
||||
|
||||
if (address == nullptr)
|
||||
{
|
||||
byteSize = MemorySizeAdjustedDown(byteSize); // restore original size
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
AZ_PROFILE_MEMORY_ALLOC_EX(MemoryReserved, fileName, lineNum, address, byteSize, name);
|
||||
AZ_MEMORY_PROFILE(ProfileAllocation(address, byteSize, alignment, name, fileName, lineNum, suppressStackRecord + 1));
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
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);
|
||||
//=========================================================================
|
||||
// DeAllocate
|
||||
// [9/2/2009]
|
||||
//=========================================================================
|
||||
void SystemAllocator::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment)
|
||||
{
|
||||
byteSize = MemorySizeAdjustedUp(byteSize);
|
||||
AZ_PROFILE_MEMORY_FREE(MemoryReserved, ptr);
|
||||
AZ_MEMORY_PROFILE(ProfileDeallocation(ptr, byteSize, alignment, nullptr));
|
||||
m_schema->DeAllocate(ptr, byteSize, alignment);
|
||||
}
|
||||
|
||||
AZ_PROFILE_MEMORY_ALLOC_EX(MemoryReserved, fileName, lineNum, address, byteSize, name);
|
||||
AZ_MEMORY_PROFILE(ProfileAllocation(address, byteSize, alignment, name, fileName, lineNum, suppressStackRecord + 1));
|
||||
//=========================================================================
|
||||
// ReAllocate
|
||||
// [9/13/2011]
|
||||
//=========================================================================
|
||||
SystemAllocator::pointer_type SystemAllocator::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment)
|
||||
{
|
||||
newSize = MemorySizeAdjustedUp(newSize);
|
||||
|
||||
return address;
|
||||
}
|
||||
AZ_MEMORY_PROFILE(ProfileReallocationBegin(ptr, newSize));
|
||||
AZ_PROFILE_MEMORY_FREE(MemoryReserved, ptr);
|
||||
pointer_type newAddress = m_schema->ReAllocate(ptr, newSize, newAlignment);
|
||||
AZ_PROFILE_MEMORY_ALLOC(MemoryReserved, newAddress, newSize, "SystemAllocator realloc");
|
||||
AZ_MEMORY_PROFILE(ProfileReallocationEnd(ptr, newAddress, newSize, newAlignment));
|
||||
|
||||
//=========================================================================
|
||||
// DeAllocate
|
||||
// [9/2/2009]
|
||||
//=========================================================================
|
||||
void
|
||||
SystemAllocator::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment)
|
||||
{
|
||||
byteSize = MemorySizeAdjustedUp(byteSize);
|
||||
AZ_PROFILE_MEMORY_FREE(MemoryReserved, ptr);
|
||||
AZ_MEMORY_PROFILE(ProfileDeallocation(ptr, byteSize, alignment, nullptr));
|
||||
m_schema->DeAllocate(ptr, byteSize, alignment);
|
||||
}
|
||||
return newAddress;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ReAllocate
|
||||
// [9/13/2011]
|
||||
//=========================================================================
|
||||
SystemAllocator::pointer_type
|
||||
SystemAllocator::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment)
|
||||
{
|
||||
newSize = MemorySizeAdjustedUp(newSize);
|
||||
//=========================================================================
|
||||
// Resize
|
||||
// [8/12/2011]
|
||||
//=========================================================================
|
||||
SystemAllocator::size_type SystemAllocator::Resize(pointer_type ptr, size_type newSize)
|
||||
{
|
||||
newSize = MemorySizeAdjustedUp(newSize);
|
||||
size_type resizedSize = m_schema->Resize(ptr, newSize);
|
||||
|
||||
AZ_MEMORY_PROFILE(ProfileReallocationBegin(ptr, newSize));
|
||||
AZ_PROFILE_MEMORY_FREE(MemoryReserved, ptr);
|
||||
pointer_type newAddress = m_schema->ReAllocate(ptr, newSize, newAlignment);
|
||||
AZ_PROFILE_MEMORY_ALLOC(MemoryReserved, newAddress, newSize, "SystemAllocator realloc");
|
||||
AZ_MEMORY_PROFILE(ProfileReallocationEnd(ptr, newAddress, newSize, newAlignment));
|
||||
AZ_MEMORY_PROFILE(ProfileResize(ptr, resizedSize));
|
||||
|
||||
return newAddress;
|
||||
}
|
||||
return MemorySizeAdjustedDown(resizedSize);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Resize
|
||||
// [8/12/2011]
|
||||
//=========================================================================
|
||||
SystemAllocator::size_type
|
||||
SystemAllocator::Resize(pointer_type ptr, size_type newSize)
|
||||
{
|
||||
newSize = MemorySizeAdjustedUp(newSize);
|
||||
size_type resizedSize = m_schema->Resize(ptr, newSize);
|
||||
|
||||
AZ_MEMORY_PROFILE(ProfileResize(ptr, resizedSize));
|
||||
|
||||
return MemorySizeAdjustedDown(resizedSize);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
//
|
||||
// [8/12/2011]
|
||||
//=========================================================================
|
||||
SystemAllocator::size_type
|
||||
SystemAllocator::AllocationSize(pointer_type ptr)
|
||||
{
|
||||
//=========================================================================
|
||||
//
|
||||
// [8/12/2011]
|
||||
//=========================================================================
|
||||
SystemAllocator::size_type SystemAllocator::AllocationSize(pointer_type ptr)
|
||||
{
|
||||
size_type allocSize = MemorySizeAdjustedDown(m_schema->AllocationSize(ptr));
|
||||
|
||||
return allocSize;
|
||||
}
|
||||
return allocSize;
|
||||
}
|
||||
|
||||
} // namespace AZ
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_SYS_ALLOCATOR_H
|
||||
#define AZCORE_SYS_ALLOCATOR_H
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
|
||||
@@ -116,7 +115,5 @@ namespace AZ
|
||||
};
|
||||
}
|
||||
|
||||
#endif // AZCORE_SYS_ALLOCATOR_H
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
@@ -1424,7 +1424,8 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
using namespace AZ;
|
||||
namespace AZ
|
||||
{
|
||||
|
||||
#ifndef AZ_USE_CUSTOM_SCRIPT_BIND
|
||||
|
||||
@@ -2254,6 +2255,7 @@ LUA_API const Node* lua_getDummyNode()
|
||||
}
|
||||
|
||||
#endif // AZ_USE_CUSTOM_SCRIPT_BIND
|
||||
} // namespace AZ
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -5825,7 +5827,6 @@ LUA_API const Node* lua_getDummyNode()
|
||||
AllocatorWrapper<Internal::LuaSystemAllocator> m_luaAllocator;
|
||||
AZStd::thread::id m_ownerThreadId; // Check if Lua methods (including EBus handlers) are called from background threads.
|
||||
};
|
||||
} // namespace AZ
|
||||
|
||||
ScriptContext::ScriptContext(ScriptContextId id, IAllocator* allocator, lua_State* nativeContext)
|
||||
{
|
||||
@@ -6116,5 +6117,6 @@ LUA_API const Node* lua_getDummyNode()
|
||||
{
|
||||
return m_impl->ConstructScriptProperty(sdc, valueIndex, name, restrictToPropertyArrays);
|
||||
}
|
||||
} // namespace AZ
|
||||
|
||||
#undef AZ_DBG_NAME_FIXER
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_SCRIPT_CONTEXT_H
|
||||
#define AZCORE_SCRIPT_CONTEXT_H
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/std/function/function_fwd.h>
|
||||
@@ -1032,4 +1031,3 @@ namespace AZ
|
||||
}
|
||||
|
||||
} // namespace AZ
|
||||
#endif // AZCORE_SCRIPT_CONTEXT_H
|
||||
|
||||
@@ -25,10 +25,8 @@ extern "C" {
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
|
||||
void LuaHook(lua_State* l, lua_Debug* ar);
|
||||
}
|
||||
|
||||
using namespace AZ;
|
||||
|
||||
/**
|
||||
* A temp class that will override the current script context error handler and store the error (without any messages)
|
||||
@@ -105,6 +103,8 @@ void ScriptContextDebug::ConnectHook()
|
||||
void ScriptContextDebug::DisconnectHook()
|
||||
{
|
||||
lua_sethook(m_context.NativeContext(), nullptr, 0, 0);
|
||||
m_currentStackLevel = -1;
|
||||
m_stepStackLevel = -1;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -597,7 +597,7 @@ static ScriptContextDebug::BreakpointId MakeBreakpointId(const char* sourceName,
|
||||
// LuaHook
|
||||
// [6/28/2012]
|
||||
//=========================================================================
|
||||
void AZ::LuaHook(lua_State* l, lua_Debug* ar)
|
||||
void LuaHook(lua_State* l, lua_Debug* ar)
|
||||
{
|
||||
// Read contexts
|
||||
lua_rawgeti(l, LUA_REGISTRYINDEX, AZ_LUA_SCRIPT_CONTEXT_REF);
|
||||
@@ -651,6 +651,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 +736,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);
|
||||
@@ -1536,4 +1541,6 @@ ScriptContextDebug::SetValue(const DebugValue& sourceValue)
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AZ
|
||||
|
||||
#endif // #if !defined(AZCORE_EXCLUDE_LUA)
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_SCRIPT_CONTEXT_DEBUG_H
|
||||
#define AZCORE_SCRIPT_CONTEXT_DEBUG_H
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Script/ScriptContext.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
@@ -213,6 +212,3 @@ namespace AZ
|
||||
ScriptContext& m_context;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // AZCORE_SCRIPT_CONTEXT_DEBUG_H
|
||||
#pragma once
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
|
||||
using namespace AZ;
|
||||
namespace AZ
|
||||
{
|
||||
|
||||
/**
|
||||
* Script lifecycle:
|
||||
@@ -44,8 +45,7 @@ using namespace AZ;
|
||||
* If the script was loaded by a ScriptComponent, Load will be called once reload is complete.
|
||||
*/
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace LocalTU_ScriptSystemComponent {
|
||||
// Called when a module has already been loaded
|
||||
static int LuaRequireLoadedModule(lua_State* l)
|
||||
{
|
||||
@@ -54,8 +54,10 @@ namespace
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// ScriptSystemComponent
|
||||
// [5/29/2012]
|
||||
@@ -479,7 +481,7 @@ int ScriptSystemComponent::DefaultRequireHook(lua_State* lua, ScriptContext* con
|
||||
scriptIt->second.m_scriptNames.emplace(module);
|
||||
// Push the value to a closure that will just return it
|
||||
lua_rawgeti(lua, LUA_REGISTRYINDEX, scriptIt->second.m_tableReference);
|
||||
lua_pushcclosure(lua, LuaRequireLoadedModule, 1);
|
||||
lua_pushcclosure(lua, LocalTU_ScriptSystemComponent::LuaRequireLoadedModule, 1);
|
||||
|
||||
// If asset reference already populated, just return now. Otherwise, capture reference
|
||||
if (scriptIt->second.m_scriptAsset.GetId().IsValid())
|
||||
@@ -519,7 +521,7 @@ int ScriptSystemComponent::DefaultRequireHook(lua_State* lua, ScriptContext* con
|
||||
}
|
||||
|
||||
// Push function returning the result
|
||||
lua_pushcclosure(lua, LuaRequireLoadedModule, 1);
|
||||
lua_pushcclosure(lua, LocalTU_ScriptSystemComponent::LuaRequireLoadedModule, 1);
|
||||
|
||||
// Set asset reference on the loaded script
|
||||
scriptIt = container->m_loadedScripts.find(scriptId.m_guid);
|
||||
@@ -565,7 +567,7 @@ int ScriptSystemComponent::InMemoryRequireHook(lua_State* lua, ScriptContext* co
|
||||
scriptIt->second.m_scriptNames.emplace(module);
|
||||
// Push the value to a closure that will just return it
|
||||
lua_rawgeti(lua, LUA_REGISTRYINDEX, scriptIt->second.m_tableReference);
|
||||
lua_pushcclosure(lua, LuaRequireLoadedModule, 1);
|
||||
lua_pushcclosure(lua, LocalTU_ScriptSystemComponent::LuaRequireLoadedModule, 1);
|
||||
|
||||
// If asset reference already populated, just return now. Otherwise, capture reference
|
||||
if (scriptIt->second.m_scriptAsset.GetId().IsValid())
|
||||
@@ -591,7 +593,7 @@ int ScriptSystemComponent::InMemoryRequireHook(lua_State* lua, ScriptContext* co
|
||||
}
|
||||
|
||||
// Push function returning the result
|
||||
lua_pushcclosure(lua, LuaRequireLoadedModule, 1);
|
||||
lua_pushcclosure(lua, LocalTU_ScriptSystemComponent::LuaRequireLoadedModule, 1);
|
||||
|
||||
// Set asset reference on the loaded script
|
||||
scriptIt = container->m_loadedScripts.find(scriptId.m_guid);
|
||||
@@ -996,4 +998,5 @@ void ScriptSystemComponent::Reflect(ReflectContext* reflection)
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AZ
|
||||
#endif // #if !defined(AZCORE_EXCLUDE_LUA)
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_SCRIPT_SYSTEM_COMPONENT_H
|
||||
#define AZCORE_SCRIPT_SYSTEM_COMPONENT_H
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
@@ -182,6 +181,3 @@ namespace AZ
|
||||
void OnAssetReloaded(Data::Asset<Data::AssetData> asset) override;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // AZCORE_SCRIPT_SYSTEM_COMPONENT_H
|
||||
#pragma once
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace AZ
|
||||
{
|
||||
// Not using InsertTypeId here to avoid needing to create the temporary value and swap it in that call.
|
||||
node.AddMember(rapidjson::StringRef(JsonSerialization::TypeIdFieldIdentifier),
|
||||
StoreTypeName(classData, context), context.GetJsonAllocator());
|
||||
StoreTypeName(classData, classData.m_typeId, context), context.GetJsonAllocator());
|
||||
result = ResultCode(Tasks::WriteValue, Outcomes::Success);
|
||||
}
|
||||
return result.Combine(StoreClass(node, object, defaultObject, classData, context));
|
||||
@@ -531,7 +531,7 @@ namespace AZ
|
||||
return ResolvePointerResult::ContinueProcessing;
|
||||
}
|
||||
|
||||
rapidjson::Value JsonSerializer::StoreTypeName(const SerializeContext::ClassData& classData, JsonSerializerContext& context)
|
||||
rapidjson::Value JsonSerializer::StoreTypeName(const SerializeContext::ClassData& classData, const Uuid& typeId, JsonSerializerContext& context)
|
||||
{
|
||||
rapidjson::Value result;
|
||||
AZStd::vector<Uuid> ids = context.GetSerializeContext()->FindClassId(Crc32(classData.m_name));
|
||||
@@ -544,7 +544,7 @@ namespace AZ
|
||||
// Only write the Uuid for the class if there are multiple classes sharing the same name.
|
||||
// In this case it wouldn't be enough to determine which class needs to be used. The
|
||||
// class name is still added as a comment for be friendlier for users to read.
|
||||
AZStd::string fullName = classData.m_typeId.ToString<AZStd::string>();
|
||||
AZStd::string fullName = typeId.ToString<AZStd::string>();
|
||||
fullName += ' ';
|
||||
fullName += classData.m_name;
|
||||
result.SetString(fullName.c_str(), aznumeric_caster(fullName.size()), context.GetJsonAllocator());
|
||||
@@ -560,7 +560,7 @@ namespace AZ
|
||||
const SerializeContext::ClassData* data = context.GetSerializeContext()->FindClassData(typeId);
|
||||
if (data)
|
||||
{
|
||||
output = JsonSerializer::StoreTypeName(*data, context);
|
||||
output = JsonSerializer::StoreTypeName(*data, typeId, context);
|
||||
return context.Report(Tasks::WriteValue, Outcomes::Success, "Type id successfully stored to json value.");
|
||||
}
|
||||
else
|
||||
@@ -580,7 +580,7 @@ namespace AZ
|
||||
{
|
||||
rapidjson::Value insertedObject(rapidjson::kObjectType);
|
||||
insertedObject.AddMember(
|
||||
rapidjson::StringRef(JsonSerialization::TypeIdFieldIdentifier), StoreTypeName(classData, context),
|
||||
rapidjson::StringRef(JsonSerialization::TypeIdFieldIdentifier), StoreTypeName(classData, classData.m_typeId, context),
|
||||
context.GetJsonAllocator());
|
||||
|
||||
for (auto& element : output.GetObject())
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace AZ
|
||||
const void*& object, const void*& defaultObject, AZStd::any& defaultObjectStorage,
|
||||
const SerializeContext::ClassData*& elementClassData, const AZ::IRttiHelper& rtti, JsonSerializerContext& context);
|
||||
|
||||
static rapidjson::Value StoreTypeName(const SerializeContext::ClassData& classData, JsonSerializerContext& context);
|
||||
static rapidjson::Value StoreTypeName(const SerializeContext::ClassData& classData, const Uuid& typeId, JsonSerializerContext& context);
|
||||
static JsonSerializationResult::ResultCode StoreTypeName(rapidjson::Value& output,
|
||||
const Uuid& typeId, JsonSerializerContext& context);
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/StackedString.h>
|
||||
#include <AzCore/Serialization/Json/PathSerializer.h>
|
||||
#include <AzCore/std/containers/array.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
|
||||
namespace AZ::JsonPathSerializerInternal
|
||||
{
|
||||
template<typename PathType>
|
||||
static JsonSerializationResult::Result Load(PathType* pathValue, const rapidjson::Value& inputValue,
|
||||
JsonDeserializerContext& context)
|
||||
{
|
||||
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
|
||||
|
||||
AZ_Assert(pathValue, "Expected a valid pointer to load from json value.");
|
||||
|
||||
switch (inputValue.GetType())
|
||||
{
|
||||
case rapidjson::kArrayType:
|
||||
case rapidjson::kObjectType:
|
||||
case rapidjson::kFalseType:
|
||||
case rapidjson::kTrueType:
|
||||
case rapidjson::kNumberType:
|
||||
[[fallthrough]];
|
||||
case rapidjson::kNullType:
|
||||
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
|
||||
"Unsupported type. String values can't be read from arrays, objects or null.");
|
||||
case rapidjson::kStringType:
|
||||
{
|
||||
size_t pathLength = inputValue.GetStringLength();
|
||||
if (pathLength <= pathValue->Native().max_size())
|
||||
{
|
||||
*pathValue = PathType(AZStd::string_view(inputValue.GetString(), pathLength)).LexicallyNormal();
|
||||
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Success, "Successfully read path.");
|
||||
}
|
||||
using UuidString = AZStd::fixed_string<AZ::Uuid::MaxStringBuffer>;
|
||||
using ErrorString = AZStd::fixed_string<256>;
|
||||
return context.Report(JsonSerializationResult::Tasks::ReadField, JSR::Outcomes::Invalid,
|
||||
ErrorString::format("Json string value is too large to fit within path type %s. It needs to be less than %zu code points",
|
||||
azrtti_typeid<PathType>().template ToString<UuidString>().c_str(), pathValue->Native().max_size()));
|
||||
}
|
||||
default:
|
||||
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unknown, "Unknown json type encountered for string value.");
|
||||
}
|
||||
}
|
||||
template<typename PathType>
|
||||
static JsonSerializationResult::Result StoreWithDefault(rapidjson::Value& outputValue, const PathType* pathValue,
|
||||
const PathType* defaultPathValue, JsonSerializerContext& context)
|
||||
{
|
||||
namespace JSR = JsonSerializationResult; // Removes name conflicts in AzCore in uber builds.
|
||||
|
||||
if (context.ShouldKeepDefaults() || defaultPathValue == nullptr || *pathValue != *defaultPathValue)
|
||||
{
|
||||
auto posixPathString = pathValue->AsPosix();
|
||||
outputValue.SetString(posixPathString.c_str(), aznumeric_caster(posixPathString.size()), context.GetJsonAllocator());
|
||||
return context.Report(JSR::Tasks::WriteValue, JSR::Outcomes::Success, "Path successfully stored.");
|
||||
}
|
||||
|
||||
return context.Report(JSR::Tasks::WriteValue, JSR::Outcomes::DefaultsUsed, "Default Path used.");
|
||||
}
|
||||
}
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR_IMPL(JsonPathSerializer, SystemAllocator, 0);
|
||||
|
||||
JsonSerializationResult::Result JsonPathSerializer::Load(void* outputValue, const Uuid& outputValueTypeId,
|
||||
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
|
||||
{
|
||||
if (outputValueTypeId == azrtti_typeid<AZ::IO::Path>())
|
||||
{
|
||||
return JsonPathSerializerInternal::Load(reinterpret_cast<AZ::IO::Path*>(outputValue), inputValue,
|
||||
context);
|
||||
}
|
||||
else if (outputValueTypeId == azrtti_typeid<AZ::IO::FixedMaxPath>())
|
||||
{
|
||||
return JsonPathSerializerInternal::Load(reinterpret_cast<AZ::IO::FixedMaxPath*>(outputValue), inputValue,
|
||||
context);
|
||||
}
|
||||
|
||||
using UuidString = AZStd::fixed_string<AZ::Uuid::MaxStringBuffer>;
|
||||
auto errorTypeIdString = outputValueTypeId.ToString<UuidString>();
|
||||
AZ_Assert(false, "Unable to serialize json string"
|
||||
" to a path of type %s", errorTypeIdString.c_str());
|
||||
|
||||
using ErrorString = AZStd::fixed_string<256>;
|
||||
return context.Report(JsonSerializationResult::Tasks::ReadField, JsonSerializationResult::Outcomes::TypeMismatch,
|
||||
ErrorString::format("Output value type ID %s is not a valid Path type", errorTypeIdString.c_str()));
|
||||
}
|
||||
|
||||
JsonSerializationResult::Result JsonPathSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
|
||||
const void* defaultValue, const Uuid& valueTypeId, JsonSerializerContext& context)
|
||||
{
|
||||
if (valueTypeId == azrtti_typeid<AZ::IO::Path>())
|
||||
{
|
||||
return JsonPathSerializerInternal::StoreWithDefault(outputValue,
|
||||
reinterpret_cast<const AZ::IO::Path*>(inputValue),
|
||||
reinterpret_cast<const AZ::IO::Path*>(defaultValue), context);
|
||||
}
|
||||
else if (valueTypeId == azrtti_typeid<AZ::IO::FixedMaxPath>())
|
||||
{
|
||||
return JsonPathSerializerInternal::StoreWithDefault(outputValue,
|
||||
reinterpret_cast<const AZ::IO::FixedMaxPath*>(inputValue),
|
||||
reinterpret_cast<const AZ::IO::FixedMaxPath*>(defaultValue), context);
|
||||
}
|
||||
|
||||
using UuidString = AZStd::fixed_string<AZ::Uuid::MaxStringBuffer>;
|
||||
auto errorTypeIdString = valueTypeId.ToString<UuidString>();
|
||||
AZ_Assert(false, "Unable to serialize path type %s to a json string",
|
||||
errorTypeIdString.c_str());
|
||||
|
||||
using ErrorString = AZStd::fixed_string<256>;
|
||||
return context.Report(JsonSerializationResult::Tasks::WriteValue, JsonSerializationResult::Outcomes::TypeMismatch,
|
||||
ErrorString::format("Input value type ID %s is not a valid Path type", errorTypeIdString.c_str()));
|
||||
}
|
||||
|
||||
} // namespace AZ
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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/Serialization/Json/BaseJsonSerializer.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class JsonPathSerializer
|
||||
: public BaseJsonSerializer
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(JsonPathSerializer, "{F6FBA901-07E0-4F03-A0B6-72A9A6CE1E96}", BaseJsonSerializer);
|
||||
AZ_CLASS_ALLOCATOR_DECL;
|
||||
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
|
||||
JsonDeserializerContext& context) override;
|
||||
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
|
||||
const Uuid& valueTypeId, JsonSerializerContext& context) override;
|
||||
};
|
||||
} // namespace AZ
|
||||
@@ -45,9 +45,8 @@ namespace AZ
|
||||
}
|
||||
else
|
||||
{
|
||||
SerializerMap::const_iterator serializerIter = m_jsonSerializers.find(typeId);
|
||||
AZ_Assert(serializerIter != m_jsonSerializers.end(), "Attempting to unregister a serializer that has not been registered yet with typeid %s", typeId.ToString<AZStd::string>().c_str());
|
||||
m_jsonSerializers.erase(serializerIter);
|
||||
[[maybe_unused]] size_t erased = m_jsonSerializers.erase(typeId);
|
||||
AZ_Assert(erased == 1, "Attempting to unregister a serializer that has not been registered yet with typeid %s", typeId.ToString<AZStd::string>().c_str());
|
||||
return SerializerBuilder(this, m_jsonSerializers.end());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
void cvar_t_simulationTickDeltaOverride_Changed(const float& value)
|
||||
void cvar_t_simulationTickDeltaOverride_Changed(const int64_t& value)
|
||||
{
|
||||
if (auto* timeSystem = AZ::Interface<ITime>::Get())
|
||||
{
|
||||
timeSystem->SetSimulationTickDeltaOverride(AZ::SecondsToTimeMs(value));
|
||||
timeSystem->SetSimulationTickDeltaOverride(static_cast<AZ::TimeMs>(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ namespace AZ
|
||||
AZ_CVAR(float, t_simulationTickScale, 1.0f, cvar_t_simulationTickScale_Changed, AZ::ConsoleFunctorFlags::Null,
|
||||
"A scalar amount to adjust time passage by, 1.0 == realtime, 0.5 == half realtime, 2.0 == doubletime");
|
||||
|
||||
AZ_CVAR(float, t_simulationTickDeltaOverride, 0.0f, cvar_t_simulationTickDeltaOverride_Changed, AZ::ConsoleFunctorFlags::Null,
|
||||
"If > 0, overrides the simulation tick delta time with the provided value (Seconds) and ignores any t_simulationTickScale value.");
|
||||
AZ_CVAR(int64_t, t_simulationTickDeltaOverride, 0, cvar_t_simulationTickDeltaOverride_Changed, AZ::ConsoleFunctorFlags::Null,
|
||||
"If > 0, overrides the simulation tick delta time with the provided value (Milliseconds) and ignores any t_simulationTickScale value.");
|
||||
|
||||
AZ_CVAR(int, t_simulationTickRate, 0, cvar_t_simulationTickRate_Changed, AZ::ConsoleFunctorFlags::Null,
|
||||
"The minimum rate to force the game simulation tick to run. 0 for as fast as possible. 30 = ~33ms, 60 = ~16ms");
|
||||
@@ -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
|
||||
@@ -171,7 +176,7 @@ namespace AZ
|
||||
if (timeUs != m_simulationTickDeltaOverride)
|
||||
{
|
||||
m_simulationTickDeltaOverride = timeUs;
|
||||
t_simulationTickDeltaOverride = AZ::TimeUsToSeconds(timeUs); //update the cvar
|
||||
t_simulationTickDeltaOverride = static_cast <int64_t>(timeMs); // update the cvar
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -531,6 +531,8 @@ set(FILES
|
||||
Serialization/Json/JsonUtils.cpp
|
||||
Serialization/Json/MapSerializer.h
|
||||
Serialization/Json/MapSerializer.cpp
|
||||
Serialization/Json/PathSerializer.h
|
||||
Serialization/Json/PathSerializer.cpp
|
||||
Serialization/Json/RegistrationContext.h
|
||||
Serialization/Json/RegistrationContext.cpp
|
||||
Serialization/Json/SmartPointerSerializer.h
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace AZ::IO
|
||||
, m_constructionOptions(options)
|
||||
{
|
||||
AZ_Assert(!drivePaths.empty(), "StorageDrive_win requires at least one drive path to work.");
|
||||
|
||||
|
||||
// Get drive paths
|
||||
m_drivePaths.reserve(drivePaths.size());
|
||||
for (AZStd::string_view drivePath : drivePaths)
|
||||
@@ -583,7 +583,7 @@ namespace AZ::IO
|
||||
// If any are unaligned to the sector sizes, make adjustments and allocate an aligned buffer.
|
||||
const bool alignedAddr = IStreamerTypes::IsAlignedTo(data->m_output, aznumeric_caster(m_physicalSectorSize));
|
||||
const bool alignedOffs = IStreamerTypes::IsAlignedTo(data->m_offset, aznumeric_caster(m_logicalSectorSize));
|
||||
|
||||
|
||||
// Adjust the offset if it's misaligned.
|
||||
// Align the offset down to next lowest sector.
|
||||
// Change the size to compensate.
|
||||
@@ -656,7 +656,7 @@ namespace AZ::IO
|
||||
Statistic::PlotImmediate(m_name, DirectReadsName, m_directReadsPercentageStat.GetMostRecentSample());
|
||||
#endif // AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
|
||||
}
|
||||
|
||||
|
||||
FileReadStatus& readStatus = m_readSlots_statusInfo[readSlot];
|
||||
LPOVERLAPPED overlapped = &readStatus.m_overlapped;
|
||||
overlapped->Offset = aznumeric_caster(readOffs);
|
||||
@@ -716,7 +716,7 @@ namespace AZ::IO
|
||||
Statistic::PlotImmediate(m_name, FileSwitchesName, m_fileSwitchPercentageStat.GetMostRecentSample());
|
||||
Statistic::PlotImmediate(m_name, SeeksName, m_seekPercentageStat.GetMostRecentSample());
|
||||
#endif // AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
|
||||
|
||||
|
||||
m_fileCache_activeReads[fileCacheSlot]++;
|
||||
m_activeCacheSlot = fileCacheSlot;
|
||||
m_activeOffset = readOffs + readSize;
|
||||
@@ -1007,7 +1007,7 @@ namespace AZ::IO
|
||||
|
||||
auto readCommand = AZStd::get_if<FileRequest::ReadData>(&fileReadInfo.m_request->GetCommand());
|
||||
AZ_Assert(readCommand != nullptr, "Request stored with the overlapped I/O call did not contain a read request.");
|
||||
|
||||
|
||||
if (fileReadInfo.m_sectorAlignedOutput && !encounteredError)
|
||||
{
|
||||
auto offsetAddress = reinterpret_cast<u8*>(fileReadInfo.m_sectorAlignedOutput) + fileReadInfo.m_copyBackOffset;
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace AZ::IO
|
||||
//! make adjustments. For the most optimal performance align read buffers to the physicalSectorSize.
|
||||
u8 m_enableUnbufferedReads : 1;
|
||||
//! Globally enable file sharing. This allows files to used outside AZ::IO::Streamer, including other applications
|
||||
//! while in use by AZ::IO::Streamer.
|
||||
//! while in use by AZ::IO::Streamer.
|
||||
u8 m_enableSharing : 1;
|
||||
//! If true, only information that's explicitly requested or issues are reported. If false, status information
|
||||
//! such as when drives are created and destroyed is reported as well.
|
||||
@@ -99,7 +99,7 @@ namespace AZ::IO
|
||||
FileRequest* m_request{ nullptr };
|
||||
void* m_sectorAlignedOutput{ nullptr }; // Internally allocated buffer that is sector aligned.
|
||||
size_t m_copyBackOffset{ 0 };
|
||||
|
||||
|
||||
void AllocateAlignedBuffer(size_t size, size_t sectorSize);
|
||||
void Clear();
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <AzCore/Math/Matrix3x3.h>
|
||||
#include <AzCore/Math/Transform.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AZTestShared/Math/MathTestHelpers.h>
|
||||
|
||||
using namespace AZ;
|
||||
|
||||
@@ -408,4 +409,118 @@ namespace UnitTest
|
||||
Matrix4x4 m = Matrix4x4::CreateFromQuaternion(rotQuat);
|
||||
AZ_TEST_ASSERT(m.IsClose(rotMatrix));
|
||||
}
|
||||
|
||||
class QuaternionScaledAxisAngleConversionFixture
|
||||
: public ::testing::TestWithParam<AZ::Quaternion>
|
||||
{
|
||||
public:
|
||||
AZ::Quaternion GetAbs(const AZ::Quaternion& in)
|
||||
{
|
||||
// Take the shortest path for quaternions containing rotations bigger than 180.0°.
|
||||
if (in.GetW() < 0.0f)
|
||||
{
|
||||
return -in;
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
};
|
||||
|
||||
static const AZ::Quaternion RotationRepresentationConversionTestQuats[] =
|
||||
{
|
||||
AZ::Quaternion::CreateIdentity(),
|
||||
-AZ::Quaternion::CreateIdentity(),
|
||||
AZ::Quaternion::CreateRotationX(AZ::Constants::TwoPi),
|
||||
AZ::Quaternion::CreateRotationY(AZ::Constants::Pi),
|
||||
AZ::Quaternion::CreateRotationZ(AZ::Constants::HalfPi),
|
||||
AZ::Quaternion::CreateRotationX(AZ::Constants::QuarterPi),
|
||||
AZ::Quaternion(0.64f, 0.36f, 0.48f, 0.48f),
|
||||
AZ::Quaternion(0.70f, -0.34f, 0.10f, 0.62f),
|
||||
AZ::Quaternion(-0.38f, 0.34f, 0.70f, -0.50f),
|
||||
AZ::Quaternion(0.70f, -0.34f, -0.38f, 0.50f),
|
||||
AZ::Quaternion(0.00f, 0.00f, -0.28f, 0.96f),
|
||||
AZ::Quaternion(0.24f, -0.64f, 0.72f, 0.12f),
|
||||
AZ::Quaternion(-0.66f, 0.62f, 0.42f, 0.06f)
|
||||
};
|
||||
|
||||
TEST_P(QuaternionScaledAxisAngleConversionFixture, ScaledAxisAngleQuatRoundtripTests)
|
||||
{
|
||||
const AZ::Quaternion testQuat = GetAbs(GetParam());
|
||||
|
||||
// Convert test quaternion to scaled axis-angle representation.
|
||||
const AZ::Vector3 scaledAxisAngle = testQuat.ConvertToScaledAxisAngle();
|
||||
|
||||
// Convert the scaled axis-angle back into a quaternion.
|
||||
AZ::Quaternion backFromScaledAxisAngle = AZ::Quaternion::CreateFromScaledAxisAngle(scaledAxisAngle);
|
||||
|
||||
// Compare the original quaternion with the one after the conversion.
|
||||
EXPECT_THAT(testQuat, IsCloseTolerance(backFromScaledAxisAngle, 1e-6f));
|
||||
}
|
||||
|
||||
TEST_P(QuaternionScaledAxisAngleConversionFixture, AxisAngleQuatRoundtripTests)
|
||||
{
|
||||
const AZ::Quaternion testQuat = GetAbs(GetParam());
|
||||
|
||||
// Convert test quaternion to axis-angle representation.
|
||||
AZ::Vector3 axis;
|
||||
float angle;
|
||||
testQuat.ConvertToAxisAngle(axis, angle);
|
||||
|
||||
// Convert the axis-angle back into a quaternion and compare the original quaternion with the one after the conversion.
|
||||
const AZ::Quaternion backFromAxisAngle = AZ::Quaternion::CreateFromAxisAngle(axis, angle);
|
||||
EXPECT_THAT(testQuat, IsCloseTolerance(backFromAxisAngle, 1e-6f));
|
||||
}
|
||||
|
||||
TEST_P(QuaternionScaledAxisAngleConversionFixture, CompareAxisAngleConversionTests)
|
||||
{
|
||||
const AZ::Quaternion testQuat = GetAbs(GetParam());
|
||||
|
||||
// Convert test quaternion to scaled axis-angle representation.
|
||||
const AZ::Vector3 scaledAxisAngle = testQuat.ConvertToScaledAxisAngle();
|
||||
|
||||
// Convert test quaternion to axis-angle representation and scale it manually.
|
||||
AZ::Vector3 axis;
|
||||
float angle;
|
||||
testQuat.ConvertToAxisAngle(axis, angle);
|
||||
|
||||
// Compare the scaled result to the version from the helper that directly converts it to scaled axis-angle.
|
||||
AZ::Vector3 scaledResult = axis*angle;
|
||||
EXPECT_TRUE(scaledResult.IsClose(scaledAxisAngle, 1e-5f));
|
||||
}
|
||||
|
||||
TEST_P(QuaternionScaledAxisAngleConversionFixture, CompareScaledAxisAngleConversionTests)
|
||||
{
|
||||
const AZ::Quaternion testQuat = GetAbs(GetParam());
|
||||
|
||||
// Convert test quaternion to axis-angle representation and scale it manually.
|
||||
AZ::Vector3 axis;
|
||||
float angle;
|
||||
testQuat.ConvertToAxisAngle(axis, angle);
|
||||
AZ::Vector3 scaledResult = axis*angle;
|
||||
|
||||
// Special case handling for identity rotation.
|
||||
AZ::Vector3 axisFromScaledResult = scaledResult.GetNormalized();
|
||||
float angleFromScaledResult = scaledResult.GetLength();
|
||||
if (AZ::IsClose(angleFromScaledResult, 0.0f))
|
||||
{
|
||||
axisFromScaledResult = AZ::Vector3::CreateAxisY();
|
||||
}
|
||||
|
||||
const AZ::Quaternion backFromAxisAngle = AZ::Quaternion::CreateFromAxisAngle(axisFromScaledResult, angleFromScaledResult);
|
||||
EXPECT_THAT(testQuat, IsCloseTolerance(backFromAxisAngle, 1e-6f));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(MATH_Quaternion, QuaternionScaledAxisAngleConversionFixture, ::testing::ValuesIn(RotationRepresentationConversionTestQuats));
|
||||
|
||||
TEST(MATH_Quaternion, ShortestEquivalent)
|
||||
{
|
||||
const AZ::Quaternion testQuat = AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi * 3.0f);
|
||||
|
||||
AZ::Quaternion absQuat = testQuat;
|
||||
absQuat.ShortestEquivalent();
|
||||
EXPECT_THAT(testQuat.GetShortestEquivalent(), IsCloseTolerance(absQuat, 1e-6f));
|
||||
|
||||
const float angle = absQuat.GetEulerRadians().GetX();
|
||||
EXPECT_THAT(angle, testing::FloatEq(-AZ::Constants::HalfPi));
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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/PlatformIncl.h>
|
||||
#include <AzCore/Debug/Trace.h>
|
||||
|
||||
#include <malloc.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
namespace Benchmark
|
||||
{
|
||||
namespace Platform
|
||||
{
|
||||
size_t GetProcessMemoryUsageBytes()
|
||||
{
|
||||
struct rusage rusage;
|
||||
getrusage(RUSAGE_SELF, &rusage);
|
||||
return rusage.ru_maxrss * 1024L;
|
||||
}
|
||||
|
||||
size_t GetMemorySize(void* memory)
|
||||
{
|
||||
return memory ? malloc_usable_size(memory) : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,5 @@
|
||||
|
||||
set(FILES
|
||||
Tests/UtilsTests_Android.cpp
|
||||
Tests/Memory/AllocatorBenchmarks_Android.cpp
|
||||
)
|
||||
|
||||
@@ -327,17 +327,13 @@ namespace JsonSerializationTests
|
||||
SerializerWithOneType::Unreflect(m_jsonRegistrationContext.get());
|
||||
}
|
||||
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
using JsonSerializationDeathTests = JsonRegistrationContextTests;
|
||||
TEST_F(JsonSerializationDeathTests, DoubleUnregisterSerializer_Asserts)
|
||||
TEST_F(JsonRegistrationContextTests, DoubleUnregisterSerializer_Asserts)
|
||||
{
|
||||
ASSERT_DEATH({
|
||||
SerializerWithOneType::Reflect(m_jsonRegistrationContext.get());
|
||||
SerializerWithOneType::Unreflect(m_jsonRegistrationContext.get());
|
||||
SerializerWithOneType::Unreflect(m_jsonRegistrationContext.get());
|
||||
}, ".*"
|
||||
);
|
||||
SerializerWithOneType::Reflect(m_jsonRegistrationContext.get());
|
||||
SerializerWithOneType::Unreflect(m_jsonRegistrationContext.get());
|
||||
AZ_TEST_START_ASSERTTEST;
|
||||
SerializerWithOneType::Unreflect(m_jsonRegistrationContext.get());
|
||||
AZ_TEST_STOP_ASSERTTEST(1);
|
||||
}
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
|
||||
} //namespace JsonSerializationTests
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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/IO/Path/PathReflect.h>
|
||||
#include <AzCore/Serialization/Json/PathSerializer.h>
|
||||
#include <Tests/Serialization/Json/BaseJsonSerializerFixture.h>
|
||||
#include <Tests/Serialization/Json/JsonSerializerConformityTests.h>
|
||||
|
||||
namespace JsonSerializationTests
|
||||
{
|
||||
template<typename PathType>
|
||||
class PathTestDescription
|
||||
: public JsonSerializerConformityTestDescriptor<PathType>
|
||||
{
|
||||
public:
|
||||
using JsonSerializerConformityTestDescriptor<PathType>::Reflect;
|
||||
void Reflect(AZStd::unique_ptr<AZ::SerializeContext>& serializeContext) override
|
||||
{
|
||||
AZ::IO::PathReflect(serializeContext.get());
|
||||
}
|
||||
void Reflect(AZStd::unique_ptr<AZ::JsonRegistrationContext>& jsonContext) override
|
||||
{
|
||||
AZ::IO::PathReflect(jsonContext.get());
|
||||
}
|
||||
AZStd::shared_ptr<AZ::BaseJsonSerializer> CreateSerializer() override
|
||||
{
|
||||
return AZStd::make_shared<AZ::JsonPathSerializer>();
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<PathType> CreateDefaultInstance() override
|
||||
{
|
||||
return AZStd::make_shared<PathType>();
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<PathType> CreateFullySetInstance() override
|
||||
{
|
||||
return AZStd::make_shared<PathType>("O3DE/Relative/Path");
|
||||
}
|
||||
|
||||
AZStd::string_view GetJsonForFullySetInstance() override
|
||||
{
|
||||
return R"("O3DE/Relative/Path")";
|
||||
}
|
||||
|
||||
void ConfigureFeatures(JsonSerializerConformityTestDescriptorFeatures& features) override
|
||||
{
|
||||
features.EnableJsonType(rapidjson::kStringType);
|
||||
features.m_supportsPartialInitialization = false;
|
||||
features.m_supportsInjection = false;
|
||||
}
|
||||
|
||||
bool AreEqual(const PathType& lhs, const PathType& rhs) override
|
||||
{
|
||||
return lhs == rhs;
|
||||
}
|
||||
};
|
||||
|
||||
using PathConformityTestTypes = ::testing::Types<
|
||||
PathTestDescription<AZ::IO::Path>,
|
||||
PathTestDescription<AZ::IO::FixedMaxPath>
|
||||
>;
|
||||
INSTANTIATE_TYPED_TEST_CASE_P(Path, JsonSerializerConformityTests, PathConformityTestTypes);
|
||||
|
||||
|
||||
class PathSerializerTests
|
||||
: public BaseJsonSerializerFixture
|
||||
{
|
||||
public:
|
||||
AZStd::unique_ptr<AZ::JsonPathSerializer> m_serializer;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
BaseJsonSerializerFixture::SetUp();
|
||||
m_serializer = AZStd::make_unique<AZ::JsonPathSerializer>();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_serializer.reset();
|
||||
BaseJsonSerializerFixture::TearDown();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PathSerializerTests, LoadingIntoFixedMaxPath_GreaterThanMaxPathLength_Fails)
|
||||
{
|
||||
AZ::IO::Path testPath;
|
||||
// Fill a path greater than the AZ::IO::MaxPathLength in write it to Json
|
||||
testPath.Native().append(AZ::IO::MaxPathLength + 2, 'a');
|
||||
|
||||
rapidjson::Value loadPathValue;
|
||||
AZ::JsonSerializationResult::ResultCode resultCode = m_serializer->Store(loadPathValue,
|
||||
&testPath, nullptr, azrtti_typeid<AZ::IO::Path>(), *m_jsonSerializationContext);
|
||||
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::Success, resultCode.GetOutcome());
|
||||
|
||||
AZ::IO::FixedMaxPath resultPath;
|
||||
AZ::JsonSerializationResult::ResultCode result = m_serializer->Load(&resultPath, azrtti_typeid<AZ::IO::FixedMaxPath>(),
|
||||
loadPathValue, *m_jsonDeserializationContext);
|
||||
EXPECT_GE(result.GetOutcome(), AZ::JsonSerializationResult::Outcomes::Invalid);
|
||||
}
|
||||
} // namespace JsonSerializationTests
|
||||
@@ -10,6 +10,77 @@
|
||||
#include <Tests/Serialization/Json/JsonSerializationTests.h>
|
||||
#include <Tests/Serialization/Json/TestCases_Classes.h>
|
||||
#include <Tests/Serialization/Json/TestCases_Pointers.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
template<typename T>
|
||||
struct SerializeGenericTypeInfo<JsonSerializationTests::TemplatedClass<T>>
|
||||
{
|
||||
using ThisType = JsonSerializationTests::TemplatedClass<T>;
|
||||
|
||||
class GenericTemplatedClassInfo : public GenericClassInfo
|
||||
{
|
||||
public:
|
||||
GenericTemplatedClassInfo()
|
||||
: m_classData{ SerializeContext::ClassData::Create<ThisType>(
|
||||
"TemplatedClass", "{CA4ADF74-66E7-4D16-B4AC-F71278C60EC7}", nullptr, nullptr) }
|
||||
{
|
||||
}
|
||||
|
||||
SerializeContext::ClassData* GetClassData() override
|
||||
{
|
||||
return &m_classData;
|
||||
}
|
||||
|
||||
size_t GetNumTemplatedArguments() override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
const Uuid& GetSpecializedTypeId() const override
|
||||
{
|
||||
return m_classData.m_typeId;
|
||||
}
|
||||
|
||||
const Uuid& GetGenericTypeId() const override
|
||||
{
|
||||
return m_classData.m_typeId;
|
||||
}
|
||||
|
||||
const Uuid& GetTemplatedTypeId(size_t element) override
|
||||
{
|
||||
(void)element;
|
||||
return SerializeGenericTypeInfo<T>::GetClassTypeId();
|
||||
}
|
||||
|
||||
void Reflect(SerializeContext* serializeContext) override
|
||||
{
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->RegisterGenericClassInfo(
|
||||
GetSpecializedTypeId(), this, &AZ::AnyTypeInfoConcept<Data::Asset<Data::AssetData>>::CreateAny);
|
||||
serializeContext->RegisterGenericClassInfo(
|
||||
azrtti_typeid<ThisType>(), this,
|
||||
&AZ::AnyTypeInfoConcept<ThisType>::CreateAny);
|
||||
}
|
||||
}
|
||||
|
||||
SerializeContext::ClassData m_classData;
|
||||
};
|
||||
|
||||
using ClassInfoType = GenericTemplatedClassInfo;
|
||||
static ClassInfoType* GetGenericInfo()
|
||||
{
|
||||
return GetCurrentSerializeContextModule().CreateGenericClassInfo<ThisType>();
|
||||
}
|
||||
|
||||
static const Uuid& GetClassTypeId()
|
||||
{
|
||||
return GetGenericInfo()->GetClassData()->m_typeId;
|
||||
}
|
||||
};
|
||||
} // namespace AZ
|
||||
|
||||
namespace JsonSerializationTests
|
||||
{
|
||||
@@ -286,4 +357,32 @@ namespace JsonSerializationTests
|
||||
EXPECT_EQ(Processing::Halted, result.GetProcessing());
|
||||
EXPECT_EQ(Outcomes::Unknown, result.GetOutcome());
|
||||
}
|
||||
|
||||
TEST_F(JsonSerializationTests, StoreTypeId_TemplatedType_StoresUuidWithName)
|
||||
{
|
||||
using namespace AZ;
|
||||
using namespace AZ::JsonSerializationResult;
|
||||
|
||||
m_serializeContext->RegisterGenericType<TemplatedClass<A::Inherited>>();
|
||||
m_serializeContext->RegisterGenericType<TemplatedClass<BaseClass>>();
|
||||
|
||||
Uuid input = azrtti_typeid<TemplatedClass<A::Inherited>>();
|
||||
ResultCode result = JsonSerialization::StoreTypeId(
|
||||
*m_jsonDocument, m_jsonDocument->GetAllocator(), input, AZStd::string_view{}, *m_serializationSettings);
|
||||
|
||||
EXPECT_EQ(Processing::Completed, result.GetProcessing());
|
||||
|
||||
AZStd::string expected =
|
||||
AZStd::string::format(R"("%s TemplatedClass")", azrtti_typeid<TemplatedClass<A::Inherited>>().ToString<AZStd::string>().c_str());
|
||||
Expect_DocStrEq(expected.c_str(), false);
|
||||
|
||||
input = azrtti_typeid<TemplatedClass<BaseClass>>();
|
||||
result = JsonSerialization::StoreTypeId(
|
||||
*m_jsonDocument, m_jsonDocument->GetAllocator(), input, AZStd::string_view{}, *m_serializationSettings);
|
||||
|
||||
expected =
|
||||
AZStd::string::format(R"("%s TemplatedClass")", azrtti_typeid<TemplatedClass<BaseClass>>().ToString<AZStd::string>().c_str());
|
||||
EXPECT_EQ(Processing::Completed, result.GetProcessing());
|
||||
Expect_DocStrEq(expected.c_str(), false);
|
||||
}
|
||||
} // namespace JsonSerializationTests
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace AZ::IO
|
||||
m_context = nullptr;
|
||||
|
||||
AllocatorInstance<ThreadPoolAllocator>::Destroy();
|
||||
AllocatorInstance<PoolAllocator>::Destroy();
|
||||
AllocatorInstance<PoolAllocator>::Destroy();
|
||||
|
||||
UnitTest::AllocatorsFixture::TearDown();
|
||||
}
|
||||
@@ -123,7 +123,7 @@ namespace AZ::IO
|
||||
.WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*m_mock, QueueRequest(_));
|
||||
EXPECT_CALL(*m_mock, UpdateStatus(_)).Times(AnyNumber());
|
||||
|
||||
|
||||
switch (mockResult)
|
||||
{
|
||||
case ReadResult::Success:
|
||||
@@ -267,7 +267,7 @@ namespace AZ::IO
|
||||
{
|
||||
allCompleted = allCompleted && request.GetStatus() == IStreamerTypes::RequestStatus::Completed;
|
||||
};
|
||||
|
||||
|
||||
FileRequest* requests[count];
|
||||
AZStd::unique_ptr<u32[]> buffers[count];
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
@@ -300,7 +300,7 @@ namespace AZ::IO
|
||||
size = size >> 2;
|
||||
for (u64 i = 0; i < size; ++i)
|
||||
{
|
||||
// Using assert here because in case of a problem EXPECT would
|
||||
// Using assert here because in case of a problem EXPECT would
|
||||
// cause a large amount of log noise.
|
||||
ASSERT_EQ(buffer[i], offset + (i << 2));
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ namespace AZ::IO
|
||||
.Times(2)
|
||||
.WillRepeatedly([this](FileRequest* request) { m_context.MarkRequestAsCompleted(request); });
|
||||
m_context.FinalizeCompletedRequests();
|
||||
|
||||
|
||||
azfree(memory);
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@ namespace AZ::IO
|
||||
m_context.FinalizeCompletedRequests();
|
||||
|
||||
EXPECT_EQ(2, completedRequests);
|
||||
|
||||
|
||||
azfree(memory1);
|
||||
azfree(memory0);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace AZ::IO
|
||||
{
|
||||
using ::testing::_;
|
||||
using ::testing::AnyNumber;
|
||||
|
||||
|
||||
UnitTest::AllocatorsFixture::SetUp();
|
||||
|
||||
m_mock = AZStd::make_shared<StreamStackEntryMock>();
|
||||
@@ -78,7 +78,7 @@ namespace AZ::IO
|
||||
{
|
||||
using ::testing::_;
|
||||
using ::testing::AtLeast;
|
||||
|
||||
|
||||
EXPECT_CALL(*m_mock, UpdateStatus(_)).Times(AtLeast(1));
|
||||
EXPECT_CALL(*m_mock, UpdateCompletionEstimates(_, _, _, _)).Times(AtLeast(1));
|
||||
EXPECT_CALL(*m_mock, PrepareRequest(_))
|
||||
@@ -115,7 +115,7 @@ namespace AZ::IO
|
||||
void MockAllocatorForUnclaimedMemory(IStreamerTypes::RequestMemoryAllocatorMock& mock, AZStd::binary_semaphore& sync)
|
||||
{
|
||||
using ::testing::_;
|
||||
|
||||
|
||||
EXPECT_CALL(mock, LockAllocator()).Times(1);
|
||||
EXPECT_CALL(mock, UnlockAllocator())
|
||||
.Times(1)
|
||||
@@ -256,13 +256,13 @@ namespace AZ::IO
|
||||
using ::testing::_;
|
||||
using ::testing::AtLeast;
|
||||
using ::testing::Return;
|
||||
|
||||
|
||||
EXPECT_CALL(*m_mock, UpdateStatus(_)).Times(AtLeast(1));
|
||||
EXPECT_CALL(*m_mock, UpdateCompletionEstimates(_, _, _, _)).Times(AtLeast(1));
|
||||
EXPECT_CALL(*m_mock, PrepareRequest(_)).Times(AtLeast(1));
|
||||
EXPECT_CALL(*m_mock, ExecuteRequests()).Times(AtLeast(1));
|
||||
EXPECT_CALL(*m_mock, QueueRequest(_)).Times(1);
|
||||
|
||||
|
||||
AZStd::atomic_int counter = 2;
|
||||
AZStd::binary_semaphore sync;
|
||||
auto wait = [&sync, &counter](FileRequestHandle)
|
||||
@@ -350,7 +350,7 @@ namespace AZ::IO
|
||||
|
||||
EXPECT_CALL(*m_mock, UpdateStatus(_)).Times(AnyNumber());
|
||||
EXPECT_CALL(*m_mock, UpdateCompletionEstimates(_, _, _, _)).Times(AnyNumber());
|
||||
|
||||
|
||||
// Pretend to be busy [Iterations] times, then set the status to idle so the Scheduler thread can exit.
|
||||
EXPECT_CALL(*m_mock, ExecuteRequests())
|
||||
.Times(Iterations + 1)
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace AZ::IO
|
||||
TYPED_TEST_P(StreamStackEntryConformityTests, SetContext_ContextIsForwardedToNext_SetContextOnMockIsCalled)
|
||||
{
|
||||
using ::testing::_;
|
||||
|
||||
|
||||
auto mock = AZStd::make_shared<StreamStackEntryMock>();
|
||||
auto entry = this->m_description.CreateInstance();
|
||||
entry.SetNext(mock);
|
||||
@@ -194,14 +194,14 @@ namespace AZ::IO
|
||||
TYPED_TEST_P(StreamStackEntryConformityTests, UpdateStatus_ForwardsCallToNext_NextRecievedCall)
|
||||
{
|
||||
using ::testing::_;
|
||||
|
||||
|
||||
auto mock = AZStd::make_shared<StreamStackEntryMock>();
|
||||
auto entry = this->m_description.CreateInstance();
|
||||
entry.SetNext(mock);
|
||||
|
||||
EXPECT_CALL(*mock, UpdateStatus(_))
|
||||
.Times(1);
|
||||
|
||||
|
||||
StreamStackEntry::Status status;
|
||||
entry.UpdateStatus(status);
|
||||
}
|
||||
@@ -241,7 +241,7 @@ namespace AZ::IO
|
||||
TYPED_TEST_P(StreamStackEntryConformityTests, UpdateStatus_NextHasSmallerNumSlots_ReturnsSmallestNumSlots)
|
||||
{
|
||||
using ::testing::_;
|
||||
|
||||
|
||||
if (this->m_description.UsesSlots())
|
||||
{
|
||||
auto mock = AZStd::make_shared<StreamStackEntryMock>();
|
||||
@@ -264,7 +264,7 @@ namespace AZ::IO
|
||||
TYPED_TEST_P(StreamStackEntryConformityTests, UpdateStatus_NextHasLargerNumSlots_ReturnsSmallestNumSlots)
|
||||
{
|
||||
using ::testing::_;
|
||||
|
||||
|
||||
if (this->m_description.UsesSlots())
|
||||
{
|
||||
auto mock = AZStd::make_shared<StreamStackEntryMock>();
|
||||
@@ -289,7 +289,7 @@ namespace AZ::IO
|
||||
TYPED_TEST_P(StreamStackEntryConformityTests, UpdateCompletionEstimates_ForwardsCallToNext_NextRecievedCall)
|
||||
{
|
||||
using ::testing::_;
|
||||
|
||||
|
||||
auto mock = AZStd::make_shared<StreamStackEntryMock>();
|
||||
auto entry = this->m_description.CreateInstance();
|
||||
entry.SetNext(mock);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,7 @@
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class TimeTests
|
||||
: public AllocatorsFixture
|
||||
class TimeTests : public AllocatorsFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
@@ -77,4 +76,4 @@ namespace UnitTest
|
||||
int64_t delta = static_cast<int64_t>(timeMs) - static_cast<int64_t>(timeUsToMs);
|
||||
EXPECT_LT(abs(delta), 1);
|
||||
}
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -111,6 +111,7 @@ set(FILES
|
||||
Serialization/Json/MapSerializerTests.cpp
|
||||
Serialization/Json/MathVectorSerializerTests.cpp
|
||||
Serialization/Json/MathMatrixSerializerTests.cpp
|
||||
Serialization/Json/PathSerializerTests.cpp
|
||||
Serialization/Json/SmartPointerSerializerTests.cpp
|
||||
Serialization/Json/StringSerializerTests.cpp
|
||||
Serialization/Json/TestCases.h
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace AzFramework
|
||||
{
|
||||
activeFile = &m_filePaths[m_activeCacheSlot];
|
||||
}
|
||||
|
||||
|
||||
// Estimate requests in this stack entry.
|
||||
for (FileRequest* request : m_pendingRequests)
|
||||
{
|
||||
@@ -279,7 +279,7 @@ namespace AzFramework
|
||||
using namespace AZ::IO;
|
||||
|
||||
AZ_PROFILE_FUNCTION(AzCore);
|
||||
|
||||
|
||||
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
|
||||
AZ_Assert(data, "Request doing reading in the RemoteStorageDrive didn't contain read data.");
|
||||
|
||||
@@ -292,7 +292,7 @@ namespace AzFramework
|
||||
file = m_fileHandles[cacheIndex];
|
||||
m_fileLastUsed[cacheIndex] = AZStd::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
|
||||
// If the file is not open, eject the oldest entry from the cache and open the file for reading.
|
||||
if (file == InvalidHandle)
|
||||
{
|
||||
@@ -325,7 +325,7 @@ namespace AzFramework
|
||||
}
|
||||
m_activeCacheSlot = cacheIndex;
|
||||
|
||||
AZ_Assert(file != InvalidHandle,
|
||||
AZ_Assert(file != InvalidHandle,
|
||||
"While searching for file '%s' RemoteStorageDevice::ReadFile encountered a problem that wasn't reported.", data->m_path.GetRelativePath());
|
||||
{
|
||||
TIMED_AVERAGE_WINDOW_SCOPE(m_readTimeAverage);
|
||||
@@ -357,7 +357,7 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
m_readSizeAverage.PushEntry(data->m_size);
|
||||
|
||||
|
||||
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
|
||||
m_context->MarkRequestAsCompleted(request);
|
||||
}
|
||||
@@ -507,7 +507,7 @@ namespace AzFramework
|
||||
using namespace AZ::IO;
|
||||
|
||||
using DoubleSeconds = AZStd::chrono::duration<double>;
|
||||
|
||||
|
||||
double totalBytesReadMB = m_readSizeAverage.GetTotal() / (1024.0 * 1024.0);
|
||||
double totalReadTimeSec = AZStd::chrono::duration_cast<DoubleSeconds>(m_readTimeAverage.GetTotal()).count();
|
||||
if (m_readSizeAverage.GetTotal() > 1) // A default is always added.
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace AzFramework
|
||||
|
||||
protected:
|
||||
static constexpr AZ::s32 s_maxRequests = 1;
|
||||
|
||||
|
||||
void ReadFile(AZ::IO::FileRequest* request);
|
||||
bool CancelRequest(AZ::IO::FileRequest* cancelRequest, AZ::IO::FileRequestPtr& target);
|
||||
void FileExistsRequest(AZ::IO::FileRequest* request);
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace AzFramework
|
||||
AZ::Outcome<void, AZStd::string> CompileScript(ScriptCompileRequest& request, AZ::ScriptContext& scriptContext)
|
||||
{
|
||||
AZ_TracePrintf(request.m_errorWindow.data(), "Starting script compile.\n");
|
||||
|
||||
|
||||
AZStd::string debugName = "@";
|
||||
debugName += request.m_sourceFile;
|
||||
AZStd::to_lower(debugName.begin(), debugName.end());
|
||||
@@ -180,14 +180,14 @@ namespace AzFramework
|
||||
{
|
||||
using namespace AZ::IO;
|
||||
FileIOStream outputStream;
|
||||
|
||||
|
||||
if (!outputStream.Open(request.m_destPath.c_str(), OpenMode::ModeWrite | OpenMode::ModeBinary))
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Failed to open output file %s", request.m_destPath.data()));
|
||||
}
|
||||
|
||||
request.m_output = &outputStream;
|
||||
|
||||
|
||||
if (writeAssetInfo)
|
||||
{
|
||||
if (request.m_prewriteCallback)
|
||||
@@ -292,7 +292,7 @@ namespace AzFramework
|
||||
|
||||
namespace Internal
|
||||
{
|
||||
|
||||
|
||||
AZStd::string PrintLuaValue(lua_State* lua, int stackIdx, int depth = 0)
|
||||
{
|
||||
constexpr int MaxDepth = 4;
|
||||
@@ -302,7 +302,7 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
const int elementType = lua_type(lua, stackIdx);
|
||||
|
||||
|
||||
switch (elementType)
|
||||
{
|
||||
case LUA_TSTRING:
|
||||
@@ -347,7 +347,7 @@ namespace AzFramework
|
||||
{
|
||||
keyValuePairs += " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tableStr += keyValuePairs.length() < 1024 ? keyValuePairs : AZStd::string::format("too many keys (%i)!", keyCount);
|
||||
@@ -891,18 +891,18 @@ namespace AzFramework
|
||||
// This is the root table (properties) it will be used as properties for all sub tables
|
||||
// ScriptComponents can share the same lua script asset, but each instance's Properties table needs to be unique.
|
||||
// This way the script can change a property at runtime and not affect the other ScriptComponents which are using the same script.
|
||||
// For normal properties we will create new variable instances, but NetSynched variables aren't stored in Lua, and instead
|
||||
// For normal properties we will create new variable instances, but NetSynched variables aren't stored in Lua, and instead
|
||||
// are retrieved using the __index and __newIndex metamethods.
|
||||
// Ensure that this instance of Properties table has the proper __index and __newIndex metamethods.
|
||||
lua_newtable(lua); // This new table will become the Properties instance metatable. Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {}
|
||||
lua_newtable(lua); // This new table will become the Properties instance metatable. Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {}
|
||||
lua_pushliteral(lua, "__index"); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index
|
||||
lua_pushcclosure(lua, &Internal::Properties__Index, 0); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index function
|
||||
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index}
|
||||
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index}
|
||||
|
||||
lua_pushliteral(lua, "__newindex");
|
||||
lua_pushcclosure(lua, &Internal::Properties__NewIndex, 0);
|
||||
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex}
|
||||
lua_setmetatable(lua, -2); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {Meta{__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex} }
|
||||
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex}
|
||||
lua_setmetatable(lua, -2); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {Meta{__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex} }
|
||||
|
||||
metatableIndex = lua_gettop(lua); // This will be the metatable for all subtables
|
||||
}
|
||||
|
||||
@@ -213,27 +213,33 @@ namespace AzFramework
|
||||
|
||||
Camera Cameras::StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime)
|
||||
{
|
||||
for (int i = 0; i < m_idleCameraInputs.size();)
|
||||
for (int idleIndex = 0; idleIndex < m_idleCameraInputs.size();)
|
||||
{
|
||||
auto& cameraInput = m_idleCameraInputs[i];
|
||||
auto& cameraInput = m_idleCameraInputs[idleIndex];
|
||||
const bool canBegin = cameraInput->Beginning() &&
|
||||
AZStd::all_of(m_activeCameraInputs.cbegin(), m_activeCameraInputs.cend(),
|
||||
[](const auto& input)
|
||||
{
|
||||
return !input->Exclusive();
|
||||
}) &&
|
||||
(!cameraInput->Exclusive() || (cameraInput->Exclusive() && m_activeCameraInputs.empty()));
|
||||
(!cameraInput->Exclusive() || m_activeCameraInputs.empty());
|
||||
|
||||
if (canBegin)
|
||||
{
|
||||
m_activeCameraInputs.push_back(cameraInput);
|
||||
using AZStd::swap;
|
||||
swap(m_idleCameraInputs[i], m_idleCameraInputs[m_idleCameraInputs.size() - 1]);
|
||||
swap(m_idleCameraInputs[idleIndex], m_idleCameraInputs[m_idleCameraInputs.size() - 1]);
|
||||
m_idleCameraInputs.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
// if a camera attempted to start but was not allowed to, ensure activation is cancelled
|
||||
if (!cameraInput->Idle())
|
||||
{
|
||||
cameraInput->CancelActivation();
|
||||
}
|
||||
|
||||
idleIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,21 +251,21 @@ namespace AzFramework
|
||||
return acc;
|
||||
});
|
||||
|
||||
for (int i = 0; i < m_activeCameraInputs.size();)
|
||||
for (int activeIndex = 0; activeIndex < m_activeCameraInputs.size();)
|
||||
{
|
||||
auto& cameraInput = m_activeCameraInputs[i];
|
||||
auto& cameraInput = m_activeCameraInputs[activeIndex];
|
||||
if (cameraInput->Ending())
|
||||
{
|
||||
cameraInput->ClearActivation();
|
||||
m_idleCameraInputs.push_back(cameraInput);
|
||||
using AZStd::swap;
|
||||
swap(m_activeCameraInputs[i], m_activeCameraInputs[m_activeCameraInputs.size() - 1]);
|
||||
swap(m_activeCameraInputs[activeIndex], m_activeCameraInputs[m_activeCameraInputs.size() - 1]);
|
||||
m_activeCameraInputs.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
cameraInput->ContinueActivation();
|
||||
i++;
|
||||
activeIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,9 +476,10 @@ namespace AzFramework
|
||||
{
|
||||
if (input->m_state == InputChannel::State::Began)
|
||||
{
|
||||
m_translation |= TranslationFromKey(input->m_channelId, m_translateCameraInputChannelIds);
|
||||
if (m_translation != TranslationType::Nil)
|
||||
if (auto translation = TranslationFromKey(input->m_channelId, m_translateCameraInputChannelIds);
|
||||
translation != TranslationType::Nil)
|
||||
{
|
||||
m_translation |= translation;
|
||||
BeginActivation();
|
||||
}
|
||||
|
||||
@@ -484,11 +491,16 @@ namespace AzFramework
|
||||
// ensure we don't process end events in the idle state
|
||||
else if (input->m_state == InputChannel::State::Ended && !Idle())
|
||||
{
|
||||
m_translation &= ~(TranslationFromKey(input->m_channelId, m_translateCameraInputChannelIds));
|
||||
if (m_translation == TranslationType::Nil)
|
||||
if (auto translation = TranslationFromKey(input->m_channelId, m_translateCameraInputChannelIds);
|
||||
translation != TranslationType::Nil)
|
||||
{
|
||||
EndActivation();
|
||||
m_translation &= ~translation;
|
||||
if (m_translation == TranslationType::Nil)
|
||||
{
|
||||
EndActivation();
|
||||
}
|
||||
}
|
||||
|
||||
if (input->m_channelId == m_translateCameraInputChannelIds.m_boostChannelId)
|
||||
{
|
||||
m_boost = false;
|
||||
|
||||
@@ -185,6 +185,11 @@ namespace AzFramework
|
||||
m_activation = Activation::Ending;
|
||||
}
|
||||
|
||||
void CancelActivation()
|
||||
{
|
||||
m_activation = Activation::Idle;
|
||||
}
|
||||
|
||||
void ContinueActivation()
|
||||
{
|
||||
// continue activation is called after the first step of the camera input,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzCore/Math/Vector2.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/RTTI/TypeInfoSimple.h>
|
||||
#include <AzCore/base.h>
|
||||
|
||||
@@ -203,6 +204,12 @@ namespace AzFramework
|
||||
return AZ::Vector2(aznumeric_cast<float>(screenPoint.m_x), aznumeric_cast<float>(screenPoint.m_y));
|
||||
}
|
||||
|
||||
//! Return an AZ::Vector3 from a ScreenPoint (including z/depth value, defaulting to 0.0f).
|
||||
inline AZ::Vector3 Vector3FromScreenPoint(const ScreenPoint& screenPoint, const float z = 0.0f)
|
||||
{
|
||||
return AZ::Vector3(aznumeric_cast<float>(screenPoint.m_x), aznumeric_cast<float>(screenPoint.m_y), z);
|
||||
}
|
||||
|
||||
//! Return an AZ::Vector2 from a ScreenVector.
|
||||
inline AZ::Vector2 Vector2FromScreenVector(const ScreenVector& screenVector)
|
||||
{
|
||||
|
||||
@@ -270,8 +270,6 @@ set(FILES
|
||||
Physics/WindBus.h
|
||||
Process/ProcessCommunicator.cpp
|
||||
Process/ProcessCommunicator.h
|
||||
Process/ProcessWatcher.cpp
|
||||
Process/ProcessWatcher.h
|
||||
Process/ProcessCommon_fwd.h
|
||||
Process/ProcessCommunicator.h
|
||||
Process/ProcessWatcher.cpp
|
||||
|
||||
+5
@@ -83,4 +83,9 @@ namespace AzFramework
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const
|
||||
{
|
||||
return AZStd::string{};
|
||||
}
|
||||
} //namespace AzFramework
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
|
||||
#include <Psapi.h>
|
||||
|
||||
AZ_CVAR(bool, ap_tether_lifetime, false, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
AZ_CVAR(bool, ap_tether_lifetime, true, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
"If enabled, a parent process that launches the AP will terminate the AP on exit");
|
||||
|
||||
namespace AzFramework::AssetSystem::Platform
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace AzFramework
|
||||
AZStd::string operator()(const AZStd::vector<AZStd::string>& commandLineArray) const
|
||||
{
|
||||
AZStd::string commandLineResult;
|
||||
Az::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " ");
|
||||
AZ::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " ");
|
||||
return commandLineResult;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,11 +92,7 @@ protected:
|
||||
|
||||
};
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
TEST_F(APConnectionTest, DISABLED_TestAddRemoveCallbacks)
|
||||
#else
|
||||
TEST_F(APConnectionTest, TestAddRemoveCallbacks)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
{
|
||||
using namespace AzFramework;
|
||||
|
||||
@@ -218,11 +214,7 @@ TEST_F(APConnectionTest, TestAddRemoveCallbacks)
|
||||
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
TEST_F(APConnectionTest, DISABLED_TestAddRemoveCallbacks_RemoveDuringCallback_DoesNotCrash)
|
||||
#else
|
||||
TEST_F(APConnectionTest, TestAddRemoveCallbacks_RemoveDuringCallback_DoesNotCrash)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
{
|
||||
using namespace AzFramework;
|
||||
|
||||
@@ -313,11 +305,7 @@ TEST_F(APConnectionTest, TestAddRemoveCallbacks_RemoveDuringCallback_DoesNotCras
|
||||
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
TEST_F(APConnectionTest, DISABLED_TestAddRemoveCallbacks_AddDuringCallback_DoesNotCrash)
|
||||
#else
|
||||
TEST_F(APConnectionTest, TestAddRemoveCallbacks_AddDuringCallback_DoesNotCrash)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
{
|
||||
using namespace AzFramework;
|
||||
|
||||
@@ -451,11 +439,7 @@ TEST_F(APConnectionTest, TestAddRemoveCallbacks_AddDuringCallback_DoesNotCrash)
|
||||
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apConnection, SocketConnection::EConnectionState::Disconnected));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
TEST_F(APConnectionTest, DISABLED_TestConnection)
|
||||
#else
|
||||
TEST_F(APConnectionTest, TestConnection)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
{
|
||||
using namespace AzFramework;
|
||||
|
||||
@@ -557,11 +541,7 @@ TEST_F(APConnectionTest, TestConnection)
|
||||
EXPECT_TRUE(WaitForConnectionStateToBeEqual(apListener, SocketConnection::EConnectionState::Disconnected));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
TEST_F(APConnectionTest, DISABLED_TestReconnect)
|
||||
#else
|
||||
TEST_F(APConnectionTest, TestReconnect)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS
|
||||
{
|
||||
using namespace AzFramework;
|
||||
|
||||
|
||||
@@ -416,4 +416,78 @@ namespace UnitTest
|
||||
using ::testing::FloatNear;
|
||||
EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f));
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, InvalidTranslationInputKeyCannotBeginTranslateCameraInputAgain)
|
||||
{
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
|
||||
AzFramework::InputChannel::State::Began });
|
||||
|
||||
const bool consumed =
|
||||
m_cameraSystem->HandleEvents(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
|
||||
|
||||
using ::testing::IsFalse;
|
||||
using ::testing::IsTrue;
|
||||
EXPECT_THAT(consumed, IsTrue());
|
||||
EXPECT_THAT(m_firstPersonTranslateCamera->Beginning(), IsFalse());
|
||||
EXPECT_THAT(m_firstPersonTranslateCamera->Active(), IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, InvalidTranslationInputKeyDownCannotBeginTranslateCameraInputAgain)
|
||||
{
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
|
||||
AzFramework::InputChannel::State::Began });
|
||||
|
||||
const bool consumed =
|
||||
m_cameraSystem->HandleEvents(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
|
||||
|
||||
using ::testing::IsFalse;
|
||||
using ::testing::IsTrue;
|
||||
EXPECT_THAT(consumed, IsTrue());
|
||||
EXPECT_THAT(m_firstPersonTranslateCamera->Beginning(), IsFalse());
|
||||
EXPECT_THAT(m_firstPersonTranslateCamera->Active(), IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, InvalidTranslationInputKeyUpDoesNotAffectTranslateCameraInputEnd)
|
||||
{
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
|
||||
AzFramework::InputChannel::State::Began });
|
||||
|
||||
const bool consumed =
|
||||
m_cameraSystem->HandleEvents(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
|
||||
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
|
||||
AzFramework::InputChannel::State::Ended });
|
||||
|
||||
using ::testing::IsFalse;
|
||||
using ::testing::IsTrue;
|
||||
EXPECT_THAT(consumed, IsTrue());
|
||||
EXPECT_THAT(m_firstPersonTranslateCamera->Idle(), IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, OrbitCameraInputCannotBeLeftInInvalidStateIfItCannotFullyBeginAfterInputChannelBegin)
|
||||
{
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
|
||||
AzFramework::InputChannel::State::Began });
|
||||
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
|
||||
|
||||
using ::testing::IsFalse;
|
||||
using ::testing::IsTrue;
|
||||
EXPECT_THAT(m_orbitCamera->Beginning(), IsFalse());
|
||||
EXPECT_THAT(m_orbitCamera->Idle(), IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(CameraInputFixture, OrbitCameraInputCannotBeLeftInInvalidStateIfItCannotFullyBeginAfterInputChannelBeginAndEnd)
|
||||
{
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
|
||||
AzFramework::InputChannel::State::Began });
|
||||
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
|
||||
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Ended });
|
||||
|
||||
using ::testing::IsFalse;
|
||||
using ::testing::IsTrue;
|
||||
EXPECT_THAT(m_orbitCamera->Ending(), IsFalse());
|
||||
EXPECT_THAT(m_orbitCamera->Idle(), IsTrue());
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
+14
-10
@@ -26,29 +26,33 @@ namespace AzManipulatorTestFramework
|
||||
{
|
||||
public:
|
||||
virtual ~ViewportInteractionInterface() = default;
|
||||
//! Return the camera state.
|
||||
//! Returns the camera state.
|
||||
virtual AzFramework::CameraState GetCameraState() = 0;
|
||||
//! Set the camera state.
|
||||
//! Sets the camera state.
|
||||
virtual void SetCameraState(const AzFramework::CameraState& cameraState) = 0;
|
||||
//! Retrieve the debug display.
|
||||
//! Retrieves the debug display.
|
||||
virtual AzFramework::DebugDisplayRequests& GetDebugDisplay() = 0;
|
||||
//! Set if grid snapping is enabled or not.
|
||||
//! Sets if grid snapping is enabled or not.
|
||||
virtual void SetGridSnapping(bool enabled) = 0;
|
||||
//! Set if angular snapping is enabled or not.
|
||||
//! Sets if angular snapping is enabled or not.
|
||||
virtual void SetAngularSnapping(bool enabled) = 0;
|
||||
//! Set the grid size.
|
||||
//! Sets the grid size.
|
||||
virtual void SetGridSize(float size) = 0;
|
||||
//! Set the angular step.
|
||||
//! Sets the angular step.
|
||||
virtual void SetAngularStep(float step) = 0;
|
||||
//! Get the viewport id.
|
||||
//! Gets the viewport id.
|
||||
virtual AzFramework::ViewportId GetViewportId() const = 0;
|
||||
//! Updates the visibility state.
|
||||
//! Updates which entities are currently visible given the current camera state.
|
||||
virtual void UpdateVisibility() = 0;
|
||||
//! Set if sticky select is enabled or not.
|
||||
//! Sets if sticky select is enabled or not.
|
||||
virtual void SetStickySelect(bool enabled) = 0;
|
||||
//! Get default Editor Camera Position.
|
||||
//! Gets default Editor Camera Position.
|
||||
virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0;
|
||||
//! Sets if icons are visible in the viewport.
|
||||
virtual void SetIconsVisible(bool visible) = 0;
|
||||
//! Sets if helpers are visible in the viewport.
|
||||
virtual void SetHelpersVisible(bool visible) = 0;
|
||||
};
|
||||
|
||||
//! This interface is used to simulate the manipulator manager while the manipulators are under test.
|
||||
|
||||
+1
-1
@@ -26,6 +26,7 @@ namespace UnitTest
|
||||
using IndirectCallManipulatorViewportInteraction = AzManipulatorTestFramework::IndirectCallManipulatorViewportInteraction;
|
||||
using ImmediateModeActionDispatcher = AzManipulatorTestFramework::ImmediateModeActionDispatcher;
|
||||
|
||||
public:
|
||||
void SetUpEditorFixtureImpl() override
|
||||
{
|
||||
ToolsApplicationFixtureT::SetUpEditorFixtureImpl();
|
||||
@@ -43,7 +44,6 @@ namespace UnitTest
|
||||
ToolsApplicationFixtureT::TearDownEditorFixtureImpl();
|
||||
}
|
||||
|
||||
public:
|
||||
AzFramework::CameraState m_cameraState;
|
||||
AZStd::unique_ptr<ImmediateModeActionDispatcher> m_actionDispatcher;
|
||||
AZStd::unique_ptr<IndirectCallManipulatorViewportInteraction> m_viewportManipulatorInteraction;
|
||||
|
||||
-23
@@ -27,29 +27,6 @@ namespace AzManipulatorTestFramework
|
||||
const AZ::Vector3& position = AZ::Vector3::CreateZero(),
|
||||
float radius = 1.0f);
|
||||
|
||||
//! Create a mouse pick from the specified ray and screen point.
|
||||
AzToolsFramework::ViewportInteraction::MousePick CreateMousePick(
|
||||
const AZ::Vector3& origin, const AZ::Vector3& direction, const AzFramework::ScreenPoint& screenPoint);
|
||||
|
||||
//! Build a mouse pick from the specified mouse position and camera state.
|
||||
AzToolsFramework::ViewportInteraction::MousePick BuildMousePick(
|
||||
const AzFramework::ScreenPoint& screenPoint, const AzFramework::CameraState& cameraState);
|
||||
|
||||
//! Create a mouse interaction from the specified pick, buttons, interaction id and keyboard modifiers.
|
||||
AzToolsFramework::ViewportInteraction::MouseInteraction CreateMouseInteraction(
|
||||
const AzToolsFramework::ViewportInteraction::MousePick& mousePick,
|
||||
AzToolsFramework::ViewportInteraction::MouseButtons buttons,
|
||||
AzToolsFramework::ViewportInteraction::InteractionId interactionId,
|
||||
AzToolsFramework::ViewportInteraction::KeyboardModifiers modifiers);
|
||||
|
||||
//! Create a mouse buttons from the specified mouse button.
|
||||
AzToolsFramework::ViewportInteraction::MouseButtons CreateMouseButtons(AzToolsFramework::ViewportInteraction::MouseButton button);
|
||||
|
||||
//! Create a mouse interaction event from the specified interaction and event.
|
||||
AzToolsFramework::ViewportInteraction::MouseInteractionEvent CreateMouseInteractionEvent(
|
||||
const AzToolsFramework::ViewportInteraction::MouseInteraction& mouseInteraction,
|
||||
AzToolsFramework::ViewportInteraction::MouseEvent event);
|
||||
|
||||
//! Dispatch a mouse event to the main manipulator manager via a bus call.
|
||||
void DispatchMouseInteractionEvent(const AzToolsFramework::ViewportInteraction::MouseInteractionEvent& event);
|
||||
|
||||
|
||||
+4
-1
@@ -24,9 +24,12 @@ namespace AzManipulatorTestFramework
|
||||
explicit IndirectCallManipulatorViewportInteraction(AZStd::shared_ptr<AzFramework::DebugDisplayRequests> debugDisplayRequests);
|
||||
~IndirectCallManipulatorViewportInteraction();
|
||||
|
||||
// ManipulatorViewportInteractionInterface ...
|
||||
// ManipulatorViewportInteraction overrides ...
|
||||
const ViewportInteractionInterface& GetViewportInteraction() const override;
|
||||
const ManipulatorManagerInterface& GetManipulatorManager() const override;
|
||||
// make non-const overloads visible
|
||||
using ManipulatorViewportInteraction::GetViewportInteraction;
|
||||
using ManipulatorViewportInteraction::GetManipulatorManager;
|
||||
|
||||
private:
|
||||
AZStd::unique_ptr<ViewportInteraction> m_viewportInteraction;
|
||||
|
||||
+9
-3
@@ -39,7 +39,8 @@ namespace AzManipulatorTestFramework
|
||||
AzFramework::ViewportId GetViewportId() const override;
|
||||
void UpdateVisibility() override;
|
||||
void SetStickySelect(bool enabled) override;
|
||||
AZ::Vector3 DefaultEditorCameraPosition() const override;
|
||||
void SetIconsVisible(bool visible) override;
|
||||
void SetHelpersVisible(bool visible) override;
|
||||
|
||||
// ViewportInteractionRequestBus overrides ...
|
||||
AzFramework::CameraState GetCameraState() override;
|
||||
@@ -58,6 +59,9 @@ namespace AzManipulatorTestFramework
|
||||
float ManipulatorLineBoundWidth() const override;
|
||||
float ManipulatorCircleBoundWidth() const override;
|
||||
bool StickySelectEnabled() const override;
|
||||
AZ::Vector3 DefaultEditorCameraPosition() const override;
|
||||
bool IconsVisible() const override;
|
||||
bool HelpersVisible() const override;
|
||||
|
||||
// EditorEntityViewportInteractionRequestBus overrides ...
|
||||
void FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntities) override;
|
||||
@@ -68,10 +72,12 @@ namespace AzManipulatorTestFramework
|
||||
AzFramework::EntityVisibilityQuery m_entityVisibilityQuery;
|
||||
AZStd::shared_ptr<AzFramework::DebugDisplayRequests> m_debugDisplayRequests;
|
||||
AzFramework::CameraState m_cameraState;
|
||||
float m_gridSize = 1.0f;
|
||||
float m_angularStep = 0.0f;
|
||||
bool m_gridSnapping = false;
|
||||
bool m_angularSnapping = false;
|
||||
bool m_stickySelect = true;
|
||||
float m_gridSize = 1.0f;
|
||||
float m_angularStep = 0.0f;
|
||||
bool m_iconsVisible = true;
|
||||
bool m_helpersVisible = true;
|
||||
};
|
||||
} // namespace AzManipulatorTestFramework
|
||||
|
||||
@@ -82,49 +82,6 @@ namespace AzManipulatorTestFramework
|
||||
return manipulator;
|
||||
}
|
||||
|
||||
AzToolsFramework::ViewportInteraction::MousePick CreateMousePick(
|
||||
const AZ::Vector3& origin, const AZ::Vector3& direction, const AzFramework::ScreenPoint& screenPoint)
|
||||
{
|
||||
return { origin, direction, screenPoint };
|
||||
}
|
||||
|
||||
AzToolsFramework::ViewportInteraction::MousePick BuildMousePick(
|
||||
const AzFramework::ScreenPoint& screenPoint, const AzFramework::CameraState& cameraState)
|
||||
{
|
||||
const auto nearPlaneWorldPosition = AzFramework::ScreenToWorld(screenPoint, cameraState);
|
||||
|
||||
AzToolsFramework::ViewportInteraction::MousePick mousePick;
|
||||
mousePick.m_screenCoordinates = screenPoint;
|
||||
mousePick.m_rayOrigin = nearPlaneWorldPosition;
|
||||
mousePick.m_rayDirection = (nearPlaneWorldPosition - cameraState.m_position).GetNormalized();
|
||||
|
||||
return mousePick;
|
||||
}
|
||||
|
||||
MouseInteraction CreateMouseInteraction(
|
||||
const MousePick& mousePick, MouseButtons buttons, InteractionId interactionId, KeyboardModifiers modifiers)
|
||||
{
|
||||
AzToolsFramework::ViewportInteraction::MouseInteraction interaction;
|
||||
interaction.m_mousePick = mousePick;
|
||||
interaction.m_mouseButtons = buttons;
|
||||
interaction.m_interactionId = interactionId;
|
||||
interaction.m_keyboardModifiers = modifiers;
|
||||
|
||||
return interaction;
|
||||
}
|
||||
|
||||
MouseButtons CreateMouseButtons(MouseButton button)
|
||||
{
|
||||
MouseButtons buttons;
|
||||
buttons.m_mouseButtons = static_cast<AZ::u32>(button);
|
||||
return buttons;
|
||||
}
|
||||
|
||||
MouseInteractionEvent CreateMouseInteractionEvent(const MouseInteraction& mouseInteraction, MouseEvent event)
|
||||
{
|
||||
return MouseInteractionEvent(mouseInteraction, event, /*captured=*/false);
|
||||
}
|
||||
|
||||
void DispatchMouseInteractionEvent(const MouseInteractionEvent& event)
|
||||
{
|
||||
AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::Event(
|
||||
|
||||
@@ -120,7 +120,8 @@ namespace AzManipulatorTestFramework
|
||||
void ImmediateModeActionDispatcher::MousePositionImpl(const AzFramework::ScreenPoint& position)
|
||||
{
|
||||
const auto cameraState = m_manipulatorViewportInteraction.GetViewportInteraction().GetCameraState();
|
||||
GetMouseInteractionEvent()->m_mouseInteraction.m_mousePick = BuildMousePick(position, cameraState);
|
||||
GetMouseInteractionEvent()->m_mouseInteraction.m_mousePick =
|
||||
AzToolsFramework::ViewportInteraction::BuildMousePick(cameraState, position);
|
||||
GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Move;
|
||||
m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <AzFramework/Viewport/ViewportScreen.h>
|
||||
#include <AzManipulatorTestFramework/ViewportInteraction.h>
|
||||
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
|
||||
|
||||
namespace AzManipulatorTestFramework
|
||||
{
|
||||
@@ -19,6 +20,9 @@ namespace AzManipulatorTestFramework
|
||||
AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusConnect(m_viewportId);
|
||||
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler::BusConnect(m_viewportId);
|
||||
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusConnect(m_viewportId);
|
||||
|
||||
m_cameraState =
|
||||
AzFramework::CreateIdentityDefaultCamera(AZ::Vector3::CreateZero(), AzManipulatorTestFramework::DefaultViewportSize);
|
||||
}
|
||||
|
||||
ViewportInteraction::~ViewportInteraction()
|
||||
@@ -113,6 +117,16 @@ namespace AzManipulatorTestFramework
|
||||
m_stickySelect = enabled;
|
||||
}
|
||||
|
||||
void ViewportInteraction::SetIconsVisible(const bool visible)
|
||||
{
|
||||
m_iconsVisible = visible;
|
||||
}
|
||||
|
||||
void ViewportInteraction::SetHelpersVisible(const bool visible)
|
||||
{
|
||||
m_helpersVisible = visible;
|
||||
}
|
||||
|
||||
AZ::Vector3 ViewportInteraction::DefaultEditorCameraPosition() const
|
||||
{
|
||||
return {};
|
||||
@@ -148,4 +162,14 @@ namespace AzManipulatorTestFramework
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
bool ViewportInteraction::IconsVisible() const
|
||||
{
|
||||
return m_iconsVisible;
|
||||
}
|
||||
|
||||
bool ViewportInteraction::HelpersVisible() const
|
||||
{
|
||||
return m_helpersVisible;
|
||||
}
|
||||
} // namespace AzManipulatorTestFramework
|
||||
|
||||
+20
-19
@@ -8,21 +8,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class LinearManipulatorTestFixture
|
||||
: public ToolsApplicationFixture
|
||||
class LinearManipulatorTestFixture : public ToolsApplicationFixture
|
||||
{
|
||||
protected:
|
||||
LinearManipulatorTestFixture(const AzToolsFramework::ManipulatorManagerId& manipulatorManagerId)
|
||||
: m_manipulatorManagerId(manipulatorManagerId) {}
|
||||
: m_manipulatorManagerId(manipulatorManagerId)
|
||||
{
|
||||
}
|
||||
|
||||
void SetUpEditorFixtureImpl() override
|
||||
{
|
||||
{
|
||||
m_linearManipulator = AzManipulatorTestFramework::CreateLinearManipulator(
|
||||
m_manipulatorManagerId,
|
||||
/*position=*/AZ::Vector3::CreateZero(),
|
||||
@@ -31,21 +32,21 @@ namespace UnitTest
|
||||
// default sanity check call backs
|
||||
m_linearManipulator->InstallLeftMouseDownCallback(
|
||||
[this](const AzToolsFramework::LinearManipulator::Action& /*action*/)
|
||||
{
|
||||
m_receivedLeftMouseDown = true;
|
||||
});
|
||||
{
|
||||
m_receivedLeftMouseDown = true;
|
||||
});
|
||||
|
||||
m_linearManipulator->InstallMouseMoveCallback(
|
||||
[this](const AzToolsFramework::LinearManipulator::Action& /*action*/)
|
||||
{
|
||||
m_receivedMouseMove = true;
|
||||
});
|
||||
{
|
||||
m_receivedMouseMove = true;
|
||||
});
|
||||
|
||||
m_linearManipulator->InstallLeftMouseUpCallback(
|
||||
[this](const AzToolsFramework::LinearManipulator::Action& /*action*/)
|
||||
{
|
||||
m_receivedLeftMouseUp = true;
|
||||
});
|
||||
{
|
||||
m_receivedLeftMouseUp = true;
|
||||
});
|
||||
}
|
||||
|
||||
void TearDownEditorFixtureImpl() override
|
||||
@@ -63,16 +64,16 @@ namespace UnitTest
|
||||
bool m_receivedLeftMouseUp = false;
|
||||
|
||||
// initial world space starting position for mouse interaction
|
||||
const AzToolsFramework::ViewportInteraction::MousePick m_mouseStartingPositionRay =
|
||||
AzManipulatorTestFramework::CreateMousePick(
|
||||
AZ::Vector3(0.0f, -2.0f, 0.0f), AZ::Vector3(0.0f, 1.0f, 0.0f), AzFramework::ScreenPoint( 0,0 ));
|
||||
const AzToolsFramework::ViewportInteraction::MousePick m_mouseStartingPositionRay{ AZ::Vector3(0.0f, -2.0f, 0.0f),
|
||||
AZ::Vector3(0.0f, 1.0f, 0.0f),
|
||||
AzFramework::ScreenPoint(0, 0) };
|
||||
|
||||
// left mouse down ray in world space 2 units back from origin looking down +y axis with a null interaction
|
||||
// id and no keyboard modifiers
|
||||
AzToolsFramework::ViewportInteraction::MouseInteraction m_interaction =
|
||||
AzManipulatorTestFramework::CreateMouseInteraction(
|
||||
AzToolsFramework::ViewportInteraction::BuildMouseInteraction(
|
||||
m_mouseStartingPositionRay,
|
||||
AzManipulatorTestFramework::CreateMouseButtons(AzToolsFramework::ViewportInteraction::MouseButton::Left),
|
||||
AzToolsFramework::ViewportInteraction::BuildMouseButtons(AzToolsFramework::ViewportInteraction::MouseButton::Left),
|
||||
AzToolsFramework::ViewportInteraction::InteractionId(AZ::EntityId(0), 0),
|
||||
AzToolsFramework::ViewportInteraction::KeyboardModifiers(0));
|
||||
};
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
*/
|
||||
|
||||
#include "AzManipulatorTestFrameworkTestFixtures.h"
|
||||
|
||||
#include <AzToolsFramework/Viewport/ViewportTypes.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorDefaultSelection.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
|
||||
|
||||
@@ -34,8 +36,8 @@ namespace UnitTest
|
||||
TEST_F(AzManipulatorTestFrameworkBusCallTestFixture, ConsumeViewportLeftMouseClick)
|
||||
{
|
||||
// given a left mouse down ray in world space
|
||||
auto event =
|
||||
AzManipulatorTestFramework::CreateMouseInteractionEvent(m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
|
||||
auto event = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
|
||||
m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
|
||||
|
||||
// consume the mouse down and up events
|
||||
AzManipulatorTestFramework::DispatchMouseInteractionEvent(event);
|
||||
@@ -53,8 +55,8 @@ namespace UnitTest
|
||||
TEST_F(AzManipulatorTestFrameworkBusCallTestFixture, ConsumeViewportMouseMoveHover)
|
||||
{
|
||||
// given a left mouse down ray in world space
|
||||
const auto event =
|
||||
AzManipulatorTestFramework::CreateMouseInteractionEvent(m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Move);
|
||||
const auto event = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
|
||||
m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Move);
|
||||
|
||||
// consume the mouse move event
|
||||
AzManipulatorTestFramework::DispatchMouseInteractionEvent(event);
|
||||
@@ -72,8 +74,8 @@ namespace UnitTest
|
||||
TEST_F(AzManipulatorTestFrameworkBusCallTestFixture, ConsumeViewportMouseMoveActive)
|
||||
{
|
||||
// given a left mouse down ray in world space
|
||||
auto event =
|
||||
AzManipulatorTestFramework::CreateMouseInteractionEvent(m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
|
||||
auto event = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
|
||||
m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
|
||||
|
||||
// consume the mouse down event
|
||||
AzManipulatorTestFramework::DispatchMouseInteractionEvent(event);
|
||||
@@ -113,8 +115,8 @@ namespace UnitTest
|
||||
});
|
||||
|
||||
// given a left mouse down ray in world space
|
||||
auto event =
|
||||
AzManipulatorTestFramework::CreateMouseInteractionEvent(m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
|
||||
auto event = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
|
||||
m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
|
||||
|
||||
// consume the mouse down event
|
||||
AzManipulatorTestFramework::DispatchMouseInteractionEvent(event);
|
||||
|
||||
@@ -243,7 +243,7 @@ SliderDoubleCombo::SliderDoubleCombo(QWidget* parent)
|
||||
|
||||
InitialiseSliderCombo(this, layout, m_spinbox, m_slider);
|
||||
|
||||
connect(m_slider, &SliderDouble::valueChanged, this, &SliderDoubleCombo::setValue);
|
||||
connect(m_slider, &SliderDouble::valueChanged, this, &SliderDoubleCombo::setValueSlider);
|
||||
connect(m_spinbox, QOverload<double>::of(&DoubleSpinBox::valueChanged), this, &SliderDoubleCombo::setValue);
|
||||
connect(m_slider, &SliderDouble::sliderReleased, this, &SliderDoubleCombo::editingFinished);
|
||||
connect(m_spinbox, &DoubleSpinBox::editingFinished, this, &SliderDoubleCombo::editingFinished);
|
||||
@@ -254,7 +254,7 @@ SliderDoubleCombo::~SliderDoubleCombo()
|
||||
{
|
||||
}
|
||||
|
||||
void SliderDoubleCombo::setValue(double value)
|
||||
void SliderDoubleCombo::setValueSlider(double value)
|
||||
{
|
||||
const bool doEmit = m_value != value;
|
||||
m_value = value;
|
||||
@@ -264,10 +264,34 @@ void SliderDoubleCombo::setValue(double value)
|
||||
|
||||
if (doEmit)
|
||||
{
|
||||
// We don't want to update the slider from setValue as this
|
||||
// causes rounding errors in the tooltip hint.
|
||||
m_fromSlider = true;
|
||||
Q_EMIT valueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SliderDoubleCombo::setValue(double value)
|
||||
{
|
||||
const bool doEmit = m_value != value;
|
||||
m_value = value;
|
||||
|
||||
updateSpinBox();
|
||||
if (!m_fromSlider)
|
||||
{
|
||||
updateSlider();
|
||||
|
||||
if (doEmit)
|
||||
{
|
||||
Q_EMIT valueChanged();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fromSlider = false;
|
||||
}
|
||||
}
|
||||
|
||||
SliderDouble* SliderDoubleCombo::slider() const
|
||||
{
|
||||
return m_slider;
|
||||
|
||||
@@ -151,6 +151,8 @@ namespace AzQtComponents
|
||||
|
||||
//! Sets the current value.
|
||||
void setValue(double value);
|
||||
//! Sets the current value.
|
||||
void setValueSlider(double value);
|
||||
//! Return the current value.
|
||||
Q_REQUIRED_RESULT double value() const;
|
||||
|
||||
@@ -235,5 +237,6 @@ namespace AzQtComponents
|
||||
double m_softMinimum = 0.0;
|
||||
double m_softMaximum = 100.0;
|
||||
double m_value = 0.0;
|
||||
bool m_fromSlider{ false };
|
||||
};
|
||||
} // namespace AzQtComponents
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user