From 24a37beb6b79b5c1e7c4a35cb771aa3aa26a8e6f Mon Sep 17 00:00:00 2001 From: Steve Peters Date: Thu, 1 Aug 2024 14:05:51 -0700 Subject: [PATCH] Specify System::PreUpdate, Update execution order (#2487) Specifying an integer value in a tag for a system will control the order in which System PreUpdate and Update callbacks are executed, with lower values executing first. The PriorityPrinter example plugin is added to illustrate the feature. Signed-off-by: Steve Peters --- .../priority_printer_plugin/CMakeLists.txt | 17 +++ .../PriorityPrinter.cc | 80 +++++++++++ .../PriorityPrinter.hh | 51 +++++++ .../plugin/priority_printer_plugin/README.md | 128 ++++++++++++++++++ .../priority_printer_plugin.sdf | 36 +++++ include/gz/sim/System.hh | 22 +++ src/SimulationRunner.cc | 18 ++- src/SystemManager.cc | 73 +++++++--- src/SystemManager.hh | 36 +++-- src/SystemManager_TEST.cc | 6 + 10 files changed, 433 insertions(+), 34 deletions(-) create mode 100644 examples/plugin/priority_printer_plugin/CMakeLists.txt create mode 100644 examples/plugin/priority_printer_plugin/PriorityPrinter.cc create mode 100644 examples/plugin/priority_printer_plugin/PriorityPrinter.hh create mode 100644 examples/plugin/priority_printer_plugin/README.md create mode 100644 examples/plugin/priority_printer_plugin/priority_printer_plugin.sdf diff --git a/examples/plugin/priority_printer_plugin/CMakeLists.txt b/examples/plugin/priority_printer_plugin/CMakeLists.txt new file mode 100644 index 0000000000..8af962296d --- /dev/null +++ b/examples/plugin/priority_printer_plugin/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR) + +find_package(gz-cmake3 REQUIRED) + +project(Priority_printer) + +gz_find_package(gz-plugin2 REQUIRED COMPONENTS register) +set(GZ_PLUGIN_VER ${gz-plugin2_VERSION_MAJOR}) + +gz_find_package(gz-sim8 REQUIRED) +set(GZ_SIM_VER ${gz-sim8_VERSION_MAJOR}) + +add_library(PriorityPrinter SHARED PriorityPrinter.cc) +set_property(TARGET PriorityPrinter PROPERTY CXX_STANDARD 17) +target_link_libraries(PriorityPrinter + PRIVATE gz-plugin${GZ_PLUGIN_VER}::gz-plugin${GZ_PLUGIN_VER} + PRIVATE gz-sim${GZ_SIM_VER}::gz-sim${GZ_SIM_VER}) diff --git a/examples/plugin/priority_printer_plugin/PriorityPrinter.cc b/examples/plugin/priority_printer_plugin/PriorityPrinter.cc new file mode 100644 index 0000000000..164596d968 --- /dev/null +++ b/examples/plugin/priority_printer_plugin/PriorityPrinter.cc @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +// We'll use a string and the gzmsg command below for a brief example. +// Remove these includes if your plugin doesn't need them. +#include +#include +#include + +// This header is required to register plugins. It's good practice to place it +// in the cc file, like it's done here. +#include + +// Don't forget to include the plugin's header. +#include "PriorityPrinter.hh" + +// This is required to register the plugin. Make sure the interfaces match +// what's in the header. +GZ_ADD_PLUGIN( + priority_printer::PriorityPrinter, + gz::sim::System, + priority_printer::PriorityPrinter::ISystemConfigure, + priority_printer::PriorityPrinter::ISystemPreUpdate, + priority_printer::PriorityPrinter::ISystemUpdate) + +using namespace priority_printer; + +void PriorityPrinter::Configure( + const gz::sim::Entity &_entity, + const std::shared_ptr &_sdf, + gz::sim::EntityComponentManager &_ecm, + gz::sim::EventManager &_eventMgr) +{ + // Parse priority value as a string for printing + const std::string priorityElementName {gz::sim::System::kPriorityElementName}; + if (_sdf && _sdf->HasElement(priorityElementName)) + { + this->systemPriority = _sdf->Get(priorityElementName); + } + + const std::string labelElementName {"label"}; + if (_sdf && _sdf->HasElement(labelElementName)) + { + this->systemLabel = _sdf->Get(labelElementName); + } +} + +void PriorityPrinter::PreUpdate(const gz::sim::UpdateInfo &_info, + gz::sim::EntityComponentManager &/*_ecm*/) +{ + gzmsg << "PreUpdate: " + << "Iteration " << _info.iterations + << ", system priority " << this->systemPriority + << ", system label " << this->systemLabel + << '\n'; +} + +void PriorityPrinter::Update(const gz::sim::UpdateInfo &_info, + gz::sim::EntityComponentManager &/*_ecm*/) +{ + gzmsg << "Update: " + << "Iteration " << _info.iterations + << ", system priority " << this->systemPriority + << ", system label " << this->systemLabel + << '\n'; +} diff --git a/examples/plugin/priority_printer_plugin/PriorityPrinter.hh b/examples/plugin/priority_printer_plugin/PriorityPrinter.hh new file mode 100644 index 0000000000..741c2f596c --- /dev/null +++ b/examples/plugin/priority_printer_plugin/PriorityPrinter.hh @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef EXAMPLE_PLUGIN_PRIORITYPRINTER_HH_ +#define EXAMPLE_PLUGIN_PRIORITYPRINTER_HH_ + +#include +#include +#include + +namespace priority_printer +{ + // This plugin prints the number of elapsed simulation iterations, + // this system's priority value from the XML configuration, + // and a custom label from the XML configuration during the Update callback. + class PriorityPrinter: + public gz::sim::System, + public gz::sim::ISystemConfigure, + public gz::sim::ISystemPreUpdate, + public gz::sim::ISystemUpdate + { + public: void Configure(const gz::sim::Entity &_entity, + const std::shared_ptr &_sdf, + gz::sim::EntityComponentManager &_ecm, + gz::sim::EventManager &_eventMgr) override; + + public: void PreUpdate(const gz::sim::UpdateInfo &_info, + gz::sim::EntityComponentManager &_ecm) override; + + public: void Update(const gz::sim::UpdateInfo &_info, + gz::sim::EntityComponentManager &_ecm) override; + + public: std::string systemPriority{"unset"}; + public: std::string systemLabel{"unset"}; + }; +} +#endif diff --git a/examples/plugin/priority_printer_plugin/README.md b/examples/plugin/priority_printer_plugin/README.md new file mode 100644 index 0000000000..670c269979 --- /dev/null +++ b/examples/plugin/priority_printer_plugin/README.md @@ -0,0 +1,128 @@ +# Priority Printer + +This example illustrates how to control the order of execution of System +PreUpdate and Update callbacks. As documented in +[gz/sim/System.hh](https://github.com/gazebosim/gz-sim/tree/main/include/gz/sim/System.hh), +the PreUpdate and Update phases are executed sequentially in the same +thread, and the order of execution of these phases can be +controlled by specifying a signed integer priority value for the System +in its XML configuration. The default priority value is zero, and +smaller values are executed earlier. Systems with the same priority +value are executed in the order in which they are loaded. + +## Build + +From the root of the `gz-sim` repository, do the following to build the example: + +~~~ +cd gz-sim/examples/plugins/priority_printer +mkdir build +cd build +cmake .. +make +~~~ + +This will generate the `PriorityPrinter` library under `build`. + +## Run + +Multiple instances of the `PriorityPrinter` plugin are added to the +[priority\_printer\_plugin.sdf](priority_printer_plugin.sdf) world file +with various priority values and unique labels corresponding to the order +in which the plugins are specified ("first" for the first plugin and so on). +Without priority values, the systems would be executed in the order they are +specified in XML ("first", then "second", etc.). +With the priority values specified, the systems with smallest integer priority +values are executed first. For systems with the same priority value, the +system that is specified earlier in the XML file will be executed first. + +Before starting Gazebo, we must make sure it can find the plugin by doing: + +~~~ +cd gz-sim/examples/plugins/priority_printer +export GZ_SIM_SYSTEM_PLUGIN_PATH=`pwd`/build +~~~ + +Then load the example world and run for 5 iterations: + + gz sim -v 3 priority_printer_plugin.sdf -s -r --iterations 5 + +You should see green messages on the terminal like those given below. +Note that the system with priority `-100` was executed first, despite being +the fifth system in the XML ordering. There are two instances of systems with +the same priority value: the fourth and sixth systems with priority 0 (with +"unset" defaulting to 0) and the first and seventh systems with priority 100. +In each case, the system declared earlier in XML executed first. + +``` +[Msg] PreUpdate: Iteration 1, system priority -100, system label fifth +[Msg] PreUpdate: Iteration 1, system priority -10, system label third +[Msg] PreUpdate: Iteration 1, system priority unset, system label fourth +[Msg] PreUpdate: Iteration 1, system priority 0, system label sixth +[Msg] PreUpdate: Iteration 1, system priority 10, system label second +[Msg] PreUpdate: Iteration 1, system priority 100, system label first +[Msg] PreUpdate: Iteration 1, system priority 100, system label seventh +[Msg] Update: Iteration 1, system priority -100, system label fifth +[Msg] Update: Iteration 1, system priority -10, system label third +[Msg] Update: Iteration 1, system priority unset, system label fourth +[Msg] Update: Iteration 1, system priority 0, system label sixth +[Msg] Update: Iteration 1, system priority 10, system label second +[Msg] Update: Iteration 1, system priority 100, system label first +[Msg] Update: Iteration 1, system priority 100, system label seventh +[Msg] PreUpdate: Iteration 2, system priority -100, system label fifth +[Msg] PreUpdate: Iteration 2, system priority -10, system label third +[Msg] PreUpdate: Iteration 2, system priority unset, system label fourth +[Msg] PreUpdate: Iteration 2, system priority 0, system label sixth +[Msg] PreUpdate: Iteration 2, system priority 10, system label second +[Msg] PreUpdate: Iteration 2, system priority 100, system label first +[Msg] PreUpdate: Iteration 2, system priority 100, system label seventh +[Msg] Update: Iteration 2, system priority -100, system label fifth +[Msg] Update: Iteration 2, system priority -10, system label third +[Msg] Update: Iteration 2, system priority unset, system label fourth +[Msg] Update: Iteration 2, system priority 0, system label sixth +[Msg] Update: Iteration 2, system priority 10, system label second +[Msg] Update: Iteration 2, system priority 100, system label first +[Msg] Update: Iteration 2, system priority 100, system label seventh +[Msg] PreUpdate: Iteration 3, system priority -100, system label fifth +[Msg] PreUpdate: Iteration 3, system priority -10, system label third +[Msg] PreUpdate: Iteration 3, system priority unset, system label fourth +[Msg] PreUpdate: Iteration 3, system priority 0, system label sixth +[Msg] PreUpdate: Iteration 3, system priority 10, system label second +[Msg] PreUpdate: Iteration 3, system priority 100, system label first +[Msg] PreUpdate: Iteration 3, system priority 100, system label seventh +[Msg] Update: Iteration 3, system priority -100, system label fifth +[Msg] Update: Iteration 3, system priority -10, system label third +[Msg] Update: Iteration 3, system priority unset, system label fourth +[Msg] Update: Iteration 3, system priority 0, system label sixth +[Msg] Update: Iteration 3, system priority 10, system label second +[Msg] Update: Iteration 3, system priority 100, system label first +[Msg] Update: Iteration 3, system priority 100, system label seventh +[Msg] PreUpdate: Iteration 4, system priority -100, system label fifth +[Msg] PreUpdate: Iteration 4, system priority -10, system label third +[Msg] PreUpdate: Iteration 4, system priority unset, system label fourth +[Msg] PreUpdate: Iteration 4, system priority 0, system label sixth +[Msg] PreUpdate: Iteration 4, system priority 10, system label second +[Msg] PreUpdate: Iteration 4, system priority 100, system label first +[Msg] PreUpdate: Iteration 4, system priority 100, system label seventh +[Msg] Update: Iteration 4, system priority -100, system label fifth +[Msg] Update: Iteration 4, system priority -10, system label third +[Msg] Update: Iteration 4, system priority unset, system label fourth +[Msg] Update: Iteration 4, system priority 0, system label sixth +[Msg] Update: Iteration 4, system priority 10, system label second +[Msg] Update: Iteration 4, system priority 100, system label first +[Msg] Update: Iteration 4, system priority 100, system label seventh +[Msg] PreUpdate: Iteration 5, system priority -100, system label fifth +[Msg] PreUpdate: Iteration 5, system priority -10, system label third +[Msg] PreUpdate: Iteration 5, system priority unset, system label fourth +[Msg] PreUpdate: Iteration 5, system priority 0, system label sixth +[Msg] PreUpdate: Iteration 5, system priority 10, system label second +[Msg] PreUpdate: Iteration 5, system priority 100, system label first +[Msg] PreUpdate: Iteration 5, system priority 100, system label seventh +[Msg] Update: Iteration 5, system priority -100, system label fifth +[Msg] Update: Iteration 5, system priority -10, system label third +[Msg] Update: Iteration 5, system priority unset, system label fourth +[Msg] Update: Iteration 5, system priority 0, system label sixth +[Msg] Update: Iteration 5, system priority 10, system label second +[Msg] Update: Iteration 5, system priority 100, system label first +[Msg] Update: Iteration 5, system priority 100, system label seventh +``` diff --git a/examples/plugin/priority_printer_plugin/priority_printer_plugin.sdf b/examples/plugin/priority_printer_plugin/priority_printer_plugin.sdf new file mode 100644 index 0000000000..e60d75a497 --- /dev/null +++ b/examples/plugin/priority_printer_plugin/priority_printer_plugin.sdf @@ -0,0 +1,36 @@ + + + + + + 100 + + + + 10 + + + + -10 + + + + + + + -100 + + + + 0 + + + + 100 + + + + diff --git a/include/gz/sim/System.hh b/include/gz/sim/System.hh index cc0139161e..f531c1d023 100644 --- a/include/gz/sim/System.hh +++ b/include/gz/sim/System.hh @@ -17,6 +17,7 @@ #ifndef GZ_SIM_SYSTEM_HH_ #define GZ_SIM_SYSTEM_HH_ +#include #include #include @@ -64,6 +65,14 @@ namespace gz /// * Used to read out results at the end of a simulation step to be used /// for sensor or controller updates. /// + /// The PreUpdate and Update phases are executed sequentially in the same + /// thread, while the PostUpdate phase is executed in parallel in multiple + /// threads. The order of execution of PreUpdate and Update phases can be + /// controlled by specifying a signed integer Priority value for the System + /// in its XML configuration. The default Priority value is zero, and + /// smaller values are executed earlier. Systems with the same Priority + /// value are executed in the order in which they are loaded. + /// /// It's important to note that UpdateInfo::simTime does not refer to the /// current time, but the time reached after the PreUpdate and Update calls /// have finished. So, if any of the *Update functions are called with @@ -74,6 +83,19 @@ namespace gz /// simulation is started un-paused. class System { + /// \brief Signed integer type used for specifying priority of the + /// execution order of PreUpdate and Update phases. + public: using PriorityType = int32_t; + + /// \brief Default priority value for execution order of the PreUpdate + /// and Update phases. + public: constexpr static PriorityType kDefaultPriority = {0}; + + /// \brief Name of the XML element from which the priority value will be + /// parsed. + public: constexpr static std::string_view kPriorityElementName = + {"gz:system_priority"}; + /// \brief Constructor public: System() = default; diff --git a/src/SimulationRunner.cc b/src/SimulationRunner.cc index 87f72d1ce2..69be9a6a37 100644 --- a/src/SimulationRunner.cc +++ b/src/SimulationRunner.cc @@ -601,14 +601,24 @@ void SimulationRunner::UpdateSystems() { GZ_PROFILE("PreUpdate"); - for (auto& system : this->systemMgr->SystemsPreUpdate()) - system->PreUpdate(this->currentInfo, this->entityCompMgr); + for (auto& [priority, systems] : this->systemMgr->SystemsPreUpdate()) + { + for (auto& system : systems) + { + system->PreUpdate(this->currentInfo, this->entityCompMgr); + } + } } { GZ_PROFILE("Update"); - for (auto& system : this->systemMgr->SystemsUpdate()) - system->Update(this->currentInfo, this->entityCompMgr); + for (auto& [priority, systems] : this->systemMgr->SystemsUpdate()) + { + for (auto& system : systems) + { + system->Update(this->currentInfo, this->entityCompMgr); + } + } } { diff --git a/src/SystemManager.cc b/src/SystemManager.cc index 289c7ce5d2..9d86af1241 100644 --- a/src/SystemManager.cc +++ b/src/SystemManager.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,7 @@ #include "SystemInternal.hh" #include "gz/sim/components/SystemPluginInfo.hh" #include "gz/sim/Conversions.hh" +#include "gz/sim/System.hh" #include "gz/sim/Util.hh" #include "SystemManager.hh" @@ -110,6 +112,17 @@ size_t SystemManager::ActivatePendingSystems() { this->systems.push_back(system); + PriorityType p {System::kDefaultPriority}; + const std::string kPriorityElementName + {gz::sim::System::kPriorityElementName}; + if (system.configureSdf && + system.configureSdf->HasElement(kPriorityElementName)) + { + PriorityType newPriority = + system.configureSdf->Get(kPriorityElementName); + p = newPriority; + } + if (system.configure) this->systemsConfigure.push_back(system.configure); @@ -120,10 +133,16 @@ size_t SystemManager::ActivatePendingSystems() this->systemsReset.push_back(system.reset); if (system.preupdate) - this->systemsPreupdate.push_back(system.preupdate); + { + this->systemsPreupdate.try_emplace(p); + this->systemsPreupdate[p].push_back(system.preupdate); + } if (system.update) - this->systemsUpdate.push_back(system.update); + { + this->systemsUpdate.try_emplace(p); + this->systemsUpdate[p].push_back(system.update); + } if (system.postupdate) { @@ -301,13 +320,15 @@ const std::vector &SystemManager::SystemsReset() } ////////////////////////////////////////////////// -const std::vector& SystemManager::SystemsPreUpdate() +const SystemManager::PrioritizedSystems& +SystemManager::SystemsPreUpdate() { return this->systemsPreupdate; } ////////////////////////////////////////////////// -const std::vector& SystemManager::SystemsUpdate() +const SystemManager::PrioritizedSystems& +SystemManager::SystemsUpdate() { return this->systemsUpdate; } @@ -514,20 +535,36 @@ void SystemManager::ProcessRemovedEntities( } return false; }); - RemoveFromVectorIf(this->systemsPreupdate, - [&](const auto& system) { - if (preupdateSystemsToBeRemoved.count(system)) { - return true; - } - return false; - }); - RemoveFromVectorIf(this->systemsUpdate, - [&](const auto& system) { - if (updateSystemsToBeRemoved.count(system)) { - return true; - } - return false; - }); + for (auto it = this->systemsPreupdate.begin(); + it != this->systemsPreupdate.end();) + { + RemoveFromVectorIf(it->second, + [&](const auto& system) { + if (preupdateSystemsToBeRemoved.count(system)) { + return true; + } + return false; + }); + if (it->second.empty()) + it = this->systemsPreupdate.erase(it); + else + ++it; + } + for (auto it = this->systemsUpdate.begin(); + it != this->systemsUpdate.end();) + { + RemoveFromVectorIf(it->second, + [&](const auto& system) { + if (updateSystemsToBeRemoved.count(system)) { + return true; + } + return false; + }); + if (it->second.empty()) + it = this->systemsUpdate.erase(it); + else + ++it; + } RemoveFromVectorIf(this->systemsPostupdate, [&](const auto& system) { diff --git a/src/SystemManager.hh b/src/SystemManager.hh index d523c4e740..c9c61c7944 100644 --- a/src/SystemManager.hh +++ b/src/SystemManager.hh @@ -19,6 +19,8 @@ #include +#include +#include #include #include #include @@ -29,6 +31,7 @@ #include "gz/sim/config.hh" #include "gz/sim/EntityComponentManager.hh" #include "gz/sim/Export.hh" +#include "gz/sim/System.hh" #include "gz/sim/SystemLoader.hh" #include "gz/sim/Types.hh" @@ -44,6 +47,13 @@ namespace gz /// \brief Used to load / unload sysetms as well as iterate over them. class GZ_SIM_VISIBLE SystemManager { + /// \brief Ordered map of priority values to a vector of System + /// interfaces. + using PriorityType = System::PriorityType; + template + class PrioritizedSystems : public std::map> + {}; + /// \brief Constructor /// \param[in] _systemLoader A pointer to a SystemLoader to load plugins /// from files @@ -116,29 +126,31 @@ namespace gz /// \return Vector of systems' configure interfaces. public: const std::vector& SystemsConfigure(); - /// \brief Get an vector of all active systems implementing + /// \brief Get a vector of all active systems implementing /// "ConfigureParameters" /// \return Vector of systems's configure interfaces. public: const std::vector& SystemsConfigureParameters(); - /// \brief Get an vector of all active systems implementing "Reset" + /// \brief Get a vector of all active systems implementing "Reset" /// \return Vector of systems' reset interfaces. public: const std::vector& SystemsReset(); - /// \brief Get an vector of all active systems implementing "PreUpdate" - /// \return Vector of systems's pre-update interfaces. - public: const std::vector& SystemsPreUpdate(); + /// \brief Get an ordered map of systems by priority that implement + /// "PreUpdate" + /// \return Priortized map of systems's pre-update interfaces. + public: const PrioritizedSystems& SystemsPreUpdate(); - /// \brief Get an vector of all active systems implementing "Update" - /// \return Vector of systems's update interfaces. - public: const std::vector& SystemsUpdate(); + /// \brief Get an ordered map of systems by priority that implement + /// "Update" + /// \return Priortized map of systems's update interfaces. + public: const PrioritizedSystems& SystemsUpdate(); - /// \brief Get an vector of all active systems implementing "PostUpdate" + /// \brief Get a vector of all active systems implementing "PostUpdate" /// \return Vector of systems's post-update interfaces. public: const std::vector& SystemsPostUpdate(); - /// \brief Get an vector of all systems attached to a given entity. + /// \brief Get a vector of all systems attached to a given entity. /// \return Vector of systems. public: std::vector TotalByEntity(Entity _entity); @@ -205,10 +217,10 @@ namespace gz private: std::vector systemsReset; /// \brief Systems implementing PreUpdate - private: std::vector systemsPreupdate; + private: PrioritizedSystems systemsPreupdate; /// \brief Systems implementing Update - private: std::vector systemsUpdate; + private: PrioritizedSystems systemsUpdate; /// \brief Systems implementing PostUpdate private: std::vector systemsPostupdate; diff --git a/src/SystemManager_TEST.cc b/src/SystemManager_TEST.cc index 1265cc5a5f..38ad82d726 100644 --- a/src/SystemManager_TEST.cc +++ b/src/SystemManager_TEST.cc @@ -143,8 +143,14 @@ TEST(SystemManager, AddSystemNoEcm) EXPECT_EQ(0u, systemMgr.PendingCount()); EXPECT_EQ(2u, systemMgr.TotalCount()); EXPECT_EQ(1u, systemMgr.SystemsConfigure().size()); + // Expect PreUpdate and Update to contain one map entry with Priority 0 and + // a vector of length 1. EXPECT_EQ(1u, systemMgr.SystemsPreUpdate().size()); + EXPECT_EQ(1u, systemMgr.SystemsPreUpdate().count(0)); + EXPECT_EQ(1u, systemMgr.SystemsPreUpdate().at(0).size()); EXPECT_EQ(1u, systemMgr.SystemsUpdate().size()); + EXPECT_EQ(1u, systemMgr.SystemsUpdate().count(0)); + EXPECT_EQ(1u, systemMgr.SystemsUpdate().at(0).size()); EXPECT_EQ(1u, systemMgr.SystemsPostUpdate().size()); EXPECT_EQ(1u, systemMgr.TotalByEntity(updateEntity).size()); }