Files
o3de/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp
T
lumberyard-employee-dm b3b646dad9 Moved the Asset Catalog loading from LmbrCentral to the AzFramework::Application (#4568)
* Moved the loading of the AssetCatalog from LmbrCentralSystemComponent to AzFramework Application

Modified the AssetCatalog::InitializeCatalog function to no longer rely on the TickBus to send out the `AssetCatalogEventBus::OnCatalogLoaded` event.
It now queues a function on the AssetCatalogRequestBus to send the OnCatalogLoaded event as soon as the dispatching for the AssetCatalogRequestBus has completed on the current thread.
This is done by updating the AssetCatalogRequestBus to use EBus ThreadDispatchPolicy to add a callback to invoke any queued function has soon a thread has finished dispatching and has released its DispatchMutex

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the AssetCatalogRequestBus to add a custom DispatchLockGuard

The AssetCatalogRequestBus uses the custom lock guard to dispatch queued
events after it has unlocked it's context mutex for the current thread.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed GetContext call from the
AssetCatalogRequests::PostThreadDispatchInvoker

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the definition of FileTagQueryManager::GetDefaultFileTagFilePath
function to return a path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the AZ_CONSOLEFREEFUNC macro to actually use the _NAME

The _NAME parameter was not being used before, resulting in the Console
stringified name of the function being used.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed CrySystem dependencies from the BundlingSystemComponent

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Moved the loading of the AssetCatalog from LmbrCentralSystemComponent to AzFramework Application

Modified the AssetCatalog::InitializeCatalog function to no longer rely on the TickBus to send out the `AssetCatalogEventBus::OnCatalogLoaded` event.
It now queues a function on the AssetCatalogRequestBus to send the OnCatalogLoaded event as soon as the dispatching for the AssetCatalogRequestBus has completed on the current thread.
This is done by updating the AssetCatalogRequestBus to use EBus ThreadDispatchPolicy to add a callback to invoke any queued function has soon a thread has finished dispatching and has released its DispatchMutex

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the AssetCatalogRequestBus to add a custom DispatchLockGuard

The AssetCatalogRequestBus uses the custom lock guard to dispatch queued
events after it has unlocked it's context mutex for the current thread.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed GetContext call from the
AssetCatalogRequests::PostThreadDispatchInvoker

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the definition of FileTagQueryManager::GetDefaultFileTagFilePath
function to return a path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the AZ_CONSOLEFREEFUNC macro to actually use the _NAME

The _NAME parameter was not being used before, resulting in the Console
stringified name of the function being used.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed CrySystem dependencies from the BundlingSystemComponent

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Addded missing template parameter to AssetCatalogRequests

The fixes the compile error.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Adding AssetBus::MultiHandler::BusDisconnect call

The BlastSystemComponent was connecting to the Bus, but not
disconnecting from it, causing an assert to fire to it being a
multi-thread bus

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Added support for DataDrive lifecycle events to the ComponentApplication

The events are using the SettingsRegistry NotifyEvent to track when
certain keys are modified to signal handlers.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Corrected invalid JSON creation in ModuleManager::DeactivateEntities

Resolved clang warning about used type alias

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fix for dangling reference in lambda registered to the SettingsRegistry
Notifier event

This was causing the EditorPythonBinding tests to crash due to the
following circumstances.

First Python has created an instance of a SettingsRegistryProxy
Second the SettingsRegistry sends an event during the time when the
SettingsRegistryProxy exists.

This issue was exposed due to the ComponentApplication Lifecycle events
using the SettingsRegistry to dispatch during various times of the
application workflow.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-10-26 10:31:31 -05:00

490 lines
17 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Components/BlastSystemComponent.h>
#include <Asset/BlastAsset.h>
#include <Asset/BlastAssetHandler.h>
#include <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h>
#include <Atom/RPI.Public/Scene.h>
#include <AzCore/Jobs/JobCompletion.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Blast/BlastActorData.h>
#include <Blast/BlastDebug.h>
#include <Blast/BlastFamilyComponentBus.h>
#include <Blast/BlastMaterial.h>
#include <IConsole.h>
#include <NvBlastExtPxSerialization.h>
#include <NvBlastExtTkSerialization.h>
#include <NvBlastPxCallbacks.h>
#include <NvBlastTkJoint.h>
#include <PhysX/SystemComponentBus.h>
#include <System/PhysXCpuDispatcher.h>
#ifdef BLAST_EDITOR
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#endif
#include <CryCommon/ISystem.h>
namespace Blast
{
static const char* const DefaultConfigurationPath = "default.blastconfiguration";
void BlastSystemComponent::Reflect(AZ::ReflectContext* context)
{
BlastGlobalConfiguration::Reflect(context);
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<BlastSystemComponent, AZ::Component>()->Version(1);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<BlastSystemComponent>("Blast", "Adds support for the NVIDIA Blast destruction system")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
}
}
}
void BlastGlobalConfiguration::Reflect(AZ::ReflectContext* context)
{
BlastMaterialLibraryAsset::Reflect(context);
BlastMaterialConfiguration::Reflect(context);
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<BlastGlobalConfiguration>()
->Field("BlastMaterialLibrary", &BlastGlobalConfiguration::m_materialLibrary)
->Field("StressSolverIterations", &BlastGlobalConfiguration::m_stressSolverIterations)
->Version(1);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<BlastGlobalConfiguration>(
"Blast global configuration", "Set of configuration that are applied globally within Blast gem.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(
AZ::Edit::UIHandlers::Default, &BlastGlobalConfiguration::m_materialLibrary,
"Blast material library", "Material library asset to be used globally.")
->DataElement(
AZ::Edit::UIHandlers::Default, &BlastGlobalConfiguration::m_stressSolverIterations,
"Stress solver iterations",
"Number of iterations stress solver on each family runs for each tick.")
->Attribute(AZ::Edit::Attributes::Min, 0)
->Attribute(AZ::Edit::Attributes::Max, 50000);
}
}
}
void BlastSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("BlastService"));
}
void BlastSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("BlastService"));
}
void BlastSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("PhysXService"));
}
void BlastSystemComponent::Init()
{
// Route NvBlast allocations through the AZ system allocator
NvBlastGlobalSetAllocatorCallback(&m_blastAllocatorCallback);
m_debugRenderMode = DebugRenderDisabled;
}
void BlastSystemComponent::Activate()
{
AZ_PROFILE_FUNCTION(Physics);
auto blastAssetHandler = aznew BlastAssetHandler();
blastAssetHandler->Register();
m_assetHandlers.emplace_back(blastAssetHandler);
auto materialAsset = aznew AzFramework::GenericAssetHandler<BlastMaterialLibraryAsset>(
"Blast Material", "Blast", "blastmaterial");
materialAsset->Register();
m_assetHandlers.emplace_back(materialAsset);
// Add asset types and extensions to AssetCatalog. Uses "AssetCatalogService".
auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
if (assetCatalog)
{
assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo<BlastAsset>::Uuid());
assetCatalog->AddExtension("blast");
}
m_registered = false;
BlastSystemRequestBus::Handler::BusConnect();
AZ::TickBus::Handler::BusConnect();
CrySystemEventBus::Handler::BusConnect();
InitPhysics();
}
void BlastSystemComponent::Deactivate()
{
AZ_PROFILE_FUNCTION(Physics);
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
CrySystemEventBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
BlastSystemRequestBus::Handler::BusDisconnect();
SaveConfiguration();
DeactivatePhysics();
m_assetHandlers.clear();
};
void BlastSystemComponent::InitPhysics()
{
// Create blast singletons
m_tkFramework.reset(NvBlastTkFrameworkCreate());
m_dispatcher = PhysX::PhysXCpuDispatcherCreate();
physx::PxCooking* cooking = nullptr;
PhysX::SystemRequestsBus::BroadcastResult(cooking, &PhysX::SystemRequests::GetCooking);
m_defaultTaskManager.reset(physx::PxTaskManager::createTaskManager(NvBlastGetPxErrorCallback(), m_dispatcher));
m_extSerialization.reset(NvBlastExtSerializationCreate());
if (m_extSerialization != nullptr)
{
NvBlastExtPxSerializerLoadSet(*m_tkFramework, PxGetPhysics(), *cooking, *m_extSerialization);
NvBlastExtTkSerializerLoadSet(*m_tkFramework, *m_extSerialization);
}
NvBlastProfilerSetCallback(&m_blastProfilerCallback);
NvBlastProfilerSetDetail(Nv::Blast::ProfilerDetail::HIGH);
}
void BlastSystemComponent::DeactivatePhysics()
{
m_extSerialization = nullptr;
m_defaultTaskManager = nullptr;
m_tkFramework = nullptr;
delete m_dispatcher;
m_dispatcher = nullptr;
}
void BlastSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
AZ_PROFILE_FUNCTION(Physics);
AZ::JobCompletion jobCompletion;
BlastFamilyComponentRequestBus::EnumerateHandlers(
[&jobCompletion](BlastFamilyComponentRequests* handler)
{
auto stressDamageJob = AZ::CreateJobFunction(
[handler]() -> void
{
handler->ApplyStressDamage();
},
true);
stressDamageJob->SetDependent(&jobCompletion);
stressDamageJob->Start();
return true;
});
BlastFamilyComponentRequestBus::EnumerateHandlers(
[](BlastFamilyComponentRequests* handler)
{
handler->SyncMeshes();
return true;
});
jobCompletion.StartAndWaitForCompletion();
// Run groups
for (auto i = 0; i < m_groups.size(); ++i)
{
if (m_groups[i].m_tkGroup->getActorCount() == 0)
{
AZStd::swap(m_groups[i], m_groups.back());
m_groups.pop_back();
}
}
for (auto& group : m_groups)
{
AZ_PROFILE_SCOPE(Physics, "ExtGroupTaskManager::process");
group.m_extGroupTaskManager->process();
}
for (auto& group : m_groups)
{
AZ_PROFILE_SCOPE(Physics, "ExtGroupTaskManager::wait");
group.m_extGroupTaskManager->wait();
}
// Clean up damage descriptions and program params now that groups have run.
{
AZ_PROFILE_SCOPE(Physics, "BlastSystemComponent::OnTick::Cleanup");
m_radialDamageDescs.clear();
m_capsuleDamageDescs.clear();
m_shearDamageDescs.clear();
m_triangleDamageDescs.clear();
m_impactDamageDescs.clear();
m_programParams.clear();
}
if (gEnv && m_debugRenderMode)
{
AZ_PROFILE_SCOPE(Physics, "BlastSystemComponent::OnTick::DebugRender");
DebugRenderBuffer buffer;
BlastFamilyComponentRequestBus::Broadcast(
&BlastFamilyComponentRequests::FillDebugRenderBuffer, buffer, m_debugRenderMode);
// This is a system component, and thus is not associated with a specific scene, so use the default scene
// for the debug drawing
const auto defaultScene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
auto drawQueue = AZ::RPI::AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(defaultScene);
for (DebugLine& line : buffer.m_lines)
{
AZ::RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments drawArguments;
drawArguments.m_verts = &line.m_p0;
drawArguments.m_vertCount = 2;
drawArguments.m_colors = &line.m_color;
drawArguments.m_colorCount = 1;
drawQueue->DrawLines(drawArguments);
}
}
}
void BlastSystemComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (m_configuration.m_materialLibrary == asset)
{
m_configuration.m_materialLibrary = asset;
}
}
void BlastSystemComponent::LoadConfiguration()
{
BlastGlobalConfiguration globalConfiguration;
[[maybe_unused]] bool loaded = AZ::Utils::LoadObjectFromFileInPlace<BlastGlobalConfiguration>(
DefaultConfigurationPath, globalConfiguration);
AZ_Warning("Blast", loaded, "Failed to load Blast configuration, initializing with default configs.");
ApplyGlobalConfiguration(globalConfiguration);
if (!loaded)
{
SaveConfiguration();
}
}
void BlastSystemComponent::SaveConfiguration()
{
auto assetRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@projectroot@");
if (!assetRoot)
{
return;
}
AZStd::string fullPath;
AzFramework::StringFunc::Path::Join(assetRoot, DefaultConfigurationPath, fullPath);
[[maybe_unused]] bool saved = AZ::Utils::SaveObjectToFile<BlastGlobalConfiguration>(
fullPath.c_str(), AZ::DataStream::ST_XML, &m_configuration);
AZ_Warning("BlastSystemComponent", saved, "Failed to save Blast configuration");
}
void BlastSystemComponent::CheckoutConfiguration()
{
const auto assetRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@projectroot@");
AZStd::string fullPath;
AzFramework::StringFunc::Path::Join(assetRoot, DefaultConfigurationPath, fullPath);
#ifdef BLAST_EDITOR
AzToolsFramework::SourceControlCommandBus::Broadcast(
&AzToolsFramework::SourceControlCommandBus::Events::RequestEdit, fullPath.c_str(), true,
[](bool /*success*/, const AzToolsFramework::SourceControlFileInfo& info)
{
// File is checked out
});
#endif
}
void BlastSystemComponent::OnCrySystemInitialized(ISystem&, const SSystemInitParams&)
{
LoadConfiguration();
RegisterCommands();
}
void BlastSystemComponent::OnCryEditorInitialized()
{
CheckoutConfiguration();
}
Nv::Blast::TkFramework* BlastSystemComponent::GetTkFramework() const
{
return m_tkFramework.get();
}
Nv::Blast::ExtSerialization* BlastSystemComponent::GetExtSerialization() const
{
return m_extSerialization.get();
}
Nv::Blast::TkGroup* BlastSystemComponent::GetTkGroup()
{
m_groups.emplace_back();
Nv::Blast::TkGroupDesc groupDesc;
groupDesc.workerCount = m_defaultTaskManager->getCpuDispatcher()->getWorkerCount();
m_groups.back().m_tkGroup.reset(m_tkFramework->createGroup(groupDesc));
m_groups.back().m_extGroupTaskManager.reset(
Nv::Blast::ExtGroupTaskManager::create(*m_defaultTaskManager, m_groups.back().m_tkGroup.get()));
return m_groups.back().m_tkGroup.get();
}
void BlastSystemComponent::AddDamageDesc(AZStd::unique_ptr<NvBlastExtRadialDamageDesc> desc)
{
m_radialDamageDescs.push_back(AZStd::move(desc));
}
void BlastSystemComponent::AddDamageDesc(AZStd::unique_ptr<NvBlastExtCapsuleRadialDamageDesc> desc)
{
m_capsuleDamageDescs.push_back(AZStd::move(desc));
}
void BlastSystemComponent::AddDamageDesc(AZStd::unique_ptr<NvBlastExtShearDamageDesc> desc)
{
m_shearDamageDescs.push_back(AZStd::move(desc));
}
void BlastSystemComponent::AddDamageDesc(AZStd::unique_ptr<NvBlastExtTriangleIntersectionDamageDesc> desc)
{
m_triangleDamageDescs.push_back(AZStd::move(desc));
}
void BlastSystemComponent::AddDamageDesc(AZStd::unique_ptr<NvBlastExtImpactSpreadDamageDesc> desc)
{
m_impactDamageDescs.push_back(AZStd::move(desc));
}
void BlastSystemComponent::AddProgramParams(AZStd::unique_ptr<NvBlastExtProgramParams> program)
{
m_programParams.emplace_back(AZStd::move(program));
}
const BlastGlobalConfiguration& BlastSystemComponent::GetGlobalConfiguration() const
{
return m_configuration;
}
void BlastSystemComponent::SetGlobalConfiguration(const BlastGlobalConfiguration& globalConfiguration)
{
ApplyGlobalConfiguration(globalConfiguration);
SaveConfiguration();
}
void BlastSystemComponent::ApplyGlobalConfiguration(const BlastGlobalConfiguration& globalConfiguration)
{
m_configuration = globalConfiguration;
{
AZ::Data::Asset<Blast::BlastMaterialLibraryAsset>& materialLibrary = m_configuration.m_materialLibrary;
if (!materialLibrary.GetId().IsValid())
{
AZ_Warning("Blast", false, "LoadDefaultMaterialLibrary: Default Material Library asset ID is invalid.");
return;
}
materialLibrary = AZ::Data::AssetManager::Instance().GetAsset<Blast::BlastMaterialLibraryAsset>(
materialLibrary.GetId(), AZ::Data::AssetLoadBehavior::QueueLoad);
materialLibrary.BlockUntilLoadComplete();
// Listen for material library asset modification events
if (!AZ::Data::AssetBus::MultiHandler::BusIsConnectedId(materialLibrary.GetId()))
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
AZ::Data::AssetBus::MultiHandler::BusConnect(materialLibrary.GetId());
}
AZ_Warning(
"Blast", (materialLibrary.GetData() != nullptr),
"LoadDefaultMaterialLibrary: Default Material Library asset data is invalid.");
}
#ifdef BLAST_EDITOR
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
#endif
}
void BlastSystemComponent::AZBlastProfilerCallback::zoneStart(const char* eventName)
{
AZ_PROFILE_BEGIN(Physics, eventName);
}
void BlastSystemComponent::AZBlastProfilerCallback::zoneEnd()
{
AZ_PROFILE_END(Physics);
}
static void CmdToggleBlastDebugVisualization(IConsoleCmdArgs* args)
{
const int argumentCount = args->GetArgCount();
if (argumentCount == 2)
{
const auto userPreference = static_cast<DebugRenderMode>(strtol(args->GetArg(1), nullptr, 10));
BlastSystemRequestBus::Broadcast(&BlastSystemRequests::SetDebugRenderMode, userPreference);
}
else
{
AZ_Warning(
"Blast", false,
"Invalid blast_debug Arguments. Please use blast_debug 1 to enable, blast_debug 0 to disable.");
}
}
void BlastSystemComponent::RegisterCommands()
{
if (m_registered)
{
return;
}
if (gEnv)
{
IConsole* console = gEnv->pSystem->GetIConsole();
if (console)
{
console->AddCommand("blast_debug", CmdToggleBlastDebugVisualization);
}
m_registered = true;
}
}
void BlastSystemComponent::SetDebugRenderMode(DebugRenderMode debugRenderMode)
{
m_debugRenderMode = debugRenderMode;
}
} // namespace Blast