Skip to main content

ecs/View.ixx File

Lightweight view for iterating entities with specific components. More...

Included Headers

#include <tuple> #include <vector> #include <functional> #include <helios.ecs.Entity> #include <helios.ecs.EntityManager> #include <helios.ecs.types.TypeDefs> #include <helios.ecs.types.EntityHandle> #include <helios.ecs.SparseSet>

Namespaces Index

namespacehelios
namespaceecs

Generic, reusable ECS primitives. More...

Classes Index

classView<TEntityManager, Components>

A view class to iterate over entities having specific components. More...

structIterator

Forward iterator for View traversal. More...

Description

Lightweight view for iterating entities with specific components.

File Listing

The file content with the documentation metadata removed is:

1/**
2 * @file View.ixx
3 * @brief Lightweight view for iterating entities with specific components.
4 */
5module;
6
7#include <tuple>
8#include <vector>
9#include <functional>
10
11export module helios.ecs.View;
12
13import helios.ecs.SparseSet;
14import helios.ecs.types.TypeDefs;
15import helios.ecs.EntityManager;
16import helios.ecs.Entity;
17import helios.ecs.types.EntityHandle;
18
19using namespace helios::ecs::types;
20export namespace helios::ecs {
21
22 /**
23 * @brief A view class to iterate over entities having specific components.
24 *
25 * The View acts as a lightweight iterator over the SparseSets of the requested
26 * components. It uses the first component type (Lead) as the primary iterator
27 * and cross-references existence in other sets.
28 *
29 * ## Usage
30 *
31 * ```cpp
32 * for (auto [entity, transform, velocity, active] : world.view<
33 * GameHandle,
34 * TransformComponent,
35 * VelocityComponent,
36 * Active
37 * >().whereEnabled()) {
38 * // Process entity
39 * }
40 * ```
41 *
42 * @tparam TEntityManager The concrete `EntityManager` specialisation to
43 * iterate over. Determines the handle type and
44 * component storage used.
45 * @tparam Components The component types to query for.
46 *
47 * @see EntityManager
48 * @see SparseSet
49 * @see TypedHandleWorld
50 */
51 template<typename TEntityManager, typename... Components>
52 class View {
53
54 private:
55 TEntityManager* em_;
56
57 /**
58 * @brief Pointers to the SparseSets of the included components.
59 */
60 std::tuple<SparseSet<Components>*... > includeSets_;
61
62 /**
63 * @brief List of exclusion predicates.
64 * Stores functions that return true if an entity should be EXCLUDED.
65 * Operates on EntityId (index) because the SparseSet uses it internally.
66 */
67 std::vector<std::function<bool(EntityId)>> excludeChecks_;
68
69 /**
70 * @brief Flag to filter only enabled components.
71 */
72 bool filterEnabledOnly_ = false;
73
74 bool filterActiveOnly_ = false;
75
76 public:
77 /**
78 * @brief Constructs the view and retrieves the necessary component sets.
79 *
80 * @param em Pointer to the EntityManager to retrieve sets and construct Entities.
81 */
82 explicit View(TEntityManager* em) : em_(em) {
83 // Retrieve pointers to the specific component sets immediately.
84 includeSets_ = std::make_tuple(em_->getSparseSet<Components>()...);
85 };
86
87 /**
88 * @brief Excludes entities that have a specific component.
89 *
90 * @details Entities possessing the specified component type will be
91 * skipped during iteration. Multiple exclusions can be chained.
92 *
93 * ```cpp
94 * // Skip entities with Shield or Invincible
95 * for (auto [e, health] : world->view<HealthComponent>()
96 * .exclude<ShieldComponent>()
97 * .exclude<InvincibleComponent>()) {
98 * // Only vulnerable entities
99 * }
100 * ```
101 *
102 * @tparam T The component type to exclude.
103 *
104 * @return Reference to this View for method chaining.
105 */
106 template<typename T>
108 auto* set = em_->getSparseSet<T>();
109
110 if (set) {
111 excludeChecks_.emplace_back([set](EntityId entityId) {
112 return set->contains(entityId);
113 });
114 }
115 return *this;
116 }
117
118 [[nodiscard]] bool empty() {
119 auto* leadSet = std::get<0>(includeSets_);
120 if (!leadSet) {
121 return true;
122 }
123 return begin() == end();
124 }
125
126 /**
127 * @brief Filters to only include entities with enabled components.
128 *
129 * @details Components must implement `isEnabled()` returning bool.
130 * Components without this method are assumed to be enabled.
131 *
132 * @return Reference to this View for method chaining.
133 */
135 filterEnabledOnly_ = true;
136 return *this;
137 }
138
139 /**
140 * @brief Forward iterator for View traversal.
141 *
142 * @details Uses the first component type as the "lead" iterator and
143 * validates each entity against all include/exclude/enabled criteria
144 * before yielding.
145 */
146 struct Iterator {
147
149
150 /**
151 * @brief The first component type determines iteration order.
152 */
153 using LeadComponent = std::tuple_element_t<0, std::tuple<Components...>>;
154
155 /**
156 * @brief Iterator type from the lead component's SparseSet.
157 */
159
162 const View* view_;
163
164 /**
165 * @brief Default constructor creating an invalid iterator.
166 */
167 Iterator() = default;
168
169 /**
170 * @brief Constructs an iterator with the given range and view.
171 *
172 * @param current Iterator to the current position.
173 * @param end Iterator to the end position.
174 * @param view Pointer to the owning View for filter access.
175 */
176 Iterator(LeadIterator current, LeadIterator end, const View* view)
177 : current_(current), end_(end), view_(view) {}
178
179 /**
180 * @brief Validates if the current entity matches all filter criteria.
181 *
182 * @details Performs the following checks in order:
183 * 1 Entity validity in the registry
184 * 2 Include check - entity has all required components
185 * 3 Exclude check - entity has none of the excluded components
186 * 4 Enabled check - all components pass isEnabled() (if filtered)
187 *
188 * @return True if the entity passes all checks, false otherwise.
189 */
190 [[nodiscard]] bool isValid() const {
191 if (current_ == end_) {
192 return true;
193 }
194
195 // 1 Get Entity ID (from the Lead Iterator)
196 EntityId entityId = current_.entityId();
197
198 if (!view_->em_->isValid(entityId)) {
199 return false;
200 }
201
202 // 2 INCLUDE CHECK (Do we have all other required components?)
203 // We iterate over the tuple of sets and check 'contains' for each.
204 bool hasAllIncludes = std::apply([entityId](auto*... sets) {
205 return ((sets && sets->contains(entityId)) && ...);
206 }, view_->includeSets_);
207
208 if (!hasAllIncludes) {
209 return false;
210 }
211
212 // 3 EXCLUDE CHECK (Must NOT be present)
213 for (const auto& excludeCheck : view_->excludeChecks_) {
214 if (excludeCheck(entityId)) {
215 return false; // If check returns true (has component), the entity is invalid.
216 }
217 }
218
219 // 4 ENABLED CHECK (State)
220 if (view_->filterEnabledOnly_) {
221
222 // SFINAE Helper Lambda: Checks if .isEnabled() exists.
223 auto isComponentEnabled = [](const auto& comp) -> bool {
224 if constexpr (requires { comp.isEnabled(); }) {
225 return comp.isEnabled();
226 } else {
227 return true; // Assume enabled if method is missing.
228 }
229 };
230
231 // Check the Lead component (*current_ returns the component reference)
232 if (!isComponentEnabled(*current_)) {
233 return false;
234 }
235
236 // Check all other included components
237 bool allEnabled = std::apply([&](auto*... sets) {
238 // sets->get(id) returns a pointer, *ptr gives the reference.
239 return (isComponentEnabled(*sets->get(entityId)) && ...);
240 }, view_->includeSets_);
241
242 if (!allEnabled) {
243 return false;
244 }
245 }
246
247 return true;
248 }
249
250 /**
251 * @brief Advances to the next valid entity.
252 *
253 * @details Increments the underlying iterator and skips invalid
254 * entities until a valid one is found or end is reached.
255 */
256 void advance() {
257 do {
258 ++current_;
259 } while (current_ != end_ && !isValid());
260 }
261
262 /**
263 * @brief Pre-increment operator.
264 *
265 * @return Reference to this iterator after advancing.
266 */
267 Iterator& operator++() noexcept {
268 advance();
269 return *this;
270 }
271
272 /**
273 * @brief Inequality comparison.
274 *
275 * @param other The iterator to compare against.
276 *
277 * @return True if iterators point to different positions.
278 */
279 bool operator!=(const Iterator& other) const noexcept {
280 return current_ != other.current_;
281 }
282
283 /**
284 * @brief Dereference operator.
285 *
286 * @return A tuple containing {GameObject, Component*...}.
287 *
288 * @note Returns by value to support C++17 Structured Binding (auto [go, comp] : view).
289 */
290 [[nodiscard]] auto operator*() const {
291 EntityId entityId = current_.entityId();
292
293 auto handle = view_->em_->handle(entityId);
294
295
296 return std::tuple_cat(
297 std::make_tuple(Entity_type(handle, view_->em_)),
298 std::apply([entityId](auto*... sets) {
299 return std::make_tuple(sets->get(entityId)...);
300 }, view_->includeSets_)
301 );
302 }
303
304 [[nodiscard]] bool operator==(const Iterator& other) const noexcept {
305 return current_ == other.current_;
306 }
307 };
308
309 /**
310 * @brief Returns an iterator to the first valid entity.
311 *
312 * @details Uses the first component type's SparseSet as the lead.
313 * If the first entity is invalid, advances to the next valid one.
314 *
315 * @return Iterator to the first valid entity, or end() if none found.
316 */
317 [[nodiscard]] Iterator begin() {
318 auto* leadSet = std::get<0>(includeSets_);
319
320 if (!leadSet) {
321 return Iterator{};
322 }
323
324 Iterator it{leadSet->begin(), leadSet->end(), this};
325
326 if (!it.isValid()) {
327 it.advance();
328 }
329
330 return it;
331 }
332
333 /**
334 * @brief Returns an iterator to the end (past the last entity).
335 *
336 * @return End iterator for comparison.
337 */
338 [[nodiscard]] Iterator end() {
339 auto* leadSet = std::get<0>(includeSets_);
340
341 if (!leadSet) {
342 return Iterator{};
343 }
344
345 return Iterator{leadSet->end(), leadSet->end(), this};
346 }
347
348 };
349}

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.