Skip to content

Commit 55a0f55

Browse files
arjo129iche033
authored andcommitted
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. Signed-off-by: Arjo Chakravarty <arjo@openrobotics.org>
1 parent 9891844 commit 55a0f55

7 files changed

+219
-7
lines changed

include/gz/sim/EntityComponentManager.hh

+3
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,9 @@ namespace ignition
836836
friend class GuiRunner;
837837
friend class SimulationRunner;
838838

839+
// Make SystemManager friend so it has access to removals
840+
friend class SystemManager;
841+
839842
// Make network managers friends so they have control over component
840843
// states. Like the runners, the managers are internal.
841844
friend class NetworkManagerPrimary;

src/SimulationRunner.cc

+8-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <algorithm>
2121

2222
#include <sdf/Root.hh>
23+
#include <vector>
2324

2425
#include "gz/common/Profiler.hh"
2526
#include "gz/sim/components/Model.hh"
@@ -483,12 +484,15 @@ void SimulationRunner::ProcessSystemQueue()
483484
{
484485
auto pending = this->systemMgr->PendingCount();
485486

486-
if (0 == pending)
487+
if (0 == pending && !this->threadsNeedCleanUp)
487488
return;
488489

489-
// If additional systems are to be added, stop the existing threads.
490+
// If additional systems are to be added or have been removed,
491+
// stop the existing threads.
490492
this->StopWorkerThreads();
491493

494+
this->threadsNeedCleanUp = false;
495+
492496
this->systemMgr->ActivatePendingSystems();
493497

494498
auto threadCount = this->systemMgr->SystemsPostUpdate().size() + 1u;
@@ -852,6 +856,8 @@ void SimulationRunner::Step(const UpdateInfo &_info)
852856
this->ProcessRecreateEntitiesCreate();
853857

854858
// Process entity removals.
859+
this->systemMgr->ProcessRemovedEntities(this->entityCompMgr,
860+
this->threadsNeedCleanUp);
855861
this->entityCompMgr.ProcessRemoveEntityRequests();
856862

857863
// Process components removals

src/SimulationRunner.hh

+3
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,9 @@ namespace ignition
532532
/// at the appropriate time.
533533
private: std::unique_ptr<msgs::WorldControlState> newWorldControlState;
534534

535+
/// \brief Set if we need to remove systems due to entity removal
536+
private: bool threadsNeedCleanUp;
537+
535538
friend class LevelManager;
536539
};
537540
}

src/SimulationRunner_TEST.cc

+11-5
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ void rootClockCb(const msgs::Clock &_msg)
105105
rootClockMsgs.push_back(_msg);
106106
}
107107

108-
109108
/////////////////////////////////////////////////
110109
TEST_P(SimulationRunnerTest, CreateEntities)
111110
{
@@ -1503,8 +1502,7 @@ TEST_P(SimulationRunnerTest,
15031502
EXPECT_TRUE(runner.EntityCompMgr().EntityHasComponentType(sphereEntity,
15041503
componentId)) << componentId;
15051504

1506-
// Remove entities that have plugin - this is not unloading or destroying
1507-
// the plugin though!
1505+
// Remove entities that have plugin
15081506
auto entityCount = runner.EntityCompMgr().EntityCount();
15091507
const_cast<EntityComponentManager &>(
15101508
runner.EntityCompMgr()).RequestRemoveEntity(boxEntity);
@@ -1552,8 +1550,16 @@ TEST_P(SimulationRunnerTest,
15521550
SimulationRunner runner(rootWithout.WorldByIndex(0), systemLoader,
15531551
serverConfig);
15541552

1555-
// 1 model plugin from SDF and 2 world plugins from config
1556-
ASSERT_EQ(3u, runner.SystemCount());
1553+
// 1 model plugin from SDF and 1 world plugin from config
1554+
// and 1 model plugin from theconfig
1555+
EXPECT_EQ(3u, runner.SystemCount());
1556+
runner.SetPaused(false);
1557+
runner.Run(1);
1558+
1559+
// Remove the model. Only 1 world plugin should remain.
1560+
EXPECT_TRUE(runner.RequestRemoveEntity("box"));
1561+
runner.Run(2);
1562+
EXPECT_EQ(1u, runner.SystemCount());
15571563
}
15581564

15591565
/////////////////////////////////////////////////

src/SystemManager.cc

+121
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"
@@ -115,7 +118,9 @@ size_t SystemManager::ActivatePendingSystems()
115118
this->systemsUpdate.push_back(system.update);
116119

117120
if (system.postupdate)
121+
{
118122
this->systemsPostupdate.push_back(system.postupdate);
123+
}
119124
}
120125

121126
this->pendingSystems.clear();
@@ -318,3 +323,119 @@ void SystemManager::ProcessPendingEntitySystems()
318323
}
319324
this->systemsToAdd.clear();
320325
}
326+
327+
template <typename T>
328+
struct identity
329+
{
330+
using type = T;
331+
};
332+
333+
//////////////////////////////////////////////////
334+
/// TODO(arjo) - When we move to C++20 we can just use erase_if
335+
/// Removes all items that match the given predicate.
336+
/// This function runs in O(n) time and uses memory in-place
337+
template<typename Tp>
338+
void RemoveFromVectorIf(std::vector<Tp>& vec,
339+
typename identity<std::function<bool(const Tp&)>>::type pred)
340+
{
341+
vec.erase(std::remove_if(vec.begin(), vec.end(), pred), vec.end());
342+
}
343+
344+
//////////////////////////////////////////////////
345+
void SystemManager::ProcessRemovedEntities(
346+
const EntityComponentManager &_ecm,
347+
bool &_needsCleanUp)
348+
{
349+
// Note: This function has O(n) time when an entity is removed
350+
// where n is number of systems. Ideally we would only iterate
351+
// over entities marked for removal but that would involve having
352+
// a key value map. This can be marked as a future improvement.
353+
if (!_ecm.HasEntitiesMarkedForRemoval())
354+
{
355+
return;
356+
}
357+
358+
std::unordered_set<ISystemPreUpdate *> preupdateSystemsToBeRemoved;
359+
std::unordered_set<ISystemUpdate *> updateSystemsToBeRemoved;
360+
std::unordered_set<ISystemPostUpdate *> postupdateSystemsToBeRemoved;
361+
std::unordered_set<ISystemConfigure *> configureSystemsToBeRemoved;
362+
std::unordered_set<ISystemConfigureParameters *>
363+
configureParametersSystemsToBeRemoved;
364+
for (const auto &system : this->systems)
365+
{
366+
if (_ecm.IsMarkedForRemoval(system.parentEntity))
367+
{
368+
if (system.preupdate)
369+
{
370+
preupdateSystemsToBeRemoved.insert(system.preupdate);
371+
}
372+
if (system.update)
373+
{
374+
updateSystemsToBeRemoved.insert(system.update);
375+
}
376+
if (system.postupdate)
377+
{
378+
postupdateSystemsToBeRemoved.insert(system.postupdate);
379+
// If system with a PostUpdate is marked for removal
380+
// mark all worker threads for removal.
381+
_needsCleanUp = true;
382+
}
383+
if (system.configure)
384+
{
385+
configureSystemsToBeRemoved.insert(system.configure);
386+
}
387+
if (system.configureParameters)
388+
{
389+
configureParametersSystemsToBeRemoved.insert(
390+
system.configureParameters);
391+
}
392+
}
393+
}
394+
395+
RemoveFromVectorIf(this->systemsPreupdate,
396+
[&](const auto& system) {
397+
if (preupdateSystemsToBeRemoved.count(system)) {
398+
return true;
399+
}
400+
return false;
401+
});
402+
RemoveFromVectorIf(this->systemsUpdate,
403+
[&](const auto& system) {
404+
if (updateSystemsToBeRemoved.count(system)) {
405+
return true;
406+
}
407+
return false;
408+
});
409+
410+
RemoveFromVectorIf(this->systemsPostupdate,
411+
[&](const auto& system) {
412+
if (postupdateSystemsToBeRemoved.count(system)) {
413+
return true;
414+
}
415+
return false;
416+
});
417+
RemoveFromVectorIf(this->systemsConfigure,
418+
[&](const auto& system) {
419+
if (configureSystemsToBeRemoved.count(system)) {
420+
return true;
421+
}
422+
return false;
423+
});
424+
RemoveFromVectorIf(this->systemsConfigureParameters,
425+
[&](const auto& system) {
426+
if (configureParametersSystemsToBeRemoved.count(system)) {
427+
return true;
428+
}
429+
return false;
430+
});
431+
RemoveFromVectorIf(this->systems,
432+
[&](const SystemInternal& _arg) {
433+
return _ecm.IsMarkedForRemoval(_arg.parentEntity);
434+
});
435+
436+
std::lock_guard lock(this->pendingSystemsMutex);
437+
RemoveFromVectorIf(this->pendingSystems,
438+
[&](const SystemInternal& _system) {
439+
return _ecm.IsMarkedForRemoval(_system.parentEntity);
440+
});
441+
}

src/SystemManager.hh

+8
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ namespace ignition
127127
/// \brief Process system messages and add systems to entities
128128
public: void ProcessPendingEntitySystems();
129129

130+
/// \brief Remove systems that are attached to removed entities
131+
/// \param[in] _entityCompMgr - ECM with entities marked for removal
132+
/// \param[out] _needsCleanUp - Set to true if a system with a
133+
/// PostUpdate was removed, and its thread needs to be terminated
134+
public: void ProcessRemovedEntities(
135+
const EntityComponentManager &_entityCompMgr,
136+
bool &_needsCleanUp);
137+
130138
/// \brief Implementation for AddSystem functions that takes an SDF
131139
/// element. This calls the AddSystemImpl that accepts an SDF Plugin.
132140
/// \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)