• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Razakhel / RaZ / 23162462833

16 Mar 2026 12:55PM UTC coverage: 74.498% (-0.006%) from 74.504%
23162462833

push

github

Razakhel
[World] Entity removal is now delayed to the next world update

- The previous behavior could easily produce crashes when removing entities from a system run, as it would break iteration and access to destroyed entities

11 of 13 new or added lines in 2 files covered. (84.62%)

1 existing line in 1 file now uncovered.

8644 of 11603 relevant lines covered (74.5%)

1704.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.65
/src/RaZ/World.cpp
1
#include "RaZ/World.hpp"
2

3
#include "tracy/Tracy.hpp"
4

5
namespace Raz {
6

7
Entity& World::addEntity(bool enabled) {
70✔
8
  m_entities.emplace_back(Entity::create(m_maxEntityIndex++, enabled));
70✔
9
  m_activeEntityCount += enabled;
70✔
10

11
  return *m_entities.back();
70✔
12
}
13

14
bool World::update(const FrameTimeInfo& timeInfo) {
70✔
15
  ZoneScopedN("World::update");
16

17
  refresh();
70✔
18

19
  for (std::size_t systemIndex = 0; systemIndex < m_systems.size(); ++systemIndex) {
267✔
20
    if (!m_activeSystems[systemIndex])
199✔
21
      continue;
135✔
22

23
    const bool isSystemActive = m_systems[systemIndex]->update(timeInfo);
64✔
24

25
    if (!isSystemActive)
63✔
26
      m_activeSystems.setBit(systemIndex, false);
1✔
27
  }
28

29
  return !m_activeSystems.isEmpty();
68✔
30
}
31

32
void World::refresh() {
75✔
33
  ZoneScopedN("World::refresh");
34

35
  cleanEntities();
75✔
36

37
  if (m_entities.empty())
74✔
38
    return;
2✔
39

40
  sortEntities();
72✔
41

42
  for (std::size_t entityIndex = 0; entityIndex < m_activeEntityCount; ++entityIndex) {
231✔
43
    const EntityPtr& entity = m_entities[entityIndex];
159✔
44

45
    if (!entity->isEnabled())
159✔
46
      continue;
×
47

48
    for (std::size_t systemIndex = 0; systemIndex < m_systems.size(); ++systemIndex) {
613✔
49
      const SystemPtr& system = m_systems[systemIndex];
454✔
50

51
      if (system == nullptr || !m_activeSystems[systemIndex])
454✔
52
        continue;
311✔
53

54
      const Bitset matchingComponents = system->getAcceptedComponents() & entity->getEnabledComponents();
143✔
55

56
      // If the system does not contain the entity, check if it should (if it possesses the accepted components); if yes, link it
57
      // Else, if the system contains the entity but should not, unlink it
58
      if (!system->containsEntity(*entity)) {
143✔
59
        if (!matchingComponents.isEmpty())
63✔
60
          system->linkEntity(entity);
57✔
61
      } else {
62
        if (matchingComponents.isEmpty())
80✔
63
          system->unlinkEntity(entity);
1✔
64
      }
65
    }
143✔
66
  }
67
}
68

69
void World::destroy() {
37✔
70
  ZoneScopedN("World::destroy");
71

72
  // Entities must be released before the systems, since their destruction may depend on those
73
  m_entities.clear();
37✔
74
  m_activeEntityCount = 0;
37✔
75
  m_maxEntityIndex    = 0;
37✔
76

77
  // This means that no entity must be used in any system destructor, since they will all be invalid
78
  // Their list is thus cleared to avoid any invalid usage
79
  for (const SystemPtr& system : m_systems) {
125✔
80
    if (system)
88✔
81
      system->m_entities.clear();
34✔
82
  }
83

84
  m_systems.clear();
37✔
85
  m_activeSystems.clear();
37✔
86
}
37✔
87

88
void World::sortEntities() {
72✔
89
  ZoneScopedN("World::sortEntities");
90

91
  // Reorganizing the entities, swapping enabled & disabled ones so that the enabled ones are in front
92
  auto firstEntity = m_entities.begin();
72✔
93
  auto lastEntity  = m_entities.end() - 1;
72✔
94

95
  while (firstEntity != lastEntity) {
162✔
96
    // Iterating from the beginning to the end, trying to find a disabled entity
97
    if ((*firstEntity)->isEnabled()) {
90✔
98
      ++firstEntity;
87✔
99
      continue;
87✔
100
    }
101

102
    // Iterating from the end to the beginning, trying to find an enabled entity
103
    while (firstEntity != lastEntity && (*lastEntity == nullptr || !(*lastEntity)->isEnabled()))
4✔
104
      --lastEntity;
1✔
105

106
    // If both iterators are equal to each other, the list is sorted
107
    if (firstEntity == lastEntity)
3✔
108
      break;
×
109

110
    std::swap(*firstEntity, *lastEntity);
3✔
111
    --lastEntity;
3✔
112
  }
113

114
  m_activeEntityCount = static_cast<std::size_t>(std::distance(m_entities.begin(), lastEntity) + 1);
72✔
115
}
72✔
116

117
void World::cleanEntities() {
75✔
118
  for (const Entity* entity : m_entitiesToRemove) {
78✔
119
    const auto entityIter = std::ranges::find_if(m_entities, [entity] (const EntityPtr& entityPtr) noexcept {
4✔
120
      return (entity == entityPtr.get());
7✔
121
    });
122

123
    if (entityIter == m_entities.end())
4✔
124
      throw std::invalid_argument("[World] The entity to be removed isn't owned by this world");
1✔
125

126
    for (const SystemPtr& system : m_systems)
3✔
NEW
127
      system->unlinkEntity(*entityIter);
×
128

129
    m_entities.erase(entityIter);
3✔
130
  }
131

132
  m_entitiesToRemove.clear();
74✔
133
}
74✔
134

135
} // namespace Raz
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc