Skip to content

Commit 2994087

Browse files
authored
Remove systems if their parent entity is removed (#2232)
n particular if a user despawns an entity, the associated plugin gets removed. This should prevent issues like #2165. TBH I'm not sure if this is the right way forward as a system should technically be able to access any entity in a traditional ECS. The PR has now been reworked and greatly simplified. All we do is stop all worker threads if an entity is removed and then recreate remaining threads.
1 parent 1d3eee9 commit 2994087

7 files changed

+231
-8
lines changed

include/gz/sim/EntityComponentManager.hh

+3
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,9 @@ namespace gz
828828
friend class GuiRunner;
829829
friend class SimulationRunner;
830830

831+
// Make SystemManager friend so it has access to removals
832+
friend class SystemManager;
833+
831834
// Make network managers friends so they have control over component
832835
// states. Like the runners, the managers are internal.
833836
friend class NetworkManagerPrimary;

src/SimulationRunner.cc

+8-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <gz/msgs/world_stats.pb.h>
3434

3535
#include <sdf/Root.hh>
36+
#include <vector>
3637

3738
#include "gz/common/Profiler.hh"
3839
#include "gz/sim/components/Model.hh"
@@ -533,12 +534,15 @@ void SimulationRunner::ProcessSystemQueue()
533534
{
534535
auto pending = this->systemMgr->PendingCount();
535536

536-
if (0 == pending)
537+
if (0 == pending && !this->threadsNeedCleanUp)
537538
return;
538539

539-
// If additional systems are to be added, stop the existing threads.
540+
// If additional systems are to be added or have been removed,
541+
// stop the existing threads.
540542
this->StopWorkerThreads();
541543

544+
this->threadsNeedCleanUp = false;
545+
542546
this->systemMgr->ActivatePendingSystems();
543547

544548
unsigned int threadCount =
@@ -922,6 +926,8 @@ void SimulationRunner::Step(const UpdateInfo &_info)
922926
this->ProcessRecreateEntitiesCreate();
923927

924928
// Process entity removals.
929+
this->systemMgr->ProcessRemovedEntities(this->entityCompMgr,
930+
this->threadsNeedCleanUp);
925931
this->entityCompMgr.ProcessRemoveEntityRequests();
926932

927933
// Process components removals

src/SimulationRunner.hh

+3
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,9 @@ namespace gz
543543
/// at the appropriate time.
544544
private: std::unique_ptr<msgs::WorldControlState> newWorldControlState;
545545

546+
/// \brief Set if we need to remove systems due to entity removal
547+
private: bool threadsNeedCleanUp;
548+
546549
private: bool resetInitiated{false};
547550
friend class LevelManager;
548551
};

src/SimulationRunner_TEST.cc

+11-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717

1818
#include <gtest/gtest.h>
19-
2019
#include <tinyxml2.h>
2120

2221
#include <gz/msgs/clock.pb.h>
@@ -111,7 +110,6 @@ void rootClockCb(const msgs::Clock &_msg)
111110
rootClockMsgs.push_back(_msg);
112111
}
113112

114-
115113
/////////////////////////////////////////////////
116114
TEST_P(SimulationRunnerTest, CreateEntities)
117115
{
@@ -1484,8 +1482,7 @@ TEST_P(SimulationRunnerTest,
14841482
EXPECT_TRUE(runner.EntityCompMgr().EntityHasComponentType(sphereEntity,
14851483
componentId)) << componentId;
14861484

1487-
// Remove entities that have plugin - this is not unloading or destroying
1488-
// the plugin though!
1485+
// Remove entities that have plugin
14891486
auto entityCount = runner.EntityCompMgr().EntityCount();
14901487
const_cast<EntityComponentManager &>(
14911488
runner.EntityCompMgr()).RequestRemoveEntity(boxEntity);
@@ -1533,8 +1530,16 @@ TEST_P(SimulationRunnerTest,
15331530
SimulationRunner runner(rootWithout.WorldByIndex(0), systemLoader,
15341531
serverConfig);
15351532

1536-
// 1 model plugin from SDF and 2 world plugins from config
1537-
ASSERT_EQ(3u, runner.SystemCount());
1533+
// 1 model plugin from SDF and 1 world plugin from config
1534+
// and 1 model plugin from theconfig
1535+
EXPECT_EQ(3u, runner.SystemCount());
1536+
runner.SetPaused(false);
1537+
runner.Run(1);
1538+
1539+
// Remove the model. Only 1 world plugin should remain.
1540+
EXPECT_TRUE(runner.RequestRemoveEntity("box"));
1541+
runner.Run(2);
1542+
EXPECT_EQ(1u, runner.SystemCount());
15381543
}
15391544

15401545
/////////////////////////////////////////////////

src/SystemManager.cc

+133
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
*/
1717

1818
#include <list>
19+
#include <mutex>
1920
#include <set>
21+
#include <unordered_set>
2022

2123
#include <gz/common/StringUtils.hh>
2224

25+
#include "SystemInternal.hh"
2326
#include "gz/sim/components/SystemPluginInfo.hh"
2427
#include "gz/sim/Conversions.hh"
2528
#include "SystemManager.hh"
@@ -122,7 +125,9 @@ size_t SystemManager::ActivatePendingSystems()
122125
this->systemsUpdate.push_back(system.update);
123126

124127
if (system.postupdate)
128+
{
125129
this->systemsPostupdate.push_back(system.postupdate);
130+
}
126131
}
127132

128133
this->pendingSystems.clear();
@@ -409,3 +414,131 @@ void SystemManager::ProcessPendingEntitySystems()
409414
}
410415
this->systemsToAdd.clear();
411416
}
417+
418+
template <typename T>
419+
struct identity
420+
{
421+
using type = T;
422+
};
423+
424+
//////////////////////////////////////////////////
425+
/// TODO(arjo) - When we move to C++20 we can just use erase_if
426+
/// Removes all items that match the given predicate.
427+
/// This function runs in O(n) time and uses memory in-place
428+
template<typename Tp>
429+
void RemoveFromVectorIf(std::vector<Tp>& vec,
430+
typename identity<std::function<bool(const Tp&)>>::type pred)
431+
{
432+
vec.erase(std::remove_if(vec.begin(), vec.end(), pred), vec.end());
433+
}
434+
435+
//////////////////////////////////////////////////
436+
void SystemManager::ProcessRemovedEntities(
437+
const EntityComponentManager &_ecm,
438+
bool &_needsCleanUp)
439+
{
440+
// Note: This function has O(n) time when an entity is removed
441+
// where n is number of systems. Ideally we would only iterate
442+
// over entities marked for removal but that would involve having
443+
// a key value map. This can be marked as a future improvement.
444+
if (!_ecm.HasEntitiesMarkedForRemoval())
445+
{
446+
return;
447+
}
448+
449+
std::unordered_set<ISystemReset *> resetSystemsToBeRemoved;
450+
std::unordered_set<ISystemPreUpdate *> preupdateSystemsToBeRemoved;
451+
std::unordered_set<ISystemUpdate *> updateSystemsToBeRemoved;
452+
std::unordered_set<ISystemPostUpdate *> postupdateSystemsToBeRemoved;
453+
std::unordered_set<ISystemConfigure *> configureSystemsToBeRemoved;
454+
std::unordered_set<ISystemConfigureParameters *>
455+
configureParametersSystemsToBeRemoved;
456+
for (const auto &system : this->systems)
457+
{
458+
if (_ecm.IsMarkedForRemoval(system.parentEntity))
459+
{
460+
if (system.reset)
461+
{
462+
resetSystemsToBeRemoved.insert(system.reset);
463+
}
464+
if (system.preupdate)
465+
{
466+
preupdateSystemsToBeRemoved.insert(system.preupdate);
467+
}
468+
if (system.update)
469+
{
470+
updateSystemsToBeRemoved.insert(system.update);
471+
}
472+
if (system.postupdate)
473+
{
474+
postupdateSystemsToBeRemoved.insert(system.postupdate);
475+
// If system with a PostUpdate is marked for removal
476+
// mark all worker threads for removal.
477+
_needsCleanUp = true;
478+
}
479+
if (system.configure)
480+
{
481+
configureSystemsToBeRemoved.insert(system.configure);
482+
}
483+
if (system.configureParameters)
484+
{
485+
configureParametersSystemsToBeRemoved.insert(
486+
system.configureParameters);
487+
}
488+
}
489+
}
490+
491+
RemoveFromVectorIf(this->systemsReset,
492+
[&](const auto system) {
493+
if (resetSystemsToBeRemoved.count(system)) {
494+
return true;
495+
}
496+
return false;
497+
});
498+
RemoveFromVectorIf(this->systemsPreupdate,
499+
[&](const auto& system) {
500+
if (preupdateSystemsToBeRemoved.count(system)) {
501+
return true;
502+
}
503+
return false;
504+
});
505+
RemoveFromVectorIf(this->systemsUpdate,
506+
[&](const auto& system) {
507+
if (updateSystemsToBeRemoved.count(system)) {
508+
return true;
509+
}
510+
return false;
511+
});
512+
513+
RemoveFromVectorIf(this->systemsPostupdate,
514+
[&](const auto& system) {
515+
if (postupdateSystemsToBeRemoved.count(system)) {
516+
return true;
517+
}
518+
return false;
519+
});
520+
RemoveFromVectorIf(this->systemsConfigure,
521+
[&](const auto& system) {
522+
if (configureSystemsToBeRemoved.count(system)) {
523+
return true;
524+
}
525+
return false;
526+
});
527+
RemoveFromVectorIf(this->systemsConfigureParameters,
528+
[&](const auto& system) {
529+
if (configureParametersSystemsToBeRemoved.count(system)) {
530+
return true;
531+
}
532+
return false;
533+
});
534+
RemoveFromVectorIf(this->systems,
535+
[&](const SystemInternal& _arg) {
536+
return _ecm.IsMarkedForRemoval(_arg.parentEntity);
537+
});
538+
539+
std::lock_guard lock(this->pendingSystemsMutex);
540+
RemoveFromVectorIf(this->pendingSystems,
541+
[&](const SystemInternal& _system) {
542+
return _ecm.IsMarkedForRemoval(_system.parentEntity);
543+
});
544+
}

src/SystemManager.hh

+8
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ namespace gz
145145
/// \brief Process system messages and add systems to entities
146146
public: void ProcessPendingEntitySystems();
147147

148+
/// \brief Remove systems that are attached to removed entities
149+
/// \param[in] _entityCompMgr - ECM with entities marked for removal
150+
/// \param[out] _needsCleanUp - Set to true if a system with a
151+
/// PostUpdate was removed, and its thread needs to be terminated
152+
public: void ProcessRemovedEntities(
153+
const EntityComponentManager &_entityCompMgr,
154+
bool &_needsCleanUp);
155+
148156
/// \brief Implementation for AddSystem functions that takes an SDF
149157
/// element. This calls the AddSystemImpl that accepts an SDF Plugin.
150158
/// \param[in] _system Generic representation of a system.

src/SystemManager_TEST.cc

+65
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,71 @@ TEST(SystemManager, AddSystemEcm)
214214
EXPECT_EQ(1u, systemMgr.SystemsPostUpdate().size());
215215
}
216216

217+
/////////////////////////////////////////////////
218+
TEST(SystemManager, AddAndRemoveSystemEcm)
219+
{
220+
auto loader = std::make_shared<SystemLoader>();
221+
222+
auto ecm = EntityComponentManager();
223+
auto eventManager = EventManager();
224+
225+
auto paramRegistry = std::make_unique<
226+
gz::transport::parameters::ParametersRegistry>("SystemManager_TEST");
227+
SystemManager systemMgr(
228+
loader, &ecm, &eventManager, std::string(), paramRegistry.get());
229+
230+
EXPECT_EQ(0u, systemMgr.ActiveCount());
231+
EXPECT_EQ(0u, systemMgr.PendingCount());
232+
EXPECT_EQ(0u, systemMgr.TotalCount());
233+
EXPECT_EQ(0u, systemMgr.SystemsConfigure().size());
234+
EXPECT_EQ(0u, systemMgr.SystemsPreUpdate().size());
235+
EXPECT_EQ(0u, systemMgr.SystemsUpdate().size());
236+
EXPECT_EQ(0u, systemMgr.SystemsPostUpdate().size());
237+
238+
auto configSystem = std::make_shared<SystemWithConfigure>();
239+
systemMgr.AddSystem(configSystem, kNullEntity, nullptr);
240+
241+
auto entity = ecm.CreateEntity();
242+
243+
auto updateSystemWithChild = std::make_shared<SystemWithUpdates>();
244+
systemMgr.AddSystem(updateSystemWithChild, entity, nullptr);
245+
246+
// Configure called during AddSystem
247+
EXPECT_EQ(1, configSystem->configured);
248+
EXPECT_EQ(1, configSystem->configuredParameters);
249+
250+
EXPECT_EQ(0u, systemMgr.ActiveCount());
251+
EXPECT_EQ(2u, systemMgr.PendingCount());
252+
EXPECT_EQ(2u, systemMgr.TotalCount());
253+
EXPECT_EQ(0u, systemMgr.SystemsConfigure().size());
254+
EXPECT_EQ(0u, systemMgr.SystemsPreUpdate().size());
255+
EXPECT_EQ(0u, systemMgr.SystemsUpdate().size());
256+
EXPECT_EQ(0u, systemMgr.SystemsPostUpdate().size());
257+
258+
systemMgr.ActivatePendingSystems();
259+
EXPECT_EQ(2u, systemMgr.ActiveCount());
260+
EXPECT_EQ(0u, systemMgr.PendingCount());
261+
EXPECT_EQ(2u, systemMgr.TotalCount());
262+
EXPECT_EQ(1u, systemMgr.SystemsConfigure().size());
263+
EXPECT_EQ(1u, systemMgr.SystemsPreUpdate().size());
264+
EXPECT_EQ(1u, systemMgr.SystemsUpdate().size());
265+
EXPECT_EQ(1u, systemMgr.SystemsPostUpdate().size());
266+
267+
// Remove the entity
268+
ecm.RequestRemoveEntity(entity);
269+
bool needsCleanUp;
270+
systemMgr.ProcessRemovedEntities(ecm, needsCleanUp);
271+
272+
EXPECT_TRUE(needsCleanUp);
273+
EXPECT_EQ(1u, systemMgr.ActiveCount());
274+
EXPECT_EQ(0u, systemMgr.PendingCount());
275+
EXPECT_EQ(1u, systemMgr.TotalCount());
276+
EXPECT_EQ(1u, systemMgr.SystemsConfigure().size());
277+
EXPECT_EQ(0u, systemMgr.SystemsPreUpdate().size());
278+
EXPECT_EQ(0u, systemMgr.SystemsUpdate().size());
279+
EXPECT_EQ(0u, systemMgr.SystemsPostUpdate().size());
280+
}
281+
217282
/////////////////////////////////////////////////
218283
TEST(SystemManager, AddSystemWithInfo)
219284
{

0 commit comments

Comments
 (0)