-
Notifications
You must be signed in to change notification settings - Fork 3
[2409] Csv Spawner Notification Bus #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: o3de-2409
Are you sure you want to change the base?
Changes from 44 commits
6470d8a
6ccf97e
915d029
1b9affd
57322f8
ee5c565
399c2d1
5b7dd26
3fc175d
9cf218a
4651355
45bd34c
ffa7f34
23e9891
7049997
c91a03a
f70295a
5373024
09e44bf
ee39e1c
2fbc807
002c35b
139bb38
318d19f
0c45656
bb29e40
5a24a36
54e6f12
22c3628
51e1941
476f2b4
26cb4bd
62358ef
9e3cf1a
95b70d1
b30aac2
75e3739
61fc9aa
13671a4
2c17d49
896f067
687aff9
b30dda3
3694333
2e4d01a
3345918
48cd8d2
24c4e8d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/** | ||
* Copyright (C) Robotec AI - All Rights Reserved | ||
* | ||
* This source code is protected under international copyright law. All rights | ||
* reserved and protected by the copyright holders. | ||
* This file is confidential and only available to authorized individuals with | ||
* the permission of the copyright holders. If you encounter this file and do | ||
* not have permission, please contact the copyright holders and delete this | ||
* file. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <CsvSpawner/CsvSpawnerUtils.h> | ||
|
||
#include <AzCore/EBus/EBus.h> | ||
#include <AzCore/RTTI/BehaviorContext.h> | ||
|
||
namespace CsvSpawner | ||
{ | ||
/** | ||
* @brief Interface for handling entity spawn events for Csv Spawner. | ||
* | ||
* CsvSpawnerInterface is an Event Bus interface that notifies multiple | ||
* listeners when entity spawning begins and finishes. | ||
*/ | ||
class CsvSpawnerInterface : public AZ::EBusTraits | ||
w-czerski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
public: | ||
AZ_RTTI(CsvSpawnerInterface, CsvSpawnerInterfaceTypeId); | ||
virtual ~CsvSpawnerInterface() = default; | ||
|
||
/** | ||
* @brief Called when entity spawning begins. | ||
* @param m_spawnInfo Struct holding information about entities to be spawned. | ||
*/ | ||
virtual void OnEntitiesSpawnBegin(CsvSpawnerUtils::SpawnInfo& m_spawnInfo) = 0; | ||
|
||
/** | ||
* @brief Called when entity spawning finishes. | ||
* @param m_spawnInfo Struct holding information about entities to be spawned. | ||
* @param m_statusCode Status code indicating success, failure and warnings of the spawn. | ||
*/ | ||
virtual void OnEntitiesSpawnFinished(CsvSpawnerUtils::SpawnInfo& m_spawnInfo, CsvSpawnerUtils::SpawnStatus m_statusCode) = 0; | ||
|
||
/// EBus Configuration - Allows multiple listeners to handle events. | ||
static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; | ||
}; | ||
|
||
// Create an EBus using the notification interface | ||
using CsvSpawnerNotificationBus = AZ::EBus<CsvSpawnerInterface>; | ||
|
||
class CsvSpawnerNotificationBusHandler | ||
: public CsvSpawnerNotificationBus::Handler | ||
, public AZ::BehaviorEBusHandler | ||
{ | ||
public: | ||
AZ_EBUS_BEHAVIOR_BINDER( | ||
CsvSpawnerNotificationBusHandler, | ||
CsvSpawnerNotificationBusHandlerTypeId, | ||
AZ::SystemAllocator, | ||
OnEntitiesSpawnBegin, | ||
OnEntitiesSpawnFinished); | ||
|
||
void OnEntitiesSpawnBegin(CsvSpawnerUtils::SpawnInfo& m_spawnInfo) override | ||
{ | ||
Call(FN_OnEntitiesSpawnBegin, m_spawnInfo); | ||
} | ||
|
||
void OnEntitiesSpawnFinished(CsvSpawnerUtils::SpawnInfo& m_spawnInfo, CsvSpawnerUtils::SpawnStatus m_statusCode) override | ||
{ | ||
Call(FN_OnEntitiesSpawnFinished, m_spawnInfo, m_statusCode); | ||
} | ||
}; | ||
} // namespace CsvSpawner |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,11 +11,15 @@ | |
|
||
#include "CsvSpawnerUtils.h" | ||
|
||
|
||
#include <AzFramework/Physics/CollisionBus.h> | ||
norbertprokopiuk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#include <CsvSpawner/CsvSpawnerInterface.h> | ||
|
||
#include <AzCore/Asset/AssetSerializer.h> | ||
#include <AzCore/Serialization/EditContext.h> | ||
#include <AzCore/Serialization/SerializeContext.h> | ||
#include <AzFramework/Components/TransformComponent.h> | ||
#include <AzFramework/Physics/CollisionBus.h> | ||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h> | ||
#include <AzFramework/Physics/Common/PhysicsSceneQueries.h> | ||
#include <AzFramework/Physics/PhysicsScene.h> | ||
#include <AzFramework/Physics/PhysicsSystem.h> | ||
|
@@ -37,6 +41,32 @@ namespace CsvSpawner::CsvSpawnerUtils | |
->Field("Transform", &CsvSpawnableEntityInfo::m_transform) | ||
->Field("Name", &CsvSpawnableEntityInfo::m_name) | ||
->Field("Seed", &CsvSpawnableEntityInfo::m_seed); | ||
|
||
if (AZ::EditContext* editContext = serializeContext->GetEditContext()) | ||
{ | ||
editContext->Class<CsvSpawnableEntityInfo>("Csv Spawnable Entity Info", "An entity configuration for spawning") | ||
->ClassElement(AZ::Edit::ClassElements::EditorData, "") | ||
->DataElement(AZ::Edit::UIHandlers::Default, &CsvSpawnableEntityInfo::m_id, "ID", "Optional ID for the entity") | ||
->DataElement( | ||
AZ::Edit::UIHandlers::Default, &CsvSpawnableEntityInfo::m_transform, "Transform", "Transform of the entity") | ||
->DataElement( | ||
AZ::Edit::UIHandlers::Default, | ||
&CsvSpawnableEntityInfo::m_name, | ||
"Name", | ||
"Name of the spawnable entity configuration") | ||
->DataElement( | ||
AZ::Edit::UIHandlers::Default, &CsvSpawnableEntityInfo::m_seed, "Seed", "Optional seed value for randomization"); | ||
} | ||
} | ||
|
||
if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context)) | ||
{ | ||
behaviorContext->Class<CsvSpawnableEntityInfo>("CsvSpawnableEntityInfo") | ||
->Constructor<>() | ||
->Property("Id", BehaviorValueProperty(&CsvSpawnableEntityInfo::m_id)) | ||
->Property("Transform", BehaviorValueProperty(&CsvSpawnableEntityInfo::m_transform)) | ||
->Property("Name", BehaviorValueProperty(&CsvSpawnableEntityInfo::m_name)) | ||
->Property("Seed", BehaviorValueProperty(&CsvSpawnableEntityInfo::m_seed)); | ||
} | ||
} | ||
void CsvSpawnableAssetConfiguration::Reflect(AZ::ReflectContext* context) | ||
|
@@ -193,6 +223,13 @@ namespace CsvSpawner::CsvSpawnerUtils | |
const AZStd::string& physicsSceneName, | ||
AZ::EntityId parentId) | ||
{ | ||
SpawnInfo broadcastSpawnInfo = | ||
SpawnInfo{ entitiesToSpawn, physicsSceneName, parentId }; // Spawn Info used in CsvSpawner EBus notify. | ||
SpawnStatus spawnStatusCode = SpawnStatus::Success; // Spawn Status Code used for CsvSpawner EBus notify - OnEntitiesSpawnFinished. | ||
|
||
// Call CsvSpawner EBus notification - Begin | ||
CsvSpawnerNotificationBus::Broadcast(&CsvSpawnerInterface::OnEntitiesSpawnBegin, broadcastSpawnInfo); | ||
|
||
auto sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get(); | ||
AZ_Assert(sceneInterface, "Unable to get physics scene interface"); | ||
const auto sceneHandle = sceneInterface->GetSceneHandle(physicsSceneName); | ||
|
@@ -216,11 +253,18 @@ namespace CsvSpawner::CsvSpawnerUtils | |
} | ||
} | ||
|
||
// Track how many tickets are spawned and how many have completed | ||
size_t totalTickets = 0; | ||
std::atomic_size_t completedTickets = 0; | ||
|
||
for (const auto& entityConfig : entitiesToSpawn) | ||
{ | ||
if (!spawnableAssetConfiguration.contains(entityConfig.m_name)) | ||
{ | ||
AZ_Error("CsvSpawner", false, "SpawnableAssetConfiguration %s not found", entityConfig.m_name.c_str()); | ||
|
||
// Add notify code status | ||
spawnStatusCode |= SpawnStatus::Warning; | ||
continue; | ||
} | ||
|
||
|
@@ -250,6 +294,9 @@ namespace CsvSpawner::CsvSpawnerUtils | |
} | ||
else | ||
{ | ||
// Add notify code status | ||
spawnStatusCode |= SpawnStatus::Warning; | ||
|
||
continue; // Skip this entity if we can't find a valid position and | ||
// place on terrain is enabled. | ||
} | ||
|
@@ -259,10 +306,13 @@ namespace CsvSpawner::CsvSpawnerUtils | |
AzFramework::EntitySpawnTicket ticket(spawnable); | ||
// Set the pre-spawn callback to set the name of the root entity to the name | ||
// of the spawnable | ||
optionalArgs.m_preInsertionCallback = [transform](auto id, auto view) | ||
optionalArgs.m_preInsertionCallback = [transform, &spawnStatusCode](auto id, auto view) | ||
{ | ||
if (view.empty()) | ||
{ | ||
// Add notify code status | ||
spawnStatusCode |= SpawnStatus::Warning | SpawnStatus::Stopped; | ||
|
||
return; | ||
} | ||
AZ::Entity* root = *view.begin(); | ||
|
@@ -272,23 +322,103 @@ namespace CsvSpawner::CsvSpawnerUtils | |
transformInterface->SetWorldTM(transform); | ||
}; | ||
optionalArgs.m_completionCallback = | ||
[parentId]( | ||
[parentId, &spawnStatusCode, &broadcastSpawnInfo, &tickets, totalTickets, &completedTickets]( | ||
|
||
[[maybe_unused]] AzFramework::EntitySpawnTicket::Id ticketId, AzFramework::SpawnableConstEntityContainerView view) | ||
{ | ||
if (view.empty()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code within this if statement exhibits undefined behavior because |
||
{ | ||
// Add notify code status | ||
spawnStatusCode |= SpawnStatus::Warning | SpawnStatus::Stopped; | ||
|
||
return; | ||
} | ||
const AZ::Entity* root = *view.begin(); | ||
AZ::TransformBus::Event(root->GetId(), &AZ::TransformBus::Events::SetParent, parentId); | ||
|
||
completedTickets++; | ||
if (completedTickets == totalTickets) | ||
{ | ||
// Call CsvSpawner EBus notification - Finished | ||
CsvSpawnerNotificationBus::Broadcast( | ||
&CsvSpawnerInterface::OnEntitiesSpawnFinished, broadcastSpawnInfo, spawnStatusCode); | ||
} | ||
}; | ||
optionalArgs.m_priority = AzFramework::SpawnablePriority_Lowest; | ||
spawner->SpawnAllEntities(ticket, optionalArgs); | ||
tickets[entityConfig.m_id] = AZStd::move(ticket); | ||
|
||
totalTickets++; | ||
} | ||
|
||
// If no tickets were created at all (no entities spawned), send finish immediately | ||
if (totalTickets == 0) | ||
{ | ||
spawnStatusCode |= SpawnStatus::Fail; | ||
// Call CsvSpawner EBus notification - Finished | ||
CsvSpawnerNotificationBus::Broadcast(&CsvSpawnerInterface::OnEntitiesSpawnFinished, broadcastSpawnInfo, spawnStatusCode); | ||
} | ||
|
||
return tickets; | ||
} | ||
|
||
void SpawnInfo::Reflect(AZ::ReflectContext* context) | ||
{ | ||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) | ||
{ | ||
// Reflect SpawnStatus enum | ||
serializeContext->Enum<SpawnStatus>() | ||
->Version(0) | ||
->Value("Success", SpawnStatus::Success) | ||
->Value("Fail", SpawnStatus::Fail) | ||
->Value("Stopped", SpawnStatus::Stopped) | ||
->Value("Warning", SpawnStatus::Warning); | ||
|
||
// Reflect SpawnInfo struct | ||
serializeContext->Class<SpawnInfo>() | ||
->Version(0) | ||
->Field("EntitiesToSpawn", &SpawnInfo::m_entitiesToSpawn) | ||
->Field("PhysicsSceneName", &SpawnInfo::m_physicsSceneName) | ||
->Field("SpawnerParentEntityId", &SpawnInfo::m_spawnerParentEntityId); | ||
|
||
if (auto* editContext = serializeContext->GetEditContext()) | ||
{ | ||
editContext->Class<SpawnInfo>("Spawn Info", "Information about entities being spawned") | ||
->ClassElement(AZ::Edit::ClassElements::EditorData, "") | ||
->DataElement( | ||
AZ::Edit::UIHandlers::Default, | ||
&SpawnInfo::m_entitiesToSpawn, | ||
"Entities to Spawn", | ||
"List of entities to be spawned.") | ||
->DataElement( | ||
AZ::Edit::UIHandlers::Default, | ||
&SpawnInfo::m_physicsSceneName, | ||
"Physics Scene", | ||
"Name of the physics scene where entities will be spawned.") | ||
->DataElement( | ||
AZ::Edit::UIHandlers::Default, | ||
&SpawnInfo::m_spawnerParentEntityId, | ||
"Parent Entity", | ||
"Parent entity ID responsible for spawning."); | ||
} | ||
} | ||
|
||
if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context)) | ||
{ | ||
behaviorContext->EnumProperty<static_cast<int>(CsvSpawnerUtils::SpawnStatus::Success)>("SpawnStatus_Success"); | ||
behaviorContext->EnumProperty<static_cast<int>(CsvSpawnerUtils::SpawnStatus::Fail)>("SpawnStatus_Fail"); | ||
behaviorContext->EnumProperty<static_cast<int>(CsvSpawnerUtils::SpawnStatus::Stopped)>("SpawnStatus_Stopped"); | ||
behaviorContext->EnumProperty<static_cast<int>(CsvSpawnerUtils::SpawnStatus::Warning)>("SpawnStatus_Warning"); | ||
|
||
behaviorContext->Class<SpawnInfo>("SpawnInfo") | ||
->Constructor() | ||
->Attribute(AZ::Script::Attributes::Category, "CsvSpawner") | ||
->Attribute(AZ::Script::Attributes::Module, "editor") | ||
->Property("m_entitiesToSpawn", BehaviorValueProperty(&SpawnInfo::m_entitiesToSpawn)) | ||
->Property("m_physicsSceneName", BehaviorValueProperty(&SpawnInfo::m_physicsSceneName)) | ||
->Property("m_spawnerParentEntityId", BehaviorValueProperty(&SpawnInfo::m_spawnerParentEntityId)); | ||
} | ||
} | ||
|
||
bool IsTerrainAvailable() | ||
{ | ||
return AzFramework::Terrain::TerrainDataRequestBus::HasHandlers(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that created notification methods are correct, but I've noticed that you set
AddressPolicy
asSingle
. We can have multiple CSVSpawner components on the scene. It means that we should be send notification either when all components finishes their jobs (you would need system component for that), or you can change this notification busAddressPolicy
toById
to allow each component to send their own notification. Then it would be client call to decide on which entity/component notification they want to connect with.