diff --git a/engine/cmake/generated_files.cmake b/engine/cmake/generated_files.cmake index 6644cd17f76486878a73b74112c053f759d08b2b..4582b70e4dccc0d1401de8ab06a8ffbf70ccf40d 100644 --- a/engine/cmake/generated_files.cmake +++ b/engine/cmake/generated_files.cmake @@ -190,6 +190,7 @@ list(APPEND ${PROJECT_NAME}_SOURCES src/Conversion/OscToNode/ParseStory.cpp src/Conversion/OscToNode/ParseStoryboard.cpp src/Conversion/OscToNode/ParseTrafficSignals.cpp + src/Node/ActNode.cpp src/Node/ByEntityConditionNode.cpp src/Node/ConditionGroupsNode.cpp src/Node/ConditionNode.cpp @@ -242,6 +243,7 @@ list(APPEND ${PROJECT_NAME}_SOURCES src/Utils/EntityUtils.cpp src/Utils/EventPrioritizer.cpp src/Utils/Logger.cpp + src/Utils/StoryBoardElementStateManager.cpp src/Utils/TrafficSignalBuilder.cpp ) @@ -570,6 +572,7 @@ list(APPEND ${PROJECT_NAME}_HEADERS src/Node/PrivateNode.h src/Node/RepeatingSequenceNode.h src/Node/RootNode.h + src/Node/StateAwareActionNode.h src/Node/StoryboardNode.h src/Node/TrafficSignalPhaseNode.h src/Node/TriggerableCompositeNode.h @@ -610,5 +613,9 @@ list(APPEND ${PROJECT_NAME}_HEADERS src/Utils/IControllerService.h src/Utils/IEventPrioritizer.h src/Utils/Logger.h + src/Utils/StoryBoardElementState.h + src/Utils/StoryBoardElementStateManager.h + src/Utils/StoryBoardElementTransition.h + src/Utils/StoryBoardElementType.h src/Utils/TrafficSignalBuilder.h ) diff --git a/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition.h b/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition.h index 37d3519f392a1765eb80911cbf2d66c7b79eab3f..fcb081f7221c876e6f319310cbfd46b6405e7764 100644 --- a/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition.h +++ b/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition.h @@ -17,6 +17,7 @@ #include <utility> #include "Storyboard/ByValueCondition/StoryboardElementStateCondition_impl.h" +#include "Utils/StoryBoardElementStateManager.h" namespace OpenScenarioEngine::v1_3::Node { @@ -41,12 +42,15 @@ private: void lookupAndRegisterData(yase::Blackboard& blackboard) final { auto environment = blackboard.get<std::shared_ptr<mantle_api::IEnvironment>>("Environment"); - + auto story_board_element_state_manager = blackboard.get<std::shared_ptr<StoryBoardElementStateManager>>("StoryBoardElementStateManager"); impl_ = std::make_unique<OpenScenarioEngine::v1_3::StoryboardElementStateCondition>( OpenScenarioEngine::v1_3::StoryboardElementStateCondition::Values{ - ConvertScenarioStoryboardElement(storyboardElementStateCondition_->GetStoryboardElementRef())}, + ConvertScenarioStoryboardElementState(storyboardElementStateCondition_->GetState()), + ConvertScenarioStoryboardElementTransition(storyboardElementStateCondition_->GetState()), + ConvertScenarioStoryboardElement(storyboardElementStateCondition_->GetStoryboardElementRef()), + ConvertScenarioStoryboardElement(storyboardElementStateCondition_->GetStoryboardElementType())}, OpenScenarioEngine::v1_3::StoryboardElementStateCondition::Interfaces{ - environment}); + environment, story_board_element_state_manager}); } std::unique_ptr<OpenScenarioEngine::v1_3::StoryboardElementStateCondition> impl_{nullptr}; diff --git a/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition_impl.cpp b/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition_impl.cpp index 0105b20652ca453f78a8304313ff2169797dd33c..c1098eb3c5906da04957de67495b3910c13151d9 100644 --- a/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition_impl.cpp +++ b/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition_impl.cpp @@ -17,11 +17,12 @@ namespace OpenScenarioEngine::v1_3 bool StoryboardElementStateCondition::IsSatisfied() const { - // Note: - // - Access to values parse to mantle/ose datatypes: this->values.xxx - // - Access to mantle interfaces: this->mantle.xxx - Logger::Error("Method StoryboardElementStateCondition::IsSatisfied() not implemented yet (returning \"true\" by default)"); - return true; + const auto& manager = mantle.story_board_element_state_manager; + const auto state_opt = manager->GetState(values.storyboardElementRef, values.storyboardElementType); + const auto transition_opt = manager->GetTransition(values.storyboardElementRef, values.storyboardElementType); + + return (state_opt && *state_opt == values.state) || + (transition_opt && *transition_opt == values.transition); } } // namespace OpenScenarioEngine::v1_3 diff --git a/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition_impl.h b/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition_impl.h index 9c5d42434428a5778274e719a16f8d4688026844..6f75ab1557fdab50aed5e3d30693146e27d49d4b 100644 --- a/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition_impl.h +++ b/engine/gen/Storyboard/ByValueCondition/StoryboardElementStateCondition_impl.h @@ -13,6 +13,7 @@ #include <MantleAPI/Execution/i_environment.h> #include "Conversion/OscToMantle/ConvertScenarioStoryboardElement.h" +#include "Utils/StoryBoardElementStateManager.h" namespace OpenScenarioEngine::v1_3 { @@ -22,11 +23,15 @@ class StoryboardElementStateCondition public: struct Values { + StoryBoardElementState state; + StoryBoardElementTransition transition; StoryboardElement storyboardElementRef; + StoryBoardElementType storyboardElementType; }; struct Interfaces { std::shared_ptr<mantle_api::IEnvironment> environment; + std::shared_ptr<StoryBoardElementStateManager> story_board_element_state_manager; }; StoryboardElementStateCondition(Values values, Interfaces interfaces) @@ -40,4 +45,4 @@ private: Interfaces mantle; }; -} // namespace OpenScenarioEngine::v1_3 \ No newline at end of file +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/gen/Storyboard/MotionControlAction/SpeedAction.h b/engine/gen/Storyboard/MotionControlAction/SpeedAction.h index b866ac69ac2213a115e916850d2894b2cc766d93..95a9f4c1cb11d020003d88f443d3faaaabe295c3 100644 --- a/engine/gen/Storyboard/MotionControlAction/SpeedAction.h +++ b/engine/gen/Storyboard/MotionControlAction/SpeedAction.h @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2021-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 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 @@ -16,33 +17,33 @@ #include <cassert> #include <utility> +#include "Node/StateAwareActionNode.h" #include "Storyboard/MotionControlAction/MotionControlAction.h" #include "Storyboard/MotionControlAction/SpeedAction_impl.h" #include "Utils/EntityBroker.h" +#include "Utils/StoryBoardElementStateManager.h" namespace OpenScenarioEngine::v1_3::Node { -class SpeedAction : public yase::ActionNode +class SpeedAction : public StateAwareActionNode { public: - SpeedAction(std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::ISpeedAction> speedAction) - : yase::ActionNode{"SpeedAction"}, - speedAction_{speedAction} + explicit SpeedAction(std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::ISpeedAction> speedAction) + : StateAwareActionNode("SpeedAction", [this]() + { + assert(impl_); + return impl_->Step(); }), + speedAction_{std::move(speedAction)} { } void onInit() override{}; private: - yase::NodeStatus tick() override + void lookupAndRegisterData(yase::Blackboard& blackboard) override { - assert(impl_); - const auto is_finished = impl_->Step(); - return is_finished ? yase::NodeStatus::kSuccess : yase::NodeStatus::kRunning; - }; + StateAwareActionNode::lookupAndRegisterData(blackboard); - void lookupAndRegisterData(yase::Blackboard& blackboard) final - { auto environment = blackboard.get<std::shared_ptr<mantle_api::IEnvironment>>("Environment"); const auto entityBroker = blackboard.get<EntityBroker::Ptr>("EntityBroker"); @@ -51,12 +52,14 @@ private: entityBroker->GetEntities(), ConvertScenarioTransitionDynamics(speedAction_->GetSpeedActionDynamics()), [=]() - { return ConvertScenarioSpeedActionTarget(environment, speedAction_->GetSpeedActionTarget()); }}, + { + return ConvertScenarioSpeedActionTarget(environment, speedAction_->GetSpeedActionTarget()); + }}, OpenScenarioEngine::v1_3::SpeedAction::Interfaces{ environment}); } - std::unique_ptr<OpenScenarioEngine::v1_3::MotionControlAction<OpenScenarioEngine::v1_3::SpeedAction>> impl_{nullptr}; + std::unique_ptr<OpenScenarioEngine::v1_3::MotionControlAction<OpenScenarioEngine::v1_3::SpeedAction>> impl_; std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::ISpeedAction> speedAction_; }; diff --git a/engine/src/Conversion/OscToMantle/ConvertScenarioStoryboardElement.h b/engine/src/Conversion/OscToMantle/ConvertScenarioStoryboardElement.h index bb965a11fea4667da84215396925574636ffc8ca..7925025379d4ca17130a656d330ccd089fb4926b 100644 --- a/engine/src/Conversion/OscToMantle/ConvertScenarioStoryboardElement.h +++ b/engine/src/Conversion/OscToMantle/ConvertScenarioStoryboardElement.h @@ -16,8 +16,13 @@ #include <memory> #include <string> +#include "Utils/StoryBoardElementState.h" +#include "Utils/StoryBoardElementTransition.h" +#include "Utils/StoryBoardElementType.h" + namespace OpenScenarioEngine::v1_3 { + using StoryboardElement = std::string; inline StoryboardElement ConvertScenarioStoryboardElement(const std::shared_ptr<NET_ASAM_OPENSCENARIO::INamedReference<NET_ASAM_OPENSCENARIO::v1_3::IStoryboardElement>>& storyboardElement) @@ -25,9 +30,83 @@ inline StoryboardElement ConvertScenarioStoryboardElement(const std::shared_ptr< return storyboardElement->GetNameRef(); } -inline StoryboardElement ConvertScenarioStoryboardElement(const std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::IStoryboardElement>& /*storyboardElement*/) +inline StoryBoardElementType ConvertScenarioStoryboardElement( + const NET_ASAM_OPENSCENARIO::v1_3::StoryboardElementType& type) +{ + using SourceType = NET_ASAM_OPENSCENARIO::v1_3::StoryboardElementType::StoryboardElementTypeEnum; + + if (type == SourceType::ACT) + { + return StoryBoardElementType::kAct; + } + if (type == SourceType::ACTION) + { + return StoryBoardElementType::kAction; + } + if (type == SourceType::EVENT) + { + return StoryBoardElementType::kEvent; + } + if (type == SourceType::MANEUVER) + { + return StoryBoardElementType::kManeuver; + } + if (type == SourceType::MANEUVER_GROUP) + { + return StoryBoardElementType::kManeuverGroup; + } + if (type == SourceType::STORY) + { + return StoryBoardElementType::kStory; + } + + throw std::runtime_error("ConvertScenarioStoryBoardElement: Unsupported Element Type"); +} + +inline StoryBoardElementState ConvertScenarioStoryboardElementState( + const NET_ASAM_OPENSCENARIO::v1_3::StoryboardElementState& state) { - return {}; + using SourceState = NET_ASAM_OPENSCENARIO::v1_3::StoryboardElementState::StoryboardElementStateEnum; + + if (state == SourceState::COMPLETE_STATE) + { + return StoryBoardElementState::kCompleteState; + } + if (state == SourceState::RUNNING_STATE) + { + return StoryBoardElementState::kRunningState; + } + if (state == SourceState::STANDBY_STATE) + { + return StoryBoardElementState::kStandbyState; + } + + return StoryBoardElementState::kUnknown; +} + +inline StoryBoardElementTransition ConvertScenarioStoryboardElementTransition( + const NET_ASAM_OPENSCENARIO::v1_3::StoryboardElementState& state) +{ + using SourceState = NET_ASAM_OPENSCENARIO::v1_3::StoryboardElementState::StoryboardElementStateEnum; + + if (state == SourceState::END_TRANSITION) + { + return StoryBoardElementTransition::kEndTransition; + } + if (state == SourceState::SKIP_TRANSITION) + { + return StoryBoardElementTransition::kSkipTransition; + } + if (state == SourceState::START_TRANSITION) + { + return StoryBoardElementTransition::kStartTransition; + } + if (state == SourceState::STOP_TRANSITION) + { + return StoryBoardElementTransition::kStopTransition; + } + + return StoryBoardElementTransition::kUnknown; } -} // namespace OpenScenarioEngine::v1_3 \ No newline at end of file +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Node/ActNode.cpp b/engine/src/Node/ActNode.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4b33e9cef1c4a557ad2093e21ce6201c4e936ff8 --- /dev/null +++ b/engine/src/Node/ActNode.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 "Node/ActNode.h" + +namespace OpenScenarioEngine::v1_3::Node +{ + +ActNode::ActNode(const std::string& name, + yase::BehaviorNode::Ptr maneuver_groups, + yase::BehaviorNode::Ptr start_trigger, + yase::BehaviorNode::Ptr stop_trigger) + : TriggerableCompositeNode{name}, start_trigger_(start_trigger), stop_trigger_(stop_trigger) +{ + set(std::move(maneuver_groups), StopTriggerPtr{std::move(stop_trigger)}, StartTriggerPtr{std::move(start_trigger)}); +} + +void ActNode::lookupAndRegisterData(yase::Blackboard& blackboard) +{ + story_board_element_state_manager_ = blackboard.get<std::shared_ptr<OpenScenarioEngine::v1_3::StoryBoardElementStateManager>>("StoryBoardElementStateManager"); + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kAct, StoryBoardElementState::kStandbyState); +} + +yase::NodeStatus ActNode::tick() +{ + // reset transition to unknown in every tick + story_board_element_state_manager_->ResetTransition(getOriginalName(), StoryBoardElementType::kAct); + + auto current_status = TriggerableCompositeNode::tick(); + + if (start_trigger_ == nullptr || start_trigger_->status() == yase::NodeStatus::kSuccess) + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kAct, StoryBoardElementTransition::kStartTransition); + } + if (stop_trigger_ != nullptr && stop_trigger_->status() == yase::NodeStatus::kSuccess) + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kAct, StoryBoardElementTransition::kStopTransition); + } + + if (current_status == yase::NodeStatus::kSuccess) + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kAct, StoryBoardElementTransition::kEndTransition); + // Since maximumExecutionCount is not supported, after status is kSuccess, the state will be updated to kCompleteState rather that kStandbyState. + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kAct, StoryBoardElementState::kCompleteState); + } + else if (current_status == yase::NodeStatus::kRunning) + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kAct, StoryBoardElementState::kRunningState); + } + else + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kAct, StoryBoardElementTransition::kUnknown); + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kAct, StoryBoardElementState::kUnknown); + } + + return current_status; +} + +} // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Node/ActNode.h b/engine/src/Node/ActNode.h index 7ec0683a31c853c0a8171c9aca05c5ee8eff90bc..ac8c7ddaf4e8c7f51de2214f071070bd632bb88c 100644 --- a/engine/src/Node/ActNode.h +++ b/engine/src/Node/ActNode.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 @@ -14,6 +15,7 @@ #include <utility> #include "Node/TriggerableCompositeNode.h" +#include "Utils/StoryBoardElementStateManager.h" namespace OpenScenarioEngine::v1_3::Node { @@ -23,11 +25,16 @@ public: ActNode(const std::string& name, yase::BehaviorNode::Ptr maneuver_groups, yase::BehaviorNode::Ptr start_trigger = nullptr, - yase::BehaviorNode::Ptr stop_trigger = nullptr) - : TriggerableCompositeNode{name} - { - set(std::move(maneuver_groups), StopTriggerPtr{std::move(stop_trigger)}, StartTriggerPtr{std::move(start_trigger)}); - } + yase::BehaviorNode::Ptr stop_trigger = nullptr); + + void lookupAndRegisterData(yase::Blackboard& blackboard) override; + + yase::NodeStatus tick() override; + +private: + yase::BehaviorNode::Ptr start_trigger_; + yase::BehaviorNode::Ptr stop_trigger_; + std::shared_ptr<StoryBoardElementStateManager> story_board_element_state_manager_; }; -} // namespace OpenScenarioEngine::v1_3::Node \ No newline at end of file +} // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Node/EventNode.cpp b/engine/src/Node/EventNode.cpp index 532f5765a23e6713115eded5fd38c5910cc64ccf..e14e52291468afdba81750ec4b15e492b437e6b0 100644 --- a/engine/src/Node/EventNode.cpp +++ b/engine/src/Node/EventNode.cpp @@ -25,14 +25,28 @@ EventNode::EventNode(const std::string& name, void EventNode::lookupAndRegisterData(yase::Blackboard& blackboard) { - event_prioritizer_ = blackboard.get<std::shared_ptr<OpenScenarioEngine::v1_3::IEventPrioritizer> >("EventPrioritizer"); + event_prioritizer_ = blackboard.get<std::shared_ptr<OpenScenarioEngine::v1_3::IEventPrioritizer>>("EventPrioritizer"); event_prioritizer_->RegisterEvent(name(), event_priority_); + story_board_element_state_manager_ = blackboard.get<std::shared_ptr<OpenScenarioEngine::v1_3::StoryBoardElementStateManager>>("StoryBoardElementStateManager"); + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementState::kStandbyState); } yase::NodeStatus EventNode::tick() { - if (event_prioritizer_->ShouldStopChild(name()) || event_prioritizer_->ShouldSkipChild(name())) + // reset transition to unknown in every tick + story_board_element_state_manager_->ResetTransition(getOriginalName(), StoryBoardElementType::kEvent); + + if (event_prioritizer_->ShouldStopChild(name())) + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementTransition::kStopTransition); + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementState::kCompleteState); + return yase::NodeStatus::kSuccess; + } + + if (event_prioritizer_->ShouldSkipChild(name())) { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementTransition::kSkipTransition); + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementState::kStandbyState); return yase::NodeStatus::kSuccess; } @@ -40,9 +54,26 @@ yase::NodeStatus EventNode::tick() if (start_trigger_ == nullptr || start_trigger_->status() == yase::NodeStatus::kSuccess) { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementTransition::kStartTransition); event_prioritizer_->EventStarted(name()); } + if (current_status == yase::NodeStatus::kSuccess) + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementTransition::kEndTransition); + // Since maximumExecutionCount is not supported, after status is kSuccess, the state will be updated to kCompleteState rather that kStandbyState. + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementState::kCompleteState); + } + else if (current_status == yase::NodeStatus::kRunning) + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementState::kRunningState); + } + else + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementTransition::kUnknown); + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kEvent, StoryBoardElementState::kUnknown); + } + return current_status; } diff --git a/engine/src/Node/EventNode.h b/engine/src/Node/EventNode.h index 2ea9f955bb194b9385ad3214e52370702fd631f7..4aac076cacabfa0405ec29513274ba55cf04f536 100644 --- a/engine/src/Node/EventNode.h +++ b/engine/src/Node/EventNode.h @@ -15,6 +15,7 @@ #include "Node/TriggerableCompositeNode.h" #include "Utils/EventPriority.h" #include "Utils/IEventPrioritizer.h" +#include "Utils/StoryBoardElementStateManager.h" namespace OpenScenarioEngine::v1_3::Node { @@ -42,6 +43,7 @@ private: EventPriority event_priority_; yase::BehaviorNode::Ptr start_trigger_; std::shared_ptr<IEventPrioritizer> event_prioritizer_; + std::shared_ptr<StoryBoardElementStateManager> story_board_element_state_manager_; }; } // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Node/ManeuverNode.cpp b/engine/src/Node/ManeuverNode.cpp index 30f70cb523c3f555714301d9b32f16226056438b..0398e046ebc5170742d9f59d4d37d027afc0629f 100644 --- a/engine/src/Node/ManeuverNode.cpp +++ b/engine/src/Node/ManeuverNode.cpp @@ -15,19 +15,44 @@ namespace OpenScenarioEngine::v1_3::Node { ManeuverNode::ManeuverNode(const std::string& name) - : yase::DecoratorNode{name}, eventPrioritizer_{std::make_shared<EventPrioritizer>()} + : yase::DecoratorNode{name}, eventPrioritizer_{std::make_shared<EventPrioritizer>()}, original_name_(name) { } +const std::string& ManeuverNode::getOriginalName() const +{ + return original_name_; +} + yase::NodeStatus ManeuverNode::tick() { eventPrioritizer_->UpdateOverriddenEvents(); - return child().executeTick(); + + auto current_status = child().executeTick(); + + if (current_status == yase::NodeStatus::kSuccess) + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kManeuver, StoryBoardElementTransition::kEndTransition); + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kManeuver, StoryBoardElementState::kCompleteState); + } + else if (current_status == yase::NodeStatus::kRunning) + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kManeuver, StoryBoardElementState::kRunningState); + } + else + { + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kManeuver, StoryBoardElementTransition::kUnknown); + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kManeuver, StoryBoardElementState::kUnknown); + } + + return current_status; } -void ManeuverNode::lookupAndRegisterData(yase::Blackboard &blackboard) +void ManeuverNode::lookupAndRegisterData(yase::Blackboard& blackboard) { blackboard.set("EventPrioritizer", eventPrioritizer_); + story_board_element_state_manager_ = blackboard.get<std::shared_ptr<OpenScenarioEngine::v1_3::StoryBoardElementStateManager>>("StoryBoardElementStateManager"); + story_board_element_state_manager_->Update(getOriginalName(), StoryBoardElementType::kManeuverGroup, StoryBoardElementState::kStandbyState); } } // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Node/ManeuverNode.h b/engine/src/Node/ManeuverNode.h index 9fbadfab283e5580f06a6742eebcf8d3924c99e2..0e6855a8a5091327e130cd5ad30eb645e02b1d69 100644 --- a/engine/src/Node/ManeuverNode.h +++ b/engine/src/Node/ManeuverNode.h @@ -12,6 +12,8 @@ #include <agnostic_behavior_tree/decorator_node.h> +#include "Utils/StoryBoardElementStateManager.h" + namespace OpenScenarioEngine::v1_3 { class IEventPrioritizer; @@ -28,11 +30,15 @@ public: /// @param name Name of the maneuver node explicit ManeuverNode(const std::string& name); + [[nodiscard]] const std::string& getOriginalName() const; + private: void lookupAndRegisterData(yase::Blackboard& blackboard) override; yase::NodeStatus tick() final; std::shared_ptr<IEventPrioritizer> eventPrioritizer_; + std::shared_ptr<StoryBoardElementStateManager> story_board_element_state_manager_; + std::string original_name_; }; } // namespace Node diff --git a/engine/src/Node/RootNode.cpp b/engine/src/Node/RootNode.cpp index ae0a474b6eaae26360dcc10ddb1ef71723956bd8..fc22221fc45158555919455bb7efe5dfffa43e94 100644 --- a/engine/src/Node/RootNode.cpp +++ b/engine/src/Node/RootNode.cpp @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2021-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 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 @@ -25,7 +26,8 @@ RootNode::RootNode(std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::IScenarioDefinit environment_{environment}, controller_service_{controller_service}, stochastics_{stochastics}, - engine_abort_flags_{engine_abort_flags} + engine_abort_flags_{engine_abort_flags}, + story_board_element_state_manager_{std::make_shared<StoryBoardElementStateManager>()} { if (auto storyboard = scenarioDefinition->GetStoryboard(); storyboard) { @@ -44,6 +46,7 @@ void RootNode::lookupAndRegisterData(yase::Blackboard& blackboard) blackboard.set("ControllerService", controller_service_); blackboard.set("Stochastics", stochastics_); blackboard.set("EngineAbortFlags", engine_abort_flags_); + blackboard.set("StoryBoardElementStateManager", story_board_element_state_manager_); } } // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Node/RootNode.h b/engine/src/Node/RootNode.h index 9b4b7e65888ddb63c1dc31f2aae1a1a64267cbb5..91e76da3c32b7d969b40007e8cc2089f0c687aa1 100644 --- a/engine/src/Node/RootNode.h +++ b/engine/src/Node/RootNode.h @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2021-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 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 @@ -15,9 +16,9 @@ #include <memory> -#include "Utils/EngineAbortFlags.h" #include "Stochastics/StochasticsInterface.h" - +#include "Utils/EngineAbortFlags.h" +#include "Utils/StoryBoardElementStateManager.h" namespace OpenScenarioEngine::v1_3 { @@ -45,8 +46,9 @@ private: std::shared_ptr<IControllerService> controller_service_; std::shared_ptr<StochasticsInterface> stochastics_; std::shared_ptr<EngineAbortFlags> engine_abort_flags_; + std::shared_ptr<StoryBoardElementStateManager> story_board_element_state_manager_; }; } // namespace Node -} // namespace OpenScenarioEngine::v1_3 \ No newline at end of file +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Node/StateAwareActionNode.h b/engine/src/Node/StateAwareActionNode.h new file mode 100644 index 0000000000000000000000000000000000000000..7c2834f1cac42dfb2bdea215795709441e504e3b --- /dev/null +++ b/engine/src/Node/StateAwareActionNode.h @@ -0,0 +1,56 @@ +/******************************************************************************** + * 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/action_node.h> + +#include "Utils/StoryBoardElementStateManager.h" + +namespace OpenScenarioEngine::v1_3::Node +{ + +using ImplStepFunction = std::function<bool()>; + +class StateAwareActionNode : public yase::ActionNode +{ +public: + StateAwareActionNode(const std::string& name, ImplStepFunction step_fn) + : yase::ActionNode{name}, step_fn_{std::move(step_fn)} {} + + yase::NodeStatus tick() override + { + const bool is_finished = step_fn_(); + + if (is_finished) + { + state_manager_->Update(name(), type_, StoryBoardElementTransition::kEndTransition); + state_manager_->Update(name(), type_, StoryBoardElementState::kCompleteState); + return yase::NodeStatus::kSuccess; + } + + state_manager_->Update(name(), type_, StoryBoardElementState::kRunningState); + return yase::NodeStatus::kRunning; + } + + void lookupAndRegisterData(yase::Blackboard& blackboard) override + { + state_manager_ = blackboard.get<std::shared_ptr<StoryBoardElementStateManager>>("StoryBoardElementStateManager"); + state_manager_->Update(name(), type_, StoryBoardElementState::kStandbyState); + } + +protected: + StoryBoardElementType type_{StoryBoardElementType::kAction}; + +private: + ImplStepFunction step_fn_; + std::shared_ptr<StoryBoardElementStateManager> state_manager_; +}; +} // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Node/TriggerableCompositeNode.h b/engine/src/Node/TriggerableCompositeNode.h index 5f932de2b5a43f3a8bc14efa981141e7001deefb..e4c1c11234325ef193eb4b2e536151aec9e3bc3f 100644 --- a/engine/src/Node/TriggerableCompositeNode.h +++ b/engine/src/Node/TriggerableCompositeNode.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 @@ -111,7 +112,7 @@ class TriggerableCompositeNode : public yase::CompositeNode { public: explicit TriggerableCompositeNode(const std::string& name) - : CompositeNode{name} {}; + : CompositeNode{name}, original_name_(name){}; ~TriggerableCompositeNode() override = default; void onInit() override @@ -150,6 +151,8 @@ public: } } + [[nodiscard]] const std::string& getOriginalName() const { return original_name_; } + protected: using yase::CompositeNode::addChild; @@ -265,6 +268,7 @@ private: TransientNode::Ptr m_child_node{nullptr}; TransientNode::Ptr stop_trigger_node_{nullptr}; yase::NodeStatus stop_trigger_status_{yase::NodeStatus::kIdle}; + std::string original_name_; }; -} // namespace OpenScenarioEngine::v1_3::Node \ No newline at end of file +} // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Storyboard/MotionControlAction/LongitudinalDistanceAction.h b/engine/src/Storyboard/MotionControlAction/LongitudinalDistanceAction.h index 67f7bcf1be1f97a3b0567f7f87f46368abf4c592..0adcd69c735cae57e568e35f068c44eeeb09121e 100644 --- a/engine/src/Storyboard/MotionControlAction/LongitudinalDistanceAction.h +++ b/engine/src/Storyboard/MotionControlAction/LongitudinalDistanceAction.h @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2021-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 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 @@ -16,17 +17,22 @@ #include <cassert> #include <utility> +#include "Node/StateAwareActionNode.h" #include "Storyboard/MotionControlAction/LongitudinalDistanceAction_impl.h" #include "Storyboard/MotionControlAction/MotionControlAction.h" #include "Utils/EntityBroker.h" +#include "Utils/StoryBoardElementStateManager.h" namespace OpenScenarioEngine::v1_3::Node { -class LongitudinalDistanceAction : public yase::ActionNode +class LongitudinalDistanceAction : public StateAwareActionNode { public: explicit LongitudinalDistanceAction(std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::ILongitudinalDistanceAction> longitudinalDistanceAction) - : yase::ActionNode{"LongitudinalDistanceAction"}, + : StateAwareActionNode("LongitudinalDistanceAction", [this]() + { + assert(impl_); + return impl_->Step(); }), longitudinalDistanceAction_{std::move(longitudinalDistanceAction)} { } @@ -34,15 +40,10 @@ public: void onInit() override{}; private: - yase::NodeStatus tick() override + void lookupAndRegisterData(yase::Blackboard& blackboard) override { - assert(impl_); - const auto is_finished = impl_->Step(); - return is_finished ? yase::NodeStatus::kSuccess : yase::NodeStatus::kRunning; - }; + StateAwareActionNode::lookupAndRegisterData(blackboard); - void lookupAndRegisterData(yase::Blackboard& blackboard) final - { auto environment = blackboard.get<std::shared_ptr<mantle_api::IEnvironment>>("Environment"); const auto entityBroker = blackboard.get<EntityBroker::Ptr>("EntityBroker"); @@ -51,7 +52,8 @@ private: entityBroker->GetEntities(), longitudinalDistanceAction_->GetContinuous(), longitudinalDistanceAction_->GetFreespace(), - [=]() { + [=]() + { return ConvertScenarioLongitudinalDistance( environment, longitudinalDistanceAction_->IsSetDistance(), @@ -64,12 +66,11 @@ private: ConvertScenarioDynamicConstraints(longitudinalDistanceAction_->IsSetDynamicConstraints(), longitudinalDistanceAction_->GetDynamicConstraints()), ConvertScenarioEntity(longitudinalDistanceAction_->GetEntityRef()), ConvertScenarioCoordinateSystem(longitudinalDistanceAction_->GetCoordinateSystem())}, - OpenScenarioEngine::v1_3::LongitudinalDistanceAction::Interfaces{ - environment}); + OpenScenarioEngine::v1_3::LongitudinalDistanceAction::Interfaces{environment}); } - std::unique_ptr<OpenScenarioEngine::v1_3::MotionControlAction<OpenScenarioEngine::v1_3::LongitudinalDistanceAction>> impl_{nullptr}; + std::unique_ptr<OpenScenarioEngine::v1_3::MotionControlAction<OpenScenarioEngine::v1_3::LongitudinalDistanceAction>> impl_; std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::ILongitudinalDistanceAction> longitudinalDistanceAction_; }; -} // namespace OpenScenarioEngine::v1_3::Node \ No newline at end of file +} // namespace OpenScenarioEngine::v1_3::Node diff --git a/engine/src/Utils/StoryBoardElementState.h b/engine/src/Utils/StoryBoardElementState.h new file mode 100644 index 0000000000000000000000000000000000000000..b76edea6b278682a0172c5ea4bd5993e849ee896 --- /dev/null +++ b/engine/src/Utils/StoryBoardElementState.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 +#include <cstdint> + +namespace OpenScenarioEngine::v1_3 +{ +/// Definition of StoryBoardElementState +/// +/// \see https://publications.pages.asam.net/standards/ASAM_OpenSCENARIO/ASAM_OpenSCENARIO_XML/latest/generated/content/StoryboardElementState.html +enum class StoryBoardElementState : std::uint8_t +{ + kCompleteState, + kRunningState, + kStandbyState, + kUnknown +}; +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Utils/StoryBoardElementStateManager.cpp b/engine/src/Utils/StoryBoardElementStateManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..815b6b4b1e603c3ea0eb9086e912ed3e5c29610d --- /dev/null +++ b/engine/src/Utils/StoryBoardElementStateManager.cpp @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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/StoryBoardElementStateManager.h" + +namespace OpenScenarioEngine::v1_3 +{ + +void StoryBoardElementStateManager::Update(const StoryBoardElement& name, StoryBoardElementType type, StoryBoardElementState state) +{ + std::lock_guard<std::mutex> lock(mutex_); + states_[{name, type}] = state; +} + +void StoryBoardElementStateManager::Update(const StoryBoardElement& name, StoryBoardElementType type, StoryBoardElementTransition transition) +{ + std::lock_guard<std::mutex> lock(mutex_); + transitions_[{name, type}] = transition; +} + +void StoryBoardElementStateManager::ResetState(const StoryBoardElement& name, StoryBoardElementType type) +{ + std::lock_guard<std::mutex> lock(mutex_); + states_[{name, type}] = StoryBoardElementState::kUnknown; +} + +void StoryBoardElementStateManager::ResetTransition(const StoryBoardElement& name, StoryBoardElementType type) +{ + std::lock_guard<std::mutex> lock(mutex_); + transitions_[{name, type}] = StoryBoardElementTransition::kUnknown; +} + +std::optional<StoryBoardElementState> StoryBoardElementStateManager::GetState(const StoryBoardElement& name, StoryBoardElementType type) const +{ + std::lock_guard<std::mutex> lock(mutex_); + + StoryBoardElementKey key{name, type}; + auto it = states_.find(key); + if (it != states_.end()) + { + return it->second; + } + return std::nullopt; +} + +std::optional<StoryBoardElementTransition> StoryBoardElementStateManager::GetTransition(const StoryBoardElement& name, StoryBoardElementType type) const +{ + std::lock_guard<std::mutex> lock(mutex_); + + StoryBoardElementKey key{name, type}; + auto it = transitions_.find(key); + if (it != transitions_.end()) + { + return it->second; + } + return std::nullopt; +} +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Utils/StoryBoardElementStateManager.h b/engine/src/Utils/StoryBoardElementStateManager.h new file mode 100644 index 0000000000000000000000000000000000000000..e945d1aeced177afb7beaf54d77e15547180da21 --- /dev/null +++ b/engine/src/Utils/StoryBoardElementStateManager.h @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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 <mutex> +#include <optional> +#include <unordered_map> + +#include "Utils/StoryBoardElementState.h" +#include "Utils/StoryBoardElementTransition.h" +#include "Utils/StoryBoardElementType.h" + +namespace OpenScenarioEngine::v1_3 +{ + +using StoryBoardElement = std::string; + +struct StoryBoardElementKey +{ + StoryBoardElement name; + StoryBoardElementType type; + + bool operator==(const StoryBoardElementKey& other) const + { + return name == other.name && type == other.type; + } +}; + +struct StoryBoardElementKeyHasher +{ + std::size_t operator()(const StoryBoardElementKey& key) const + { + std::size_t h1 = std::hash<std::string>{}(key.name); + std::size_t h2 = std::hash<std::underlying_type_t<StoryBoardElementType>>{}(static_cast<std::underlying_type_t<StoryBoardElementType>>(key.type)); + return h1 ^ (h2 << 1); + } +}; + +class StoryBoardElementStateManager +{ +public: + void Update(const StoryBoardElement& name, StoryBoardElementType type, StoryBoardElementState state); + void Update(const StoryBoardElement& name, StoryBoardElementType type, StoryBoardElementTransition transition); + void ResetState(const StoryBoardElement& name, StoryBoardElementType type); + void ResetTransition(const StoryBoardElement& name, StoryBoardElementType type); + [[nodiscard]] std::optional<StoryBoardElementState> GetState(const StoryBoardElement& name, StoryBoardElementType type) const; + [[nodiscard]] std::optional<StoryBoardElementTransition> GetTransition(const StoryBoardElement& name, StoryBoardElementType type) const; + +private: + mutable std::mutex mutex_; + std::unordered_map<StoryBoardElementKey, StoryBoardElementState, StoryBoardElementKeyHasher> states_; + std::unordered_map<StoryBoardElementKey, StoryBoardElementTransition, StoryBoardElementKeyHasher> transitions_; +}; +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Utils/StoryBoardElementTransition.h b/engine/src/Utils/StoryBoardElementTransition.h new file mode 100644 index 0000000000000000000000000000000000000000..998203b5ababc0f8fe5f89a5fd5efabeca72a580 --- /dev/null +++ b/engine/src/Utils/StoryBoardElementTransition.h @@ -0,0 +1,27 @@ +/******************************************************************************* + * 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 <cstdint> + +namespace OpenScenarioEngine::v1_3 +{ +/// Definition of StoryBoardElementTransition +/// +/// \see https://publications.pages.asam.net/standards/ASAM_OpenSCENARIO/ASAM_OpenSCENARIO_XML/latest/generated/content/StoryboardElementState.html +enum class StoryBoardElementTransition : std::uint8_t +{ + kEndTransition, + kSkipTransition, + kStartTransition, + kStopTransition, + kUnknown +}; +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/src/Utils/StoryBoardElementType.h b/engine/src/Utils/StoryBoardElementType.h new file mode 100644 index 0000000000000000000000000000000000000000..512dda127dc995c7a3608ac32db504c4f3b3b7e3 --- /dev/null +++ b/engine/src/Utils/StoryBoardElementType.h @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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 <cstdint> + +namespace OpenScenarioEngine::v1_3 +{ +/// Definition of StoryBoardElementType +/// +/// \see https://publications.pages.asam.net/standards/ASAM_OpenSCENARIO/ASAM_OpenSCENARIO_XML/latest/generated/content/StoryboardElementType.html +enum class StoryBoardElementType : std::uint8_t +{ + kAct, + kAction, + kEvent, + kManeuver, + kManeuverGroup, + kStory, + kUnknown +}; +} // namespace OpenScenarioEngine::v1_3 diff --git a/engine/tests/Node/EventNodeTest.cpp b/engine/tests/Node/EventNodeTest.cpp index 899c66c4800377ef907e605fad3d184951b057a7..2df19a3f7b8a0b5d6e88da8710ac721353186057 100644 --- a/engine/tests/Node/EventNodeTest.cpp +++ b/engine/tests/Node/EventNodeTest.cpp @@ -18,9 +18,11 @@ #include "Node/EventNode.h" #include "Node/Testing/FakeActionNode.h" #include "TestUtils/MockEventPrioritizer.h" +#include "Utils/StoryBoardElementStateManager.h" using OpenScenarioEngine::v1_3::EventPriority; using OpenScenarioEngine::v1_3::IEventPrioritizer; +using OpenScenarioEngine::v1_3::StoryBoardElementStateManager; using OpenScenarioEngine::v1_3::Node::EventNode; using testing::Return; using testing::OpenScenarioEngine::v1_3::FakeActionNode; @@ -30,13 +32,16 @@ class TreeWithPrioritizer { public: TreeWithPrioritizer() - : event_prioritizer_{std::make_shared<MockEventPrioritizer>()} + : event_prioritizer_{std::make_shared<MockEventPrioritizer>()}, story_board_element_state_manager_{std::make_shared<StoryBoardElementStateManager>()} { - auto declaration = std::make_unique< - yase::TypeDeclarer< - std::shared_ptr< - IEventPrioritizer>>>("EventPrioritizer", event_prioritizer_); - root_ = std::make_shared<yase::DataDeclarationNode>("RootWithEventPrioritizer", std::move(declaration)); + std::vector<yase::DataDeclaration::UPtr> declarations; + declarations.push_back(std::make_unique<yase::TypeDeclarer<std::shared_ptr<IEventPrioritizer>>>( + "EventPrioritizer", event_prioritizer_)); + declarations.push_back(std::make_unique<yase::TypeDeclarer<std::shared_ptr<StoryBoardElementStateManager>>>( + "StoryBoardElementStateManager", story_board_element_state_manager_)); + + root_ = std::make_shared<yase::DataDeclarationNode>( + "RootWithEventPrioritizer", std::move(declarations)); } yase::DecoratorNode& get_root() @@ -51,6 +56,7 @@ public: private: std::shared_ptr<MockEventPrioritizer> event_prioritizer_; + std::shared_ptr<StoryBoardElementStateManager> story_board_element_state_manager_; yase::DataDeclarationNode::Ptr root_; }; diff --git a/engine/tests/Node/ManeuverNodeTest.cpp b/engine/tests/Node/ManeuverNodeTest.cpp index 310adc444e71af184d54cadc7e055423b4e6aa2b..f4bdfcde2ad57e0d92c845300123ac95f87345ba 100644 --- a/engine/tests/Node/ManeuverNodeTest.cpp +++ b/engine/tests/Node/ManeuverNodeTest.cpp @@ -8,8 +8,8 @@ * SPDX-License-Identifier: EPL-2.0 ********************************************************************************/ -#include <agnostic_behavior_tree/decorator/data_declaration_node.h> #include <agnostic_behavior_tree/composite/parallel_node.h> +#include <agnostic_behavior_tree/decorator/data_declaration_node.h> #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -18,12 +18,14 @@ #include "Node/EventNode.h" #include "Node/ManeuverNode.h" #include "Node/Testing/FakeActionNode.h" +#include "TestUtils.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; +using testing::OpenScenarioEngine::v1_3::FakeRootNode; enum class OscStatus : std::uint8_t { @@ -42,6 +44,7 @@ protected: ManeuverNodeTest() { maneuver_node->setChild(events_node); + root_node->setChild(maneuver_node); } EventWithStartTrigger AddEventWithPriority(EventPriority priority) @@ -96,6 +99,7 @@ protected: 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"); + std::shared_ptr<FakeRootNode> root_node = std::make_shared<FakeRootNode>(nullptr, nullptr, nullptr); }; TEST_F(ManeuverNodeTest, GivenMultipleNodesWithParallelPriority_WhenManeuverTicks_ThenAllEventsRunning) @@ -103,14 +107,14 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithParallelPriority_WhenManeuverTick 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(); + root_node->distributeData(); + root_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(); + root_node->executeTick(); EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kRunning); @@ -122,8 +126,8 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithParallelorSkipPriority_WhenManeuv 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(); + root_node->distributeData(); + root_node->executeTick(); EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); @@ -137,15 +141,15 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithOneOverridePriority_WhenManeuverT auto [event3, start_trigger3] = AddEventWithPriority(EventPriority::kParallel); auto [event4, start_trigger4] = AddEventWithPriority(EventPriority::kOverride); - maneuver_node->distributeData(); - maneuver_node->executeTick(); + root_node->distributeData(); + root_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(); + root_node->executeTick(); EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kSuccess); EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); @@ -157,28 +161,28 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithOneOverridePriority_WhenManeuverT { auto [event1, start_trigger1] = AddEventWithPriority(EventPriority::kParallel); EXPECT_CALL(start_trigger1, tick()) - .WillOnce(Return(yase::NodeStatus::kRunning)) - .WillRepeatedly(Return(yase::NodeStatus::kSuccess)); + .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(); + root_node->distributeData(); + root_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(); + root_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(); + root_node->executeTick(); EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); @@ -186,7 +190,6 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithOneOverridePriority_WhenManeuverT EXPECT_THAT(GetEventStatus(event4, start_trigger4), OscStatus::kRunning); } - TEST_F(ManeuverNodeTest, GivenMultipleNodesWithMultipleOverridePriority_WhenManeuverTicks_ThenOnlyStartedEventsOverridden) { auto [event1, start_trigger1] = AddEventWithPriority(EventPriority::kParallel); @@ -195,16 +198,16 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithMultipleOverridePriority_WhenMane 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)); + .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)); + .WillOnce(Return(yase::NodeStatus::kRunning)) + .WillOnce(Return(yase::NodeStatus::kRunning)) + .WillRepeatedly(Return(yase::NodeStatus::kSuccess)); - maneuver_node->distributeData(); - maneuver_node->executeTick(); + root_node->distributeData(); + root_node->executeTick(); EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kRunning); EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); @@ -213,7 +216,7 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithMultipleOverridePriority_WhenMane EXPECT_THAT(GetEventStatus(event5, start_trigger5), OscStatus::kStandby); EXPECT_THAT(GetEventStatus(event6, start_trigger6), OscStatus::kStandby); - maneuver_node->executeTick(); + root_node->executeTick(); EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kSuccess); EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); @@ -222,7 +225,7 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithMultipleOverridePriority_WhenMane EXPECT_THAT(GetEventStatus(event5, start_trigger5), OscStatus::kRunning); EXPECT_THAT(GetEventStatus(event6, start_trigger6), OscStatus::kStandby); - maneuver_node->executeTick(); + root_node->executeTick(); EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kSuccess); EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); @@ -231,7 +234,7 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithMultipleOverridePriority_WhenMane EXPECT_THAT(GetEventStatus(event5, start_trigger5), OscStatus::kRunning); EXPECT_THAT(GetEventStatus(event6, start_trigger6), OscStatus::kRunning); - maneuver_node->executeTick(); + root_node->executeTick(); EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kSuccess); EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); @@ -241,7 +244,6 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithMultipleOverridePriority_WhenMane EXPECT_THAT(GetEventStatus(event6, start_trigger6), OscStatus::kRunning); } - TEST_F(ManeuverNodeTest, GivenMultipleNodesWithOneOverwritePriority_WhenManeuverTicks_ThenParallelEventsOverridden) { auto [event1, start_trigger1] = AddEventWithPriority(EventPriority::kParallel); @@ -249,15 +251,15 @@ TEST_F(ManeuverNodeTest, GivenMultipleNodesWithOneOverwritePriority_WhenManeuver auto [event3, start_trigger3] = AddEventWithPriority(EventPriority::kParallel); auto [event4, start_trigger4] = AddEventWithPriority(EventPriority::kOverwrite); - maneuver_node->distributeData(); - maneuver_node->executeTick(); + root_node->distributeData(); + root_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(); + root_node->executeTick(); EXPECT_THAT(GetEventStatus(event1, start_trigger1), OscStatus::kSuccess); EXPECT_THAT(GetEventStatus(event2, start_trigger2), OscStatus::kSkipped); diff --git a/engine/tests/TestUtils.h b/engine/tests/TestUtils.h index 7408a93901f80e061b9b836e83301df45aa70f45..7ad4c21a6f0b15b4eb45a8dea90880a0f43612da 100644 --- a/engine/tests/TestUtils.h +++ b/engine/tests/TestUtils.h @@ -1,6 +1,6 @@ /******************************************************************************* * Copyright (c) 2021-2024, Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2023 Ansys, Inc. + * Copyright (c) 2023-2025 Ansys, Inc. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -25,6 +25,7 @@ #include "Utils/ControllerCreator.h" #include "Utils/EngineAbortFlags.h" #include "Utils/EntityBroker.h" +#include "Utils/StoryBoardElementStateManager.h" namespace testing::OpenScenarioEngine::v1_3 { @@ -32,121 +33,124 @@ using namespace units::literals; class FakeRootNode : public yase::DecoratorNode { - public: - FakeRootNode(std::shared_ptr<mantle_api::IEnvironment> environment, - std::shared_ptr<::OpenScenarioEngine::v1_3::EngineAbortFlags> engine_abort_flags, - std::shared_ptr<::OpenScenarioEngine::v1_3::EntityBroker> entity_broker) - : yase::DecoratorNode{"OpenScenarioEngine"}, - environment_{environment}, - engine_abort_flags_{engine_abort_flags}, - entity_broker_{entity_broker} - { - distributeData(); - } - - private: - void lookupAndRegisterData(yase::Blackboard& blackboard) override - { - blackboard.set("Environment", environment_); - blackboard.set("EngineAbortFlags", engine_abort_flags_); - blackboard.set("EntityBroker", entity_broker_); - } - - yase::NodeStatus tick() final - { - return child().executeTick(); - } - - std::shared_ptr<mantle_api::IEnvironment> environment_; - std::shared_ptr<::OpenScenarioEngine::v1_3::EngineAbortFlags> engine_abort_flags_; - std::shared_ptr<::OpenScenarioEngine::v1_3::EntityBroker> entity_broker_; +public: + FakeRootNode(std::shared_ptr<mantle_api::IEnvironment> environment, + std::shared_ptr<::OpenScenarioEngine::v1_3::EngineAbortFlags> engine_abort_flags, + std::shared_ptr<::OpenScenarioEngine::v1_3::EntityBroker> entity_broker) + : yase::DecoratorNode{"OpenScenarioEngine"}, + environment_{environment}, + engine_abort_flags_{engine_abort_flags}, + entity_broker_{entity_broker}, + story_board_element_state_manager_{std::make_shared<::OpenScenarioEngine::v1_3::StoryBoardElementStateManager>()} + { + distributeData(); + } + +private: + void lookupAndRegisterData(yase::Blackboard& blackboard) override + { + blackboard.set("Environment", environment_); + blackboard.set("EngineAbortFlags", engine_abort_flags_); + blackboard.set("EntityBroker", entity_broker_); + blackboard.set("StoryBoardElementStateManager", story_board_element_state_manager_); + } + + yase::NodeStatus tick() final + { + return child().executeTick(); + } + + std::shared_ptr<mantle_api::IEnvironment> environment_; + std::shared_ptr<::OpenScenarioEngine::v1_3::EngineAbortFlags> engine_abort_flags_; + std::shared_ptr<::OpenScenarioEngine::v1_3::EntityBroker> entity_broker_; + std::shared_ptr<::OpenScenarioEngine::v1_3::StoryBoardElementStateManager> story_board_element_state_manager_; }; inline std::string GetScenariosPath(const std::string& test_file_path) { - std::filesystem::path file_path{test_file_path}; - return {file_path.remove_filename().string() + "data/Scenarios/"}; + std::filesystem::path file_path{test_file_path}; + return {file_path.remove_filename().string() + "data/Scenarios/"}; } class OpenScenarioEngineLibraryTestBase : public ::testing::Test { - protected: - void SetUp() override - { - env_ = std::make_shared<mantle_api::MockEnvironment>(); - ON_CALL(env_->GetControllerRepository(), Create(testing::_)).WillByDefault(testing::ReturnRef(controller_)); - } - - std::shared_ptr<mantle_api::MockEnvironment> env_; - mantle_api::MockController controller_; - std::string default_xosc_scenario_{ - "AutomatedLaneKeepingSystemScenarios/ALKS_Scenario_4.1_1_FreeDriving_TEMPLATE.xosc"}; +protected: + void SetUp() override + { + env_ = std::make_shared<mantle_api::MockEnvironment>(); + ON_CALL(env_->GetControllerRepository(), Create(testing::_)).WillByDefault(testing::ReturnRef(controller_)); + } + + std::shared_ptr<mantle_api::MockEnvironment> env_; + mantle_api::MockController controller_; + std::string default_xosc_scenario_{ + "AutomatedLaneKeepingSystemScenarios/ALKS_Scenario_4.1_1_FreeDriving_TEMPLATE.xosc"}; }; class EngineSubModuleTestBase : public OpenScenarioEngineLibraryTestBase { - protected: - virtual void LoadScenario(const std::string& scenario_file_path) - { - auto message_logger = - std::make_shared<NET_ASAM_OPENSCENARIO::SimpleMessageLogger>(NET_ASAM_OPENSCENARIO::ErrorLevel::INFO); - auto catalog_message_logger = - std::make_shared<NET_ASAM_OPENSCENARIO::SimpleMessageLogger>(NET_ASAM_OPENSCENARIO::ErrorLevel::INFO); - auto loader_factory = - NET_ASAM_OPENSCENARIO::v1_3::XmlScenarioImportLoaderFactory(catalog_message_logger, scenario_file_path); - auto loader = loader_factory.CreateLoader(std::make_shared<NET_ASAM_OPENSCENARIO::FileResourceLocator>()); - - scenario_ptr_ = std::static_pointer_cast<NET_ASAM_OPENSCENARIO::v1_3::IOpenScenario>( - loader->Load(message_logger)->GetAdapter(typeid(NET_ASAM_OPENSCENARIO::v1_3::IOpenScenario).name())); - } - - std::string GetScenariosPath(const std::string& test_file_path) const - { - std::filesystem::path file_path{test_file_path}; - // The first .parent_path() removes the filename, the second removes "/Utils" - return {file_path.parent_path().parent_path().string() + "/data/Scenarios/"}; - } - - std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::IOpenScenario> scenario_ptr_; +protected: + virtual void LoadScenario(const std::string& scenario_file_path) + { + auto message_logger = + std::make_shared<NET_ASAM_OPENSCENARIO::SimpleMessageLogger>(NET_ASAM_OPENSCENARIO::ErrorLevel::INFO); + auto catalog_message_logger = + std::make_shared<NET_ASAM_OPENSCENARIO::SimpleMessageLogger>(NET_ASAM_OPENSCENARIO::ErrorLevel::INFO); + auto loader_factory = + NET_ASAM_OPENSCENARIO::v1_3::XmlScenarioImportLoaderFactory(catalog_message_logger, scenario_file_path); + auto loader = loader_factory.CreateLoader(std::make_shared<NET_ASAM_OPENSCENARIO::FileResourceLocator>()); + + scenario_ptr_ = std::static_pointer_cast<NET_ASAM_OPENSCENARIO::v1_3::IOpenScenario>( + loader->Load(message_logger)->GetAdapter(typeid(NET_ASAM_OPENSCENARIO::v1_3::IOpenScenario).name())); + } + + std::string GetScenariosPath(const std::string& test_file_path) const + { + std::filesystem::path file_path{test_file_path}; + // The first .parent_path() removes the filename, the second removes "/Utils" + return {file_path.parent_path().parent_path().string() + "/data/Scenarios/"}; + } + + std::shared_ptr<NET_ASAM_OPENSCENARIO::v1_3::IOpenScenario> scenario_ptr_; }; class OpenScenarioEngineTestBase : public OpenScenarioEngineLibraryTestBase { - protected: - void SetUp() override - { - OpenScenarioEngineLibraryTestBase::SetUp(); - - mantle_api::VehicleProperties ego_properties{}; - ego_properties.type = mantle_api::EntityType::kVehicle; - ego_properties.classification = mantle_api::VehicleClass::kMedium_car; - ego_properties.model = "medium_car"; - ego_properties.bounding_box.dimension.length = 5.0_m; - ego_properties.bounding_box.dimension.width = 2.0_m; - ego_properties.bounding_box.dimension.height = 1.8_m; - ego_properties.bounding_box.geometric_center.x = 1.4_m; - ego_properties.bounding_box.geometric_center.y = 0.0_m; - ego_properties.bounding_box.geometric_center.z = 0.9_m; - ego_properties.performance.max_acceleration = 10_mps_sq; - ego_properties.performance.max_deceleration = 10_mps_sq; - ego_properties.performance.max_speed = 70_mps; - ego_properties.front_axle.bb_center_to_axle_center = {1.58_m, 0.0_m, -0.5_m}; - ego_properties.front_axle.max_steering = 0.5_rad; - ego_properties.front_axle.track_width = 1.68_m; - ego_properties.front_axle.wheel_diameter = 0.8_m; - ego_properties.rear_axle.bb_center_to_axle_center = {-1.4_m, 0.0_m, -0.5_m}; - ego_properties.rear_axle.max_steering = 0_rad; - ego_properties.rear_axle.track_width = 1.68_m; - ego_properties.rear_axle.wheel_diameter = 0.8_m; - ego_properties.is_host = true; - // Necessary because Create() is always called in engine init and will otherwise not return a MockVehicle ref - // which results in an exception - ON_CALL(dynamic_cast<mantle_api::MockEntityRepository&>(env_->GetEntityRepository()), - Create(testing::_, ego_properties)) - .WillByDefault(testing::ReturnRef(mock_vehicle_)); - } - - mantle_api::MockVehicle mock_vehicle_{}; +protected: + void SetUp() override + { + OpenScenarioEngineLibraryTestBase::SetUp(); + + mantle_api::VehicleProperties ego_properties{}; + ego_properties.type = mantle_api::EntityType::kVehicle; + ego_properties.classification = mantle_api::VehicleClass::kMedium_car; + ego_properties.model = "medium_car"; + ego_properties.bounding_box.dimension.length = 5.0_m; + ego_properties.bounding_box.dimension.width = 2.0_m; + ego_properties.bounding_box.dimension.height = 1.8_m; + ego_properties.bounding_box.geometric_center.x = 1.4_m; + ego_properties.bounding_box.geometric_center.y = 0.0_m; + ego_properties.bounding_box.geometric_center.z = 0.9_m; + ego_properties.performance.max_acceleration = 10_mps_sq; + ego_properties.performance.max_deceleration = 10_mps_sq; + ego_properties.performance.max_speed = 70_mps; + ego_properties.front_axle.bb_center_to_axle_center = {1.58_m, 0.0_m, -0.5_m}; + ego_properties.front_axle.max_steering = 0.5_rad; + ego_properties.front_axle.track_width = 1.68_m; + ego_properties.front_axle.wheel_diameter = 0.8_m; + ego_properties.rear_axle.bb_center_to_axle_center = {-1.4_m, 0.0_m, -0.5_m}; + ego_properties.rear_axle.max_steering = 0_rad; + ego_properties.rear_axle.track_width = 1.68_m; + ego_properties.rear_axle.wheel_diameter = 0.8_m; + ego_properties.is_host = true; + // Necessary because Create() is always called in engine init and will otherwise not return a MockVehicle ref + // which results in an exception + ON_CALL(dynamic_cast<mantle_api::MockEntityRepository&>(env_->GetEntityRepository()), + Create(testing::_, ego_properties)) + .WillByDefault(testing::ReturnRef(mock_vehicle_)); + } + + mantle_api::MockVehicle mock_vehicle_{}; }; } // namespace testing::OpenScenarioEngine::v1_3