Preapre codebase for FileRequest compiletime improvements (#6192)
* Preapre codebase for FileRequest compiletime improvements This is preparing grounds for the next PR that will contain the 'meat' of the changes. Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove spurious newline. Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>
This commit is contained in:
@@ -442,7 +442,7 @@ AZ::Outcome<void, AZStd::string> CGameEngine::Init(
|
||||
REGISTER_COMMAND("quit", CGameEngine::HandleQuitRequest, VF_RESTRICTEDMODE, "Quit/Shutdown the engine");
|
||||
|
||||
EBUS_EVENT(CrySystemEventBus, OnCryEditorInitialized);
|
||||
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
@@ -465,7 +465,7 @@ void CGameEngine::SetLevelPath(const QString& path)
|
||||
const char* oldExtension = EditorUtils::LevelFile::GetOldCryFileExtension();
|
||||
const char* defaultExtension = EditorUtils::LevelFile::GetDefaultFileExtension();
|
||||
|
||||
// Store off if
|
||||
// Store off if
|
||||
if (QFileInfo(path + oldExtension).exists())
|
||||
{
|
||||
m_levelExtension = oldExtension;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ namespace AssetProcessor
|
||||
public AZ::Data::AssetCatalog
|
||||
{
|
||||
public:
|
||||
|
||||
|
||||
AZ_COMPONENT(ToolsAssetCatalogComponent, "{AE68E46B-0E21-499A-8309-41408BCBE4BF}");
|
||||
|
||||
ToolsAssetCatalogComponent() = default;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,243 +16,239 @@
|
||||
#include <Rendering/SharedBuffer.h>
|
||||
#include <Rendering/HairCommon.h>
|
||||
|
||||
namespace AZ
|
||||
namespace AZ::Render
|
||||
{
|
||||
namespace Render
|
||||
//! Setting the constructor as private will create compile error to remind the developer to set
|
||||
//! the buffer Init in the FeatureProcessor and initialize properly
|
||||
|
||||
SharedBuffer::SharedBuffer()
|
||||
{
|
||||
//! Setting the constructor as private will create compile error to remind the developer to set
|
||||
//! the buffer Init in the FeatureProcessor and initialize properly
|
||||
AZ_Warning("SharedBuffer", false, "Missing information to properly create SharedBuffer. Init is required");
|
||||
}
|
||||
|
||||
SharedBuffer::SharedBuffer()
|
||||
SharedBuffer::SharedBuffer(AZStd::string bufferName, AZStd::vector<SrgBufferDescriptor>& buffersDescriptors)
|
||||
{
|
||||
m_bufferName = bufferName;
|
||||
Init(bufferName, buffersDescriptors);
|
||||
}
|
||||
|
||||
SharedBuffer::~SharedBuffer()
|
||||
{
|
||||
m_bufferAsset = {};
|
||||
}
|
||||
|
||||
//! Crucial method that will ensure that the alignment for the BufferViews is always kept.
|
||||
//! This is important when requesting a BufferView as the offset needs to be aligned according
|
||||
//! to the element type of the buffer.
|
||||
void SharedBuffer::CalculateAlignment(AZStd::vector<SrgBufferDescriptor>& buffersDescriptors)
|
||||
{
|
||||
m_alignment = 1;
|
||||
for (uint8_t bufferIndex = 0; bufferIndex < buffersDescriptors.size() ; ++bufferIndex)
|
||||
{
|
||||
AZ_Warning("SharedBuffer", false, "Missing information to properly create SharedBuffer. Init is required");
|
||||
// Using the least common multiple enables resource views to be typed and ensures they can get
|
||||
// an offset in bytes that is a multiple of an element count
|
||||
m_alignment = std::lcm(m_alignment, buffersDescriptors[bufferIndex].m_elementSize);
|
||||
}
|
||||
}
|
||||
|
||||
void SharedBuffer::InitAllocator()
|
||||
{
|
||||
RHI::FreeListAllocator::Descriptor allocatorDescriptor;
|
||||
allocatorDescriptor.m_alignmentInBytes = m_alignment;
|
||||
allocatorDescriptor.m_capacityInBytes = m_sizeInBytes;
|
||||
allocatorDescriptor.m_policy = RHI::FreeListAllocatorPolicy::BestFit;
|
||||
allocatorDescriptor.m_garbageCollectLatency = 0;
|
||||
m_freeListAllocator.Init(allocatorDescriptor);
|
||||
}
|
||||
|
||||
void SharedBuffer::CreateBuffer()
|
||||
{
|
||||
SrgBufferDescriptor descriptor = SrgBufferDescriptor(
|
||||
RPI::CommonBufferPoolType::ReadWrite, RHI::Format::Unknown,
|
||||
sizeof(float), uint32_t(m_sizeInBytes / sizeof(float)),
|
||||
Name{ "HairSharedDynamicBuffer" }, Name{ "m_skinnedHairSharedBuffer" }, 0, 0
|
||||
);
|
||||
m_buffer = Hair::UtilityClass::CreateBuffer("Hair Gem", descriptor, nullptr);
|
||||
}
|
||||
|
||||
void SharedBuffer::CreateBufferAsset()
|
||||
{
|
||||
// Create the shared buffer pool
|
||||
{
|
||||
auto bufferPoolDesc = AZStd::make_unique<RHI::BufferPoolDescriptor>();
|
||||
// Output buffers are both written to during skinning and used as input assembly buffers
|
||||
bufferPoolDesc->m_bindFlags = RHI::BufferBindFlags::ShaderReadWrite | RHI::BufferBindFlags::Indirect;
|
||||
bufferPoolDesc->m_heapMemoryLevel = RHI::HeapMemoryLevel::Device;
|
||||
bufferPoolDesc->m_hostMemoryAccess = RHI::HostMemoryAccess::Write;
|
||||
|
||||
RPI::ResourcePoolAssetCreator creator;
|
||||
creator.Begin(Uuid::CreateRandom());
|
||||
creator.SetPoolDescriptor(AZStd::move(bufferPoolDesc));
|
||||
creator.SetPoolName("SharedBufferPool");
|
||||
creator.End(m_bufferPoolAsset);
|
||||
}
|
||||
|
||||
SharedBuffer::SharedBuffer(AZStd::string bufferName, AZStd::vector<SrgBufferDescriptor>& buffersDescriptors)
|
||||
// Create the shared buffer
|
||||
{
|
||||
m_bufferName = bufferName;
|
||||
Init(bufferName, buffersDescriptors);
|
||||
RPI::BufferAssetCreator creator;
|
||||
Uuid uuid = Uuid::CreateRandom();
|
||||
creator.Begin(uuid);
|
||||
creator.SetBufferName(m_bufferName);
|
||||
creator.SetPoolAsset(m_bufferPoolAsset);
|
||||
|
||||
RHI::BufferDescriptor bufferDescriptor;
|
||||
bufferDescriptor.m_bindFlags = RHI::BufferBindFlags::ShaderReadWrite | RHI::BufferBindFlags::Indirect;
|
||||
bufferDescriptor.m_byteCount = m_sizeInBytes;
|
||||
bufferDescriptor.m_alignment = m_alignment;
|
||||
creator.SetBuffer(nullptr, 0, bufferDescriptor);
|
||||
|
||||
RHI::BufferViewDescriptor viewDescriptor;
|
||||
viewDescriptor.m_elementFormat = RHI::Format::Unknown;
|
||||
|
||||
// [To Do] - set this as AZ::Vector4 for offset approach shader code optimization
|
||||
viewDescriptor.m_elementSize = sizeof(float);
|
||||
viewDescriptor.m_elementCount = aznumeric_cast<uint32_t>(m_sizeInBytes) / sizeof(float);
|
||||
viewDescriptor.m_elementOffset = 0;
|
||||
creator.SetBufferViewDescriptor(viewDescriptor);
|
||||
|
||||
creator.End(m_bufferAsset);
|
||||
}
|
||||
}
|
||||
|
||||
SharedBuffer::~SharedBuffer()
|
||||
{
|
||||
m_bufferAsset = {};
|
||||
}
|
||||
void SharedBuffer::Init(AZStd::string bufferName, AZStd::vector<SrgBufferDescriptor>& buffersDescriptors)
|
||||
{
|
||||
m_bufferName = bufferName;
|
||||
// m_sizeInBytes = 256u * (1024u * 1024u);
|
||||
//
|
||||
// [To Do] replace this with max size request for allocation that can be given by the calling function
|
||||
// This has the following problems:
|
||||
// 1. The need to have this aggregated size in advance
|
||||
// 2. The size might grow dynamically between frames
|
||||
// 3. Due to having several stream buffers (position, tangent, structured), alignment padding
|
||||
// size calculation must be added.
|
||||
// Requirement: the buffer already has an assert on allocation beyond the memory. In the future it should
|
||||
// support greedy memory allocation when memory has reached its end. This must not invalidate the buffer during
|
||||
// the current frame, hence allocation of second buffer, fence and a copy must take place.
|
||||
|
||||
//! Crucial method that will ensure that the alignment for the BufferViews is always kept.
|
||||
//! This is important when requesting a BufferView as the offset needs to be aligned according
|
||||
//! to the element type of the buffer.
|
||||
void SharedBuffer::CalculateAlignment(AZStd::vector<SrgBufferDescriptor>& buffersDescriptors)
|
||||
{
|
||||
m_alignment = 1;
|
||||
for (uint8_t bufferIndex = 0; bufferIndex < buffersDescriptors.size() ; ++bufferIndex)
|
||||
{
|
||||
// Using the least common multiple enables resource views to be typed and ensures they can get
|
||||
// an offset in bytes that is a multiple of an element count
|
||||
m_alignment = std::lcm(m_alignment, buffersDescriptors[bufferIndex].m_elementSize);
|
||||
}
|
||||
}
|
||||
CalculateAlignment(buffersDescriptors);
|
||||
|
||||
void SharedBuffer::InitAllocator()
|
||||
{
|
||||
RHI::FreeListAllocator::Descriptor allocatorDescriptor;
|
||||
allocatorDescriptor.m_alignmentInBytes = m_alignment;
|
||||
allocatorDescriptor.m_capacityInBytes = m_sizeInBytes;
|
||||
allocatorDescriptor.m_policy = RHI::FreeListAllocatorPolicy::BestFit;
|
||||
allocatorDescriptor.m_garbageCollectLatency = 0;
|
||||
m_freeListAllocator.Init(allocatorDescriptor);
|
||||
}
|
||||
InitAllocator();
|
||||
|
||||
void SharedBuffer::CreateBuffer()
|
||||
{
|
||||
SrgBufferDescriptor descriptor = SrgBufferDescriptor(
|
||||
RPI::CommonBufferPoolType::ReadWrite, RHI::Format::Unknown,
|
||||
sizeof(float), uint32_t(m_sizeInBytes / sizeof(float)),
|
||||
Name{ "HairSharedDynamicBuffer" }, Name{ "m_skinnedHairSharedBuffer" }, 0, 0
|
||||
);
|
||||
m_buffer = Hair::UtilityClass::CreateBuffer("Hair Gem", descriptor, nullptr);
|
||||
}
|
||||
|
||||
void SharedBuffer::CreateBufferAsset()
|
||||
{
|
||||
// Create the shared buffer pool
|
||||
{
|
||||
auto bufferPoolDesc = AZStd::make_unique<RHI::BufferPoolDescriptor>();
|
||||
// Output buffers are both written to during skinning and used as input assembly buffers
|
||||
bufferPoolDesc->m_bindFlags = RHI::BufferBindFlags::ShaderReadWrite | RHI::BufferBindFlags::Indirect;
|
||||
bufferPoolDesc->m_heapMemoryLevel = RHI::HeapMemoryLevel::Device;
|
||||
bufferPoolDesc->m_hostMemoryAccess = RHI::HostMemoryAccess::Write;
|
||||
CreateBuffer();
|
||||
|
||||
RPI::ResourcePoolAssetCreator creator;
|
||||
creator.Begin(Uuid::CreateRandom());
|
||||
creator.SetPoolDescriptor(AZStd::move(bufferPoolDesc));
|
||||
creator.SetPoolName("SharedBufferPool");
|
||||
creator.End(m_bufferPoolAsset);
|
||||
}
|
||||
SystemTickBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
// Create the shared buffer
|
||||
{
|
||||
RPI::BufferAssetCreator creator;
|
||||
Uuid uuid = Uuid::CreateRandom();
|
||||
creator.Begin(uuid);
|
||||
creator.SetBufferName(m_bufferName);
|
||||
creator.SetPoolAsset(m_bufferPoolAsset);
|
||||
|
||||
RHI::BufferDescriptor bufferDescriptor;
|
||||
bufferDescriptor.m_bindFlags = RHI::BufferBindFlags::ShaderReadWrite | RHI::BufferBindFlags::Indirect;
|
||||
bufferDescriptor.m_byteCount = m_sizeInBytes;
|
||||
bufferDescriptor.m_alignment = m_alignment;
|
||||
creator.SetBuffer(nullptr, 0, bufferDescriptor);
|
||||
|
||||
RHI::BufferViewDescriptor viewDescriptor;
|
||||
viewDescriptor.m_elementFormat = RHI::Format::Unknown;
|
||||
|
||||
// [To Do] - set this as AZ::Vector4 for offset approach shader code optimization
|
||||
viewDescriptor.m_elementSize = sizeof(float);
|
||||
viewDescriptor.m_elementCount = aznumeric_cast<uint32_t>(m_sizeInBytes) / sizeof(float);
|
||||
viewDescriptor.m_elementOffset = 0;
|
||||
creator.SetBufferViewDescriptor(viewDescriptor);
|
||||
|
||||
creator.End(m_bufferAsset);
|
||||
}
|
||||
}
|
||||
|
||||
void SharedBuffer::Init(AZStd::string bufferName, AZStd::vector<SrgBufferDescriptor>& buffersDescriptors)
|
||||
{
|
||||
m_bufferName = bufferName;
|
||||
// m_sizeInBytes = 256u * (1024u * 1024u);
|
||||
//
|
||||
// [To Do] replace this with max size request for allocation that can be given by the calling function
|
||||
// This has the following problems:
|
||||
// 1. The need to have this aggregated size in advance
|
||||
// 2. The size might grow dynamically between frames
|
||||
// 3. Due to having several stream buffers (position, tangent, structured), alignment padding
|
||||
// size calculation must be added.
|
||||
// Requirement: the buffer already has an assert on allocation beyond the memory. In the future it should
|
||||
// support greedy memory allocation when memory has reached its end. This must not invalidate the buffer during
|
||||
// the current frame, hence allocation of second buffer, fence and a copy must take place.
|
||||
|
||||
CalculateAlignment(buffersDescriptors);
|
||||
|
||||
InitAllocator();
|
||||
|
||||
CreateBuffer();
|
||||
|
||||
SystemTickBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
AZStd::intrusive_ptr<HairSharedBufferAllocation> SharedBuffer::Allocate(size_t byteCount)
|
||||
{
|
||||
RHI::VirtualAddress result;
|
||||
{
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
|
||||
result = m_freeListAllocator.Allocate(byteCount, m_alignment);
|
||||
}
|
||||
|
||||
if (result.IsValid())
|
||||
{
|
||||
return aznew HairSharedBufferAllocation(result);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SharedBuffer::DeAllocate(RHI::VirtualAddress allocation)
|
||||
{
|
||||
if (allocation.IsValid())
|
||||
{
|
||||
{
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
|
||||
m_freeListAllocator.DeAllocate(allocation);
|
||||
}
|
||||
|
||||
m_memoryWasFreed = true;
|
||||
m_broadcastMemoryAvailableEvent = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SharedBuffer::DeAllocateNoSignal(RHI::VirtualAddress allocation)
|
||||
{
|
||||
if (allocation.IsValid())
|
||||
{
|
||||
{
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
|
||||
m_freeListAllocator.DeAllocate(allocation);
|
||||
}
|
||||
m_memoryWasFreed = true;
|
||||
}
|
||||
}
|
||||
|
||||
Data::Asset<RPI::BufferAsset> SharedBuffer::GetBufferAsset() const
|
||||
{
|
||||
return m_bufferAsset;
|
||||
}
|
||||
|
||||
Data::Instance<RPI::Buffer> SharedBuffer::GetBuffer()
|
||||
{
|
||||
if (!m_buffer)
|
||||
{
|
||||
m_buffer = RPI::Buffer::FindOrCreate(m_bufferAsset);
|
||||
}
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
//! Update buffer's content with sourceData at an offset of bufferByteOffset
|
||||
bool SharedBuffer::UpdateData(const void* sourceData, uint64_t sourceDataSizeInBytes, uint64_t bufferByteOffset)
|
||||
AZStd::intrusive_ptr<HairSharedBufferAllocation> SharedBuffer::Allocate(size_t byteCount)
|
||||
{
|
||||
RHI::VirtualAddress result;
|
||||
{
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
|
||||
if (m_buffer.get())
|
||||
result = m_freeListAllocator.Allocate(byteCount, m_alignment);
|
||||
}
|
||||
|
||||
if (result.IsValid())
|
||||
{
|
||||
return aznew HairSharedBufferAllocation(result);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SharedBuffer::DeAllocate(RHI::VirtualAddress allocation)
|
||||
{
|
||||
if (allocation.IsValid())
|
||||
{
|
||||
{
|
||||
return m_buffer->UpdateData(sourceData, sourceDataSizeInBytes, bufferByteOffset);
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
|
||||
m_freeListAllocator.DeAllocate(allocation);
|
||||
}
|
||||
AZ_Assert(false, "SharedBuffer error in data allocation - the buffer doesn't exist yet");
|
||||
return false;
|
||||
}
|
||||
|
||||
void SharedBuffer::OnSystemTick()
|
||||
{
|
||||
GarbageCollect();
|
||||
m_memoryWasFreed = true;
|
||||
m_broadcastMemoryAvailableEvent = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SharedBuffer::GarbageCollect()
|
||||
void SharedBuffer::DeAllocateNoSignal(RHI::VirtualAddress allocation)
|
||||
{
|
||||
if (allocation.IsValid())
|
||||
{
|
||||
if (m_memoryWasFreed)
|
||||
{
|
||||
m_memoryWasFreed = false;
|
||||
{
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
|
||||
m_freeListAllocator.GarbageCollect();
|
||||
}
|
||||
if (m_broadcastMemoryAvailableEvent)
|
||||
{
|
||||
SharedBufferNotificationBus::Broadcast(&SharedBufferNotificationBus::Events::OnSharedBufferMemoryAvailable);
|
||||
m_broadcastMemoryAvailableEvent = false;
|
||||
}
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
|
||||
m_freeListAllocator.DeAllocate(allocation);
|
||||
}
|
||||
m_memoryWasFreed = true;
|
||||
}
|
||||
}
|
||||
|
||||
Data::Asset<RPI::BufferAsset> SharedBuffer::GetBufferAsset() const
|
||||
{
|
||||
return m_bufferAsset;
|
||||
}
|
||||
|
||||
Data::Instance<RPI::Buffer> SharedBuffer::GetBuffer()
|
||||
{
|
||||
if (!m_buffer)
|
||||
{
|
||||
m_buffer = RPI::Buffer::FindOrCreate(m_bufferAsset);
|
||||
}
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
//! Update buffer's content with sourceData at an offset of bufferByteOffset
|
||||
bool SharedBuffer::UpdateData(const void* sourceData, uint64_t sourceDataSizeInBytes, uint64_t bufferByteOffset)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
|
||||
if (m_buffer.get())
|
||||
{
|
||||
return m_buffer->UpdateData(sourceData, sourceDataSizeInBytes, bufferByteOffset);
|
||||
}
|
||||
AZ_Assert(false, "SharedBuffer error in data allocation - the buffer doesn't exist yet");
|
||||
return false;
|
||||
}
|
||||
|
||||
void SharedBuffer::OnSystemTick()
|
||||
{
|
||||
GarbageCollect();
|
||||
}
|
||||
|
||||
void SharedBuffer::GarbageCollect()
|
||||
{
|
||||
if (m_memoryWasFreed)
|
||||
{
|
||||
m_memoryWasFreed = false;
|
||||
{
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_allocatorMutex);
|
||||
m_freeListAllocator.GarbageCollect();
|
||||
}
|
||||
if (m_broadcastMemoryAvailableEvent)
|
||||
{
|
||||
SharedBufferNotificationBus::Broadcast(&SharedBufferNotificationBus::Events::OnSharedBufferMemoryAvailable);
|
||||
m_broadcastMemoryAvailableEvent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! Utility function to create a resource view of different type than the shared buffer data.
|
||||
//! Since this class is sub-buffer container, this method should be used after creating
|
||||
//! a new allocation to be used as a sub-buffer.
|
||||
//! Notice the alignment required according to the element size - this might need
|
||||
RHI::BufferViewDescriptor SharedBuffer::CreateResourceViewWithDifferentFormat(
|
||||
uint32_t offsetInBytes, uint32_t elementCount, uint32_t elementSize,
|
||||
RHI::Format format, RHI::BufferBindFlags overrideBindFlags)
|
||||
{
|
||||
RHI::BufferViewDescriptor viewDescriptor;
|
||||
//! Utility function to create a resource view of different type than the shared buffer data.
|
||||
//! Since this class is sub-buffer container, this method should be used after creating
|
||||
//! a new allocation to be used as a sub-buffer.
|
||||
//! Notice the alignment required according to the element size - this might need
|
||||
RHI::BufferViewDescriptor SharedBuffer::CreateResourceViewWithDifferentFormat(
|
||||
uint32_t offsetInBytes, uint32_t elementCount, uint32_t elementSize,
|
||||
RHI::Format format, RHI::BufferBindFlags overrideBindFlags)
|
||||
{
|
||||
RHI::BufferViewDescriptor viewDescriptor;
|
||||
|
||||
// In the following line I use the element size and not the size based of the
|
||||
// element format since in the more interesting case of structured buffer, the
|
||||
// size will result in an error.
|
||||
uint32_t elementOffset = offsetInBytes / elementSize;
|
||||
viewDescriptor.m_elementOffset = elementOffset;
|
||||
viewDescriptor.m_elementCount = elementCount;
|
||||
viewDescriptor.m_elementFormat = format;
|
||||
viewDescriptor.m_elementSize = elementSize;
|
||||
viewDescriptor.m_overrideBindFlags = overrideBindFlags;
|
||||
return viewDescriptor;
|
||||
}
|
||||
|
||||
}// namespace Render
|
||||
}// namespace AZ
|
||||
// In the following line I use the element size and not the size based of the
|
||||
// element format since in the more interesting case of structured buffer, the
|
||||
// size will result in an error.
|
||||
uint32_t elementOffset = offsetInBytes / elementSize;
|
||||
viewDescriptor.m_elementOffset = elementOffset;
|
||||
viewDescriptor.m_elementCount = elementCount;
|
||||
viewDescriptor.m_elementFormat = format;
|
||||
viewDescriptor.m_elementSize = elementSize;
|
||||
viewDescriptor.m_overrideBindFlags = overrideBindFlags;
|
||||
return viewDescriptor;
|
||||
}
|
||||
|
||||
} // namespace AZ::Render
|
||||
|
||||
@@ -435,7 +435,7 @@ namespace Audio
|
||||
}
|
||||
|
||||
// Format: "relative/path/filename.ext (230 KiB) [2]"
|
||||
auxGeom.Draw2dLabel(positionX, positionY, entryDrawSize, color, false,
|
||||
auxGeom.Draw2dLabel(positionX, positionY, entryDrawSize, color, false,
|
||||
"%s (%zu %s) [%zu]",
|
||||
audioFileEntry->m_filePath.c_str(),
|
||||
fileSize,
|
||||
@@ -626,7 +626,7 @@ namespace Audio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool CFileCacheManager::AllocateMemoryBlockInternal(CATLAudioFileEntry* const audioFileEntry)
|
||||
{
|
||||
|
||||
@@ -1051,7 +1051,7 @@ public:
|
||||
// Replace with a new LocalFileIO...
|
||||
m_fileIO = AZStd::make_unique<AZ::IO::LocalFileIO>();
|
||||
AZ::IO::FileIOBase::SetInstance(m_fileIO.get());
|
||||
|
||||
|
||||
AZStd::string rootFolder(AZ::Test::GetCurrentExecutablePath());
|
||||
AZ::StringFunc::Path::Join(rootFolder.c_str(), "Test.Assets/Gems/AudioSystem/ATLData", rootFolder);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Audio
|
||||
MOCK_METHOD3(FinishCachingFileInternal, bool(CATLAudioFileEntry* const, AZ::IO::SizeType, AZ::IO::IStreamerTypes::RequestStatus));
|
||||
|
||||
MOCK_METHOD1(FinishAsyncStreamRequest, void(AZ::IO::FileRequestHandle));
|
||||
|
||||
|
||||
MOCK_METHOD1(AllocateMemoryBlockInternal, bool(CATLAudioFileEntry* const));
|
||||
MOCK_METHOD1(UncacheFile, void(CATLAudioFileEntry* const));
|
||||
MOCK_METHOD0(TryToUncacheFiles, void());
|
||||
|
||||
@@ -1032,7 +1032,7 @@ namespace EMotionFX
|
||||
AZ::Vector3* normals = (AZ::Vector3*)mesh->FindVertexData(Mesh::ATTRIB_NORMALS);
|
||||
AZ::Vector3 norm = MCore::BarycentricInterpolate<AZ::Vector3>(
|
||||
closestBaryU, closestBaryV,
|
||||
normals[closestIndices[0]], normals[closestIndices[1]], normals[closestIndices[2]]);
|
||||
normals[closestIndices[0]], normals[closestIndices[1]], normals[closestIndices[2]]);
|
||||
norm = closestTransform.TransformVector(norm);
|
||||
norm.Normalize();
|
||||
*outNormal = norm;
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace LmbrCentral
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,468 +18,466 @@
|
||||
#include <Source/Joint/PhysXJointUtils.h>
|
||||
#include <Include/PhysX/NativeTypeIdentifiers.h>
|
||||
|
||||
namespace PhysX {
|
||||
namespace Utils
|
||||
namespace PhysX::Utils
|
||||
{
|
||||
struct PxJointActorData
|
||||
{
|
||||
struct PxJointActorData
|
||||
static PxJointActorData InvalidPxJointActorData;
|
||||
|
||||
physx::PxRigidActor* parentActor = nullptr;
|
||||
physx::PxRigidActor* childActor = nullptr;
|
||||
};
|
||||
PxJointActorData PxJointActorData::InvalidPxJointActorData;
|
||||
|
||||
PxJointActorData GetJointPxActors(
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle)
|
||||
{
|
||||
auto* parentBody = GetSimulatedBodyFromHandle(sceneHandle, parentBodyHandle);
|
||||
auto* childBody = GetSimulatedBodyFromHandle(sceneHandle, childBodyHandle);
|
||||
|
||||
if (!IsAtLeastOneDynamic(parentBody, childBody))
|
||||
{
|
||||
static PxJointActorData InvalidPxJointActorData;
|
||||
AZ_Warning("PhysX Joint", false, "CreateJoint failed - at least one body must be dynamic.");
|
||||
return PxJointActorData::InvalidPxJointActorData;
|
||||
}
|
||||
|
||||
physx::PxRigidActor* parentActor = nullptr;
|
||||
physx::PxRigidActor* childActor = nullptr;
|
||||
physx::PxRigidActor* parentActor = GetPxRigidActor(sceneHandle, parentBodyHandle);
|
||||
physx::PxRigidActor* childActor = GetPxRigidActor(sceneHandle, childBodyHandle);
|
||||
|
||||
if (!parentActor && !childActor)
|
||||
{
|
||||
AZ_Warning("PhysX Joint", false, "CreateJoint failed - at least one body must be a PxRigidActor.");
|
||||
return PxJointActorData::InvalidPxJointActorData;
|
||||
}
|
||||
|
||||
return PxJointActorData{
|
||||
parentActor,
|
||||
childActor
|
||||
};
|
||||
PxJointActorData PxJointActorData::InvalidPxJointActorData;
|
||||
}
|
||||
|
||||
PxJointActorData GetJointPxActors(
|
||||
bool IsAtLeastOneDynamic(AzPhysics::SimulatedBody* body0,
|
||||
AzPhysics::SimulatedBody* body1)
|
||||
{
|
||||
for (const AzPhysics::SimulatedBody* body : { body0, body1 })
|
||||
{
|
||||
if (body)
|
||||
{
|
||||
if (body->GetNativeType() == NativeTypeIdentifiers::RigidBody ||
|
||||
body->GetNativeType() == NativeTypeIdentifiers::ArticulationLink)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
physx::PxRigidActor* GetPxRigidActor(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle worldBodyHandle)
|
||||
{
|
||||
auto* worldBody = GetSimulatedBodyFromHandle(sceneHandle, worldBodyHandle);
|
||||
if (worldBody != nullptr
|
||||
&& static_cast<physx::PxBase*>(worldBody->GetNativePointer())->is<physx::PxRigidActor>())
|
||||
{
|
||||
return static_cast<physx::PxRigidActor*>(worldBody->GetNativePointer());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ReleasePxJoint(physx::PxJoint* joint)
|
||||
{
|
||||
PHYSX_SCENE_WRITE_LOCK(joint->getScene());
|
||||
joint->userData = nullptr;
|
||||
joint->release();
|
||||
}
|
||||
|
||||
AzPhysics::SimulatedBody* GetSimulatedBodyFromHandle(AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle bodyHandle)
|
||||
{
|
||||
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
|
||||
{
|
||||
return sceneInterface->GetSimulatedBodyFromHandle(sceneHandle, bodyHandle);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void InitializeGenericProperties(const JointGenericProperties& properties, physx::PxJoint* nativeJoint)
|
||||
{
|
||||
if (!nativeJoint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
PHYSX_SCENE_WRITE_LOCK(nativeJoint->getScene());
|
||||
nativeJoint->setConstraintFlag(
|
||||
physx::PxConstraintFlag::eCOLLISION_ENABLED,
|
||||
properties.IsFlagSet(JointGenericProperties::GenericJointFlag::SelfCollide));
|
||||
|
||||
if (properties.IsFlagSet(JointGenericProperties::GenericJointFlag::Breakable))
|
||||
{
|
||||
nativeJoint->setBreakForce(properties.m_forceMax, properties.m_torqueMax);
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeSphericalLimitProperties(const JointLimitProperties& properties, physx::PxSphericalJoint* nativeJoint)
|
||||
{
|
||||
if (!nativeJoint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!properties.m_isLimited)
|
||||
{
|
||||
nativeJoint->setSphericalJointFlag(physx::PxSphericalJointFlag::eLIMIT_ENABLED, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hard limit uses a tolerance value (distance to limit at which limit becomes active).
|
||||
// Soft limit allows angle to exceed limit but springs back with configurable spring stiffness and damping.
|
||||
physx::PxJointLimitCone swingLimit(
|
||||
AZ::DegToRad(properties.m_limitFirst),
|
||||
AZ::DegToRad(properties.m_limitSecond),
|
||||
properties.m_tolerance);
|
||||
|
||||
if (properties.m_isSoftLimit)
|
||||
{
|
||||
swingLimit.stiffness = properties.m_stiffness;
|
||||
swingLimit.damping = properties.m_damping;
|
||||
}
|
||||
|
||||
nativeJoint->setLimitCone(swingLimit);
|
||||
nativeJoint->setSphericalJointFlag(physx::PxSphericalJointFlag::eLIMIT_ENABLED, true);
|
||||
}
|
||||
|
||||
void InitializeRevoluteLimitProperties(const JointLimitProperties& properties, physx::PxRevoluteJoint* nativeJoint)
|
||||
{
|
||||
if (!nativeJoint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!properties.m_isLimited)
|
||||
{
|
||||
nativeJoint->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eLIMIT_ENABLED, false);
|
||||
return;
|
||||
}
|
||||
|
||||
physx::PxJointAngularLimitPair limitPair(
|
||||
AZ::DegToRad(properties.m_limitSecond),
|
||||
AZ::DegToRad(properties.m_limitFirst),
|
||||
properties.m_tolerance);
|
||||
|
||||
if (properties.m_isSoftLimit)
|
||||
{
|
||||
limitPair.stiffness = properties.m_stiffness;
|
||||
limitPair.damping = properties.m_damping;
|
||||
}
|
||||
|
||||
nativeJoint->setLimit(limitPair);
|
||||
nativeJoint->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eLIMIT_ENABLED, true);
|
||||
}
|
||||
|
||||
namespace PxJointFactories
|
||||
{
|
||||
PxJointUniquePtr CreatePxD6Joint(
|
||||
const PhysX::D6JointLimitConfiguration& configuration,
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle)
|
||||
{
|
||||
auto* parentBody = GetSimulatedBodyFromHandle(sceneHandle, parentBodyHandle);
|
||||
auto* childBody = GetSimulatedBodyFromHandle(sceneHandle, childBodyHandle);
|
||||
PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
|
||||
|
||||
if (!IsAtLeastOneDynamic(parentBody, childBody))
|
||||
{
|
||||
AZ_Warning("PhysX Joint", false, "CreateJoint failed - at least one body must be dynamic.");
|
||||
return PxJointActorData::InvalidPxJointActorData;
|
||||
}
|
||||
|
||||
physx::PxRigidActor* parentActor = GetPxRigidActor(sceneHandle, parentBodyHandle);
|
||||
physx::PxRigidActor* childActor = GetPxRigidActor(sceneHandle, childBodyHandle);
|
||||
|
||||
if (!parentActor && !childActor)
|
||||
if (actorData.parentActor == nullptr && actorData.childActor == nullptr)
|
||||
{
|
||||
AZ_Warning("PhysX Joint", false, "CreateJoint failed - at least one body must be a PxRigidActor.");
|
||||
return PxJointActorData::InvalidPxJointActorData;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return PxJointActorData{
|
||||
parentActor,
|
||||
childActor
|
||||
};
|
||||
const physx::PxTransform parentWorldTransform =
|
||||
actorData.parentActor ? actorData.parentActor->getGlobalPose() : physx::PxTransform(physx::PxIdentity);
|
||||
const physx::PxTransform childWorldTransform =
|
||||
actorData.childActor ? actorData.childActor->getGlobalPose() : physx::PxTransform(physx::PxIdentity);
|
||||
const physx::PxVec3 childOffset = childWorldTransform.p - parentWorldTransform.p;
|
||||
physx::PxTransform parentLocalTransform(PxMathConvert(configuration.m_parentLocalRotation).getNormalized());
|
||||
const physx::PxTransform childLocalTransform(PxMathConvert(configuration.m_childLocalRotation).getNormalized());
|
||||
parentLocalTransform.p = parentWorldTransform.q.rotateInv(childOffset);
|
||||
|
||||
physx::PxD6Joint* joint = PxD6JointCreate(PxGetPhysics(),
|
||||
actorData.parentActor, parentLocalTransform, actorData.childActor, childLocalTransform);
|
||||
|
||||
joint->setMotion(physx::PxD6Axis::eTWIST, physx::PxD6Motion::eLIMITED);
|
||||
joint->setMotion(physx::PxD6Axis::eSWING1, physx::PxD6Motion::eLIMITED);
|
||||
joint->setMotion(physx::PxD6Axis::eSWING2, physx::PxD6Motion::eLIMITED);
|
||||
|
||||
AZ_Warning("PhysX Joint",
|
||||
configuration.m_swingLimitY >= JointConstants::MinSwingLimitDegrees && configuration.m_swingLimitZ >= JointConstants::MinSwingLimitDegrees,
|
||||
"Very small swing limit requested for joint between \"%s\" and \"%s\", increasing to %f degrees to improve stability",
|
||||
actorData.parentActor ? actorData.parentActor->getName() : "world",
|
||||
actorData.childActor ? actorData.childActor->getName() : "world",
|
||||
JointConstants::MinSwingLimitDegrees);
|
||||
|
||||
const float swingLimitY = AZ::DegToRad(AZ::GetMax(JointConstants::MinSwingLimitDegrees, configuration.m_swingLimitY));
|
||||
const float swingLimitZ = AZ::DegToRad(AZ::GetMax(JointConstants::MinSwingLimitDegrees, configuration.m_swingLimitZ));
|
||||
physx::PxJointLimitCone limitCone(swingLimitY, swingLimitZ);
|
||||
joint->setSwingLimit(limitCone);
|
||||
|
||||
float twistLower = AZ::DegToRad(AZStd::GetMin(configuration.m_twistLimitLower, configuration.m_twistLimitUpper));
|
||||
float twistUpper = AZ::DegToRad(AZStd::GetMax(configuration.m_twistLimitLower, configuration.m_twistLimitUpper));
|
||||
// make sure there is at least a small difference between the lower and upper limits to avoid problems in PhysX
|
||||
const float minTwistLimitRangeRadians = AZ::DegToRad(JointConstants::MinTwistLimitRangeDegrees);
|
||||
if (const float twistLimitRange = twistUpper - twistLower;
|
||||
twistLimitRange < minTwistLimitRangeRadians)
|
||||
{
|
||||
if (twistUpper > 0.0f)
|
||||
{
|
||||
twistLower -= (minTwistLimitRangeRadians - twistLimitRange);
|
||||
}
|
||||
else
|
||||
{
|
||||
twistUpper += (minTwistLimitRangeRadians - twistLimitRange);
|
||||
}
|
||||
}
|
||||
physx::PxJointAngularLimitPair twistLimitPair(twistLower, twistUpper);
|
||||
joint->setTwistLimit(twistLimitPair);
|
||||
|
||||
return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
|
||||
}
|
||||
|
||||
bool IsAtLeastOneDynamic(AzPhysics::SimulatedBody* body0,
|
||||
AzPhysics::SimulatedBody* body1)
|
||||
PxJointUniquePtr CreatePxFixedJoint(
|
||||
const PhysX::FixedJointConfiguration& configuration,
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle)
|
||||
{
|
||||
for (const AzPhysics::SimulatedBody* body : { body0, body1 })
|
||||
PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
|
||||
|
||||
//only check the child actor, as a null parent actor means this joint is a global constraint.
|
||||
if (!actorData.childActor)
|
||||
{
|
||||
if (body)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
physx::PxFixedJoint* joint;
|
||||
const AZ::Transform parentLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_parentLocalRotation, configuration.m_parentLocalPosition);
|
||||
const AZ::Transform childLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_childLocalRotation, configuration.m_childLocalPosition);
|
||||
|
||||
{
|
||||
PHYSX_SCENE_READ_LOCK(actorData.childActor->getScene());
|
||||
joint = physx::PxFixedJointCreate(
|
||||
PxGetPhysics(),
|
||||
actorData.parentActor, PxMathConvert(parentLocalTM),
|
||||
actorData.childActor, PxMathConvert(childLocalTM));
|
||||
}
|
||||
|
||||
InitializeGenericProperties(
|
||||
configuration.m_genericProperties,
|
||||
static_cast<physx::PxJoint*>(joint));
|
||||
|
||||
return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
|
||||
}
|
||||
|
||||
PxJointUniquePtr CreatePxBallJoint(
|
||||
const PhysX::BallJointConfiguration& configuration,
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle)
|
||||
{
|
||||
PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
|
||||
|
||||
// only check the child actor, as a null parent actor means this joint is a global constraint.
|
||||
if (!actorData.childActor)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
physx::PxSphericalJoint* joint;
|
||||
const AZ::Transform parentLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_parentLocalRotation, configuration.m_parentLocalPosition);
|
||||
const AZ::Transform childLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_childLocalRotation, configuration.m_childLocalPosition);
|
||||
|
||||
{
|
||||
PHYSX_SCENE_READ_LOCK(actorData.childActor->getScene());
|
||||
joint = physx::PxSphericalJointCreate(PxGetPhysics(),
|
||||
actorData.parentActor, PxMathConvert(parentLocalTM),
|
||||
actorData.childActor, PxMathConvert(childLocalTM));
|
||||
}
|
||||
|
||||
InitializeSphericalLimitProperties(configuration.m_limitProperties, joint);
|
||||
InitializeGenericProperties(
|
||||
configuration.m_genericProperties,
|
||||
static_cast<physx::PxJoint*>(joint));
|
||||
|
||||
return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
|
||||
}
|
||||
|
||||
PxJointUniquePtr CreatePxHingeJoint(
|
||||
const PhysX::HingeJointConfiguration& configuration,
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle)
|
||||
{
|
||||
PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
|
||||
|
||||
// only check the child actor, as a null parent actor means this joint is a global constraint.
|
||||
if (!actorData.childActor)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
physx::PxRevoluteJoint* joint;
|
||||
const AZ::Transform parentLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_parentLocalRotation, configuration.m_parentLocalPosition);
|
||||
const AZ::Transform childLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_childLocalRotation, configuration.m_childLocalPosition);
|
||||
|
||||
{
|
||||
PHYSX_SCENE_READ_LOCK(actorData.childActor->getScene());
|
||||
joint = physx::PxRevoluteJointCreate(PxGetPhysics(),
|
||||
actorData.parentActor, PxMathConvert(parentLocalTM),
|
||||
actorData.childActor, PxMathConvert(childLocalTM));
|
||||
}
|
||||
|
||||
InitializeRevoluteLimitProperties(configuration.m_limitProperties, joint);
|
||||
InitializeGenericProperties(
|
||||
configuration.m_genericProperties,
|
||||
static_cast<physx::PxJoint*>(joint));
|
||||
|
||||
return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
|
||||
}
|
||||
} // namespace PxJointFactories
|
||||
|
||||
namespace Joints
|
||||
{
|
||||
bool IsD6SwingValid(float swingAngleY, float swingAngleZ, float swingLimitY, float swingLimitZ)
|
||||
{
|
||||
const float epsilon = AZ::Constants::FloatEpsilon;
|
||||
const float yFactor = AZStd::tan(0.25f * swingAngleY) / AZStd::GetMax(epsilon, AZStd::tan(0.25f * swingLimitY));
|
||||
const float zFactor = AZStd::tan(0.25f * swingAngleZ) / AZStd::GetMax(epsilon, AZStd::tan(0.25f * swingLimitZ));
|
||||
|
||||
return (yFactor * yFactor + zFactor * zFactor <= 1.0f + epsilon);
|
||||
}
|
||||
|
||||
void AppendD6SwingConeToLineBuffer(
|
||||
const AZ::Quaternion& parentLocalRotation,
|
||||
float swingAngleY,
|
||||
float swingAngleZ,
|
||||
float swingLimitY,
|
||||
float swingLimitZ,
|
||||
float scale,
|
||||
AZ::u32 angularSubdivisions,
|
||||
AZ::u32 radialSubdivisions,
|
||||
AZStd::vector<AZ::Vector3>& lineBufferOut,
|
||||
AZStd::vector<bool>& lineValidityBufferOut)
|
||||
{
|
||||
const AZ::u32 numLinesSwingCone = angularSubdivisions * (1u + radialSubdivisions);
|
||||
lineBufferOut.reserve(lineBufferOut.size() + 2u * numLinesSwingCone);
|
||||
lineValidityBufferOut.reserve(lineValidityBufferOut.size() + numLinesSwingCone);
|
||||
|
||||
// the orientation quat for a radial line in the cone can be represented in terms of sin and cos half angles
|
||||
// these expressions can be efficiently calculated using tan quarter angles as follows:
|
||||
// writing t = tan(x / 4)
|
||||
// sin(x / 2) = 2 * t / (1 + t * t)
|
||||
// cos(x / 2) = (1 - t * t) / (1 + t * t)
|
||||
const float tanQuarterSwingZ = AZStd::tan(0.25f * swingLimitZ);
|
||||
const float tanQuarterSwingY = AZStd::tan(0.25f * swingLimitY);
|
||||
|
||||
AZ::Vector3 previousRadialVector = AZ::Vector3::CreateZero();
|
||||
for (AZ::u32 angularIndex = 0; angularIndex <= angularSubdivisions; angularIndex++)
|
||||
{
|
||||
const float angle = AZ::Constants::TwoPi / angularSubdivisions * angularIndex;
|
||||
// the axis about which to rotate the x-axis to get the radial vector for this segment of the cone
|
||||
const AZ::Vector3 rotationAxis(0, -tanQuarterSwingY * sinf(angle), tanQuarterSwingZ * cosf(angle));
|
||||
const float normalizationFactor = rotationAxis.GetLengthSq();
|
||||
const AZ::Quaternion radialVectorRotation = 1.0f / (1.0f + normalizationFactor) *
|
||||
AZ::Quaternion::CreateFromVector3AndValue(2.0f * rotationAxis, 1.0f - normalizationFactor);
|
||||
const AZ::Vector3 radialVector =
|
||||
(parentLocalRotation * radialVectorRotation).TransformVector(AZ::Vector3::CreateAxisX(scale));
|
||||
|
||||
if (angularIndex > 0)
|
||||
{
|
||||
if (body->GetNativeType() == NativeTypeIdentifiers::RigidBody ||
|
||||
body->GetNativeType() == NativeTypeIdentifiers::ArticulationLink)
|
||||
for (AZ::u32 radialIndex = 1; radialIndex <= radialSubdivisions; radialIndex++)
|
||||
{
|
||||
return true;
|
||||
float radiusFraction = 1.0f / radialSubdivisions * radialIndex;
|
||||
lineBufferOut.push_back(radiusFraction * radialVector);
|
||||
lineBufferOut.push_back(radiusFraction * previousRadialVector);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
physx::PxRigidActor* GetPxRigidActor(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle worldBodyHandle)
|
||||
{
|
||||
auto* worldBody = GetSimulatedBodyFromHandle(sceneHandle, worldBodyHandle);
|
||||
if (worldBody != nullptr
|
||||
&& static_cast<physx::PxBase*>(worldBody->GetNativePointer())->is<physx::PxRigidActor>())
|
||||
{
|
||||
return static_cast<physx::PxRigidActor*>(worldBody->GetNativePointer());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ReleasePxJoint(physx::PxJoint* joint)
|
||||
{
|
||||
PHYSX_SCENE_WRITE_LOCK(joint->getScene());
|
||||
joint->userData = nullptr;
|
||||
joint->release();
|
||||
}
|
||||
|
||||
AzPhysics::SimulatedBody* GetSimulatedBodyFromHandle(AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle bodyHandle)
|
||||
{
|
||||
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
|
||||
{
|
||||
return sceneInterface->GetSimulatedBodyFromHandle(sceneHandle, bodyHandle);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void InitializeGenericProperties(const JointGenericProperties& properties, physx::PxJoint* nativeJoint)
|
||||
{
|
||||
if (!nativeJoint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
PHYSX_SCENE_WRITE_LOCK(nativeJoint->getScene());
|
||||
nativeJoint->setConstraintFlag(
|
||||
physx::PxConstraintFlag::eCOLLISION_ENABLED,
|
||||
properties.IsFlagSet(JointGenericProperties::GenericJointFlag::SelfCollide));
|
||||
|
||||
if (properties.IsFlagSet(JointGenericProperties::GenericJointFlag::Breakable))
|
||||
{
|
||||
nativeJoint->setBreakForce(properties.m_forceMax, properties.m_torqueMax);
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeSphericalLimitProperties(const JointLimitProperties& properties, physx::PxSphericalJoint* nativeJoint)
|
||||
{
|
||||
if (!nativeJoint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!properties.m_isLimited)
|
||||
{
|
||||
nativeJoint->setSphericalJointFlag(physx::PxSphericalJointFlag::eLIMIT_ENABLED, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hard limit uses a tolerance value (distance to limit at which limit becomes active).
|
||||
// Soft limit allows angle to exceed limit but springs back with configurable spring stiffness and damping.
|
||||
physx::PxJointLimitCone swingLimit(
|
||||
AZ::DegToRad(properties.m_limitFirst),
|
||||
AZ::DegToRad(properties.m_limitSecond),
|
||||
properties.m_tolerance);
|
||||
|
||||
if (properties.m_isSoftLimit)
|
||||
{
|
||||
swingLimit.stiffness = properties.m_stiffness;
|
||||
swingLimit.damping = properties.m_damping;
|
||||
}
|
||||
|
||||
nativeJoint->setLimitCone(swingLimit);
|
||||
nativeJoint->setSphericalJointFlag(physx::PxSphericalJointFlag::eLIMIT_ENABLED, true);
|
||||
}
|
||||
|
||||
void InitializeRevoluteLimitProperties(const JointLimitProperties& properties, physx::PxRevoluteJoint* nativeJoint)
|
||||
{
|
||||
if (!nativeJoint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!properties.m_isLimited)
|
||||
{
|
||||
nativeJoint->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eLIMIT_ENABLED, false);
|
||||
return;
|
||||
}
|
||||
|
||||
physx::PxJointAngularLimitPair limitPair(
|
||||
AZ::DegToRad(properties.m_limitSecond),
|
||||
AZ::DegToRad(properties.m_limitFirst),
|
||||
properties.m_tolerance);
|
||||
|
||||
if (properties.m_isSoftLimit)
|
||||
{
|
||||
limitPair.stiffness = properties.m_stiffness;
|
||||
limitPair.damping = properties.m_damping;
|
||||
}
|
||||
|
||||
nativeJoint->setLimit(limitPair);
|
||||
nativeJoint->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eLIMIT_ENABLED, true);
|
||||
}
|
||||
|
||||
namespace PxJointFactories
|
||||
{
|
||||
PxJointUniquePtr CreatePxD6Joint(
|
||||
const PhysX::D6JointLimitConfiguration& configuration,
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle)
|
||||
{
|
||||
PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
|
||||
|
||||
if (actorData.parentActor == nullptr && actorData.childActor == nullptr)
|
||||
if (angularIndex < angularSubdivisions)
|
||||
{
|
||||
AZ_Warning("PhysX Joint", false, "CreateJoint failed - at least one body must be a PxRigidActor.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const physx::PxTransform parentWorldTransform =
|
||||
actorData.parentActor ? actorData.parentActor->getGlobalPose() : physx::PxTransform(physx::PxIdentity);
|
||||
const physx::PxTransform childWorldTransform =
|
||||
actorData.childActor ? actorData.childActor->getGlobalPose() : physx::PxTransform(physx::PxIdentity);
|
||||
const physx::PxVec3 childOffset = childWorldTransform.p - parentWorldTransform.p;
|
||||
physx::PxTransform parentLocalTransform(PxMathConvert(configuration.m_parentLocalRotation).getNormalized());
|
||||
const physx::PxTransform childLocalTransform(PxMathConvert(configuration.m_childLocalRotation).getNormalized());
|
||||
parentLocalTransform.p = parentWorldTransform.q.rotateInv(childOffset);
|
||||
|
||||
physx::PxD6Joint* joint = PxD6JointCreate(PxGetPhysics(),
|
||||
actorData.parentActor, parentLocalTransform, actorData.childActor, childLocalTransform);
|
||||
|
||||
joint->setMotion(physx::PxD6Axis::eTWIST, physx::PxD6Motion::eLIMITED);
|
||||
joint->setMotion(physx::PxD6Axis::eSWING1, physx::PxD6Motion::eLIMITED);
|
||||
joint->setMotion(physx::PxD6Axis::eSWING2, physx::PxD6Motion::eLIMITED);
|
||||
|
||||
AZ_Warning("PhysX Joint",
|
||||
configuration.m_swingLimitY >= JointConstants::MinSwingLimitDegrees && configuration.m_swingLimitZ >= JointConstants::MinSwingLimitDegrees,
|
||||
"Very small swing limit requested for joint between \"%s\" and \"%s\", increasing to %f degrees to improve stability",
|
||||
actorData.parentActor ? actorData.parentActor->getName() : "world",
|
||||
actorData.childActor ? actorData.childActor->getName() : "world",
|
||||
JointConstants::MinSwingLimitDegrees);
|
||||
|
||||
const float swingLimitY = AZ::DegToRad(AZ::GetMax(JointConstants::MinSwingLimitDegrees, configuration.m_swingLimitY));
|
||||
const float swingLimitZ = AZ::DegToRad(AZ::GetMax(JointConstants::MinSwingLimitDegrees, configuration.m_swingLimitZ));
|
||||
physx::PxJointLimitCone limitCone(swingLimitY, swingLimitZ);
|
||||
joint->setSwingLimit(limitCone);
|
||||
|
||||
float twistLower = AZ::DegToRad(AZStd::GetMin(configuration.m_twistLimitLower, configuration.m_twistLimitUpper));
|
||||
float twistUpper = AZ::DegToRad(AZStd::GetMax(configuration.m_twistLimitLower, configuration.m_twistLimitUpper));
|
||||
// make sure there is at least a small difference between the lower and upper limits to avoid problems in PhysX
|
||||
const float minTwistLimitRangeRadians = AZ::DegToRad(JointConstants::MinTwistLimitRangeDegrees);
|
||||
if (const float twistLimitRange = twistUpper - twistLower;
|
||||
twistLimitRange < minTwistLimitRangeRadians)
|
||||
{
|
||||
if (twistUpper > 0.0f)
|
||||
{
|
||||
twistLower -= (minTwistLimitRangeRadians - twistLimitRange);
|
||||
}
|
||||
else
|
||||
{
|
||||
twistUpper += (minTwistLimitRangeRadians - twistLimitRange);
|
||||
}
|
||||
}
|
||||
physx::PxJointAngularLimitPair twistLimitPair(twistLower, twistUpper);
|
||||
joint->setTwistLimit(twistLimitPair);
|
||||
|
||||
return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
|
||||
}
|
||||
|
||||
PxJointUniquePtr CreatePxFixedJoint(
|
||||
const PhysX::FixedJointConfiguration& configuration,
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle)
|
||||
{
|
||||
PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
|
||||
|
||||
//only check the child actor, as a null parent actor means this joint is a global constraint.
|
||||
if (!actorData.childActor)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
physx::PxFixedJoint* joint;
|
||||
const AZ::Transform parentLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_parentLocalRotation, configuration.m_parentLocalPosition);
|
||||
const AZ::Transform childLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_childLocalRotation, configuration.m_childLocalPosition);
|
||||
|
||||
{
|
||||
PHYSX_SCENE_READ_LOCK(actorData.childActor->getScene());
|
||||
joint = physx::PxFixedJointCreate(
|
||||
PxGetPhysics(),
|
||||
actorData.parentActor, PxMathConvert(parentLocalTM),
|
||||
actorData.childActor, PxMathConvert(childLocalTM));
|
||||
}
|
||||
|
||||
InitializeGenericProperties(
|
||||
configuration.m_genericProperties,
|
||||
static_cast<physx::PxJoint*>(joint));
|
||||
|
||||
return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
|
||||
}
|
||||
|
||||
PxJointUniquePtr CreatePxBallJoint(
|
||||
const PhysX::BallJointConfiguration& configuration,
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle)
|
||||
{
|
||||
PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
|
||||
|
||||
// only check the child actor, as a null parent actor means this joint is a global constraint.
|
||||
if (!actorData.childActor)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
physx::PxSphericalJoint* joint;
|
||||
const AZ::Transform parentLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_parentLocalRotation, configuration.m_parentLocalPosition);
|
||||
const AZ::Transform childLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_childLocalRotation, configuration.m_childLocalPosition);
|
||||
|
||||
{
|
||||
PHYSX_SCENE_READ_LOCK(actorData.childActor->getScene());
|
||||
joint = physx::PxSphericalJointCreate(PxGetPhysics(),
|
||||
actorData.parentActor, PxMathConvert(parentLocalTM),
|
||||
actorData.childActor, PxMathConvert(childLocalTM));
|
||||
}
|
||||
|
||||
InitializeSphericalLimitProperties(configuration.m_limitProperties, joint);
|
||||
InitializeGenericProperties(
|
||||
configuration.m_genericProperties,
|
||||
static_cast<physx::PxJoint*>(joint));
|
||||
|
||||
return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
|
||||
}
|
||||
|
||||
PxJointUniquePtr CreatePxHingeJoint(
|
||||
const PhysX::HingeJointConfiguration& configuration,
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle)
|
||||
{
|
||||
PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
|
||||
|
||||
// only check the child actor, as a null parent actor means this joint is a global constraint.
|
||||
if (!actorData.childActor)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
physx::PxRevoluteJoint* joint;
|
||||
const AZ::Transform parentLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_parentLocalRotation, configuration.m_parentLocalPosition);
|
||||
const AZ::Transform childLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
|
||||
configuration.m_childLocalRotation, configuration.m_childLocalPosition);
|
||||
|
||||
{
|
||||
PHYSX_SCENE_READ_LOCK(actorData.childActor->getScene());
|
||||
joint = physx::PxRevoluteJointCreate(PxGetPhysics(),
|
||||
actorData.parentActor, PxMathConvert(parentLocalTM),
|
||||
actorData.childActor, PxMathConvert(childLocalTM));
|
||||
}
|
||||
|
||||
InitializeRevoluteLimitProperties(configuration.m_limitProperties, joint);
|
||||
InitializeGenericProperties(
|
||||
configuration.m_genericProperties,
|
||||
static_cast<physx::PxJoint*>(joint));
|
||||
|
||||
return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
|
||||
}
|
||||
} // namespace PxJointFactories
|
||||
|
||||
namespace Joints
|
||||
{
|
||||
bool IsD6SwingValid(float swingAngleY, float swingAngleZ, float swingLimitY, float swingLimitZ)
|
||||
{
|
||||
const float epsilon = AZ::Constants::FloatEpsilon;
|
||||
const float yFactor = AZStd::tan(0.25f * swingAngleY) / AZStd::GetMax(epsilon, AZStd::tan(0.25f * swingLimitY));
|
||||
const float zFactor = AZStd::tan(0.25f * swingAngleZ) / AZStd::GetMax(epsilon, AZStd::tan(0.25f * swingLimitZ));
|
||||
|
||||
return (yFactor * yFactor + zFactor * zFactor <= 1.0f + epsilon);
|
||||
}
|
||||
|
||||
void AppendD6SwingConeToLineBuffer(
|
||||
const AZ::Quaternion& parentLocalRotation,
|
||||
float swingAngleY,
|
||||
float swingAngleZ,
|
||||
float swingLimitY,
|
||||
float swingLimitZ,
|
||||
float scale,
|
||||
AZ::u32 angularSubdivisions,
|
||||
AZ::u32 radialSubdivisions,
|
||||
AZStd::vector<AZ::Vector3>& lineBufferOut,
|
||||
AZStd::vector<bool>& lineValidityBufferOut)
|
||||
{
|
||||
const AZ::u32 numLinesSwingCone = angularSubdivisions * (1u + radialSubdivisions);
|
||||
lineBufferOut.reserve(lineBufferOut.size() + 2u * numLinesSwingCone);
|
||||
lineValidityBufferOut.reserve(lineValidityBufferOut.size() + numLinesSwingCone);
|
||||
|
||||
// the orientation quat for a radial line in the cone can be represented in terms of sin and cos half angles
|
||||
// these expressions can be efficiently calculated using tan quarter angles as follows:
|
||||
// writing t = tan(x / 4)
|
||||
// sin(x / 2) = 2 * t / (1 + t * t)
|
||||
// cos(x / 2) = (1 - t * t) / (1 + t * t)
|
||||
const float tanQuarterSwingZ = AZStd::tan(0.25f * swingLimitZ);
|
||||
const float tanQuarterSwingY = AZStd::tan(0.25f * swingLimitY);
|
||||
|
||||
AZ::Vector3 previousRadialVector = AZ::Vector3::CreateZero();
|
||||
for (AZ::u32 angularIndex = 0; angularIndex <= angularSubdivisions; angularIndex++)
|
||||
{
|
||||
const float angle = AZ::Constants::TwoPi / angularSubdivisions * angularIndex;
|
||||
// the axis about which to rotate the x-axis to get the radial vector for this segment of the cone
|
||||
const AZ::Vector3 rotationAxis(0, -tanQuarterSwingY * sinf(angle), tanQuarterSwingZ * cosf(angle));
|
||||
const float normalizationFactor = rotationAxis.GetLengthSq();
|
||||
const AZ::Quaternion radialVectorRotation = 1.0f / (1.0f + normalizationFactor) *
|
||||
AZ::Quaternion::CreateFromVector3AndValue(2.0f * rotationAxis, 1.0f - normalizationFactor);
|
||||
const AZ::Vector3 radialVector =
|
||||
(parentLocalRotation * radialVectorRotation).TransformVector(AZ::Vector3::CreateAxisX(scale));
|
||||
|
||||
if (angularIndex > 0)
|
||||
{
|
||||
for (AZ::u32 radialIndex = 1; radialIndex <= radialSubdivisions; radialIndex++)
|
||||
{
|
||||
float radiusFraction = 1.0f / radialSubdivisions * radialIndex;
|
||||
lineBufferOut.push_back(radiusFraction * radialVector);
|
||||
lineBufferOut.push_back(radiusFraction * previousRadialVector);
|
||||
}
|
||||
}
|
||||
|
||||
if (angularIndex < angularSubdivisions)
|
||||
{
|
||||
lineBufferOut.push_back(AZ::Vector3::CreateZero());
|
||||
lineBufferOut.push_back(radialVector);
|
||||
}
|
||||
|
||||
previousRadialVector = radialVector;
|
||||
}
|
||||
|
||||
const bool swingValid = IsD6SwingValid(swingAngleY, swingAngleZ, swingLimitY, swingLimitZ);
|
||||
lineValidityBufferOut.insert(lineValidityBufferOut.end(), numLinesSwingCone, swingValid);
|
||||
}
|
||||
|
||||
void AppendD6TwistArcToLineBuffer(
|
||||
const AZ::Quaternion& parentLocalRotation,
|
||||
float twistAngle,
|
||||
float twistLimitLower,
|
||||
float twistLimitUpper,
|
||||
float scale,
|
||||
AZ::u32 angularSubdivisions,
|
||||
AZ::u32 radialSubdivisions,
|
||||
AZStd::vector<AZ::Vector3>& lineBufferOut,
|
||||
AZStd::vector<bool>& lineValidityBufferOut)
|
||||
{
|
||||
const AZ::u32 numLinesTwistArc = angularSubdivisions * (1u + radialSubdivisions) + 1u;
|
||||
lineBufferOut.reserve(lineBufferOut.size() + 2u * numLinesTwistArc);
|
||||
|
||||
AZ::Vector3 previousRadialVector = AZ::Vector3::CreateZero();
|
||||
const float twistRange = twistLimitUpper - twistLimitLower;
|
||||
|
||||
for (AZ::u32 angularIndex = 0; angularIndex <= angularSubdivisions; angularIndex++)
|
||||
{
|
||||
const float angle = twistLimitLower + twistRange / angularSubdivisions * angularIndex;
|
||||
const AZ::Vector3 radialVector =
|
||||
parentLocalRotation.TransformVector(scale * AZ::Vector3(0.0f, cosf(angle), sinf(angle)));
|
||||
|
||||
if (angularIndex > 0)
|
||||
{
|
||||
for (AZ::u32 radialIndex = 1; radialIndex <= radialSubdivisions; radialIndex++)
|
||||
{
|
||||
const float radiusFraction = 1.0f / radialSubdivisions * radialIndex;
|
||||
lineBufferOut.push_back(radiusFraction * radialVector);
|
||||
lineBufferOut.push_back(radiusFraction * previousRadialVector);
|
||||
}
|
||||
}
|
||||
|
||||
lineBufferOut.push_back(AZ::Vector3::CreateZero());
|
||||
lineBufferOut.push_back(radialVector);
|
||||
|
||||
previousRadialVector = radialVector;
|
||||
}
|
||||
|
||||
const bool twistValid = (twistAngle >= twistLimitLower && twistAngle <= twistLimitUpper);
|
||||
lineValidityBufferOut.insert(lineValidityBufferOut.end(), numLinesTwistArc, twistValid);
|
||||
previousRadialVector = radialVector;
|
||||
}
|
||||
|
||||
void AppendD6CurrentTwistToLineBuffer(
|
||||
const AZ::Quaternion& parentLocalRotation,
|
||||
float twistAngle,
|
||||
[[maybe_unused]] float twistLimitLower,
|
||||
[[maybe_unused]] float twistLimitUpper,
|
||||
float scale,
|
||||
AZStd::vector<AZ::Vector3>& lineBufferOut,
|
||||
AZStd::vector<bool>& lineValidityBufferOut)
|
||||
const bool swingValid = IsD6SwingValid(swingAngleY, swingAngleZ, swingLimitY, swingLimitZ);
|
||||
lineValidityBufferOut.insert(lineValidityBufferOut.end(), numLinesSwingCone, swingValid);
|
||||
}
|
||||
|
||||
void AppendD6TwistArcToLineBuffer(
|
||||
const AZ::Quaternion& parentLocalRotation,
|
||||
float twistAngle,
|
||||
float twistLimitLower,
|
||||
float twistLimitUpper,
|
||||
float scale,
|
||||
AZ::u32 angularSubdivisions,
|
||||
AZ::u32 radialSubdivisions,
|
||||
AZStd::vector<AZ::Vector3>& lineBufferOut,
|
||||
AZStd::vector<bool>& lineValidityBufferOut)
|
||||
{
|
||||
const AZ::u32 numLinesTwistArc = angularSubdivisions * (1u + radialSubdivisions) + 1u;
|
||||
lineBufferOut.reserve(lineBufferOut.size() + 2u * numLinesTwistArc);
|
||||
|
||||
AZ::Vector3 previousRadialVector = AZ::Vector3::CreateZero();
|
||||
const float twistRange = twistLimitUpper - twistLimitLower;
|
||||
|
||||
for (AZ::u32 angularIndex = 0; angularIndex <= angularSubdivisions; angularIndex++)
|
||||
{
|
||||
const AZ::Vector3 twistVector =
|
||||
parentLocalRotation.TransformVector(1.25f * scale * AZ::Vector3(0.0f, cosf(twistAngle), sinf(twistAngle)));
|
||||
const float angle = twistLimitLower + twistRange / angularSubdivisions * angularIndex;
|
||||
const AZ::Vector3 radialVector =
|
||||
parentLocalRotation.TransformVector(scale * AZ::Vector3(0.0f, cosf(angle), sinf(angle)));
|
||||
|
||||
if (angularIndex > 0)
|
||||
{
|
||||
for (AZ::u32 radialIndex = 1; radialIndex <= radialSubdivisions; radialIndex++)
|
||||
{
|
||||
const float radiusFraction = 1.0f / radialSubdivisions * radialIndex;
|
||||
lineBufferOut.push_back(radiusFraction * radialVector);
|
||||
lineBufferOut.push_back(radiusFraction * previousRadialVector);
|
||||
}
|
||||
}
|
||||
|
||||
lineBufferOut.push_back(AZ::Vector3::CreateZero());
|
||||
lineBufferOut.push_back(twistVector);
|
||||
lineValidityBufferOut.push_back(true);
|
||||
lineBufferOut.push_back(radialVector);
|
||||
|
||||
previousRadialVector = radialVector;
|
||||
}
|
||||
} // namespace Joints
|
||||
} // namespace Utils
|
||||
} // namespace PhysX
|
||||
|
||||
const bool twistValid = (twistAngle >= twistLimitLower && twistAngle <= twistLimitUpper);
|
||||
lineValidityBufferOut.insert(lineValidityBufferOut.end(), numLinesTwistArc, twistValid);
|
||||
}
|
||||
|
||||
void AppendD6CurrentTwistToLineBuffer(
|
||||
const AZ::Quaternion& parentLocalRotation,
|
||||
float twistAngle,
|
||||
[[maybe_unused]] float twistLimitLower,
|
||||
[[maybe_unused]] float twistLimitUpper,
|
||||
float scale,
|
||||
AZStd::vector<AZ::Vector3>& lineBufferOut,
|
||||
AZStd::vector<bool>& lineValidityBufferOut)
|
||||
{
|
||||
const AZ::Vector3 twistVector =
|
||||
parentLocalRotation.TransformVector(1.25f * scale * AZ::Vector3(0.0f, cosf(twistAngle), sinf(twistAngle)));
|
||||
lineBufferOut.push_back(AZ::Vector3::CreateZero());
|
||||
lineBufferOut.push_back(twistVector);
|
||||
lineValidityBufferOut.push_back(true);
|
||||
}
|
||||
} // namespace Joints
|
||||
} // namespace PhysX::Utils
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace PhysX
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle);
|
||||
|
||||
|
||||
PxJointUniquePtr CreatePxHingeJoint(const PhysX::HingeJointConfiguration& configuration,
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace PhysX
|
||||
}
|
||||
|
||||
JointComponent::JointComponent(
|
||||
const JointComponentConfiguration& configuration,
|
||||
const JointComponentConfiguration& configuration,
|
||||
const JointGenericProperties& genericProperties)
|
||||
: m_configuration(configuration)
|
||||
, m_genericProperties(genericProperties)
|
||||
@@ -66,7 +66,7 @@ namespace PhysX
|
||||
}
|
||||
|
||||
JointComponent::JointComponent(
|
||||
const JointComponentConfiguration& configuration,
|
||||
const JointComponentConfiguration& configuration,
|
||||
const JointGenericProperties& genericProperties,
|
||||
const JointLimitProperties& limitProperties)
|
||||
: m_configuration(configuration)
|
||||
@@ -81,8 +81,8 @@ namespace PhysX
|
||||
{
|
||||
if (m_configuration.m_followerEntity == m_configuration.m_leadEntity)
|
||||
{
|
||||
AZ_Error("JointComponent::Activate()",
|
||||
false,
|
||||
AZ_Error("JointComponent::Activate()",
|
||||
false,
|
||||
"Joint's lead entity cannot be the same as the entity in which the joint resides. Joint failed to initialize.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,10 +52,10 @@ namespace PhysX
|
||||
|
||||
JointComponent() = default;
|
||||
JointComponent(
|
||||
const JointComponentConfiguration& configuration,
|
||||
const JointComponentConfiguration& configuration,
|
||||
const JointGenericProperties& genericProperties);
|
||||
JointComponent(
|
||||
const JointComponentConfiguration& configuration,
|
||||
const JointComponentConfiguration& configuration,
|
||||
const JointGenericProperties& genericProperties,
|
||||
const JointLimitProperties& limitProperties);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace PhysX
|
||||
{
|
||||
Material::Material(Material&& material)
|
||||
Material::Material(Material&& material)
|
||||
: m_pxMaterial(AZStd::move(material.m_pxMaterial))
|
||||
, m_surfaceType(material.m_surfaceType)
|
||||
, m_surfaceString(AZStd::move(material.m_surfaceString))
|
||||
@@ -103,7 +103,7 @@ namespace PhysX
|
||||
SetDebugColor(materialConfiguration.m_debugColor);
|
||||
|
||||
Physics::LegacySurfaceTypeRequestsBus::BroadcastResult(
|
||||
m_cryEngineSurfaceId,
|
||||
m_cryEngineSurfaceId,
|
||||
&Physics::LegacySurfaceTypeRequestsBus::Events::GetLegacySurfaceTypeFronName,
|
||||
m_surfaceString);
|
||||
}
|
||||
@@ -159,7 +159,7 @@ namespace PhysX
|
||||
|
||||
void Material::SetDynamicFriction(float dynamicFriction)
|
||||
{
|
||||
AZ_Warning("PhysX Material", dynamicFriction >= 0.0f,
|
||||
AZ_Warning("PhysX Material", dynamicFriction >= 0.0f,
|
||||
"SetDynamicFriction: Dynamic friction %f for material %s is out of range [0, PX_MAX_F32)",
|
||||
dynamicFriction, m_surfaceString.c_str());
|
||||
|
||||
@@ -176,10 +176,10 @@ namespace PhysX
|
||||
|
||||
void Material::SetStaticFriction(float staticFriction)
|
||||
{
|
||||
AZ_Warning("PhysX Material", staticFriction >= 0.0f,
|
||||
AZ_Warning("PhysX Material", staticFriction >= 0.0f,
|
||||
"SetStaticFriction: Static friction %f for material %s is out of range [0, PX_MAX_F32)",
|
||||
staticFriction, m_surfaceString.c_str());
|
||||
|
||||
|
||||
if (m_pxMaterial)
|
||||
{
|
||||
m_pxMaterial->setStaticFriction(AZ::GetMax(0.0f, staticFriction));
|
||||
@@ -193,7 +193,7 @@ namespace PhysX
|
||||
|
||||
void Material::SetRestitution(float restitution)
|
||||
{
|
||||
AZ_Warning("PhysX Material", restitution >= 0 && restitution <= 1.0f,
|
||||
AZ_Warning("PhysX Material", restitution >= 0 && restitution <= 1.0f,
|
||||
"SetRestitution: Restitution %f for material %s is out of range [0, 1]",
|
||||
restitution, m_surfaceString.c_str());
|
||||
|
||||
@@ -316,8 +316,8 @@ namespace PhysX
|
||||
}
|
||||
|
||||
// It is important to return exactly the amount of materials specified in materialSelection
|
||||
// If a number of materials different to what was cooked is assigned on a physx mesh it will lead to undefined
|
||||
// behavior and subtle bugs. Unfortunately, there's no warning or assertion on physx side at the shape creation time,
|
||||
// If a number of materials different to what was cooked is assigned on a physx mesh it will lead to undefined
|
||||
// behavior and subtle bugs. Unfortunately, there's no warning or assertion on physx side at the shape creation time,
|
||||
// nor mention of this in the documentation
|
||||
outMaterials.resize(materialIdsAssignedToSlots.size(), GetDefaultMaterial());
|
||||
|
||||
@@ -390,7 +390,7 @@ namespace PhysX
|
||||
|
||||
if (!assetConfiguration.m_asset.IsReady())
|
||||
{
|
||||
// The asset is valid but is still loading,
|
||||
// The asset is valid but is still loading,
|
||||
// Do not set the empty slots in this case to avoid the entity being in invalid state
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,379 +20,373 @@
|
||||
#include <Source/Scene/PhysXScene.h>
|
||||
#include <Source/Shape.h>
|
||||
|
||||
namespace PhysX
|
||||
namespace PhysX::Utils::Characters
|
||||
{
|
||||
namespace Utils
|
||||
AZ::Outcome<size_t> GetNodeIndex(const Physics::RagdollConfiguration& configuration, const AZStd::string& nodeName)
|
||||
{
|
||||
namespace Characters
|
||||
const size_t numNodes = configuration.m_nodes.size();
|
||||
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
||||
{
|
||||
AZ::Outcome<size_t> GetNodeIndex(const Physics::RagdollConfiguration& configuration, const AZStd::string& nodeName)
|
||||
if (configuration.m_nodes[nodeIndex].m_debugName == nodeName)
|
||||
{
|
||||
const size_t numNodes = configuration.m_nodes.size();
|
||||
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
||||
return AZ::Success(nodeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return AZ::Failure();
|
||||
}
|
||||
|
||||
/// Adds the properties that exist in both the PhysX capsule and box controllers to the controller description.
|
||||
/// @param[in,out] controllerDesc The controller description to which the shape independent properties should be added.
|
||||
/// @param characterConfig Information about the character required for initialization.
|
||||
static void AppendShapeIndependentProperties(physx::PxControllerDesc& controllerDesc,
|
||||
const Physics::CharacterConfiguration& characterConfig, CharacterControllerCallbackManager* callbackManager)
|
||||
{
|
||||
AZStd::vector<AZStd::shared_ptr<Physics::Material>> materials;
|
||||
|
||||
if (characterConfig.m_materialSelection.GetMaterialIdsAssignedToSlots().empty())
|
||||
{
|
||||
// If material selection has no slots, falling back to default material.
|
||||
AZStd::shared_ptr<Physics::Material> defaultMaterial;
|
||||
Physics::PhysicsMaterialRequestBus::BroadcastResult(defaultMaterial,
|
||||
&Physics::PhysicsMaterialRequestBus::Events::GetGenericDefaultMaterial);
|
||||
if (!defaultMaterial)
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "Invalid default material.");
|
||||
return;
|
||||
}
|
||||
materials.push_back(AZStd::move(defaultMaterial));
|
||||
}
|
||||
else
|
||||
{
|
||||
Physics::PhysicsMaterialRequestBus::Broadcast(
|
||||
&Physics::PhysicsMaterialRequestBus::Events::GetMaterials,
|
||||
characterConfig.m_materialSelection,
|
||||
materials);
|
||||
if (materials.empty())
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "Could not create character controller, material list was empty.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
physx::PxMaterial* pxMaterial = static_cast<physx::PxMaterial*>(materials.front()->GetNativePointer());
|
||||
|
||||
controllerDesc.material = pxMaterial;
|
||||
controllerDesc.slopeLimit = cosf(AZ::DegToRad(characterConfig.m_maximumSlopeAngle));
|
||||
controllerDesc.stepOffset = characterConfig.m_stepHeight;
|
||||
controllerDesc.upDirection = characterConfig.m_upDirection.IsZero()
|
||||
? physx::PxVec3(0.0f, 0.0f, 1.0f)
|
||||
: PxMathConvert(characterConfig.m_upDirection).getNormalized();
|
||||
controllerDesc.userData = nullptr;
|
||||
controllerDesc.behaviorCallback = callbackManager;
|
||||
controllerDesc.reportCallback = callbackManager;
|
||||
}
|
||||
|
||||
/// Adds the properties which are PhysX specific and not included in the base generic character configuration.
|
||||
/// @param[in,out] controllerDesc The controller description to which the PhysX specific properties should be added.
|
||||
/// @param characterConfig Information about the character required for initialization.
|
||||
void AppendPhysXSpecificProperties(physx::PxControllerDesc& controllerDesc,
|
||||
const Physics::CharacterConfiguration& characterConfig)
|
||||
{
|
||||
if (characterConfig.RTTI_GetType() == CharacterControllerConfiguration::RTTI_Type())
|
||||
{
|
||||
const auto& extendedConfig = static_cast<const CharacterControllerConfiguration&>(characterConfig);
|
||||
|
||||
controllerDesc.scaleCoeff = extendedConfig.m_scaleCoefficient;
|
||||
controllerDesc.contactOffset = extendedConfig.m_contactOffset;
|
||||
controllerDesc.nonWalkableMode = extendedConfig.m_slopeBehaviour == SlopeBehaviour::PreventClimbing
|
||||
? physx::PxControllerNonWalkableMode::ePREVENT_CLIMBING
|
||||
: physx::PxControllerNonWalkableMode::ePREVENT_CLIMBING_AND_FORCE_SLIDING;
|
||||
}
|
||||
}
|
||||
|
||||
CharacterController* CreateCharacterController(PhysXScene* scene,
|
||||
const Physics::CharacterConfiguration& characterConfig)
|
||||
{
|
||||
if (scene == nullptr)
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "Failed to create character controller as the scene is null");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
physx::PxControllerManager* manager = scene->GetOrCreateControllerManager();
|
||||
if (manager == nullptr)
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "Could not retrieve character controller manager.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto callbackManager = AZStd::make_unique<CharacterControllerCallbackManager>();
|
||||
|
||||
physx::PxController* pxController = nullptr;
|
||||
auto* pxScene = static_cast<physx::PxScene*>(scene->GetNativePointer());
|
||||
|
||||
switch (characterConfig.m_shapeConfig->GetShapeType())
|
||||
{
|
||||
case Physics::ShapeType::Capsule:
|
||||
{
|
||||
physx::PxCapsuleControllerDesc capsuleDesc;
|
||||
|
||||
const Physics::CapsuleShapeConfiguration& capsuleConfig = static_cast<const Physics::CapsuleShapeConfiguration&>(*characterConfig.m_shapeConfig);
|
||||
// LY height means total height, PhysX means height of straight section
|
||||
capsuleDesc.height = AZ::GetMax(epsilon, capsuleConfig.m_height - 2.0f * capsuleConfig.m_radius);
|
||||
capsuleDesc.radius = capsuleConfig.m_radius;
|
||||
capsuleDesc.climbingMode = physx::PxCapsuleClimbingMode::eCONSTRAINED;
|
||||
|
||||
AppendShapeIndependentProperties(capsuleDesc, characterConfig, callbackManager.get());
|
||||
AppendPhysXSpecificProperties(capsuleDesc, characterConfig);
|
||||
PHYSX_SCENE_WRITE_LOCK(pxScene);
|
||||
pxController = manager->createController(capsuleDesc); // This internally adds the controller's actor to the scene
|
||||
}
|
||||
break;
|
||||
case Physics::ShapeType::Box:
|
||||
{
|
||||
physx::PxBoxControllerDesc boxDesc;
|
||||
|
||||
const Physics::BoxShapeConfiguration& boxConfig = static_cast<const Physics::BoxShapeConfiguration&>(*characterConfig.m_shapeConfig);
|
||||
boxDesc.halfHeight = 0.5f * boxConfig.m_dimensions.GetZ();
|
||||
boxDesc.halfSideExtent = 0.5f * boxConfig.m_dimensions.GetY();
|
||||
boxDesc.halfForwardExtent = 0.5f * boxConfig.m_dimensions.GetX();
|
||||
|
||||
AppendShapeIndependentProperties(boxDesc, characterConfig, callbackManager.get());
|
||||
AppendPhysXSpecificProperties(boxDesc, characterConfig);
|
||||
PHYSX_SCENE_WRITE_LOCK(pxScene);
|
||||
pxController = manager->createController(boxDesc); // This internally adds the controller's actor to the scene
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "PhysX only supports box and capsule shapes for character controllers.");
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pxController)
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "Failed to create character controller.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return aznew CharacterController(pxController, AZStd::move(callbackManager), scene->GetSceneHandle());
|
||||
}
|
||||
|
||||
Ragdoll* CreateRagdoll(Physics::RagdollConfiguration& configuration, AzPhysics::SceneHandle sceneHandle)
|
||||
{
|
||||
const size_t numNodes = configuration.m_nodes.size();
|
||||
if (numNodes != configuration.m_initialState.size())
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Mismatch between number of nodes in ragdoll configuration (%i) "
|
||||
"and number of nodes in the initial ragdoll state (%i)", numNodes, configuration.m_initialState.size());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<Ragdoll> ragdoll = AZStd::make_unique<Ragdoll>(sceneHandle);
|
||||
ragdoll->SetParentIndices(configuration.m_parentIndices);
|
||||
|
||||
auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
|
||||
if (sceneInterface == nullptr)
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Unable to Create Ragdoll, Physics Scene Interface is missing.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Set up rigid bodies
|
||||
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
||||
{
|
||||
Physics::RagdollNodeConfiguration& nodeConfig = configuration.m_nodes[nodeIndex];
|
||||
const Physics::RagdollNodeState& nodeState = configuration.m_initialState[nodeIndex];
|
||||
|
||||
Physics::CharacterColliderNodeConfiguration* colliderNodeConfig = configuration.m_colliders.FindNodeConfigByName(nodeConfig.m_debugName);
|
||||
if (colliderNodeConfig)
|
||||
{
|
||||
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> shapes;
|
||||
for (const auto& [colliderConfig, shapeConfig] : colliderNodeConfig->m_shapes)
|
||||
{
|
||||
if (configuration.m_nodes[nodeIndex].m_debugName == nodeName)
|
||||
if (colliderConfig == nullptr || shapeConfig == nullptr)
|
||||
{
|
||||
return AZ::Success(nodeIndex);
|
||||
AZ_Error("PhysX Ragdoll", false, "Failed to create collider shape for ragdoll node %s", nodeConfig.m_debugName.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (auto shape = AZStd::make_shared<Shape>(*colliderConfig, *shapeConfig))
|
||||
{
|
||||
shapes.emplace_back(shape);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Failed to create collider shape for ragdoll node %s", nodeConfig.m_debugName.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return AZ::Failure();
|
||||
nodeConfig.m_colliderAndShapeData = shapes;
|
||||
}
|
||||
nodeConfig.m_startSimulationEnabled = false;
|
||||
nodeConfig.m_position = nodeState.m_position;
|
||||
nodeConfig.m_orientation = nodeState.m_orientation;
|
||||
|
||||
/// Adds the properties that exist in both the PhysX capsule and box controllers to the controller description.
|
||||
/// @param[in,out] controllerDesc The controller description to which the shape independent properties should be added.
|
||||
/// @param characterConfig Information about the character required for initialization.
|
||||
static void AppendShapeIndependentProperties(physx::PxControllerDesc& controllerDesc,
|
||||
const Physics::CharacterConfiguration& characterConfig, CharacterControllerCallbackManager* callbackManager)
|
||||
AZStd::unique_ptr<RagdollNode> node = AZStd::make_unique<RagdollNode>(sceneHandle, nodeConfig);
|
||||
if (node->GetRigidBodyHandle() != AzPhysics::InvalidSimulatedBodyHandle)
|
||||
{
|
||||
AZStd::vector<AZStd::shared_ptr<Physics::Material>> materials;
|
||||
ragdoll->AddNode(AZStd::move(node));
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Failed to create rigid body for ragdoll node %s", nodeConfig.m_debugName.c_str());
|
||||
node.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (characterConfig.m_materialSelection.GetMaterialIdsAssignedToSlots().empty())
|
||||
// Set up joints. Needs a second pass because child nodes in the ragdoll config aren't guaranteed to have
|
||||
// larger indices than their parents.
|
||||
size_t rootIndex = SIZE_MAX;
|
||||
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
||||
{
|
||||
size_t parentIndex = configuration.m_parentIndices[nodeIndex];
|
||||
if (parentIndex < numNodes)
|
||||
{
|
||||
physx::PxRigidDynamic* parentActor = ragdoll->GetPxRigidDynamic(parentIndex);
|
||||
physx::PxRigidDynamic* childActor = ragdoll->GetPxRigidDynamic(nodeIndex);
|
||||
if (parentActor && childActor)
|
||||
{
|
||||
// If material selection has no slots, falling back to default material.
|
||||
AZStd::shared_ptr<Physics::Material> defaultMaterial;
|
||||
Physics::PhysicsMaterialRequestBus::BroadcastResult(defaultMaterial,
|
||||
&Physics::PhysicsMaterialRequestBus::Events::GetGenericDefaultMaterial);
|
||||
if (!defaultMaterial)
|
||||
physx::PxVec3 parentOffset = parentActor->getGlobalPose().q.rotateInv(
|
||||
childActor->getGlobalPose().p - parentActor->getGlobalPose().p);
|
||||
physx::PxTransform parentTM(parentOffset);
|
||||
physx::PxTransform childTM(physx::PxIdentity);
|
||||
|
||||
AZStd::shared_ptr<AzPhysics::JointConfiguration> jointConfig = configuration.m_nodes[nodeIndex].m_jointConfig;
|
||||
if (!jointConfig)
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "Invalid default material.");
|
||||
return;
|
||||
jointConfig = AZStd::make_shared<D6JointLimitConfiguration>();
|
||||
}
|
||||
materials.push_back(AZStd::move(defaultMaterial));
|
||||
|
||||
AzPhysics::JointHandle jointHandle = sceneInterface->AddJoint(
|
||||
sceneHandle, jointConfig.get(),
|
||||
ragdoll->GetNode(parentIndex)->GetRigidBody().m_bodyHandle,
|
||||
ragdoll->GetNode(nodeIndex)->GetRigidBody().m_bodyHandle);
|
||||
|
||||
AzPhysics::Joint* joint = sceneInterface->GetJointFromHandle(sceneHandle, jointHandle);
|
||||
|
||||
if (!joint)
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Failed to create joint for node index %i.", nodeIndex);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Moving from PhysX 3.4 to 4.1, the allowed range of the twist angle was expanded from -pi..pi
|
||||
// to -2*pi..2*pi.
|
||||
// In 3.4, twist angles which were outside the range were wrapped into it, which means that it
|
||||
// would be possible for a joint to have been authored under 3.4 which would be inside its twist
|
||||
// limit in 3.4 but violating the limit by up to 2*pi in 4.1.
|
||||
// If this case is detected, flipping the sign of one of the joint local pose quaternions will
|
||||
// ensure that the twist angle will have a value which would not lead to wrapping.
|
||||
auto* jointNativePointer = static_cast<physx::PxJoint*>(joint->GetNativePointer());
|
||||
if (jointNativePointer && jointNativePointer->getConcreteType() == physx::PxJointConcreteType::eD6)
|
||||
{
|
||||
auto* d6Joint = static_cast<physx::PxD6Joint*>(jointNativePointer);
|
||||
const float twist = d6Joint->getTwistAngle();
|
||||
const physx::PxJointAngularLimitPair twistLimit = d6Joint->getTwistLimit();
|
||||
if (twist < twistLimit.lower || twist > twistLimit.upper)
|
||||
{
|
||||
physx::PxTransform childLocalTransform = d6Joint->getLocalPose(physx::PxJointActorIndex::eACTOR1);
|
||||
childLocalTransform.q = -childLocalTransform.q;
|
||||
d6Joint->setLocalPose(physx::PxJointActorIndex::eACTOR1, childLocalTransform);
|
||||
}
|
||||
}
|
||||
|
||||
Physics::RagdollNode* childNode = ragdoll->GetNode(nodeIndex);
|
||||
static_cast<RagdollNode*>(childNode)->SetJoint(joint);
|
||||
}
|
||||
else
|
||||
{
|
||||
Physics::PhysicsMaterialRequestBus::Broadcast(
|
||||
&Physics::PhysicsMaterialRequestBus::Events::GetMaterials,
|
||||
characterConfig.m_materialSelection,
|
||||
materials);
|
||||
if (materials.empty())
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "Could not create character controller, material list was empty.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
physx::PxMaterial* pxMaterial = static_cast<physx::PxMaterial*>(materials.front()->GetNativePointer());
|
||||
|
||||
controllerDesc.material = pxMaterial;
|
||||
controllerDesc.slopeLimit = cosf(AZ::DegToRad(characterConfig.m_maximumSlopeAngle));
|
||||
controllerDesc.stepOffset = characterConfig.m_stepHeight;
|
||||
controllerDesc.upDirection = characterConfig.m_upDirection.IsZero()
|
||||
? physx::PxVec3(0.0f, 0.0f, 1.0f)
|
||||
: PxMathConvert(characterConfig.m_upDirection).getNormalized();
|
||||
controllerDesc.userData = nullptr;
|
||||
controllerDesc.behaviorCallback = callbackManager;
|
||||
controllerDesc.reportCallback = callbackManager;
|
||||
}
|
||||
|
||||
/// Adds the properties which are PhysX specific and not included in the base generic character configuration.
|
||||
/// @param[in,out] controllerDesc The controller description to which the PhysX specific properties should be added.
|
||||
/// @param characterConfig Information about the character required for initialization.
|
||||
void AppendPhysXSpecificProperties(physx::PxControllerDesc& controllerDesc,
|
||||
const Physics::CharacterConfiguration& characterConfig)
|
||||
{
|
||||
if (characterConfig.RTTI_GetType() == CharacterControllerConfiguration::RTTI_Type())
|
||||
{
|
||||
const auto& extendedConfig = static_cast<const CharacterControllerConfiguration&>(characterConfig);
|
||||
|
||||
controllerDesc.scaleCoeff = extendedConfig.m_scaleCoefficient;
|
||||
controllerDesc.contactOffset = extendedConfig.m_contactOffset;
|
||||
controllerDesc.nonWalkableMode = extendedConfig.m_slopeBehaviour == SlopeBehaviour::PreventClimbing
|
||||
? physx::PxControllerNonWalkableMode::ePREVENT_CLIMBING
|
||||
: physx::PxControllerNonWalkableMode::ePREVENT_CLIMBING_AND_FORCE_SLIDING;
|
||||
}
|
||||
}
|
||||
|
||||
CharacterController* CreateCharacterController(PhysXScene* scene,
|
||||
const Physics::CharacterConfiguration& characterConfig)
|
||||
{
|
||||
if (scene == nullptr)
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "Failed to create character controller as the scene is null");
|
||||
AZ_Error("PhysX Ragdoll", false, "Failed to create joint for node index %i.", nodeIndex);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
physx::PxControllerManager* manager = scene->GetOrCreateControllerManager();
|
||||
if (manager == nullptr)
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "Could not retrieve character controller manager.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto callbackManager = AZStd::make_unique<CharacterControllerCallbackManager>();
|
||||
|
||||
physx::PxController* pxController = nullptr;
|
||||
auto* pxScene = static_cast<physx::PxScene*>(scene->GetNativePointer());
|
||||
|
||||
switch (characterConfig.m_shapeConfig->GetShapeType())
|
||||
{
|
||||
case Physics::ShapeType::Capsule:
|
||||
{
|
||||
physx::PxCapsuleControllerDesc capsuleDesc;
|
||||
|
||||
const Physics::CapsuleShapeConfiguration& capsuleConfig = static_cast<const Physics::CapsuleShapeConfiguration&>(*characterConfig.m_shapeConfig);
|
||||
// LY height means total height, PhysX means height of straight section
|
||||
capsuleDesc.height = AZ::GetMax(epsilon, capsuleConfig.m_height - 2.0f * capsuleConfig.m_radius);
|
||||
capsuleDesc.radius = capsuleConfig.m_radius;
|
||||
capsuleDesc.climbingMode = physx::PxCapsuleClimbingMode::eCONSTRAINED;
|
||||
|
||||
AppendShapeIndependentProperties(capsuleDesc, characterConfig, callbackManager.get());
|
||||
AppendPhysXSpecificProperties(capsuleDesc, characterConfig);
|
||||
PHYSX_SCENE_WRITE_LOCK(pxScene);
|
||||
pxController = manager->createController(capsuleDesc); // This internally adds the controller's actor to the scene
|
||||
}
|
||||
break;
|
||||
case Physics::ShapeType::Box:
|
||||
{
|
||||
physx::PxBoxControllerDesc boxDesc;
|
||||
|
||||
const Physics::BoxShapeConfiguration& boxConfig = static_cast<const Physics::BoxShapeConfiguration&>(*characterConfig.m_shapeConfig);
|
||||
boxDesc.halfHeight = 0.5f * boxConfig.m_dimensions.GetZ();
|
||||
boxDesc.halfSideExtent = 0.5f * boxConfig.m_dimensions.GetY();
|
||||
boxDesc.halfForwardExtent = 0.5f * boxConfig.m_dimensions.GetX();
|
||||
|
||||
AppendShapeIndependentProperties(boxDesc, characterConfig, callbackManager.get());
|
||||
AppendPhysXSpecificProperties(boxDesc, characterConfig);
|
||||
PHYSX_SCENE_WRITE_LOCK(pxScene);
|
||||
pxController = manager->createController(boxDesc); // This internally adds the controller's actor to the scene
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "PhysX only supports box and capsule shapes for character controllers.");
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pxController)
|
||||
{
|
||||
AZ_Error("PhysX Character Controller", false, "Failed to create character controller.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return aznew CharacterController(pxController, AZStd::move(callbackManager), scene->GetSceneHandle());
|
||||
}
|
||||
|
||||
Ragdoll* CreateRagdoll(Physics::RagdollConfiguration& configuration, AzPhysics::SceneHandle sceneHandle)
|
||||
else
|
||||
{
|
||||
const size_t numNodes = configuration.m_nodes.size();
|
||||
if (numNodes != configuration.m_initialState.size())
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Mismatch between number of nodes in ragdoll configuration (%i) "
|
||||
"and number of nodes in the initial ragdoll state (%i)", numNodes, configuration.m_initialState.size());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<Ragdoll> ragdoll = AZStd::make_unique<Ragdoll>(sceneHandle);
|
||||
ragdoll->SetParentIndices(configuration.m_parentIndices);
|
||||
|
||||
auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
|
||||
if (sceneInterface == nullptr)
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Unable to Create Ragdoll, Physics Scene Interface is missing.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Set up rigid bodies
|
||||
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
||||
{
|
||||
Physics::RagdollNodeConfiguration& nodeConfig = configuration.m_nodes[nodeIndex];
|
||||
const Physics::RagdollNodeState& nodeState = configuration.m_initialState[nodeIndex];
|
||||
|
||||
Physics::CharacterColliderNodeConfiguration* colliderNodeConfig = configuration.m_colliders.FindNodeConfigByName(nodeConfig.m_debugName);
|
||||
if (colliderNodeConfig)
|
||||
{
|
||||
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> shapes;
|
||||
for (const auto& [colliderConfig, shapeConfig] : colliderNodeConfig->m_shapes)
|
||||
{
|
||||
if (colliderConfig == nullptr || shapeConfig == nullptr)
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Failed to create collider shape for ragdoll node %s", nodeConfig.m_debugName.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (auto shape = AZStd::make_shared<Shape>(*colliderConfig, *shapeConfig))
|
||||
{
|
||||
shapes.emplace_back(shape);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Failed to create collider shape for ragdoll node %s", nodeConfig.m_debugName.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
nodeConfig.m_colliderAndShapeData = shapes;
|
||||
}
|
||||
nodeConfig.m_startSimulationEnabled = false;
|
||||
nodeConfig.m_position = nodeState.m_position;
|
||||
nodeConfig.m_orientation = nodeState.m_orientation;
|
||||
|
||||
AZStd::unique_ptr<RagdollNode> node = AZStd::make_unique<RagdollNode>(sceneHandle, nodeConfig);
|
||||
if (node->GetRigidBodyHandle() != AzPhysics::InvalidSimulatedBodyHandle)
|
||||
{
|
||||
ragdoll->AddNode(AZStd::move(node));
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Failed to create rigid body for ragdoll node %s", nodeConfig.m_debugName.c_str());
|
||||
node.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Set up joints. Needs a second pass because child nodes in the ragdoll config aren't guaranteed to have
|
||||
// larger indices than their parents.
|
||||
size_t rootIndex = SIZE_MAX;
|
||||
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
||||
{
|
||||
size_t parentIndex = configuration.m_parentIndices[nodeIndex];
|
||||
if (parentIndex < numNodes)
|
||||
{
|
||||
physx::PxRigidDynamic* parentActor = ragdoll->GetPxRigidDynamic(parentIndex);
|
||||
physx::PxRigidDynamic* childActor = ragdoll->GetPxRigidDynamic(nodeIndex);
|
||||
if (parentActor && childActor)
|
||||
{
|
||||
physx::PxVec3 parentOffset = parentActor->getGlobalPose().q.rotateInv(
|
||||
childActor->getGlobalPose().p - parentActor->getGlobalPose().p);
|
||||
physx::PxTransform parentTM(parentOffset);
|
||||
physx::PxTransform childTM(physx::PxIdentity);
|
||||
|
||||
AZStd::shared_ptr<AzPhysics::JointConfiguration> jointConfig = configuration.m_nodes[nodeIndex].m_jointConfig;
|
||||
if (!jointConfig)
|
||||
{
|
||||
jointConfig = AZStd::make_shared<D6JointLimitConfiguration>();
|
||||
}
|
||||
|
||||
AzPhysics::JointHandle jointHandle = sceneInterface->AddJoint(
|
||||
sceneHandle, jointConfig.get(),
|
||||
ragdoll->GetNode(parentIndex)->GetRigidBody().m_bodyHandle,
|
||||
ragdoll->GetNode(nodeIndex)->GetRigidBody().m_bodyHandle);
|
||||
|
||||
AzPhysics::Joint* joint = sceneInterface->GetJointFromHandle(sceneHandle, jointHandle);
|
||||
|
||||
if (!joint)
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Failed to create joint for node index %i.", nodeIndex);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Moving from PhysX 3.4 to 4.1, the allowed range of the twist angle was expanded from -pi..pi
|
||||
// to -2*pi..2*pi.
|
||||
// In 3.4, twist angles which were outside the range were wrapped into it, which means that it
|
||||
// would be possible for a joint to have been authored under 3.4 which would be inside its twist
|
||||
// limit in 3.4 but violating the limit by up to 2*pi in 4.1.
|
||||
// If this case is detected, flipping the sign of one of the joint local pose quaternions will
|
||||
// ensure that the twist angle will have a value which would not lead to wrapping.
|
||||
auto* jointNativePointer = static_cast<physx::PxJoint*>(joint->GetNativePointer());
|
||||
if (jointNativePointer && jointNativePointer->getConcreteType() == physx::PxJointConcreteType::eD6)
|
||||
{
|
||||
auto* d6Joint = static_cast<physx::PxD6Joint*>(jointNativePointer);
|
||||
const float twist = d6Joint->getTwistAngle();
|
||||
const physx::PxJointAngularLimitPair twistLimit = d6Joint->getTwistLimit();
|
||||
if (twist < twistLimit.lower || twist > twistLimit.upper)
|
||||
{
|
||||
physx::PxTransform childLocalTransform = d6Joint->getLocalPose(physx::PxJointActorIndex::eACTOR1);
|
||||
childLocalTransform.q = -childLocalTransform.q;
|
||||
d6Joint->setLocalPose(physx::PxJointActorIndex::eACTOR1, childLocalTransform);
|
||||
}
|
||||
}
|
||||
|
||||
Physics::RagdollNode* childNode = ragdoll->GetNode(nodeIndex);
|
||||
static_cast<RagdollNode*>(childNode)->SetJoint(joint);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Failed to create joint for node index %i.", nodeIndex);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the configuration only has one root and is valid, the node without a parent must be the root.
|
||||
rootIndex = nodeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
ragdoll->SetRootIndex(rootIndex);
|
||||
|
||||
return ragdoll.release();
|
||||
// If the configuration only has one root and is valid, the node without a parent must be the root.
|
||||
rootIndex = nodeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
physx::PxD6JointDrive CreateD6JointDrive(float stiffness, float dampingRatio, float forceLimit)
|
||||
ragdoll->SetRootIndex(rootIndex);
|
||||
|
||||
return ragdoll.release();
|
||||
}
|
||||
|
||||
physx::PxD6JointDrive CreateD6JointDrive(float stiffness, float dampingRatio, float forceLimit)
|
||||
{
|
||||
if (!(std::isfinite)(stiffness) || stiffness < 0.0f)
|
||||
{
|
||||
AZ_Warning("PhysX Character Utils", false, "Invalid joint stiffness, using 0.0f instead.");
|
||||
stiffness = 0.0f;
|
||||
}
|
||||
|
||||
if (!(std::isfinite)(dampingRatio) || dampingRatio < 0.0f)
|
||||
{
|
||||
AZ_Warning("PhysX Character Utils", false, "Invalid joint damping ratio, using 1.0f instead.");
|
||||
dampingRatio = 1.0f;
|
||||
}
|
||||
|
||||
if (!(std::isfinite)(forceLimit))
|
||||
{
|
||||
AZ_Warning("PhysX Character Utils", false, "Invalid joint force limit, ignoring.");
|
||||
forceLimit = std::numeric_limits<float>::max();
|
||||
}
|
||||
|
||||
float damping = dampingRatio * 2.0f * sqrtf(stiffness);
|
||||
bool isAcceleration = true;
|
||||
return physx::PxD6JointDrive(stiffness, damping, forceLimit, isAcceleration);
|
||||
}
|
||||
|
||||
AZStd::vector<DepthData> ComputeHierarchyDepths(const AZStd::vector<size_t>& parentIndices)
|
||||
{
|
||||
const size_t numNodes = parentIndices.size();
|
||||
AZStd::vector<DepthData> nodeDepths(numNodes);
|
||||
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
||||
{
|
||||
nodeDepths[nodeIndex] = { -1, nodeIndex };
|
||||
}
|
||||
|
||||
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
||||
{
|
||||
if (nodeDepths[nodeIndex].m_depth != -1)
|
||||
{
|
||||
if (!(std::isfinite)(stiffness) || stiffness < 0.0f)
|
||||
{
|
||||
AZ_Warning("PhysX Character Utils", false, "Invalid joint stiffness, using 0.0f instead.");
|
||||
stiffness = 0.0f;
|
||||
}
|
||||
|
||||
if (!(std::isfinite)(dampingRatio) || dampingRatio < 0.0f)
|
||||
{
|
||||
AZ_Warning("PhysX Character Utils", false, "Invalid joint damping ratio, using 1.0f instead.");
|
||||
dampingRatio = 1.0f;
|
||||
}
|
||||
|
||||
if (!(std::isfinite)(forceLimit))
|
||||
{
|
||||
AZ_Warning("PhysX Character Utils", false, "Invalid joint force limit, ignoring.");
|
||||
forceLimit = std::numeric_limits<float>::max();
|
||||
}
|
||||
|
||||
float damping = dampingRatio * 2.0f * sqrtf(stiffness);
|
||||
bool isAcceleration = true;
|
||||
return physx::PxD6JointDrive(stiffness, damping, forceLimit, isAcceleration);
|
||||
continue;
|
||||
}
|
||||
|
||||
AZStd::vector<DepthData> ComputeHierarchyDepths(const AZStd::vector<size_t>& parentIndices)
|
||||
int depth = -1; // initial depth value for this node
|
||||
int ancestorDepth = 0; // the depth of the first ancestor we find when iteratively visiting parents
|
||||
bool ancestorFound = false; // whether we have found either an ancestor which already has a depth value, or the root
|
||||
size_t currentIndex = nodeIndex;
|
||||
while (!ancestorFound)
|
||||
{
|
||||
const size_t numNodes = parentIndices.size();
|
||||
AZStd::vector<DepthData> nodeDepths(numNodes);
|
||||
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
||||
depth++;
|
||||
if (depth > numNodes)
|
||||
{
|
||||
nodeDepths[nodeIndex] = { -1, nodeIndex };
|
||||
AZ_Error("PhysX Ragdoll", false, "Loop detected in hierarchy depth computation.");
|
||||
return nodeDepths;
|
||||
}
|
||||
const size_t parentIndex = parentIndices[currentIndex];
|
||||
|
||||
if (parentIndex >= numNodes || nodeDepths[currentIndex].m_depth != -1)
|
||||
{
|
||||
ancestorFound = true;
|
||||
ancestorDepth = (nodeDepths[currentIndex].m_depth != -1) ? nodeDepths[currentIndex].m_depth : 0;
|
||||
}
|
||||
|
||||
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
|
||||
{
|
||||
if (nodeDepths[nodeIndex].m_depth != -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int depth = -1; // initial depth value for this node
|
||||
int ancestorDepth = 0; // the depth of the first ancestor we find when iteratively visiting parents
|
||||
bool ancestorFound = false; // whether we have found either an ancestor which already has a depth value, or the root
|
||||
size_t currentIndex = nodeIndex;
|
||||
while (!ancestorFound)
|
||||
{
|
||||
depth++;
|
||||
if (depth > numNodes)
|
||||
{
|
||||
AZ_Error("PhysX Ragdoll", false, "Loop detected in hierarchy depth computation.");
|
||||
return nodeDepths;
|
||||
}
|
||||
const size_t parentIndex = parentIndices[currentIndex];
|
||||
|
||||
if (parentIndex >= numNodes || nodeDepths[currentIndex].m_depth != -1)
|
||||
{
|
||||
ancestorFound = true;
|
||||
ancestorDepth = (nodeDepths[currentIndex].m_depth != -1) ? nodeDepths[currentIndex].m_depth : 0;
|
||||
}
|
||||
|
||||
currentIndex = parentIndex;
|
||||
}
|
||||
|
||||
currentIndex = nodeIndex;
|
||||
for (int i = depth; i >= 0; i--)
|
||||
{
|
||||
nodeDepths[currentIndex] = { ancestorDepth + i, currentIndex };
|
||||
currentIndex = parentIndices[currentIndex];
|
||||
}
|
||||
}
|
||||
|
||||
return nodeDepths;
|
||||
currentIndex = parentIndex;
|
||||
}
|
||||
} // namespace Characters
|
||||
} // namespace Utils
|
||||
} // namespace PhysX
|
||||
|
||||
currentIndex = nodeIndex;
|
||||
for (int i = depth; i >= 0; i--)
|
||||
{
|
||||
nodeDepths[currentIndex] = { ancestorDepth + i, currentIndex };
|
||||
currentIndex = parentIndices[currentIndex];
|
||||
}
|
||||
}
|
||||
|
||||
return nodeDepths;
|
||||
}
|
||||
} // namespace PhysX::Utils::Characters
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace PhysX
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
m_joint = joint;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,216 +20,213 @@
|
||||
#include <extensions/PxSerialization.h>
|
||||
#include <extensions/PxDefaultStreams.h>
|
||||
|
||||
namespace PhysX
|
||||
namespace PhysX::Pipeline
|
||||
{
|
||||
namespace Pipeline
|
||||
const char* HeightFieldAssetHandler::s_assetFileExtension = "pxheightfield";
|
||||
|
||||
HeightFieldAssetHandler::HeightFieldAssetHandler()
|
||||
{
|
||||
const char* HeightFieldAssetHandler::s_assetFileExtension = "pxheightfield";
|
||||
Register();
|
||||
}
|
||||
|
||||
HeightFieldAssetHandler::HeightFieldAssetHandler()
|
||||
HeightFieldAssetHandler::~HeightFieldAssetHandler()
|
||||
{
|
||||
Unregister();
|
||||
}
|
||||
|
||||
void HeightFieldAssetHandler::Register()
|
||||
{
|
||||
bool assetManagerReady = AZ::Data::AssetManager::IsReady();
|
||||
AZ_Error("PhysX HeightField Asset", assetManagerReady, "Asset manager isn't ready.");
|
||||
if (assetManagerReady)
|
||||
{
|
||||
Register();
|
||||
AZ::Data::AssetManager::Instance().RegisterHandler(this, AZ::AzTypeInfo<HeightFieldAsset>::Uuid());
|
||||
}
|
||||
|
||||
HeightFieldAssetHandler::~HeightFieldAssetHandler()
|
||||
AZ::AssetTypeInfoBus::Handler::BusConnect(AZ::AzTypeInfo<HeightFieldAsset>::Uuid());
|
||||
}
|
||||
|
||||
void HeightFieldAssetHandler::Unregister()
|
||||
{
|
||||
AZ::AssetTypeInfoBus::Handler::BusDisconnect();
|
||||
|
||||
if (AZ::Data::AssetManager::IsReady())
|
||||
{
|
||||
Unregister();
|
||||
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
|
||||
}
|
||||
}
|
||||
|
||||
// AZ::AssetTypeInfoBus
|
||||
AZ::Data::AssetType HeightFieldAssetHandler::GetAssetType() const
|
||||
{
|
||||
return AZ::AzTypeInfo<HeightFieldAsset>::Uuid();
|
||||
}
|
||||
|
||||
void HeightFieldAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
|
||||
{
|
||||
extensions.push_back(HeightFieldAssetHandler::s_assetFileExtension);
|
||||
}
|
||||
|
||||
const char* HeightFieldAssetHandler::GetAssetTypeDisplayName() const
|
||||
{
|
||||
return "PhysX HeightField Mesh";
|
||||
}
|
||||
|
||||
const char* HeightFieldAssetHandler::GetBrowserIcon() const
|
||||
{
|
||||
return "Icons/Components/ColliderMesh.svg";
|
||||
}
|
||||
|
||||
const char* HeightFieldAssetHandler::GetGroup() const
|
||||
{
|
||||
return "Physics";
|
||||
}
|
||||
|
||||
AZ::Uuid HeightFieldAssetHandler::GetComponentTypeId() const
|
||||
{
|
||||
return PhysX::EditorTerrainComponentTypeId;
|
||||
}
|
||||
|
||||
// AZ::Data::AssetHandler
|
||||
AZ::Data::AssetPtr HeightFieldAssetHandler::CreateAsset([[maybe_unused]] const AZ::Data::AssetId& id, const AZ::Data::AssetType& type)
|
||||
{
|
||||
if (type == AZ::AzTypeInfo<HeightFieldAsset>::Uuid())
|
||||
{
|
||||
return aznew HeightFieldAsset();
|
||||
}
|
||||
|
||||
void HeightFieldAssetHandler::Register()
|
||||
AZ_Error("PhysX HeightField Asset", false, "This handler deals only with PhysXHeightFieldAsset type.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AZ::Data::AssetHandler::LoadResult HeightFieldAssetHandler::LoadAssetData(
|
||||
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
|
||||
[[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Physics);
|
||||
|
||||
HeightFieldAsset* physXHeightFieldAsset = asset.GetAs<HeightFieldAsset>();
|
||||
if (!physXHeightFieldAsset)
|
||||
{
|
||||
bool assetManagerReady = AZ::Data::AssetManager::IsReady();
|
||||
AZ_Error("PhysX HeightField Asset", assetManagerReady, "Asset manager isn't ready.");
|
||||
if (assetManagerReady)
|
||||
{
|
||||
AZ::Data::AssetManager::Instance().RegisterHandler(this, AZ::AzTypeInfo<HeightFieldAsset>::Uuid());
|
||||
}
|
||||
|
||||
AZ::AssetTypeInfoBus::Handler::BusConnect(AZ::AzTypeInfo<HeightFieldAsset>::Uuid());
|
||||
}
|
||||
|
||||
void HeightFieldAssetHandler::Unregister()
|
||||
{
|
||||
AZ::AssetTypeInfoBus::Handler::BusDisconnect();
|
||||
|
||||
if (AZ::Data::AssetManager::IsReady())
|
||||
{
|
||||
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
|
||||
}
|
||||
}
|
||||
|
||||
// AZ::AssetTypeInfoBus
|
||||
AZ::Data::AssetType HeightFieldAssetHandler::GetAssetType() const
|
||||
{
|
||||
return AZ::AzTypeInfo<HeightFieldAsset>::Uuid();
|
||||
}
|
||||
|
||||
void HeightFieldAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
|
||||
{
|
||||
extensions.push_back(HeightFieldAssetHandler::s_assetFileExtension);
|
||||
}
|
||||
|
||||
const char* HeightFieldAssetHandler::GetAssetTypeDisplayName() const
|
||||
{
|
||||
return "PhysX HeightField Mesh";
|
||||
}
|
||||
|
||||
const char* HeightFieldAssetHandler::GetBrowserIcon() const
|
||||
{
|
||||
return "Icons/Components/ColliderMesh.svg";
|
||||
}
|
||||
|
||||
const char* HeightFieldAssetHandler::GetGroup() const
|
||||
{
|
||||
return "Physics";
|
||||
}
|
||||
|
||||
AZ::Uuid HeightFieldAssetHandler::GetComponentTypeId() const
|
||||
{
|
||||
return PhysX::EditorTerrainComponentTypeId;
|
||||
}
|
||||
|
||||
// AZ::Data::AssetHandler
|
||||
AZ::Data::AssetPtr HeightFieldAssetHandler::CreateAsset([[maybe_unused]] const AZ::Data::AssetId& id, const AZ::Data::AssetType& type)
|
||||
{
|
||||
if (type == AZ::AzTypeInfo<HeightFieldAsset>::Uuid())
|
||||
{
|
||||
return aznew HeightFieldAsset();
|
||||
}
|
||||
|
||||
AZ_Error("PhysX HeightField Asset", false, "This handler deals only with PhysXHeightFieldAsset type.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AZ::Data::AssetHandler::LoadResult HeightFieldAssetHandler::LoadAssetData(
|
||||
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
|
||||
[[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Physics);
|
||||
|
||||
HeightFieldAsset* physXHeightFieldAsset = asset.GetAs<HeightFieldAsset>();
|
||||
if (!physXHeightFieldAsset)
|
||||
{
|
||||
AZ_Error("PhysX HeightField Asset", false, "This should be a PhysX HeightField Asset, as this is the only type we process.");
|
||||
return AZ::Data::AssetHandler::LoadResult::Error;
|
||||
}
|
||||
|
||||
// Wrap az stream behind physx interface
|
||||
PhysX::AssetDataStreamWrapper readerStream(stream);
|
||||
|
||||
// Read the file header
|
||||
HeightFieldAssetHeader header;
|
||||
readerStream.read(&header, sizeof(header));
|
||||
|
||||
// Parse the asset versions
|
||||
if (header.m_assetVersion >= 1)
|
||||
{
|
||||
if (header.m_assetDataSize > 0)
|
||||
{
|
||||
// Version 1 doesn't have min/max heights, so only read this data for versions 2+.
|
||||
if (header.m_assetVersion >= 2)
|
||||
{
|
||||
readerStream.read(&physXHeightFieldAsset->m_minHeight, sizeof(float));
|
||||
readerStream.read(&physXHeightFieldAsset->m_maxHeight, sizeof(float));
|
||||
}
|
||||
else
|
||||
{
|
||||
// In versions 0 & 1, the data is cooked assuming the data starts at origin (min height = 0)
|
||||
// and has a max height of 1024.0f.
|
||||
const float v1HardCodedMaxHeight = 1024.0f;
|
||||
physXHeightFieldAsset->m_minHeight = 0.0f;
|
||||
physXHeightFieldAsset->m_maxHeight = v1HardCodedMaxHeight;
|
||||
}
|
||||
|
||||
// Create heightfield from cooked file
|
||||
physx::PxPhysics& physx = PxGetPhysics();
|
||||
physXHeightFieldAsset->SetHeightField(physx.createHeightField(readerStream));
|
||||
|
||||
AZ_Error("PhysX HeightField Asset", physXHeightFieldAsset->m_heightField != nullptr, "Failed to construct PhysX mesh from the cooked data. Possible data corruption.");
|
||||
return (physXHeightFieldAsset->m_heightField != nullptr) ?
|
||||
AZ::Data::AssetHandler::LoadResult::LoadComplete :
|
||||
AZ::Data::AssetHandler::LoadResult::Error;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("HeightFieldAssetHandler", false, "Empty heightfield file. Try resaving your level");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("HeightFieldAssetHandler", false, "Unsupported asset version");
|
||||
}
|
||||
|
||||
AZ_Error("PhysX HeightField Asset", false, "This should be a PhysX HeightField Asset, as this is the only type we process.");
|
||||
return AZ::Data::AssetHandler::LoadResult::Error;
|
||||
}
|
||||
|
||||
bool HeightFieldAssetHandler::SaveAssetData(const AZ::Data::Asset<AZ::Data::AssetData>& asset, AZ::IO::GenericStream* stream)
|
||||
// Wrap az stream behind physx interface
|
||||
PhysX::AssetDataStreamWrapper readerStream(stream);
|
||||
|
||||
// Read the file header
|
||||
HeightFieldAssetHeader header;
|
||||
readerStream.read(&header, sizeof(header));
|
||||
|
||||
// Parse the asset versions
|
||||
if (header.m_assetVersion >= 1)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Physics);
|
||||
|
||||
HeightFieldAsset* physXHeightFieldAsset = asset.GetAs<HeightFieldAsset>();
|
||||
if (!physXHeightFieldAsset)
|
||||
if (header.m_assetDataSize > 0)
|
||||
{
|
||||
AZ_Error("PhysX HeightField Asset", false, "This should be a PhysX HeightField Asset. HeightFieldAssetHandler doesn't handle any other asset type.");
|
||||
return false;
|
||||
}
|
||||
// Version 1 doesn't have min/max heights, so only read this data for versions 2+.
|
||||
if (header.m_assetVersion >= 2)
|
||||
{
|
||||
readerStream.read(&physXHeightFieldAsset->m_minHeight, sizeof(float));
|
||||
readerStream.read(&physXHeightFieldAsset->m_maxHeight, sizeof(float));
|
||||
}
|
||||
else
|
||||
{
|
||||
// In versions 0 & 1, the data is cooked assuming the data starts at origin (min height = 0)
|
||||
// and has a max height of 1024.0f.
|
||||
const float v1HardCodedMaxHeight = 1024.0f;
|
||||
physXHeightFieldAsset->m_minHeight = 0.0f;
|
||||
physXHeightFieldAsset->m_maxHeight = v1HardCodedMaxHeight;
|
||||
}
|
||||
|
||||
physx::PxHeightField* heightField = physXHeightFieldAsset->GetHeightField();
|
||||
if (!heightField)
|
||||
{
|
||||
AZ_Warning("PhysX HeightField Asset", false, "There is no heightfield to save.");
|
||||
return false;
|
||||
}
|
||||
// Create heightfield from cooked file
|
||||
physx::PxPhysics& physx = PxGetPhysics();
|
||||
physXHeightFieldAsset->SetHeightField(physx.createHeightField(readerStream));
|
||||
|
||||
HeightFieldAssetHeader header;
|
||||
if (header.m_assetVersion == 2)
|
||||
{
|
||||
physx::PxCooking* cooking = nullptr;
|
||||
SystemRequestsBus::BroadcastResult(cooking, &SystemRequests::GetCooking);
|
||||
|
||||
// Read samples from heightfield
|
||||
AZStd::vector<physx::PxHeightFieldSample> samples;
|
||||
samples.resize(heightField->getNbColumns() * heightField->getNbRows());
|
||||
heightField->saveCells(samples.data(), (physx::PxU32)samples.size() * heightField->getSampleStride());
|
||||
|
||||
// Read description from heightfield
|
||||
physx::PxHeightFieldDesc heightFieldDesc;
|
||||
heightFieldDesc.format = heightField->getFormat();
|
||||
heightFieldDesc.nbColumns = heightField->getNbColumns();
|
||||
heightFieldDesc.nbRows = heightField->getNbRows();
|
||||
heightFieldDesc.samples.data = samples.data();
|
||||
heightFieldDesc.samples.stride = heightField->getSampleStride();
|
||||
|
||||
// Cook description to file
|
||||
physx::PxDefaultMemoryOutputStream writer;
|
||||
bool success = cooking->cookHeightField(heightFieldDesc, writer);
|
||||
header.m_assetDataSize = writer.getSize() + 2 * sizeof(float);
|
||||
|
||||
PhysX::StreamWrapper writerStream(stream);
|
||||
writerStream.write(&header, sizeof(header));
|
||||
writerStream.write(&physXHeightFieldAsset->m_minHeight, sizeof(physXHeightFieldAsset->m_minHeight));
|
||||
writerStream.write(&physXHeightFieldAsset->m_maxHeight, sizeof(physXHeightFieldAsset->m_maxHeight));
|
||||
writerStream.write(writer.getData(), writer.getSize());
|
||||
|
||||
return success;
|
||||
AZ_Error("PhysX HeightField Asset", physXHeightFieldAsset->m_heightField != nullptr, "Failed to construct PhysX mesh from the cooked data. Possible data corruption.");
|
||||
return (physXHeightFieldAsset->m_heightField != nullptr) ?
|
||||
AZ::Data::AssetHandler::LoadResult::LoadComplete :
|
||||
AZ::Data::AssetHandler::LoadResult::Error;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("HeightFieldAssetHandler", false, "Unsupported asset version");
|
||||
AZ_Warning("HeightFieldAssetHandler", false, "Empty heightfield file. Try resaving your level");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("HeightFieldAssetHandler", false, "Unsupported asset version");
|
||||
}
|
||||
|
||||
return AZ::Data::AssetHandler::LoadResult::Error;
|
||||
}
|
||||
|
||||
bool HeightFieldAssetHandler::SaveAssetData(const AZ::Data::Asset<AZ::Data::AssetData>& asset, AZ::IO::GenericStream* stream)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Physics);
|
||||
|
||||
HeightFieldAsset* physXHeightFieldAsset = asset.GetAs<HeightFieldAsset>();
|
||||
if (!physXHeightFieldAsset)
|
||||
{
|
||||
AZ_Error("PhysX HeightField Asset", false, "This should be a PhysX HeightField Asset. HeightFieldAssetHandler doesn't handle any other asset type.");
|
||||
return false;
|
||||
}
|
||||
|
||||
void HeightFieldAssetHandler::DestroyAsset(AZ::Data::AssetPtr ptr)
|
||||
physx::PxHeightField* heightField = physXHeightFieldAsset->GetHeightField();
|
||||
if (!heightField)
|
||||
{
|
||||
delete ptr;
|
||||
AZ_Warning("PhysX HeightField Asset", false, "There is no heightfield to save.");
|
||||
return false;
|
||||
}
|
||||
|
||||
void HeightFieldAssetHandler::GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes)
|
||||
HeightFieldAssetHeader header;
|
||||
if (header.m_assetVersion == 2)
|
||||
{
|
||||
assetTypes.push_back(AZ::AzTypeInfo<HeightFieldAsset>::Uuid());
|
||||
physx::PxCooking* cooking = nullptr;
|
||||
SystemRequestsBus::BroadcastResult(cooking, &SystemRequests::GetCooking);
|
||||
|
||||
// Read samples from heightfield
|
||||
AZStd::vector<physx::PxHeightFieldSample> samples;
|
||||
samples.resize(heightField->getNbColumns() * heightField->getNbRows());
|
||||
heightField->saveCells(samples.data(), (physx::PxU32)samples.size() * heightField->getSampleStride());
|
||||
|
||||
// Read description from heightfield
|
||||
physx::PxHeightFieldDesc heightFieldDesc;
|
||||
heightFieldDesc.format = heightField->getFormat();
|
||||
heightFieldDesc.nbColumns = heightField->getNbColumns();
|
||||
heightFieldDesc.nbRows = heightField->getNbRows();
|
||||
heightFieldDesc.samples.data = samples.data();
|
||||
heightFieldDesc.samples.stride = heightField->getSampleStride();
|
||||
|
||||
// Cook description to file
|
||||
physx::PxDefaultMemoryOutputStream writer;
|
||||
bool success = cooking->cookHeightField(heightFieldDesc, writer);
|
||||
header.m_assetDataSize = writer.getSize() + 2 * sizeof(float);
|
||||
|
||||
PhysX::StreamWrapper writerStream(stream);
|
||||
writerStream.write(&header, sizeof(header));
|
||||
writerStream.write(&physXHeightFieldAsset->m_minHeight, sizeof(physXHeightFieldAsset->m_minHeight));
|
||||
writerStream.write(&physXHeightFieldAsset->m_maxHeight, sizeof(physXHeightFieldAsset->m_maxHeight));
|
||||
writerStream.write(writer.getData(), writer.getSize());
|
||||
|
||||
return success;
|
||||
}
|
||||
} //namespace Pipeline
|
||||
} // namespace PhysX
|
||||
else
|
||||
{
|
||||
AZ_Warning("HeightFieldAssetHandler", false, "Unsupported asset version");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HeightFieldAssetHandler::DestroyAsset(AZ::Data::AssetPtr ptr)
|
||||
{
|
||||
delete ptr;
|
||||
}
|
||||
|
||||
void HeightFieldAssetHandler::GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes)
|
||||
{
|
||||
assetTypes.push_back(AZ::AzTypeInfo<HeightFieldAsset>::Uuid());
|
||||
}
|
||||
} // namespace PhysX::Pipeline
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace PhysX
|
||||
/// Wraps an AZ stream by provided the physx interface.
|
||||
/// This is used to prevent copying of data when going from
|
||||
/// physx streams to az streams.
|
||||
class StreamWrapper
|
||||
class StreamWrapper
|
||||
: public physx::PxInputStream
|
||||
, public physx::PxOutputStream
|
||||
|
||||
|
||||
@@ -66,12 +66,12 @@ namespace PhysX
|
||||
{
|
||||
sceneDesc.filterShader = Collision::DefaultFilterShader;
|
||||
}
|
||||
|
||||
|
||||
if (config.m_enableActiveActors)
|
||||
{
|
||||
sceneDesc.flags |= physx::PxSceneFlag::eENABLE_ACTIVE_ACTORS;
|
||||
}
|
||||
|
||||
|
||||
if (config.m_enablePcm)
|
||||
{
|
||||
sceneDesc.flags |= physx::PxSceneFlag::eENABLE_PCM;
|
||||
@@ -80,19 +80,19 @@ namespace PhysX
|
||||
{
|
||||
sceneDesc.flags &= ~physx::PxSceneFlag::eENABLE_PCM;
|
||||
}
|
||||
|
||||
|
||||
if (config.m_kinematicFiltering)
|
||||
{
|
||||
sceneDesc.kineKineFilteringMode = physx::PxPairFilteringMode::eKEEP;
|
||||
}
|
||||
|
||||
|
||||
if (config.m_kinematicStaticFiltering)
|
||||
{
|
||||
sceneDesc.staticKineFilteringMode = physx::PxPairFilteringMode::eKEEP;
|
||||
}
|
||||
|
||||
|
||||
sceneDesc.bounceThresholdVelocity = config.m_bounceThresholdVelocity;
|
||||
|
||||
|
||||
sceneDesc.filterCallback = filterCallback;
|
||||
sceneDesc.simulationEventCallback = simEventCallback;
|
||||
#ifdef ENABLE_TGS_SOLVER
|
||||
@@ -232,10 +232,10 @@ namespace PhysX
|
||||
}
|
||||
|
||||
template<class JointType, class ConfigurationType>
|
||||
AzPhysics::Joint* CreateJoint(const ConfigurationType* configuration,
|
||||
AzPhysics::Joint* CreateJoint(const ConfigurationType* configuration,
|
||||
AzPhysics::SceneHandle sceneHandle,
|
||||
AzPhysics::SimulatedBodyHandle parentBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle,
|
||||
AzPhysics::SimulatedBodyHandle childBodyHandle,
|
||||
AZ::Crc32& crc)
|
||||
{
|
||||
JointType* newBody = aznew JointType(*configuration, sceneHandle, parentBodyHandle, childBodyHandle);
|
||||
@@ -254,7 +254,7 @@ namespace PhysX
|
||||
// The filter should also use the eTOUCH flag to find all contacts with the ray.
|
||||
// Otherwise the default buffer (1 result) and eBLOCK flag is enough to find the first hit.
|
||||
physx::PxRaycastBuffer castResult;
|
||||
SceneQueryHelpers::PhysXQueryFilterCallback queryFilterCallback;
|
||||
SceneQueryHelpers::PhysXQueryFilterCallback queryFilterCallback;
|
||||
if (raycastRequest->m_reportMultipleHits)
|
||||
{
|
||||
const AZ::u64 maxSize = AZStd::min(raycastRequest->m_maxResults, sceneMaxResults);
|
||||
@@ -476,7 +476,7 @@ namespace PhysX
|
||||
//register for future changes to the buffer sizes.
|
||||
physXSystem->RegisterSystemConfigurationChangedEvent(m_physicsSystemConfigChanged);
|
||||
}
|
||||
|
||||
|
||||
PhysXScene::s_rayCastBuffer = {};
|
||||
PhysXScene::s_sweepBuffer = {};
|
||||
PhysXScene::s_overlapBuffer = {};
|
||||
@@ -503,7 +503,7 @@ namespace PhysX
|
||||
{
|
||||
if (simulatedBody.second->m_simulating)
|
||||
{
|
||||
// Disable simulation on body (not signaling OnSimulationBodySimulationDisabled event)
|
||||
// Disable simulation on body (not signaling OnSimulationBodySimulationDisabled event)
|
||||
DisableSimulationOfBodyInternal(*simulatedBody.second);
|
||||
}
|
||||
m_simulatedBodyRemovedEvent.Signal(m_sceneHandle, simulatedBody.second->m_bodyHandle);
|
||||
@@ -577,7 +577,7 @@ namespace PhysX
|
||||
// Swap the buffers, invoke callbacks, build the list of active actors.
|
||||
m_pxScene->fetchResults(true);
|
||||
}
|
||||
|
||||
|
||||
if (activeActorsEnabled)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(Physics, "PhysXScene::ActiveActors");
|
||||
@@ -753,14 +753,14 @@ namespace PhysX
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AzPhysics::SimulatedBodyIndex index = AZStd::get<AzPhysics::HandleTypeIndex::Index>(bodyHandle);
|
||||
if (index < m_simulatedBodies.size()
|
||||
&& m_simulatedBodies[index].first == AZStd::get<AzPhysics::HandleTypeIndex::Crc>(bodyHandle))
|
||||
{
|
||||
if (m_simulatedBodies[index].second->m_simulating)
|
||||
{
|
||||
// Disable simulation on body (not signaling OnSimulationBodySimulationDisabled event)
|
||||
// Disable simulation on body (not signaling OnSimulationBodySimulationDisabled event)
|
||||
DisableSimulationOfBodyInternal(*m_simulatedBodies[index].second);
|
||||
}
|
||||
|
||||
@@ -800,7 +800,7 @@ namespace PhysX
|
||||
|
||||
EnableSimulationOfBodyInternal(*body);
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
AZ_Warning("PhysXScene", false, "Unable to enable Simulated body, failed to find body.")
|
||||
}
|
||||
@@ -830,8 +830,8 @@ namespace PhysX
|
||||
}
|
||||
}
|
||||
|
||||
AzPhysics::JointHandle PhysXScene::AddJoint(const AzPhysics::JointConfiguration* jointConfig,
|
||||
AzPhysics::SimulatedBodyHandle parentBody, AzPhysics::SimulatedBodyHandle childBody)
|
||||
AzPhysics::JointHandle PhysXScene::AddJoint(const AzPhysics::JointConfiguration* jointConfig,
|
||||
AzPhysics::SimulatedBodyHandle parentBody, AzPhysics::SimulatedBodyHandle childBody)
|
||||
{
|
||||
AzPhysics::Joint* newJoint = nullptr;
|
||||
AZ::Crc32 newJointCrc;
|
||||
@@ -880,7 +880,7 @@ namespace PhysX
|
||||
return AzPhysics::InvalidJointHandle;
|
||||
}
|
||||
|
||||
AzPhysics::Joint* PhysXScene::GetJointFromHandle(AzPhysics::JointHandle jointHandle)
|
||||
AzPhysics::Joint* PhysXScene::GetJointFromHandle(AzPhysics::JointHandle jointHandle)
|
||||
{
|
||||
if (jointHandle == AzPhysics::InvalidJointHandle)
|
||||
{
|
||||
@@ -896,13 +896,13 @@ namespace PhysX
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PhysXScene::RemoveJoint(AzPhysics::JointHandle jointHandle)
|
||||
void PhysXScene::RemoveJoint(AzPhysics::JointHandle jointHandle)
|
||||
{
|
||||
if (jointHandle == AzPhysics::InvalidJointHandle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AzPhysics::JointIndex index = AZStd::get<AzPhysics::HandleTypeIndex::Index>(jointHandle);
|
||||
if (index < m_joints.size()
|
||||
&& m_joints[index].first == AZStd::get<AzPhysics::HandleTypeIndex::Crc>(jointHandle))
|
||||
@@ -921,7 +921,7 @@ namespace PhysX
|
||||
return {}; //return 0 hits
|
||||
}
|
||||
|
||||
// Query flags.
|
||||
// Query flags.
|
||||
const physx::PxQueryFlags queryFlags = SceneQueryHelpers::GetPxQueryFlags(request->m_queryType);
|
||||
const physx::PxQueryFilterData queryData(queryFlags);
|
||||
|
||||
@@ -1016,7 +1016,7 @@ namespace PhysX
|
||||
|
||||
void PhysXScene::EnableSimulationOfBodyInternal(AzPhysics::SimulatedBody& body)
|
||||
{
|
||||
//character controller is a special actor and only needs the m_simulating flag set,
|
||||
//character controller is a special actor and only needs the m_simulating flag set,
|
||||
if (!azrtti_istypeof<PhysX::CharacterController>(body) &&
|
||||
!azrtti_istypeof<PhysX::Ragdoll>(body))
|
||||
{
|
||||
@@ -1043,7 +1043,7 @@ namespace PhysX
|
||||
|
||||
void PhysXScene::DisableSimulationOfBodyInternal(AzPhysics::SimulatedBody& body)
|
||||
{
|
||||
//character controller is a special actor and only needs the m_simulating flag set,
|
||||
//character controller is a special actor and only needs the m_simulating flag set,
|
||||
if (!azrtti_istypeof<PhysX::CharacterController>(body) &&
|
||||
!azrtti_istypeof<PhysX::Ragdoll>(body))
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace PhysX
|
||||
void RemoveSimulatedBodies(AzPhysics::SimulatedBodyHandleList& bodyHandles) override;
|
||||
void EnableSimulationOfBody(AzPhysics::SimulatedBodyHandle bodyHandle) override;
|
||||
void DisableSimulationOfBody(AzPhysics::SimulatedBodyHandle bodyHandle) override;
|
||||
AzPhysics::JointHandle AddJoint(const AzPhysics::JointConfiguration* jointConfig,
|
||||
AzPhysics::JointHandle AddJoint(const AzPhysics::JointConfiguration* jointConfig,
|
||||
AzPhysics::SimulatedBodyHandle parentBody, AzPhysics::SimulatedBodyHandle childBody) override;
|
||||
AzPhysics::Joint* GetJointFromHandle(AzPhysics::JointHandle jointHandle) override;
|
||||
void RemoveJoint(AzPhysics::JointHandle jointHandle) override;
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace PhysX
|
||||
{
|
||||
m_onMaterialLibraryReloadedCallback(asset);
|
||||
}
|
||||
|
||||
|
||||
PhysXSystem::PhysXSystem(PhysXSettingsRegistryManager* registryManager, const physx::PxCookingParams& cookingParams)
|
||||
: m_registryManager(*registryManager)
|
||||
, m_materialLibraryAssetHelper(
|
||||
@@ -528,7 +528,7 @@ namespace PhysX
|
||||
|
||||
AZ_Warning("PhysX", loadedSuccessfully,
|
||||
"LoadDefaultMaterialLibrary: Default Material Library asset data is invalid.");
|
||||
|
||||
|
||||
return loadedSuccessfully;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace PhysX
|
||||
private:
|
||||
//! Initializes the PhysX SDK.
|
||||
//! This sets up the PhysX Foundation, Cooking, and other PhysX sub-systems.
|
||||
//! @param cookingParams The cooking params to use when setting up PhysX cooking interface.
|
||||
//! @param cookingParams The cooking params to use when setting up PhysX cooking interface.
|
||||
void InitializePhysXSdk(const physx::PxCookingParams& cookingParams);
|
||||
void ShutdownPhysXSdk();
|
||||
|
||||
|
||||
@@ -404,7 +404,7 @@ namespace ScriptCanvasEditor
|
||||
// This updates the asset Id with the canonical assetId on SourceFileChanged
|
||||
|
||||
// This occurs for new ScriptCanvas assets because before the SC asset is saved to disk, the asset database
|
||||
// has no asset Id associated with it, so this uses the supplied source path to find the asset Id registered
|
||||
// has no asset Id associated with it, so this uses the supplied source path to find the asset Id registered
|
||||
AZStd::string fullPath;
|
||||
AzFramework::StringFunc::Path::Join(scanFolder.data(), relativePath.data(), fullPath);
|
||||
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePath, fullPath);
|
||||
|
||||
@@ -117,10 +117,10 @@ namespace ScriptCanvasEditor
|
||||
// once the file is saved to file, its asset ID will be changed, if the file is to remain
|
||||
// open, we need to update the source AssetId to correspond to the file asset.
|
||||
//
|
||||
// The other is when an asset is loaded, we clone the asset from file and use an in-memory
|
||||
// version of the asset until it is time to save, at that moment we need to save to the
|
||||
// The other is when an asset is loaded, we clone the asset from file and use an in-memory
|
||||
// version of the asset until it is time to save, at that moment we need to save to the
|
||||
// source file
|
||||
class ScriptCanvasMemoryAsset
|
||||
class ScriptCanvasMemoryAsset
|
||||
: public MemoryAsset<ScriptCanvas::ScriptCanvasAssetBase>
|
||||
, public AZStd::enable_shared_from_this<ScriptCanvasMemoryAsset>
|
||||
, EditorGraphNotificationBus::Handler
|
||||
@@ -245,7 +245,7 @@ namespace ScriptCanvasEditor
|
||||
return m_inMemoryAsset;
|
||||
}
|
||||
|
||||
// Upon loading a graph, we clone the source data and we replace the loaded asset with
|
||||
// Upon loading a graph, we clone the source data and we replace the loaded asset with
|
||||
// a clone, this is to prevent modifications to the source data and it gives us some
|
||||
// flexibility if we need to load the source asset again
|
||||
AZ::Data::Asset<AZ::Data::AssetData> CloneAssetData(AZ::Data::AssetId newAssetId);
|
||||
|
||||
Reference in New Issue
Block a user