Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
access_policy.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/core_pch.hpp>
4
9
10#include <algorithm>
11#include <span>
12#include <string_view>
13#include <type_traits>
14#include <utility>
15#include <vector>
16
17namespace helios::app {
18
19namespace details {
20
21/**
22 * @brief Component type information with ID and name.
23 * @details Stores both type ID and name for diagnostics.
24 */
27 std::string_view name;
28
29 constexpr bool operator<(const ComponentTypeInfo& other) const noexcept { return type_id < other.type_id; }
30 constexpr bool operator==(const ComponentTypeInfo& other) const noexcept { return type_id == other.type_id; }
31};
32
33/**
34 * @brief Resource type information with ID and name.
35 * @details Stores both type ID and name for diagnostics.
36 */
39 std::string_view name;
40
41 constexpr bool operator<(const ResourceTypeInfo& other) const noexcept { return type_id < other.type_id; }
42 constexpr bool operator==(const ResourceTypeInfo& other) const noexcept { return type_id == other.type_id; }
43};
44
45/**
46 * @brief Query descriptor for AccessPolicy.
47 * @details Stores component type IDs and names for a single query specification.
48 * Component types are kept sorted for efficient conflict detection.
49 */
51 std::vector<ComponentTypeInfo> read_components; // Kept sorted by type_id
52 std::vector<ComponentTypeInfo> write_components; // Kept sorted by type_id
53};
54
55/**
56 * @brief Checks if two sorted ranges have any common elements.
57 * @details Uses a merge-like algorithm for O(n + m) complexity.
58 */
59[[nodiscard]] constexpr bool HasIntersection(std::span<const ComponentTypeInfo> lhs,
60 std::span<const ComponentTypeInfo> rhs) noexcept {
61 auto it1 = lhs.begin();
62 auto it2 = rhs.begin();
63
64 while (it1 != lhs.end() && it2 != rhs.end()) {
65 if (it1->type_id < it2->type_id) {
66 ++it1;
67 } else if (it2->type_id < it1->type_id) {
68 ++it2;
69 } else {
70 return true; // Found common element
71 }
72 }
73
74 return false;
75}
76
77/**
78 * @brief Checks if any element from one range exists in another sorted range.
79 * @details For each element in lhs, performs binary search in rhs.
80 */
81[[nodiscard]] constexpr bool HasIntersectionBinarySearch(std::span<const ResourceTypeInfo> lhs,
82 std::span<const ResourceTypeInfo> rhs) noexcept {
83 // Optimize by iterating over smaller range
84 if (lhs.size() > rhs.size()) {
85 std::swap(lhs, rhs);
86 }
87
88 return std::ranges::any_of(lhs, [rhs](const auto& item) {
89 return std::ranges::binary_search(
90 rhs, item, [](const auto& info1, const auto& info2) { return info1.type_id < info2.type_id; });
91 });
92}
93
94} // namespace details
95
96/**
97 * @brief Declares data access requirements for a system at compile time.
98 * @details AccessPolicy is used to:
99 * - Declare which components a system will query
100 * - Declare which resources a system will read/write
101 * - Enable automatic scheduling and conflict detection
102 * - Validate runtime access through SystemContext
103 * @note All methods are constexpr to enable compile-time policy construction.
104 *
105 * @example
106 * @code
107 * static constexpr auto GetAccessPolicy() {
108 * return AccessPolicy()
109 * .Query<const Transform&, const MeshRenderer&>()
110 * .Query<Transform&, const SpriteRenderer&>()
111 * .ReadResources<Camera, RenderSettings>()
112 * .WriteResources<RenderQueue>(); // Write implies read access
113 * }
114 * @endcode
115 */
117public:
122
125
126 /**
127 * @brief Declares a query over component types.
128 * @details Adds a query specification to the policy. Multiple queries can be declared.
129 * Component access is classified per-type into read-only or writable sets.
130 * Component types are automatically sorted for efficient conflict detection.
131 * @tparam Components Component access types (e.g., const Position&, Velocity&)
132 * @return Updated AccessPolicy for method chaining
133 */
134 template <ecs::ComponentTrait... Components>
135 requires((ecs::ComponentTrait<Components> && ...) && utils::UniqueTypes<Components...>)
137
138 /**
139 * @brief Declares read-only access to resource types.
140 * @details Adds resource types that the system will read but not modify.
141 * Resources are kept sorted for efficient conflict detection.
142 * @note If resource is thread-safe it will be ignored.
143 * @tparam Resources Resource types to read
144 * @return Updated AccessPolicy for method chaining
145 */
146 template <ecs::ResourceTrait... Resources>
147 requires utils::UniqueTypes<Resources...>
149
150 /**
151 * @brief Declares write access to resource types.
152 * @details Adds resource types that the system will modify.
153 * Resources are kept sorted for efficient conflict detection.
154 * @note If resource is thread-safe it will be ignored.
155 * @tparam Resources Resource types to write
156 * @return Updated AccessPolicy for method chaining
157 */
158 template <ecs::ResourceTrait... Resources>
159 requires utils::UniqueTypes<Resources...>
161
162 /**
163 * @brief Checks if this policy has query conflict with another policy.
164 * @details Two policies conflict if:
165 * - One reads from a component that the other writes to
166 * - Both write to the same component
167 * - Both access the same resource with at least one write access
168 * @param other Other access policy to check against
169 * @return True if policies have a conflict, false otherwise
170 */
172
173 /**
174 * @brief Checks if this policy has resource conflict with another policy.
175 * @details Two policies conflict if both access the same resource with at least one write access.
176 * @param other Other access policy to check against
177 * @return True if policies have a conflict, false otherwise
178 */
180
181 /**
182 * @brief Checks if this policy conflicts with another policy.
183 * @details Checks for query and resource conflict.
184 * @param other Other access policy to check against
185 * @return True if policies conflict, false otherwise
186 */
190
191 /**
192 * @brief Checks if this policy has any queries.
193 * @return True if any queries are declared, false otherwise
194 */
195 [[nodiscard]] constexpr bool HasQueries() const noexcept { return !queries_.empty(); }
196
197 /**
198 * @brief Checks if this policy has any resource access.
199 * @return True if any resources are declared, false otherwise
200 */
201 [[nodiscard]] constexpr bool HasResources() const noexcept {
202 return !read_resources_.empty() || !write_resources_.empty();
203 }
204
205 /**
206 * @brief Gets all declared query descriptors.
207 * @return Span of query descriptors
208 */
209 [[nodiscard]] constexpr auto GetQueries() const noexcept -> std::span<const details::QueryDescriptor> {
210 return queries_;
211 }
212
213 /**
214 * @brief Gets all resource types declared for reading.
215 * @return Span of resource type info (sorted)
216 */
217 [[nodiscard]] constexpr auto GetReadResources() const noexcept -> std::span<const details::ResourceTypeInfo> {
218 return read_resources_;
219 }
220
221 /**
222 * @brief Gets all resource types declared for writing.
223 * @return Span of resource type info (sorted)
224 */
225 [[nodiscard]] constexpr auto GetWriteResources() const noexcept -> std::span<const details::ResourceTypeInfo> {
226 return write_resources_;
227 }
228
229 /**
230 * @brief Checks if this policy has the specified component type for reading.
231 * @param type_id Component type ID to check
232 * @return True if component is declared for reading in any query
233 */
234 [[nodiscard]] constexpr bool HasReadComponent(ecs::ComponentTypeId type_id) const noexcept {
235 for (const auto& query : queries_) {
236 auto matches_id = [type_id](const details::ComponentTypeInfo& info) { return info.type_id == type_id; };
237 if (std::ranges::any_of(query.read_components, matches_id)) {
238 return true;
239 }
240 }
241 return false;
242 }
243
244 /**
245 * @brief Checks if this policy has the specified component type for writing.
246 * @param type_id Component type ID to check
247 * @return True if component is declared for writing in any query
248 */
249 [[nodiscard]] constexpr bool HasWriteComponent(ecs::ComponentTypeId type_id) const noexcept {
250 for (const auto& query : queries_) {
251 auto matches_id = [type_id](const details::ComponentTypeInfo& info) { return info.type_id == type_id; };
252 if (std::ranges::any_of(query.write_components, matches_id)) {
253 return true;
254 }
255 }
256 return false;
257 }
258
259 /**
260 * @brief Checks if this policy has the specified resource type for reading.
261 * @param type_id Resource type ID to check
262 * @return True if resource is declared for reading
263 */
264 [[nodiscard]] constexpr bool HasReadResource(ecs::ResourceTypeId type_id) const noexcept {
265 auto matches_id = [type_id](const details::ResourceTypeInfo& info) { return info.type_id == type_id; };
266 return std::ranges::any_of(read_resources_, matches_id);
267 }
268
269 /**
270 * @brief Checks if this policy has the specified resource type for writing.
271 * @param type_id Resource type ID to check
272 * @return True if resource is declared for writing
273 */
274 [[nodiscard]] constexpr bool HasWriteResource(ecs::ResourceTypeId type_id) const noexcept {
275 auto matches_id = [type_id](const details::ResourceTypeInfo& info) { return info.type_id == type_id; };
276 return std::ranges::any_of(write_resources_, matches_id);
277 }
278
279private:
280 /**
281 * @brief Helper to insert a resource while maintaining sorted order.
282 * @details Checks for duplicates before inserting.
283 */
284 static constexpr void InsertSorted(std::vector<details::ResourceTypeInfo>& vec, details::ResourceTypeInfo info);
285
286 std::vector<details::QueryDescriptor> queries_;
287 std::vector<details::ResourceTypeInfo> read_resources_; // Kept sorted
288 std::vector<details::ResourceTypeInfo> write_resources_; // Kept sorted
289};
290
291template <ecs::ComponentTrait... Components>
292 requires((ecs::ComponentTrait<Components> && ...) && utils::UniqueTypes<Components...>)
293constexpr auto AccessPolicy::Query(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
295
296 // Populate read vs write component lists based on constness of the parameter type.
297 // For component parameter T:
298 // - If T is const (after removing reference), treat as read-only.
299 // - Otherwise treat as writable.
300 (
301 [&query]() {
302 using Component = std::remove_cvref_t<Components>;
304 constexpr std::string_view name = ecs::ComponentNameOf<Component>();
305 if constexpr (ecs::TagComponentTrait<Component>) {
306 return;
307 }
308 // Check if the original Components type (before removing cvref) is const
309 if constexpr (std::is_const_v<std::remove_reference_t<Components>>) {
310 query.read_components.push_back({id, name});
311 } else {
312 query.write_components.push_back({id, name});
313 }
314 }(),
315 ...);
316
317 // Sort component type lists for efficient conflict detection
318 auto cmp = [](const details::ComponentTypeInfo& lhs, const details::ComponentTypeInfo& rhs) {
319 return lhs.type_id < rhs.type_id;
320 };
321 std::ranges::sort(query.read_components, cmp);
322 std::ranges::sort(query.write_components, cmp);
323
324 self.queries_.push_back(std::move(query));
325 return std::forward<decltype(self)>(self);
326}
327
328template <ecs::ResourceTrait... Resources>
329 requires utils::UniqueTypes<Resources...>
330constexpr auto AccessPolicy::ReadResources(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
331 // Insert each resource that is not thread-safe while maintaining sorted order
332 (
333 [&self]() {
335 if constexpr (Resources::ThreadSafe()) {
337 "'{}' resource was declared in AccessPolicy::ReadResources, but will be ignored since it is "
338 "thread-safe.",
340 } else {
341 self.InsertSorted(self.read_resources_,
342 {ecs::ResourceTypeIdOf<Resources>(), ecs::ResourceNameOf<Resources>()});
343 }
344 } else {
345 self.InsertSorted(self.read_resources_,
346 {ecs::ResourceTypeIdOf<Resources>(), ecs::ResourceNameOf<Resources>()});
347 }
348 }(),
349 ...);
350
351 return std::forward<decltype(self)>(self);
352}
353
354template <ecs::ResourceTrait... Resources>
355 requires utils::UniqueTypes<Resources...>
356constexpr auto AccessPolicy::WriteResources(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
357 // Insert each resource that is not thread-safe while maintaining sorted order
358 (
359 [&self]() {
361 if constexpr (Resources::ThreadSafe()) {
363 "'{}' resource was declared in AccessPolicy::WriteResources, but will be ignored since it is "
364 "thread-safe.",
366 } else {
367 self.InsertSorted(self.write_resources_,
368 {ecs::ResourceTypeIdOf<Resources>(), ecs::ResourceNameOf<Resources>()});
369 }
370 } else {
371 self.InsertSorted(self.write_resources_,
372 {ecs::ResourceTypeIdOf<Resources>(), ecs::ResourceNameOf<Resources>()});
373 }
374 }(),
375 ...);
376
377 return std::forward<decltype(self)>(self);
378}
379
380constexpr bool AccessPolicy::HasQueryConflict(const AccessPolicy& other) const noexcept {
381 if (!HasQueries() || !other.HasQueries()) {
382 return false;
383 }
384
385 // Check component conflicts using sorted-range intersection: O(n + m)
386 // Conflict cases:
387 // - my.write intersects other.write (write-write)
388 // - my.write intersects other.read (write-read)
389 // - my.read intersects other.write (read-write)
390 for (const auto& my_query : queries_) {
391 for (const auto& other_query : other.queries_) {
392 // write-write
393 if (details::HasIntersection(my_query.write_components, other_query.write_components)) {
394 return true;
395 }
396
397 // my write vs other read
398 if (details::HasIntersection(my_query.write_components, other_query.read_components)) {
399 return true;
400 }
401
402 // my read vs other write
403 if (details::HasIntersection(my_query.read_components, other_query.write_components)) {
404 return true;
405 }
406 }
407 }
408
409 return false;
410}
411constexpr bool AccessPolicy::HasResourceConflict(const AccessPolicy& other) const noexcept {
412 if (!HasResources() || !other.HasResources()) {
413 return false;
414 }
415
416 // Write-write conflicts
417 if (!write_resources_.empty() && !other.write_resources_.empty() &&
418 details::HasIntersectionBinarySearch(write_resources_, other.write_resources_)) {
419 return true;
420 }
421
422 // Write-read conflicts (my write vs other read)
423 if (!write_resources_.empty() && !other.read_resources_.empty() &&
424 details::HasIntersectionBinarySearch(write_resources_, other.read_resources_)) {
425 return true;
426 }
427
428 // Read-write conflicts (my read vs other write)
429 if (!read_resources_.empty() && !other.write_resources_.empty() &&
430 details::HasIntersectionBinarySearch(read_resources_, other.write_resources_)) {
431 return true;
432 }
433
434 return false;
435}
436
437constexpr void AccessPolicy::InsertSorted(std::vector<details::ResourceTypeInfo>& vec, details::ResourceTypeInfo info) {
439 return lhs.type_id < rhs.type_id;
440 };
441 const auto pos = std::ranges::lower_bound(vec, info, cmp);
442 // Avoid duplicates
443 if (pos == vec.end() || pos->type_id != info.type_id) {
444 vec.insert(pos, info);
445 }
446}
447
448} // namespace helios::app
constexpr bool HasWriteResource(ecs::ResourceTypeId type_id) const noexcept
Checks if this policy has the specified resource type for writing.
constexpr bool HasWriteComponent(ecs::ComponentTypeId type_id) const noexcept
Checks if this policy has the specified component type for writing.
constexpr bool ConflictsWith(const AccessPolicy &other) const noexcept
Checks if this policy conflicts with another policy.
constexpr bool HasQueries() const noexcept
Checks if this policy has any queries.
constexpr auto GetWriteResources() const noexcept -> std::span< const details::ResourceTypeInfo >
Gets all resource types declared for writing.
constexpr bool HasResources() const noexcept
Checks if this policy has any resource access.
constexpr bool HasQueryConflict(const AccessPolicy &other) const noexcept
Checks if this policy has query conflict with another policy.
constexpr auto GetQueries() const noexcept -> std::span< const details::QueryDescriptor >
Gets all declared query descriptors.
constexpr bool HasReadResource(ecs::ResourceTypeId type_id) const noexcept
Checks if this policy has the specified resource type for reading.
constexpr auto Query(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Declares a query over component types.
constexpr bool HasResourceConflict(const AccessPolicy &other) const noexcept
Checks if this policy has resource conflict with another policy.
constexpr AccessPolicy() noexcept=default
constexpr auto ReadResources(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Declares read-only access to resource types.
constexpr auto GetReadResources() const noexcept -> std::span< const details::ResourceTypeInfo >
Gets all resource types declared for reading.
constexpr bool HasReadComponent(ecs::ComponentTypeId type_id) const noexcept
Checks if this policy has the specified component type for reading.
constexpr auto WriteResources(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Declares write access to resource types.
Concept to check if a type can be used as a component.
Definition component.hpp:39
Concept for valid resource types.
Definition resource.hpp:21
Concept for resources that provide thread-safety trait.
Definition resource.hpp:39
Concept for tag components (empty).
Definition component.hpp:54
#define HELIOS_INFO(...)
Definition logger.hpp:685
constexpr bool HasIntersectionBinarySearch(std::span< const ResourceTypeInfo > lhs, std::span< const ResourceTypeInfo > rhs) noexcept
Checks if any element from one range exists in another sorted range.
constexpr bool HasIntersection(std::span< const ComponentTypeInfo > lhs, std::span< const ComponentTypeInfo > rhs) noexcept
Checks if two sorted ranges have any common elements.
size_t ResourceTypeId
Type ID for resources.
Definition resource.hpp:53
size_t ComponentTypeId
Type ID for components.
BasicQuery< World, Allocator, Components... > Query
Type alias for query with mutable world access.
Definition query.hpp:2481
STL namespace.
Component type information with ID and name.
constexpr bool operator<(const ComponentTypeInfo &other) const noexcept
constexpr bool operator==(const ComponentTypeInfo &other) const noexcept
Query descriptor for AccessPolicy.
std::vector< ComponentTypeInfo > read_components
std::vector< ComponentTypeInfo > write_components
Resource type information with ID and name.
constexpr bool operator<(const ResourceTypeInfo &other) const noexcept
constexpr bool operator==(const ResourceTypeInfo &other) const noexcept