Flecs v3.2
A fast entity component system (ECS) for C & C++
Loading...
Searching...
No Matches
invoker.hpp
Go to the documentation of this file.
1
6#pragma once
7
8namespace flecs
9{
10
11namespace _
12{
13
14// Binding ctx for component hooks
16 void *on_add = nullptr;
17 void *on_remove = nullptr;
18 void *on_set = nullptr;
19 ecs_ctx_free_t free_on_add = nullptr;
20 ecs_ctx_free_t free_on_remove = nullptr;
21 ecs_ctx_free_t free_on_set = nullptr;
22
24 if (on_add && free_on_add) {
25 free_on_add(on_add);
26 }
27 if (on_remove && free_on_remove) {
28 free_on_remove(on_remove);
29 }
30 if (on_set && free_on_set) {
31 free_on_set(on_set);
32 }
33 }
34};
35
36// Utility to convert template argument pack to array of term ptrs
37struct term_ptr {
38 void *ptr;
39 bool is_ref;
40};
41
42template <typename ... Components>
43struct term_ptrs {
44 using array = flecs::array<_::term_ptr, sizeof...(Components)>;
45
46 bool populate(const ecs_iter_t *iter) {
47 return populate(iter, 0, static_cast<
48 remove_reference_t<
49 remove_pointer_t<Components>>
50 *>(nullptr)...);
51 }
52
53 array m_terms;
54
55private:
56 /* Populate terms array without checking for references */
57 bool populate(const ecs_iter_t*, size_t) { return false; }
58
59 template <typename T, typename... Targs>
60 bool populate(const ecs_iter_t *iter, size_t index, T, Targs... comps) {
61 m_terms[index].ptr = iter->ptrs[index];
62 bool is_ref = iter->sources && iter->sources[index] != 0;
63 m_terms[index].is_ref = is_ref;
64 is_ref |= populate(iter, index + 1, comps ...);
65 return is_ref;
66 }
67};
68
69struct invoker { };
70
71// Template that figures out from the template parameters of a query/system
72// how to pass the value to the each callback
73template <typename T, typename = int>
74struct each_column { };
75
76// Base class
78 each_column_base(const _::term_ptr& term, size_t row)
79 : m_term(term), m_row(row) { }
80
81protected:
82 const _::term_ptr& m_term;
83 size_t m_row;
84};
85
86// If type is not a pointer, return a reference to the type (default case)
87template <typename T>
88struct each_column<T, if_t< !is_pointer<T>::value &&
89 !is_empty<actual_type_t<T>>::value && is_actual<T>::value > >
91{
92 each_column(const _::term_ptr& term, size_t row)
93 : each_column_base(term, row) { }
94
95 T& get_row() {
96 return static_cast<T*>(this->m_term.ptr)[this->m_row];
97 }
98};
99
100// If argument type is not the same as actual component type, return by value.
101// This requires that the actual type can be converted to the type.
102// A typical scenario where this happens is when using flecs::pair types.
103template <typename T>
104struct each_column<T, if_t< !is_pointer<T>::value &&
105 !is_empty<actual_type_t<T>>::value && !is_actual<T>::value> >
107{
108 each_column(const _::term_ptr& term, size_t row)
109 : each_column_base(term, row) { }
110
111 T get_row() {
112 return static_cast<actual_type_t<T>*>(this->m_term.ptr)[this->m_row];
113 }
114};
115
116
117// If type is empty (indicating a tag) the query will pass a nullptr. To avoid
118// returning nullptr to reference arguments, return a temporary value.
119template <typename T>
120struct each_column<T, if_t< is_empty<actual_type_t<T>>::value &&
121 !is_pointer<T>::value > >
123{
124 each_column(const _::term_ptr& term, size_t row)
125 : each_column_base(term, row) { }
126
127 T get_row() {
128 return actual_type_t<T>();
129 }
130};
131
132
133// If type is a pointer (indicating an optional value) return the type as is
134template <typename T>
135struct each_column<T, if_t< is_pointer<T>::value &&
136 !is_empty<actual_type_t<T>>::value > >
138{
139 each_column(const _::term_ptr& term, size_t row)
140 : each_column_base(term, row) { }
141
142 T get_row() {
143 if (this->m_term.ptr) {
144 return &static_cast<actual_type_t<T>>(this->m_term.ptr)[this->m_row];
145 } else {
146 // optional argument doesn't hava a value
147 return nullptr;
148 }
149 }
150};
151
152// If the query contains component references to other entities, check if the
153// current argument is one.
154template <typename T, typename = int>
155struct each_ref_column : public each_column<T> {
156 each_ref_column(const _::term_ptr& term, size_t row)
157 : each_column<T>(term, row) {
158
159 if (term.is_ref) {
160 // If this is a reference, set the row to 0 as a ref always is a
161 // single value, not an array. This prevents the application from
162 // having to do an if-check on whether the column is owned.
163 //
164 // This check only happens when the current table being iterated
165 // over caused the query to match a reference. The check is
166 // performed once per iterated table.
167 this->m_row = 0;
168 }
169 }
170};
171
172template <typename Func, typename ... Components>
173struct each_invoker : public invoker {
174 // If the number of arguments in the function signature is one more than the
175 // number of components in the query, an extra entity arg is required.
176 static constexpr bool PassEntity =
177 (sizeof...(Components) + 1) == (arity<Func>::value);
178
179 // If the number of arguments in the function is two more than the number of
180 // components in the query, extra iter + index arguments are required.
181 static constexpr bool PassIter =
182 (sizeof...(Components) + 2) == (arity<Func>::value);
183
184 static_assert(arity<Func>::value > 0,
185 "each() must have at least one argument");
186
187 using Terms = typename term_ptrs<Components ...>::array;
188
189 template < if_not_t< is_same< decay_t<Func>, decay_t<Func>& >::value > = 0>
190 explicit each_invoker(Func&& func) noexcept
191 : m_func(FLECS_MOV(func)) { }
192
193 explicit each_invoker(const Func& func) noexcept
194 : m_func(func) { }
195
196 // Invoke object directly. This operation is useful when the calling
197 // function has just constructed the invoker, such as what happens when
198 // iterating a query.
199 void invoke(ecs_iter_t *iter) const {
200 term_ptrs<Components...> terms;
201
202 if (terms.populate(iter)) {
203 invoke_callback< each_ref_column >(iter, m_func, 0, terms.m_terms);
204 } else {
205 invoke_callback< each_column >(iter, m_func, 0, terms.m_terms);
206 }
207 }
208
209 // Static function that can be used as callback for systems/triggers
210 static void run(ecs_iter_t *iter) {
211 auto self = static_cast<const each_invoker*>(iter->binding_ctx);
212 ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL);
213 self->invoke(iter);
214 }
215
216 // Static function to call for component on_add hook
217 static void run_add(ecs_iter_t *iter) {
218 component_binding_ctx *ctx = reinterpret_cast<component_binding_ctx*>(
219 iter->binding_ctx);
220 iter->binding_ctx = ctx->on_add;
221 run(iter);
222 }
223
224 // Static function to call for component on_remove hook
225 static void run_remove(ecs_iter_t *iter) {
226 component_binding_ctx *ctx = reinterpret_cast<component_binding_ctx*>(
227 iter->binding_ctx);
228 iter->binding_ctx = ctx->on_remove;
229 run(iter);
230 }
231
232 // Static function to call for component on_set hook
233 static void run_set(ecs_iter_t *iter) {
234 component_binding_ctx *ctx = reinterpret_cast<component_binding_ctx*>(
235 iter->binding_ctx);
236 iter->binding_ctx = ctx->on_set;
237 run(iter);
238 }
239
240 // Each invokers always use instanced iterators
241 static bool instanced() {
242 return true;
243 }
244
245private:
246 // Number of function arguments is one more than number of components, pass
247 // entity as argument.
248 template <template<typename X, typename = int> class ColumnType,
249 typename... Args, if_t<
250 sizeof...(Components) == sizeof...(Args) && PassEntity> = 0>
251 static void invoke_callback(
252 ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps)
253 {
254 ECS_TABLE_LOCK(iter->world, iter->table);
255
256 ecs_world_t *world = iter->world;
257 size_t count = static_cast<size_t>(iter->count);
258
259 ecs_assert(count > 0, ECS_INVALID_OPERATION,
260 "no entities returned, use each() without flecs::entity argument");
261
262 for (size_t i = 0; i < count; i ++) {
263 func(flecs::entity(world, iter->entities[i]),
264 (ColumnType< remove_reference_t<Components> >(comps, i)
265 .get_row())...);
266 }
267
268 ECS_TABLE_UNLOCK(iter->world, iter->table);
269 }
270
271 // Number of function arguments is two more than number of components, pass
272 // iter + index as argument.
273 template <template<typename X, typename = int> class ColumnType,
274 typename... Args, int Enabled = PassIter, if_t<
275 sizeof...(Components) == sizeof...(Args) && Enabled> = 0>
276 static void invoke_callback(
277 ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps)
278 {
279 size_t count = static_cast<size_t>(iter->count);
280 if (count == 0) {
281 // If query has no This terms, count can be 0. Since each does not
282 // have an entity parameter, just pass through components
283 count = 1;
284 }
285
286 flecs::iter it(iter);
287
288 ECS_TABLE_LOCK(iter->world, iter->table);
289
290 for (size_t i = 0; i < count; i ++) {
291 func(it, i, (ColumnType< remove_reference_t<Components> >(comps, i)
292 .get_row())...);
293 }
294
295 ECS_TABLE_UNLOCK(iter->world, iter->table);
296 }
297
298 // Number of function arguments is equal to number of components, no entity
299 template <template<typename X, typename = int> class ColumnType,
300 typename... Args, if_t<
301 sizeof...(Components) == sizeof...(Args) && !PassEntity && !PassIter> = 0>
302 static void invoke_callback(
303 ecs_iter_t *iter, const Func& func, size_t, Terms&, Args... comps)
304 {
305 size_t count = static_cast<size_t>(iter->count);
306 if (count == 0) {
307 // If query has no This terms, count can be 0. Since each does not
308 // have an entity parameter, just pass through components
309 count = 1;
310 }
311
312 flecs::iter it(iter);
313
314 ECS_TABLE_LOCK(iter->world, iter->table);
315
316 for (size_t i = 0; i < count; i ++) {
317 func( (ColumnType< remove_reference_t<Components> >(comps, i)
318 .get_row())...);
319 }
320
321 ECS_TABLE_UNLOCK(iter->world, iter->table);
322 }
323
324 template <template<typename X, typename = int> class ColumnType,
325 typename... Args, if_t< sizeof...(Components) != sizeof...(Args) > = 0>
326 static void invoke_callback(ecs_iter_t *iter, const Func& func,
327 size_t index, Terms& columns, Args... comps)
328 {
329 invoke_callback<ColumnType>(
330 iter, func, index + 1, columns, comps..., columns[index]);
331 }
332
333 Func m_func;
334};
335
336
340
341template <typename Func, typename ... Components>
343private:
344 static constexpr bool IterOnly = arity<Func>::value == 1;
345
346 using Terms = typename term_ptrs<Components ...>::array;
347
348public:
349 template < if_not_t< is_same< decay_t<Func>, decay_t<Func>& >::value > = 0>
350 explicit iter_invoker(Func&& func) noexcept
351 : m_func(FLECS_MOV(func)) { }
352
353 explicit iter_invoker(const Func& func) noexcept
354 : m_func(func) { }
355
356 // Invoke object directly. This operation is useful when the calling
357 // function has just constructed the invoker, such as what happens when
358 // iterating a query.
359 void invoke(ecs_iter_t *iter) const {
360 term_ptrs<Components...> terms;
361 terms.populate(iter);
362 invoke_callback(iter, m_func, 0, terms.m_terms);
363 }
364
365 // Static function that can be used as callback for systems/triggers
366 static void run(ecs_iter_t *iter) {
367 auto self = static_cast<const iter_invoker*>(iter->binding_ctx);
368 ecs_assert(self != nullptr, ECS_INTERNAL_ERROR, NULL);
369 self->invoke(iter);
370 }
371
372 // Instancing needs to be enabled explicitly for iter invokers
373 static bool instanced() {
374 return false;
375 }
376
377private:
378 template <typename... Args, if_t<!sizeof...(Args) && IterOnly> = 0>
379 static void invoke_callback(ecs_iter_t *iter, const Func& func,
380 size_t, Terms&, Args...)
381 {
382 flecs::iter it(iter);
383
384 ECS_TABLE_LOCK(iter->world, iter->table);
385
386 func(it);
387
388 ECS_TABLE_UNLOCK(iter->world, iter->table);
389 }
390
391 template <typename... Targs, if_t<!IterOnly &&
392 (sizeof...(Targs) == sizeof...(Components))> = 0>
393 static void invoke_callback(ecs_iter_t *iter, const Func& func, size_t,
394 Terms&, Targs... comps)
395 {
396 flecs::iter it(iter);
397
398 ECS_TABLE_LOCK(iter->world, iter->table);
399
400 func(it, ( static_cast<
401 remove_reference_t<
402 remove_pointer_t<
403 actual_type_t<Components> > >* >
404 (comps.ptr))...);
405
406 ECS_TABLE_UNLOCK(iter->world, iter->table);
407 }
408
409 template <typename... Targs, if_t<!IterOnly &&
410 (sizeof...(Targs) != sizeof...(Components)) > = 0>
411 static void invoke_callback(ecs_iter_t *iter, const Func& func,
412 size_t index, Terms& columns, Targs... comps)
413 {
414 invoke_callback(iter, func, index + 1, columns, comps...,
415 columns[index]);
416 }
417
418 Func m_func;
419};
420
421
425
426template<typename ... Args>
428
429template<typename ... Args>
431 using ColumnArray = flecs::array<int32_t, sizeof...(Args)>;
432 using ArrayType = flecs::array<void*, sizeof...(Args)>;
433 using DummyArray = flecs::array<int, sizeof...(Args)>;
434 using IdArray = flecs::array<id_t, sizeof...(Args)>;
435
436 static bool const_args() {
437 static flecs::array<bool, sizeof...(Args)> is_const_args ({
438 flecs::is_const<flecs::remove_reference_t<Args>>::value...
439 });
440
441 for (auto is_const : is_const_args) {
442 if (!is_const) {
443 return false;
444 }
445 }
446 return true;
447 }
448
449 static bool get_ptrs(world_t *world, const ecs_record_t *r, ecs_table_t *table,
450 ArrayType& ptrs)
451 {
452 ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);
453
455 if (!storage_table) {
456 return false;
457 }
458
459 /* table_index_of needs real world */
460 const flecs::world_t *real_world = ecs_get_world(world);
461
462 /* Get column indices for components */
463 ColumnArray columns ({
464 ecs_search_offset(real_world, storage_table, 0,
465 _::cpp_type<Args>().id(world), 0)...
466 });
467
468 /* Get pointers for columns for entity */
469 size_t i = 0;
470 for (int32_t column : columns) {
471 if (column == -1) {
472 return false;
473 }
474
475 ptrs[i ++] = ecs_record_get_column(r, column, 0);
476 }
477
478 return true;
479 }
480
481 static bool get_mut_ptrs(world_t *world, ecs_entity_t e, ArrayType& ptrs) {
482 /* Get pointers w/get_mut */
483 size_t i = 0;
484 DummyArray dummy ({
485 (ptrs[i ++] = ecs_get_mut_id(world, e,
486 _::cpp_type<Args>().id(world)), 0)...
487 });
488
489 return true;
490 }
491
492 template <typename Func>
493 static bool invoke_read(world_t *world, entity_t e, const Func& func) {
494 const ecs_record_t *r = ecs_read_begin(world, e);
495 if (!r) {
496 return false;
497 }
498
499 ecs_table_t *table = r->table;
500 if (!table) {
501 return false;
502 }
503
504 ArrayType ptrs;
505 bool has_components;
506 if ((has_components = get_ptrs(world, r, table, ptrs))) {
507 invoke_callback(func, 0, ptrs);
508 }
509
510 ecs_read_end(r);
511
512 return has_components;
513 }
514
515 template <typename Func>
516 static bool invoke_write(world_t *world, entity_t e, const Func& func) {
517 ecs_record_t *r = ecs_write_begin(world, e);
518 if (!r) {
519 return false;
520 }
521
522 ecs_table_t *table = r->table;
523 if (!table) {
524 return false;
525 }
526
527 ArrayType ptrs;
528 bool has_components;
529 if ((has_components = get_ptrs(world, r, table, ptrs))) {
530 invoke_callback(func, 0, ptrs);
531 }
532
533 ecs_write_end(r);
534
535 return has_components;
536 }
537
538 template <typename Func>
539 static bool invoke_get(world_t *world, entity_t e, const Func& func) {
540 if (const_args()) {
541 return invoke_read(world, e, func);
542 } else {
543 return invoke_write(world, e, func);
544 }
545 }
546
547 // Utility for storing id in array in pack expansion
548 static size_t store_added(IdArray& added, size_t elem, ecs_table_t *prev,
549 ecs_table_t *next, id_t id)
550 {
551 // Array should only contain ids for components that are actually added,
552 // so check if the prev and next tables are different.
553 if (prev != next) {
554 added[elem] = id;
555 elem ++;
556 }
557 return elem;
558 }
559
560 template <typename Func>
561 static bool invoke_get_mut(world_t *world, entity_t id, const Func& func) {
563
564 ArrayType ptrs;
565 ecs_table_t *table = NULL;
566
567 // When not deferred take the fast path.
568 if (!w.is_deferred()) {
569 // Bit of low level code so we only do at most one table move & one
570 // entity lookup for the entire operation.
571
572 // Make sure the object is not a stage. Operations on a stage are
573 // only allowed when the stage is in deferred mode, which is when
574 // the world is in readonly mode.
575 ecs_assert(!w.is_stage(), ECS_INVALID_PARAMETER, NULL);
576
577 // Find table for entity
578 ecs_record_t *r = ecs_record_find(world, id);
579 if (r) {
580 table = r->table;
581 }
582
583 // Find destination table that has all components
584 ecs_table_t *prev = table, *next;
585 size_t elem = 0;
586 IdArray added;
587
588 // Iterate components, only store added component ids in added array
589 DummyArray dummy_before ({ (
590 next = ecs_table_add_id(world, prev, w.id<Args>()),
591 elem = store_added(added, elem, prev, next, w.id<Args>()),
592 prev = next, 0
593 )... });
594 (void)dummy_before;
595
596 // If table is different, move entity straight to it
597 if (table != next) {
598 ecs_type_t ids;
599 ids.array = added.ptr();
600 ids.count = static_cast<ecs_size_t>(elem);
601 ecs_commit(world, id, r, next, &ids, NULL);
602 table = next;
603 }
604
605 if (!get_ptrs(w, r, table, ptrs)) {
606 ecs_abort(ECS_INTERNAL_ERROR, NULL);
607 }
608
609 ECS_TABLE_LOCK(world, table);
610
611 // When deferred, obtain pointers with regular get_mut
612 } else {
613 get_mut_ptrs(world, id, ptrs);
614 }
615
616 invoke_callback(func, 0, ptrs);
617
618 if (!w.is_deferred()) {
619 ECS_TABLE_UNLOCK(world, table);
620 }
621
622 // Call modified on each component
623 DummyArray dummy_after ({
624 ( ecs_modified_id(world, id, w.id<Args>()), 0)...
625 });
626 (void)dummy_after;
627
628 return true;
629 }
630
631private:
632 template <typename Func, typename ... TArgs,
633 if_t<sizeof...(TArgs) == sizeof...(Args)> = 0>
634 static void invoke_callback(
635 const Func& f, size_t, ArrayType&, TArgs&& ... comps)
636 {
637 f(*static_cast<typename base_arg_type<Args>::type*>(comps)...);
638 }
639
640 template <typename Func, typename ... TArgs,
641 if_t<sizeof...(TArgs) != sizeof...(Args)> = 0>
642 static void invoke_callback(const Func& f, size_t arg, ArrayType& ptrs,
643 TArgs&& ... comps)
644 {
645 invoke_callback(f, arg + 1, ptrs, comps..., ptrs[arg]);
646 }
647};
648
649template <typename Func, typename U = int>
651 static_assert(function_traits<Func>::value, "type is not callable");
652};
653
654template <typename Func>
655struct entity_with_invoker<Func, if_t< is_callable<Func>::value > >
656 : entity_with_invoker_impl< arg_list_t<Func> >
657{
658 static_assert(function_traits<Func>::arity > 0,
659 "function must have at least one argument");
660};
661
662} // namespace _
663
664} // namespace flecs
#define ecs_assert(condition, error_code,...)
Assert.
Definition: log.h:352
#define ecs_abort(error_code,...)
Abort.
Definition: log.h:343
ecs_id_t ecs_entity_t
An entity identifier.
Definition: flecs.h:277
struct ecs_world_t ecs_world_t
A world is the container for all ECS data and supporting features.
Definition: flecs.h:286
struct ecs_table_t ecs_table_t
A table is where entities and components are stored.
Definition: flecs.h:289
flecs::entity id(E value) const
Convert enum constant to entity.
void(* ecs_ctx_free_t)(void *ctx)
Function to cleanup context data.
Definition: flecs.h:473
void ecs_read_end(const ecs_record_t *record)
End read access to entity.
void ecs_write_end(ecs_record_t *record)
End exclusive write access to entity.
ecs_record_t * ecs_write_begin(ecs_world_t *world, ecs_entity_t entity)
Begin exclusive write access to entity.
void ecs_modified_id(ecs_world_t *world, ecs_entity_t entity, ecs_id_t id)
Signal that a component has been modified.
const ecs_record_t * ecs_read_begin(ecs_world_t *world, ecs_entity_t entity)
Begin read access to entity.
void * ecs_get_mut_id(ecs_world_t *world, ecs_entity_t entity, ecs_id_t id)
Get a mutable pointer to a component.
ecs_table_t * ecs_table_add_id(ecs_world_t *world, ecs_table_t *table, ecs_id_t id)
Get table that has all components of current table plus the specified id.
int32_t ecs_search_offset(const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_id_t *id_out)
Search for component id in table type starting from an offset.
bool ecs_commit(ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, const ecs_type_t *added, const ecs_type_t *removed)
Commit (move) entity to a table.
ecs_table_t * ecs_table_get_storage_table(const ecs_table_t *table)
Get storage type for table.
void * ecs_record_get_column(const ecs_record_t *r, int32_t column, size_t c_size)
Get component pointer from column/record.
ecs_record_t * ecs_record_find(const ecs_world_t *world, ecs_entity_t entity)
Find record for entity.
const ecs_world_t * ecs_get_world(const ecs_poly_t *poly)
Get world from poly.
An array with (component) ids.
Definition: flecs.h:280
Wrapper class around a column.
Definition: iter.hpp:58
Entity.
Definition: entity.hpp:30
Class that wraps around a flecs::id_t.
Definition: decl.hpp:27
Class for iterating over query results.
Definition: iter.hpp:169
Class that describes a term.
Definition: impl.hpp:16
The world.
Definition: world.hpp:113
bool is_stage() const
Test if is a stage.
Definition: world.hpp:356
bool is_deferred() const
Test whether deferring is enabled.
Definition: world.hpp:311