Skip to content
Open
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
6470d8a
init spline subscriber
w-czerski Jan 28, 2025
6ccf97e
clang format
w-czerski Jan 28, 2025
915d029
fix | get spline and convert into vector of vertex
w-czerski Jan 28, 2025
1b9affd
remove auto
w-czerski Jan 28, 2025
57322f8
apply review changes
w-czerski Jan 28, 2025
ee5c565
remove comments
w-czerski Jan 28, 2025
399c2d1
use emplace | format | apply reviews
w-czerski Jan 28, 2025
5b7dd26
change ros2 -> ROS 2
w-czerski Mar 18, 2025
3fc175d
review resolved | topic changed to spline
w-czerski Mar 18, 2025
9cf218a
review resolved | use proper ROS 2 naming
w-czerski Mar 18, 2025
4651355
get ros2frame outside tick
w-czerski Mar 18, 2025
45bd34c
add update frequency
w-czerski Mar 18, 2025
ffa7f34
add readme info | add update frequency
w-czerski Mar 18, 2025
23e9891
add readme info | add update frequency
w-czerski Mar 18, 2025
7049997
Adjust Pointcloud, ImGuiProvider, ImGuizmo code to 2505 version of o3…
michalpelka Jun 30, 2025
c91a03a
Update GeoJSONSpawner to be compatible with O3DE 2505 (#124)
patrykantosz Jun 30, 2025
f70295a
init | add notification bus interface class
w-czerski Jan 28, 2025
5373024
clang format
w-czerski Jan 28, 2025
09e44bf
remove not needed info in bus
w-czerski Jan 28, 2025
ee39e1c
remove bus info for spawn tickets
w-czerski Jan 28, 2025
2fbc807
move spawn utils to public include | spawn info bus struct
w-czerski Jan 29, 2025
002c35b
add notify status code
w-czerski Jan 29, 2025
139bb38
fix typo
w-czerski Jan 29, 2025
318d19f
more context to enum spawn
w-czerski Jan 29, 2025
0c45656
fix format | resolve errors
w-czerski Jan 29, 2025
bb29e40
rename member
w-czerski Jan 29, 2025
5a24a36
add default override implentation | make override optional
w-czerski Jan 29, 2025
54e6f12
add description
w-czerski Jan 29, 2025
22c3628
undo make public utils
w-czerski Mar 18, 2025
51e1941
remove const in func definition
w-czerski Mar 18, 2025
476f2b4
make interface clas final
w-czerski Mar 18, 2025
26cb4bd
init value definition | add spawn stop break points
w-czerski Mar 18, 2025
62358ef
remove const& from struct definition
w-czerski Mar 18, 2025
9e3cf1a
remove final from interface
w-czerski Mar 18, 2025
95b70d1
use constexpr
w-czerski Mar 18, 2025
b30aac2
move struct to utils
w-czerski Mar 18, 2025
75e3739
add lua / script canvas support
w-czerski Mar 18, 2025
61fc9aa
add info to readme
w-czerski Mar 18, 2025
13671a4
fix headers | make interface func pure virtual
w-czerski Mar 18, 2025
2c17d49
fix reflections
w-czerski Mar 18, 2025
896f067
revert 3rdParty changes
w-czerski Jul 2, 2025
687aff9
add counter to track if all entities are spawned
w-czerski Jul 2, 2025
b30dda3
clang format
w-czerski Jul 2, 2025
3694333
Merge branch 'main' into wc/csvspawner_notifications
w-czerski Jul 2, 2025
2e4d01a
remove unused directives
w-czerski Jul 2, 2025
3345918
clang format
w-czerski Jul 2, 2025
48cd8d2
remove dangling references and pointers | using shared ptr for async …
w-czerski Jul 2, 2025
24c4e8d
add spacing
w-czerski Jul 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions Gems/CsvSpawner/Code/Include/CsvSpawner/CsvSpawnerInterface.h
Copy link
Contributor

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 as Single. 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 bus AddressPolicy to ById 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.

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
{
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
Expand Up @@ -22,5 +22,7 @@ namespace CsvSpawner
inline constexpr const char* CsvSpawnerComponentTypeId = "{59b31372-1f3c-4733-b61b-0fe94b5a8f3e}";

// Interface TypeIds
inline constexpr const char* CsvSpawnerRequestsTypeId = "{77ACBD4E-069E-4610-8154-E1AC28CEE05A}";
inline constexpr const char* CsvSpawnerInterfaceTypeId = "{77ACBD4E-069E-4610-8154-E1AC28CEE05A}";
inline constexpr const char* CsvSpawnerNotificationBusHandlerTypeId = "{1F142F00-4E79-431B-9C1D-3AB157838FF8}";
inline constexpr const char* CsvSpawnerSpawnInfoTypeId = "{81E5A014-3232-4359-98F5-7F9D7152629E}";
} // namespace CsvSpawner
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@

#pragma once

#include <CsvSpawner/CsvSpawnerTypeIds.h>
#include <CsvSpawner/CsvSpawnerUtils.h>

#include "AzFramework/Terrain/TerrainDataRequestBus.h"
#include "CsvSpawnerUtils.h"

#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@
* permission, please contact the copyright holders and delete this file.
*/

#include "CsvSpawnerUtils.h"
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
#include <CsvSpawner/CsvSpawnerUtils.h>
#include <ROS2/Georeference/GeoreferenceBus.h>
#include <csv/csv.hpp>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@

#pragma once

#include "CsvSpawnerUtils.h"
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
#include <CsvSpawner/CsvSpawnerUtils.h>

namespace CsvSpawner::CsvSpawnerUtils
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "CsvSpawnerComponent.h"
#include "CsvSpawnerCsvParser.h"
#include "CsvSpawnerUtils.h"
#include <CsvSpawner/CsvSpawnerInterface.h>

#include <AzCore/Component/TransformBus.h>
#include <AzCore/Debug/Trace.h>
Expand All @@ -26,6 +27,8 @@ namespace CsvSpawner
{
void CsvSpawnerEditorComponent::Reflect(AZ::ReflectContext* context)
{
CsvSpawner::SpawnInfo::Reflect(context);

AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
Expand Down Expand Up @@ -70,6 +73,12 @@ namespace CsvSpawner
"Settings to configure spawn behaviour in editor.");
}
}

if (const auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<CsvSpawner::CsvSpawnerNotificationBus>("CsvSpawnerNotificationBus")
->Handler<CsvSpawner::CsvSpawnerNotificationBusHandler>();
}
}

void CsvSpawnerEditorComponent::Activate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@

#pragma once

#include "CsvSpawnerUtils.h"

#include <CsvSpawner/CsvSpawnerTypeIds.h>
#include <CsvSpawner/CsvSpawnerUtils.h>

#include "EditorConfigurations/CsvSpawnerEditorTerrainSettingsConfig.h"

#include <API/ToolsApplicationAPI.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/TickBus.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
Expand Down
136 changes: 133 additions & 3 deletions Gems/CsvSpawner/Code/Source/CsvSpawner/CsvSpawnerUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@

#include "CsvSpawnerUtils.h"


#include <AzFramework/Physics/CollisionBus.h>
#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>
Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}

Expand Down Expand Up @@ -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.
}
Expand All @@ -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();
Expand All @@ -272,23 +322,103 @@ namespace CsvSpawner::CsvSpawnerUtils
transformInterface->SetWorldTM(transform);
};
optionalArgs.m_completionCallback =
[parentId](
[parentId, &spawnStatusCode, &broadcastSpawnInfo, &tickets, totalTickets, &completedTickets](
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that the four references in question are currently dangling. This arises because local variables are being passed to the completion callback, which is executed after the SpawnEntities function has ended. Once the function finishes, the references to the callback become invalid since the variables they point to no longer exist. It is important to address this issue to ensure the code functions as intended.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reference to tickets is not only dangling but also remains unused, which consequently interferes with building O3DE in the profile configuration.

[[maybe_unused]] AzFramework::EntitySpawnTicket::Id ticketId, AzFramework::SpawnableConstEntityContainerView view)
{
if (view.empty())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code within this if statement exhibits undefined behavior because spawnStatusCode is a dangling reference. This may be the reason why you have not encountered any issues, as the code possibly has not executed as expected. Additionally, the code is missing a notification call to indicate a failure. A similar situation is present in the preinsertionCallback.

{
// 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();
Expand Down
Loading
Loading