Skip to main content

View.ixx File

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

Included Headers

#include <tuple> #include <vector> #include <functional> #include <type_traits> #include <helios.engine.core.data.EntityId> #include <helios.engine.ecs.EntityHandle> #include <helios.engine.ecs.GameObject> #include <helios.engine.ecs.EntityManager> #include <helios.engine.ecs.SparseSet>

Namespaces Index

namespacehelios
namespaceengine

Main engine module aggregating core infrastructure and game systems. More...

namespaceecs

Core Entity-Component-System architecture. More...

Classes Index

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

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.