diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 316f366f19bfdf7b45d0b460b2f8278a401f7b1f..3dd9426c5fc3379eedc13121158d6cd263aca5b5 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -1,5 +1,6 @@ # ***************************************************************************** # Copyright (c) 2021-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2025 Ansys, Inc. # # This program and the accompanying materials are made available under the terms # of the Eclipse Public License 2.0 which is available at @@ -178,6 +179,8 @@ target_sources( ${CMAKE_CURRENT_LIST_DIR}/tests/Node/ConditionNodeTest.cpp ${CMAKE_CURRENT_LIST_DIR}/tests/Node/ConditionsNodeTest.cpp ${CMAKE_CURRENT_LIST_DIR}/tests/Node/EntityConditionNodeTest.cpp + ${CMAKE_CURRENT_LIST_DIR}/tests/Node/EventNodeTest.cpp + ${CMAKE_CURRENT_LIST_DIR}/tests/Node/ManeuverNodeTest.cpp ${CMAKE_CURRENT_LIST_DIR}/tests/Node/TriggerableCompositeNodeTest.cpp ${CMAKE_CURRENT_LIST_DIR}/tests/OpenScenarioEngineTest.cpp ${CMAKE_CURRENT_LIST_DIR}/tests/Storyboard/GenericAction/ActivateControllerActionTest.cpp diff --git a/engine/cmake/generated_files.cmake b/engine/cmake/generated_files.cmake index 35377d54831b3d4b2c01ea48dc0fc457b3fb01a7..0bc2531654fdbedffe8595d8228871962bc9968f 100644 --- a/engine/cmake/generated_files.cmake +++ b/engine/cmake/generated_files.cmake @@ -1,5 +1,6 @@ ################################################################################ # Copyright (c) 2022-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2025 Ansys, Inc. # # This program and the accompanying materials are made available under the # terms of the Eclipse Public License 2.0 which is available at @@ -147,6 +148,7 @@ list(APPEND ${PROJECT_NAME}_SOURCES src/Conversion/OscToMantle/ConvertScenarioController.cpp src/Conversion/OscToMantle/ConvertScenarioCoordinateSystem.cpp src/Conversion/OscToMantle/ConvertScenarioEnvironment.cpp + src/Conversion/OscToMantle/ConvertScenarioEventPriority.cpp src/Conversion/OscToMantle/ConvertScenarioFinalSpeed.cpp src/Conversion/OscToMantle/ConvertScenarioLaneChangeTarget.cpp src/Conversion/OscToMantle/ConvertScenarioLateralDisplacement.cpp @@ -191,7 +193,9 @@ list(APPEND ${PROJECT_NAME}_SOURCES src/Node/ConditionNode.cpp src/Node/ConditionsNode.cpp src/Node/EntityConditionNode.cpp + src/Node/EventNode.cpp src/Node/ManeuverGroupNode.cpp + src/Node/ManeuverNode.cpp src/Node/PrivateNode.cpp src/Node/RootNode.cpp src/Node/TrafficSignalPhaseNode.cpp @@ -233,6 +237,7 @@ list(APPEND ${PROJECT_NAME}_SOURCES src/Utils/Ellipse.cpp src/Utils/EntityCreator.cpp src/Utils/EntityUtils.cpp + src/Utils/EventPrioritizer.cpp src/Utils/Logger.cpp src/Utils/ProbabilityService.cpp src/Utils/TrafficSignalBuilder.cpp @@ -506,6 +511,7 @@ list(APPEND ${PROJECT_NAME}_HEADERS src/Conversion/OscToMantle/ConvertScenarioEntity.h src/Conversion/OscToMantle/ConvertScenarioEntityRef.h src/Conversion/OscToMantle/ConvertScenarioEnvironment.h + src/Conversion/OscToMantle/ConvertScenarioEventPriority.h src/Conversion/OscToMantle/ConvertScenarioFinalSpeed.h src/Conversion/OscToMantle/ConvertScenarioLaneChangeTarget.h src/Conversion/OscToMantle/ConvertScenarioLaneOffsetActionDynamics.h @@ -558,6 +564,7 @@ list(APPEND ${PROJECT_NAME}_HEADERS src/Node/EntityConditionNode.h src/Node/EventNode.h src/Node/ManeuverGroupNode.h + src/Node/ManeuverNode.h src/Node/PrivateNode.h src/Node/RepeatingSequenceNode.h src/Node/RootNode.h @@ -595,7 +602,10 @@ list(APPEND ${PROJECT_NAME}_HEADERS src/Utils/EntityBroker.h src/Utils/EntityCreator.h src/Utils/EntityUtils.h + src/Utils/EventPrioritizer.h + src/Utils/EventPriority.h src/Utils/IControllerService.h + src/Utils/IEventPrioritizer.h src/Utils/IProbabilityService.h src/Utils/Logger.h src/Utils/ProbabilityService.h diff --git a/engine/src/Conversion/OscToMantle/ConvertScenarioEventPriority.cpp b/engine/src/Conversion/OscToMantle/ConvertScenarioEventPriority.cpp new file mode 100644 index 0000000000000000000000000000000000000000..edd85dbdd4a4bd3d8a0aeac68f9009645ef098ed --- /dev/null +++ b/engine/src/Conversion/OscToMantle/ConvertScenarioEventPriority.cpp @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#include "Conversion/OscToMantle/ConvertScenarioEventPriority.h" + +#include "Utils/EventPriority.h" + +namespace OpenScenarioEngine::v1_3 +{ +EventPriority ConvertScenarioEventPriority(const NET_ASAM_OPENSCENARIO::v1_3::Priority& priority) +{ + if (priority == NET_ASAM_OPENSCENARIO::v1_3::Priority::OVERRIDE) + { + return EventPriority::kOverride; + } + if (priority == NET_ASAM_OPENSCENARIO::v1_3::Priority::OVERWRITE) + { + return EventPriority::kOverwrite; + } + if (priority == NET_ASAM_OPENSCENARIO::v1_3::Priority::PARALLEL) + { + return EventPriority::kParallel; + } + if (priority == NET_ASAM_OPENSCENARIO::v1_3::Priority::SKIP) + { + return EventPriority::kSkip; + } + if (priority == NET_ASAM_OPENSCENARIO::v1_3::Priority::UNKNOWN) + { + return EventPriority::kUnknown; + } + + throw std::runtime_error("ConvertScenarioEventPriority: Unsupported EventPriority"); +} + +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Conversion/OscToMantle/ConvertScenarioEventPriority.h b/engine/src/Conversion/OscToMantle/ConvertScenarioEventPriority.h new file mode 100644 index 0000000000000000000000000000000000000000..9b9a8b79e9a5a47aa2b53600a47825e76e10db2b --- /dev/null +++ b/engine/src/Conversion/OscToMantle/ConvertScenarioEventPriority.h @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#pragma once + +#include <openScenarioLib/generated/v1_3/api/ApiClassInterfacesV1_3.h> + +namespace OpenScenarioEngine::v1_3 +{ +enum class EventPriority; + +EventPriority ConvertScenarioEventPriority(const NET_ASAM_OPENSCENARIO::v1_3::Priority& priority); + +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Conversion/OscToNode/ParseEvent.cpp b/engine/src/Conversion/OscToNode/ParseEvent.cpp index c388f4bbc1d3048d66fa787790e6f3f1741e95df..670fa819b325ac106ba98c8c5af98556e14794a0 100644 --- a/engine/src/Conversion/OscToNode/ParseEvent.cpp +++ b/engine/src/Conversion/OscToNode/ParseEvent.cpp @@ -12,6 +12,7 @@ #include <memory> +#include "Conversion/OscToMantle/ConvertScenarioEventPriority.h" #include "Conversion/OscToNode/ParseActions.h" #include "Conversion/OscToNode/ParseTrigger.h" #include "Node/EventNode.h" @@ -23,7 +24,8 @@ yase::BehaviorNode::Ptr parse(std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::IEven return std::make_shared<Node::EventNode>( "Event", parse(event->GetActions()), - parse(event->GetStartTrigger())); + parse(event->GetStartTrigger()), + ConvertScenarioEventPriority(event->GetPriority())); } -} // namespace OpenScenarioEngine::v1_3 \ No newline at end of file +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Conversion/OscToNode/ParseManeuver.cpp b/engine/src/Conversion/OscToNode/ParseManeuver.cpp index 5d8c8394c306b4f44a1afcf59a58fcff147b0fab..4be4d85d3689f08b78ca093943fafd54b35a1571 100644 --- a/engine/src/Conversion/OscToNode/ParseManeuver.cpp +++ b/engine/src/Conversion/OscToNode/ParseManeuver.cpp @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2021-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2025 Ansys, Inc. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -13,13 +14,17 @@ #include <memory> #include "Conversion/OscToNode/ParseEvents.h" +#include "Node/ManeuverNode.h" namespace OpenScenarioEngine::v1_3 { yase::BehaviorNode::Ptr parse(std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::IManeuver> maneuver) { - return maneuver ? parse(maneuver->GetEvents()) : nullptr; - //return maneuver ? parse(maneuver->GetParameterDeclarations()) : nullptr; + auto maneuver_node = std::make_shared<Node::ManeuverNode>("Maneuver"); + + maneuver_node->setChild(parse(maneuver->GetEvents())); + // parse(maneuver->GetParameterDeclarations()) + return maneuver_node; } -} // namespace OpenScenarioEngine::v1_3 \ No newline at end of file +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Node/EventNode.cpp b/engine/src/Node/EventNode.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a20bbe48b9c2bc0473a6397d39addab8d27369e7 --- /dev/null +++ b/engine/src/Node/EventNode.cpp @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#include "Node/EventNode.h" + +namespace OpenScenarioEngine::v1_3::Node +{ + +EventNode::EventNode(const std::string& name, + yase::BehaviorNode::Ptr actions, + yase::BehaviorNode::Ptr start_trigger, + EventPriority priority) + : TriggerableCompositeNode{name}, event_priority_{priority}, start_trigger_{start_trigger} +{ + set(std::move(actions), StopTriggerPtr{nullptr}, StartTriggerPtr{std::move(start_trigger)}); +} + +void EventNode::lookupAndRegisterData(yase::Blackboard& blackboard) +{ + event_prioritizer_ = blackboard.get<std::shared_ptr<OpenScenarioEngine::v1_3::IEventPrioritizer> >("EventPrioritizer"); + event_prioritizer_->RegisterEvent(name(), event_priority_); +} + +yase::NodeStatus EventNode::tick() +{ + if (event_prioritizer_->ShouldStopChild(name()) || event_prioritizer_->ShouldSkipChild(name())) + { + return yase::NodeStatus::kSuccess; + } + + auto current_status = TriggerableCompositeNode::tick(); + + if (start_trigger_->status() == yase::NodeStatus::kSuccess) + { + event_prioritizer_->EventStarted(name()); + } + + return current_status; +} + +} // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Node/EventNode.h b/engine/src/Node/EventNode.h index 923c9f3207e327861a7031b4fc1cb139c0b5c009..2ea9f955bb194b9385ad3214e52370702fd631f7 100644 --- a/engine/src/Node/EventNode.h +++ b/engine/src/Node/EventNode.h @@ -1,6 +1,7 @@ /******************************************************************************** * Copyright (c) 2021-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * 2021 Max Paul Bauer - Robert Bosch GmbH + * 2025 Ansys, Inc. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -11,22 +12,36 @@ #pragma once -#include <utility> - #include "Node/TriggerableCompositeNode.h" +#include "Utils/EventPriority.h" +#include "Utils/IEventPrioritizer.h" namespace OpenScenarioEngine::v1_3::Node { + +/// @brief Runs a list of actions when the start trigger is successful. It can be given a priority to interact with other nodes in the same manevuer. class EventNode : public TriggerableCompositeNode { public: + /// @brief Construct a new event node + /// + /// @param name Name of the event node + /// @param actions Actions node to run + /// @param start_trigger Start trigger for the node + /// @param priority The priority of the event EventNode(const std::string& name, yase::BehaviorNode::Ptr actions, - yase::BehaviorNode::Ptr start_trigger = nullptr) - : TriggerableCompositeNode{name} - { - set(std::move(actions), StopTriggerPtr{nullptr}, StartTriggerPtr{std::move(start_trigger)}); - } + yase::BehaviorNode::Ptr start_trigger = nullptr, + EventPriority priority = EventPriority::kUnknown); + + void lookupAndRegisterData(yase::Blackboard& blackboard) override; + + yase::NodeStatus tick() override; + +private: + EventPriority event_priority_; + yase::BehaviorNode::Ptr start_trigger_; + std::shared_ptr<IEventPrioritizer> event_prioritizer_; }; -} // namespace OpenScenarioEngine::v1_3::Node \ No newline at end of file +} // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Node/ManeuverNode.cpp b/engine/src/Node/ManeuverNode.cpp new file mode 100644 index 0000000000000000000000000000000000000000..30f70cb523c3f555714301d9b32f16226056438b --- /dev/null +++ b/engine/src/Node/ManeuverNode.cpp @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#include "Node/ManeuverNode.h" + +#include "Utils/EventPrioritizer.h" + +namespace OpenScenarioEngine::v1_3::Node +{ +ManeuverNode::ManeuverNode(const std::string& name) + : yase::DecoratorNode{name}, eventPrioritizer_{std::make_shared<EventPrioritizer>()} +{ +} + +yase::NodeStatus ManeuverNode::tick() +{ + eventPrioritizer_->UpdateOverriddenEvents(); + return child().executeTick(); +} + +void ManeuverNode::lookupAndRegisterData(yase::Blackboard &blackboard) +{ + blackboard.set("EventPrioritizer", eventPrioritizer_); +} + +} // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Node/ManeuverNode.h b/engine/src/Node/ManeuverNode.h new file mode 100644 index 0000000000000000000000000000000000000000..9fbadfab283e5580f06a6742eebcf8d3924c99e2 --- /dev/null +++ b/engine/src/Node/ManeuverNode.h @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#pragma once + +#include <agnostic_behavior_tree/decorator_node.h> + +namespace OpenScenarioEngine::v1_3 +{ +class IEventPrioritizer; + +namespace Node +{ + +/// @brief Runs a list of events. Nodes in the same maneuver can be skipped or overridden based on an event's priority. +class ManeuverNode : public yase::DecoratorNode +{ +public: + /// @brief Construct a new maneuver node + /// + /// @param name Name of the maneuver node + explicit ManeuverNode(const std::string& name); + +private: + void lookupAndRegisterData(yase::Blackboard& blackboard) override; + yase::NodeStatus tick() final; + + std::shared_ptr<IEventPrioritizer> eventPrioritizer_; +}; + +} // namespace Node + +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Utils/EventPrioritizer.cpp b/engine/src/Utils/EventPrioritizer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eab729afb912581d771d2219d2a350578474b72e --- /dev/null +++ b/engine/src/Utils/EventPrioritizer.cpp @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#include "Utils/EventPrioritizer.h" + +namespace OpenScenarioEngine::v1_3 +{ + +void EventPrioritizer::RegisterEvent(const Event& name, EventPriority priority) +{ + event_priorities_.insert({name, priority}); +} + +void EventPrioritizer::EventStarted(const Event& name) +{ + auto [_, just_started] = started_events_.insert(name); + if (just_started && + (event_priorities_.at(name) == EventPriority::kOverride || + event_priorities_.at(name) == EventPriority::kOverwrite)) + { + overriding_event_ = name; + } +} + +bool EventPrioritizer::ShouldSkipChild(const Event& name) const +{ + return event_priorities_.at(name) == EventPriority::kSkip; +} + +bool EventPrioritizer::ShouldStopChild(const Event& name) const +{ + if (auto event = events_to_override_.find(name); event != events_to_override_.end()) + { + return true; + } + return false; +} + +void EventPrioritizer::UpdateOverriddenEvents() +{ + if (!overriding_event_.empty()) + { + mark_events_overridden(overriding_event_); + } + overriding_event_.clear(); +} + +void EventPrioritizer::mark_events_overridden(const Event& overriding_event) +{ + for (const auto& event : started_events_) + { + if (event != overriding_event) + { + events_to_override_.insert(event); + } + } +} + +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Utils/EventPrioritizer.h b/engine/src/Utils/EventPrioritizer.h new file mode 100644 index 0000000000000000000000000000000000000000..774e5bdf8e9198eb0faae37b35c3a2e201fff16b --- /dev/null +++ b/engine/src/Utils/EventPrioritizer.h @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#pragma once + +#include <openScenarioLib/generated/v1_3/api/EnumerationsV1_3.h> + +#include <set> +#include <unordered_map> + +#include "Utils/EventPriority.h" +#include "Utils/IEventPrioritizer.h" + +namespace OpenScenarioEngine::v1_3 +{ + +class EventPrioritizer : public IEventPrioritizer +{ +public: + void RegisterEvent(const Event& name, EventPriority priority) override; + void EventStarted(const Event& name) override; + [[nodiscard]] bool ShouldSkipChild(const Event& name) const override; + [[nodiscard]] bool ShouldStopChild(const Event& name) const override; + void UpdateOverriddenEvents() override; + +private: + void mark_events_overridden(const Event& overriding_event); + + std::unordered_map<Event, EventPriority> event_priorities_; + std::set<Event> started_events_; + std::set<Event> events_to_override_; + Event overriding_event_; +}; + +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Utils/EventPriority.h b/engine/src/Utils/EventPriority.h new file mode 100644 index 0000000000000000000000000000000000000000..a138329485da79154fb7dbd63d569b985d130fcd --- /dev/null +++ b/engine/src/Utils/EventPriority.h @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#pragma once + +namespace OpenScenarioEngine::v1_3 +{ +/// Definition of event priority +/// +/// \see https://publications.pages.asam.net/standards/ASAM_OpenSCENARIO/ASAM_OpenSCENARIO_XML/latest/generated/content/Priority.html +enum class EventPriority +{ + kOverride, + kOverwrite, + kParallel, + kSkip, + kUnknown +}; +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Utils/IEventPrioritizer.h b/engine/src/Utils/IEventPrioritizer.h new file mode 100644 index 0000000000000000000000000000000000000000..a0e18627dbede6e09bd551d764cd1a7695afca8a --- /dev/null +++ b/engine/src/Utils/IEventPrioritizer.h @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#pragma once + +#include <string> +#include <vector> + +#include "Utils/EventPriority.h" + +namespace OpenScenarioEngine::v1_3 +{ + +using Event = std::string; +using Events = std::vector<Event>; + +/// Allows Events to check whether they should be skipped or overridden from other events in the same maneuver +class IEventPrioritizer +{ +public: + /// Default destructor + virtual ~IEventPrioritizer() = default; + + /// @brief Register an Event to be part of a maneuver + /// + /// @param name The name of the event + /// @param priority The priority of the event + virtual void RegisterEvent(const Event& name, EventPriority priority) = 0; + + /// @brief Set the state of an event to started + /// + /// @param name The name of the event + virtual void EventStarted(const Event& name) = 0; + + /// @brief Check whether an event should be skipped based on the priority + /// + /// @param name The name of the event + /// @return Whether the event should be skipped + [[nodiscard]] virtual bool ShouldSkipChild(const Event& name) const = 0; + + /// @brief Check whether an event should be stopped by being overridden by another event + /// + /// @param name The name of the event + /// @return Whether the event should be stopped + [[nodiscard]] virtual bool ShouldStopChild(const Event& name) const = 0; + + /// @brief Check whether started events should be stopped due to an override event starting + virtual void UpdateOverriddenEvents() = 0; +}; + +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/tests/Node/EventNodeTest.cpp b/engine/tests/Node/EventNodeTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8023ef7842a402635555b6263b0c55e810fedb32 --- /dev/null +++ b/engine/tests/Node/EventNodeTest.cpp @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#include <agnostic_behavior_tree/decorator/data_declaration_node.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <memory> + +#include "Node/EventNode.h" +#include "Node/Testing/FakeActionNode.h" +#include "TestUtils/MockEventPrioritizer.h" + +using OpenScenarioEngine::v1_3::EventPriority; +using OpenScenarioEngine::v1_3::IEventPrioritizer; +using OpenScenarioEngine::v1_3::Node::EventNode; +using testing::Return; +using testing::OpenScenarioEngine::v1_3::FakeActionNode; +using testing::OpenScenarioEngine::v1_3::MockEventPrioritizer; + +class TreeWithPrioritizer +{ +public: + TreeWithPrioritizer() + : event_prioritizer_{std::make_shared<MockEventPrioritizer>()} + { + auto declaration = std::make_unique< + yase::TypeDeclarer< + std::shared_ptr< + IEventPrioritizer>>>("EventPrioritizer", event_prioritizer_); + root_ = std::make_shared<yase::DataDeclarationNode>("RootWithEventPrioritizer", std::move(declaration)); + } + + yase::DecoratorNode& get_root() + { + return *root_; + } + + MockEventPrioritizer& get_event_prioritizer() + { + return *event_prioritizer_; + } + +private: + std::shared_ptr<MockEventPrioritizer> event_prioritizer_; + yase::DataDeclarationNode::Ptr root_; +}; + +class EventNodeTest : public testing::Test +{ +protected: + EventNodeTest() + : root{tree.get_root()}, event_prioritizer{tree.get_event_prioritizer()} + { + ON_CALL(event_prioritizer, RegisterEvent).WillByDefault(Return()); + ON_CALL(*mock_action_node, tick()).WillByDefault(Return(yase::NodeStatus::kSuccess)); + ON_CALL(*mock_start_trigger, tick()).WillByDefault(Return(yase::NodeStatus::kSuccess)); + } + + auto CreateEventWithPriority(EventPriority priority) + { + return std::make_shared<EventNode>("Event", mock_action_node, mock_start_trigger, priority); + } + + TreeWithPrioritizer tree; + yase::DecoratorNode& root; + MockEventPrioritizer& event_prioritizer; + std::shared_ptr<testing::NiceMock<FakeActionNode>> mock_action_node = FakeActionNode::CREATE_PTR(); + std::shared_ptr<testing::NiceMock<FakeActionNode>> mock_start_trigger = FakeActionNode::CREATE_PTR(); +}; + +TEST_F(EventNodeTest, GivenUnstartedNodeWithParallelPriority_WhenParentTicks_ThenEventTicksAndIsRunning) +{ + auto event_under_test = CreateEventWithPriority(EventPriority::kParallel); + root.setChild(event_under_test); + root.distributeData(); + + EXPECT_CALL(event_prioritizer, ShouldStopChild).Times(1).WillOnce(Return(false)); + EXPECT_CALL(event_prioritizer, ShouldSkipChild).Times(1).WillOnce(Return(false)); + EXPECT_CALL(event_prioritizer, EventStarted).Times(1).WillOnce(Return()); + ASSERT_THAT(root.executeTick(), yase::NodeStatus::kRunning); +} + +TEST_F(EventNodeTest, GivenUnstartedNodeWithOverridePriority_WhenParentTicks_ThenEventTicksAndIsRunning) +{ + auto event_under_test = CreateEventWithPriority(EventPriority::kOverride); + root.setChild(event_under_test); + root.distributeData(); + + EXPECT_CALL(event_prioritizer, ShouldStopChild).Times(1).WillOnce(Return(false)); + EXPECT_CALL(event_prioritizer, ShouldSkipChild).Times(1).WillOnce(Return(false)); + EXPECT_CALL(event_prioritizer, EventStarted).Times(1).WillOnce(Return()); + ASSERT_THAT(root.executeTick(), yase::NodeStatus::kRunning); +} + +TEST_F(EventNodeTest, GivenUnstartedNodeWithOverwritePriority_WhenParentTicks_ThenEventTicksAndIsRunning) +{ + auto event_under_test = CreateEventWithPriority(EventPriority::kOverwrite); + root.setChild(event_under_test); + root.distributeData(); + + EXPECT_CALL(event_prioritizer, ShouldStopChild).Times(1).WillOnce(Return(false)); + EXPECT_CALL(event_prioritizer, ShouldSkipChild).Times(1).WillOnce(Return(false)); + EXPECT_CALL(event_prioritizer, EventStarted).Times(1).WillOnce(Return()); + ASSERT_THAT(root.executeTick(), yase::NodeStatus::kRunning); +} + +TEST_F(EventNodeTest, GivenUnstartedNodeWithSkipPriority_WhenParentTicks_ThenEventDoesNotStartAndSucceeds) +{ + auto event_under_test = CreateEventWithPriority(EventPriority::kSkip); + root.setChild(event_under_test); + root.distributeData(); + + EXPECT_CALL(event_prioritizer, ShouldStopChild).Times(1).WillOnce(Return(false)); + EXPECT_CALL(event_prioritizer, ShouldSkipChild).Times(1).WillOnce(Return(true)); + EXPECT_CALL(event_prioritizer, EventStarted).Times(0); + ASSERT_THAT(root.executeTick(), yase::NodeStatus::kSuccess); +} + +TEST_F(EventNodeTest, GivenStartedNodeWithNonSkipPriority_WhenShouldStopChildAndParentTicks_ThenEventStopsAndSucceeds) +{ + auto event_under_test = CreateEventWithPriority(EventPriority::kParallel); + root.setChild(event_under_test); + root.distributeData(); + + EXPECT_CALL(event_prioritizer, ShouldStopChild).Times(1).WillOnce(Return(true)); + EXPECT_CALL(event_prioritizer, ShouldSkipChild).Times(0); + EXPECT_CALL(event_prioritizer, EventStarted).Times(0); + ASSERT_THAT(root.executeTick(), yase::NodeStatus::kSuccess); +} diff --git a/engine/tests/Node/ManeuverNodeTest.cpp b/engine/tests/Node/ManeuverNodeTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..310adc444e71af184d54cadc7e055423b4e6aa2b --- /dev/null +++ b/engine/tests/Node/ManeuverNodeTest.cpp @@ -0,0 +1,266 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#include <agnostic_behavior_tree/decorator/data_declaration_node.h> +#include <agnostic_behavior_tree/composite/parallel_node.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <memory> + +#include "Node/EventNode.h" +#include "Node/ManeuverNode.h" +#include "Node/Testing/FakeActionNode.h" + +using OpenScenarioEngine::v1_3::EventPriority; +using OpenScenarioEngine::v1_3::Node::EventNode; +using OpenScenarioEngine::v1_3::Node::ManeuverNode; +using testing::Return; +using testing::OpenScenarioEngine::v1_3::FakeActionNode; + +enum class OscStatus : std::uint8_t +{ + kStandby = 0, + kRunning, + kSuccess, + kSkipped, + kUnknown, +}; + +class ManeuverNodeTest : public testing::Test +{ +protected: + using EventWithStartTrigger = std::pair<yase::BehaviorNode&, FakeActionNode&>; + + ManeuverNodeTest() + { + maneuver_node->setChild(events_node); + } + + EventWithStartTrigger AddEventWithPriority(EventPriority priority) + { + auto mock_action_node = FakeActionNode::CREATE_PTR(); + auto mock_start_trigger = FakeActionNode::CREATE_PTR(); + if (priority == EventPriority::kSkip) + { + ON_CALL(*mock_action_node, tick()).WillByDefault(Return(yase::NodeStatus::kSuccess)); + ON_CALL(*mock_start_trigger, tick()).WillByDefault(Return(yase::NodeStatus::kRunning)); + } + else + { + ON_CALL(*mock_action_node, tick()).WillByDefault(Return(yase::NodeStatus::kRunning)); + ON_CALL(*mock_start_trigger, tick()).WillByDefault(Return(yase::NodeStatus::kSuccess)); + } + auto event = std::make_shared<EventNode>("Event " + std::to_string(events_node->childrenCount() + 1), mock_action_node, mock_start_trigger, priority); + events_node->addChild(event); + + return EventWithStartTrigger{events_node->child(events_node->childrenCount() - 1), *mock_start_trigger}; + } + + static OscStatus GetEventStatus(const yase::BehaviorNode& event, const FakeActionNode& start_trigger) + { + auto osc_status = OscStatus::kUnknown; + const auto& event_status = event.status(); + const auto& start_trigger_status = start_trigger.status(); + if (event_status == yase::NodeStatus::kRunning) + { + if (start_trigger_status == yase::NodeStatus::kSuccess) + { + osc_status = OscStatus::kRunning; + } + else + { + osc_status = OscStatus::kStandby; + } + } + else if (event_status == yase::NodeStatus::kSuccess) + { + if (start_trigger_status == yase::NodeStatus::kSuccess) + { + osc_status = OscStatus::kSuccess; + } + else + { + osc_status = OscStatus::kSkipped; + } + } + return osc_status; + } + + std::shared_ptr<ManeuverNode> maneuver_node = std::make_shared<ManeuverNode>("Maneuver Node"); + std::shared_ptr<yase::ParallelNode> events_node = std::make_shared<yase::ParallelNode>("Events Node"); +}; + +TEST_F(ManeuverNodeTest, GivenMultipleNodesWithParallelPriority_WhenManeuverTicks_ThenAllEventsRunning) +{ + auto [event1, start_trigger1] = AddEventWithPriority(EventPriority::kParallel); + auto [event2, start_trigger2] = AddEventWithPriority(EventPriority::kParallel); + auto [event3, start_trigger3] = AddEventWithPriority(EventPriority::kParallel); + maneuver_node->distributeData(); + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kRunning); + + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kRunning); +} + +TEST_F(ManeuverNodeTest, GivenMultipleNodesWithParallelorSkipPriority_WhenManeuverTicks_ThenSkippedEventsSucceeded) +{ + auto [event1, start_trigger1] = AddEventWithPriority(EventPriority::kParallel); + auto [event2, start_trigger2] = AddEventWithPriority(EventPriority::kSkip); + auto [event3, start_trigger3] = AddEventWithPriority(EventPriority::kParallel); + maneuver_node->distributeData(); + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kRunning); +} + +TEST_F(ManeuverNodeTest, GivenMultipleNodesWithOneOverridePriority_WhenManeuverTicks_ThenParallelEventsOverridden) +{ + auto [event1, start_trigger1] = AddEventWithPriority(EventPriority::kParallel); + auto [event2, start_trigger2] = AddEventWithPriority(EventPriority::kSkip); + auto [event3, start_trigger3] = AddEventWithPriority(EventPriority::kParallel); + auto [event4, start_trigger4] = AddEventWithPriority(EventPriority::kOverride); + + maneuver_node->distributeData(); + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); + + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); +} + +TEST_F(ManeuverNodeTest, GivenMultipleNodesWithOneOverridePriority_WhenManeuverTicks_ThenUnstartedParallelEventsNotOverridden) +{ + auto [event1, start_trigger1] = AddEventWithPriority(EventPriority::kParallel); + EXPECT_CALL(start_trigger1, tick()) + .WillOnce(Return(yase::NodeStatus::kRunning)) + .WillRepeatedly(Return(yase::NodeStatus::kSuccess)); + auto [event2, start_trigger2] = AddEventWithPriority(EventPriority::kSkip); + auto [event3, start_trigger3] = AddEventWithPriority(EventPriority::kParallel); + auto [event4, start_trigger4] = AddEventWithPriority(EventPriority::kOverride); + + maneuver_node->distributeData(); + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kStandby); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); + + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); + + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); +} + + +TEST_F(ManeuverNodeTest, GivenMultipleNodesWithMultipleOverridePriority_WhenManeuverTicks_ThenOnlyStartedEventsOverridden) +{ + auto [event1, start_trigger1] = AddEventWithPriority(EventPriority::kParallel); + auto [event2, start_trigger2] = AddEventWithPriority(EventPriority::kSkip); + auto [event3, start_trigger3] = AddEventWithPriority(EventPriority::kParallel); + auto [event4, start_trigger4] = AddEventWithPriority(EventPriority::kOverride); + auto [event5, start_trigger5] = AddEventWithPriority(EventPriority::kParallel); + EXPECT_CALL(start_trigger5, tick()) + .WillOnce(Return(yase::NodeStatus::kRunning)) + .WillRepeatedly(Return(yase::NodeStatus::kSuccess)); + auto [event6, start_trigger6] = AddEventWithPriority(EventPriority::kOverride); + EXPECT_CALL(start_trigger6, tick()) + .WillOnce(Return(yase::NodeStatus::kRunning)) + .WillOnce(Return(yase::NodeStatus::kRunning)) + .WillRepeatedly(Return(yase::NodeStatus::kSuccess)); + + maneuver_node->distributeData(); + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event5, start_trigger5), OscStatus::kStandby); + EXPECT_THAT(GetEventStatus(event6, start_trigger6), OscStatus::kStandby); + + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event5, start_trigger5), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event6, start_trigger6), OscStatus::kStandby); + + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event5, start_trigger5), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event6, start_trigger6), OscStatus::kRunning); + + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event5, start_trigger5), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event6, start_trigger6), OscStatus::kRunning); +} + + +TEST_F(ManeuverNodeTest, GivenMultipleNodesWithOneOverwritePriority_WhenManeuverTicks_ThenParallelEventsOverridden) +{ + auto [event1, start_trigger1] = AddEventWithPriority(EventPriority::kParallel); + auto [event2, start_trigger2] = AddEventWithPriority(EventPriority::kSkip); + auto [event3, start_trigger3] = AddEventWithPriority(EventPriority::kParallel); + auto [event4, start_trigger4] = AddEventWithPriority(EventPriority::kOverwrite); + + maneuver_node->distributeData(); + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kRunning); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); + + maneuver_node->executeTick(); + + EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); + EXPECT_THAT(GetEventStatus(event3, start_trigger3), OscStatus::kSuccess); + EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); +} diff --git a/engine/tests/TestUtils/MockEventPrioritizer.h b/engine/tests/TestUtils/MockEventPrioritizer.h new file mode 100644 index 0000000000000000000000000000000000000000..38f36e0a3d1168df1d818b6fe639292a281f12ab --- /dev/null +++ b/engine/tests/TestUtils/MockEventPrioritizer.h @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2025 Ansys, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +#pragma once + +#include <gmock/gmock.h> + +#include "Utils/IEventPrioritizer.h" + +namespace testing::OpenScenarioEngine::v1_3 +{ +class MockEventPrioritizer : public ::OpenScenarioEngine::v1_3::IEventPrioritizer +{ +public: + MOCK_METHOD(void, RegisterEvent, (const ::OpenScenarioEngine::v1_3::Event&, ::OpenScenarioEngine::v1_3::EventPriority), (override)); + MOCK_METHOD(void, EventStarted, (const ::OpenScenarioEngine::v1_3::Event&), (override)); + MOCK_METHOD(bool, ShouldSkipChild, (const ::OpenScenarioEngine::v1_3::Event&), (const, override)); + MOCK_METHOD(bool, ShouldStopChild, (const ::OpenScenarioEngine::v1_3::Event&), (const, override)); + MOCK_METHOD(void, UpdateOverriddenEvents, (), (override)); + + static inline std::shared_ptr<MockEventPrioritizer> make_shared() + { + return std::make_shared<MockEventPrioritizer>(); + } +}; + +} // namespace testing::OpenScenarioEngine::v1_3